UNPKG

@subsocial/api

Version:
266 lines (265 loc) 11.1 kB
"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);