@subsocial/api
Version:
JavaScript API for Subsocial blockchain.
266 lines (265 loc) • 11.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubsocialIpfsApi = exports.getCidsOfStructs = exports.getIpfsCidOfStruct = void 0;
const utils_1 = require("@subsocial/utils");
const axios_1 = __importDefault(require("axios"));
const utils_2 = require("../utils");
const ipfs_http_client_1 = require("ipfs-http-client");
const util_1 = require("@polkadot/util");
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
const getIpfsUrl = (url) => url + '/api/v0';
var CID_KIND;
(function (CID_KIND) {
CID_KIND[CID_KIND["CBOR"] = 113] = "CBOR";
CID_KIND[CID_KIND["UNIXFS"] = 112] = "UNIXFS";
})(CID_KIND || (CID_KIND = {}));
/** Try to resolve a corresponding IPFS CID of a given struct. */
function getIpfsCidOfStruct(struct) {
if ((0, utils_2.isIpfs)(struct.content)) {
return struct.content.asIpfs.toHuman();
}
return undefined;
}
exports.getIpfsCidOfStruct = getIpfsCidOfStruct;
/** Extract ids of an array of structs. */
function getCidsOfStructs(structs) {
return structs
.map(getIpfsCidOfStruct)
.filter(cid => typeof cid !== 'undefined');
}
exports.getCidsOfStructs = getCidsOfStructs;
/** Return unique cids from cids array */
const getUniqueCids = (cids) => {
const ipfsCids = (0, utils_2.getUniqueIds)(cids.map(utils_2.asIpfsCid));
if ((0, utils_1.isEmptyArray)(ipfsCids)) {
log.debug(`No content to load from IPFS: no cids provided`);
return [];
}
return ipfsCids;
};
/** Aggregated API to work with IPFS: get the content of the spaces of posts and profiles. */
class SubsocialIpfsApi {
constructor({ ipfsNodeUrl, ipfsAdminNodeUrl, ipfsClusterUrl, offchainUrl, headers }) {
this._ipfsClusterUrl = ipfsClusterUrl;
this._ipfsAdminNodeUrl = ipfsAdminNodeUrl;
this._ipfsNodeUrl = ipfsNodeUrl;
this.createIpfsClient(headers);
this.pinHeaders = headers;
if (offchainUrl) {
this.offchainUrl = `${offchainUrl}/v1`;
}
}
createIpfsClient(headers) {
this._client = (0, ipfs_http_client_1.create)({
url: getIpfsUrl(this._ipfsNodeUrl),
headers
});
this._adminClient = this._ipfsAdminNodeUrl
? (0, ipfs_http_client_1.create)({
url: getIpfsUrl(this._ipfsAdminNodeUrl),
headers
})
: this._client;
this.writeHeaders = headers;
}
static generateCrustAuthToken(auth) {
const sig = (0, util_1.u8aToHex)(auth.signedAddress);
const authHeaderRaw = `sub-${auth.publicAddress}:${sig}`;
return Buffer.from(authHeaderRaw).toString('base64');
}
get client() {
return this._client;
}
get adminClient() {
return this._adminClient;
}
// ---------------------------------------------------------------------
// Main interfaces
setWriteHeaders(headers) {
this.createIpfsClient(headers);
}
setPinHeaders(headers) {
this.pinHeaders = headers;
}
getContentArray(cids, timeout) {
return __awaiter(this, void 0, void 0, function* () {
return this.getContentArrayFromIpfs(cids, timeout);
});
}
getContent(cid, timeout) {
return __awaiter(this, void 0, void 0, function* () {
const content = yield this.getContentArray([cid], timeout);
return content[cid.toString()];
});
}
// --------------------------------------------------------------------
// IPFS functionality
/** Return object with contents from IPFS by cids array */
getContentArrayFromIpfs(cids, timeout) {
return __awaiter(this, void 0, void 0, function* () {
try {
const ipfsCids = getUniqueCids(cids);
const content = {};
const loadContentFns = ipfsCids.map((cid) => __awaiter(this, void 0, void 0, function* () {
try {
const cidStr = cid.toString();
const isCbor = cid.code === CID_KIND.CBOR;
if (isCbor) {
const res = yield this.client.dag.get(cid, {
timeout
});
content[cidStr] = res.value;
}
else {
const res = yield axios_1.default.get(`${this._ipfsNodeUrl}/ipfs/${cid.toV1()}?timeout=${timeout}`);
const data = res.data;
if (typeof data === 'object') {
content[cidStr] = data;
}
}
}
catch (err) {
log.error(`Failed to load cid ${cid.toString()}:`, err);
}
}));
yield Promise.allSettled(loadContentFns);
log.debug(`Loaded ${cids.length} cid(s)`);
return content;
}
catch (err) {
log.error(`Failed to load ${cids.length} cid(s):`, err);
return {};
}
});
}
/** Pin content in IPFS */
pinContent(cid, props) {
return __awaiter(this, void 0, void 0, function* () {
const url = (props === null || props === void 0 ? void 0 : props.asLink)
? `${this._ipfsClusterUrl}/pins/${cid.toString()}`
: `${this._ipfsClusterUrl}/pins/`;
const data = Object.assign({ cid: cid.toString() }, props);
const res = yield axios_1.default.post(url, data, {
headers: this.pinHeaders
});
if (res.status === 200) {
log.debug(`CID ${cid.toString()} was pinned to ${this._ipfsClusterUrl}`);
}
return res;
});
}
/** Unpin content in IPFS */
unpinContent(cid) {
return __awaiter(this, void 0, void 0, function* () {
const res = yield axios_1.default.delete(this._ipfsClusterUrl + '/pins/' + cid.toString(), {
headers: this.pinHeaders
});
if (res.status === 200) {
log.debug(`CID ${cid.toString()} was unpinned from ${this._ipfsClusterUrl}`);
}
return res;
});
}
/** Add content in IPFS using Crob format*/
saveContent(content) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.adminClient.dag.put(content, {
headers: this.writeHeaders
});
return data.toV1().toString();
});
}
/** Add file in IPFS using unixFs */
saveFile(file) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.adminClient.add(file, {
headers: this.writeHeaders
});
return data.cid.toV1().toString();
});
}
/** Add JSON in IPFS using unixFs */
saveJson(json) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.adminClient.add((0, json_stable_stringify_1.default)(json), {
headers: this.writeHeaders
});
return data.cid.toV1().toString();
});
}
// ---------------------------------------------------------------------
// Offchain functionality
/** @deprecated Unpin content in IPFS via Offchain */
removeContent(cid) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axios_1.default.delete(`${this.offchainUrl}/ipfs/pins/${cid}`);
if (res.status !== 200) {
log.error(`${this.removeContent.name}: offchain server responded with status code ${res.status} and message: ${res.statusText}`);
return;
}
log.info(`Unpinned content with hash: ${cid}`);
}
catch (error) {
log.error('Failed to unpin content in IPFS from client side via offchain: ', error);
}
});
}
/** Add and pin content in IPFS via Offchain */
saveContentToOffchain(content) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axios_1.default.post(`${this.offchainUrl}/ipfs/add`, content);
if (res.status !== 200) {
log.error(`${this.saveContentToOffchain.name}: Offchain server responded with status code ${res.status} and message: ${res.statusText}`);
return undefined;
}
return res.data;
}
catch (error) {
log.error('Failed to add content to IPFS from client side via offchain: ', error);
return undefined;
}
});
}
/** Add and pit file in IPFS via Offchain */
saveFileToOffchain(file) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof window === 'undefined') {
throw new Error('This function works only in a browser');
}
try {
const formData = new FormData();
formData.append('file', file);
const res = yield axios_1.default.post(`${this.offchainUrl}/ipfs/addFile`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
if (res.status !== 200) {
log.error(`${this.saveFileToOffchain.name}: Offchain server responded with status code ${res.status} and message: ${res.statusText}`);
return undefined;
}
return res.data;
}
catch (error) {
log.error('Failed to add file to IPFS from client side via offchain: ', error);
return undefined;
}
});
}
}
exports.SubsocialIpfsApi = SubsocialIpfsApi;
const log = (0, utils_1.newLogger)(SubsocialIpfsApi.name);