UNPKG

@makerx/node-ipfs

Version:

A NodeJS package that makes interacting with IPFS easier

303 lines 11.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PinataStorage = exports.PinataStorageWithCache = exports.CacheOnlyIPFS = exports.InMemoryIPFS = void 0; exports.getCIDUrl = getCIDUrl; const cid_1 = require("multiformats/cid"); const raw = __importStar(require("multiformats/codecs/raw")); const sha2_1 = require("multiformats/hashes/sha2"); const http_1 = require("./http"); async function generateCID(data) { // Mimics Pinata/Web3.Storage's implementation - manually calculating with raw doesn't work for binary, but this does const { importBytes } = await Promise.resolve().then(() => __importStar(require('ipfs-unixfs-importer'))); const { MemoryBlockstore } = await Promise.resolve().then(() => __importStar(require('blockstore-core/memory'))); const { cid } = await importBytes(data, new MemoryBlockstore(), { wrapWithDirectory: false }); return cid.toString(); } class InMemoryIPFS { constructor() { // eslint-disable-next-line @typescript-eslint/no-explicit-any this.cache = {}; } async get(cid) { const cached = this.cache[cid]; if (!cached) { throw new Error('404'); } return JSON.parse(cached); } async put(data, _name) { const cid = await this.getCID(data); this.cache[cid] = JSON.stringify(data); return { cid: cid }; } getBlob(cid) { const cached = this.cache[cid]; if (!cached) { throw new Error('404'); } return Promise.resolve(cached); } async putBlob(blob, _contentType, _name) { const cid = await this.getCID(blob); this.cache[cid] = blob; return { cid: cid.toString() }; } async getCID(data) { if (data instanceof Uint8Array) { return await generateCID(data); } else { const bytes = raw.encode(Buffer.from(JSON.stringify(data))); const hash = await sha2_1.sha256.digest(bytes); const cid = cid_1.CID.create(1, raw.code, hash); return cid.toString(); } } } exports.InMemoryIPFS = InMemoryIPFS; class CacheOnlyIPFS { constructor(cache, ipfsGatewayOptions) { this.cache = cache; this.ipfsGatewayOptions = ipfsGatewayOptions; } async get(cid) { return await this.cache.getAndCache(`ipfs-${cid}`, async (_e) => { if (!this.ipfsGatewayOptions) { throw new Error('404'); } const response = await (0, http_1.fetchWithRetry)(getCIDUrl(this.ipfsGatewayOptions.baseUrl, cid)); const json = await response.json(); return json; }, { staleAfterSeconds: undefined, returnStaleResultOnError: true, }); } async put(data, _name) { const cid = await this.getCID(data); await this.cache.getAndCache(`ipfs-${cid}`, (_e) => { return Promise.resolve(data); }, { staleAfterSeconds: 0, returnStaleResultOnError: false, }); return { cid: cid.toString() }; } async getBlob(cid) { return await this.cache.getAndCache(`ipfs-${cid}`, async (_e) => { if (!this.ipfsGatewayOptions) { throw new Error('404'); } const response = await (0, http_1.fetchWithRetry)(getCIDUrl(this.ipfsGatewayOptions.baseUrl, cid)); return Buffer.from(await response.arrayBuffer()); }, { staleAfterSeconds: undefined, returnStaleResultOnError: true, isBinary: true, }); } async putBlob(blob, _contentType, _name) { const cid = await this.getCID(blob); await this.cache.getAndCache(`ipfs-${cid}`, (_e) => { return Promise.resolve(blob); }, { staleAfterSeconds: 0, returnStaleResultOnError: false, }); return { cid: cid.toString() }; } async getCID(data) { if (data instanceof Uint8Array) { return await generateCID(data); } else { const bytes = raw.encode(Buffer.from(JSON.stringify(data))); const hash = await sha2_1.sha256.digest(bytes); const cid = cid_1.CID.create(1, raw.code, hash); return cid.toString(); } } } exports.CacheOnlyIPFS = CacheOnlyIPFS; const defaultIPFSGatewayOptions = { baseUrl: 'https://ipfs.algonode.dev/ipfs', }; class PinataStorageWithCache { // We've chosen to use the Pinata API directly rather than their JS SDK, // as it currently uses a really old version of axios, that has security vulnerabilities. constructor(pinataToken, cache, ipfsGatewayOptions) { this.pinataBaseUrl = 'https://api.pinata.cloud/pinning'; this.token = pinataToken; this.cache = cache; this.ipfsGatewayOptions = ipfsGatewayOptions ?? defaultIPFSGatewayOptions; } async get(cid) { return await this.cache.getAndCache(`ipfs-${cid}`, async (_e) => { const response = await (0, http_1.fetchWithRetry)(getCIDUrl(this.ipfsGatewayOptions.baseUrl, cid)); // eslint-disable-next-line no-console console.debug(`Cache miss for ${cid}, fetching from IPFS`); const json = await response.json(); return json; }, { staleAfterSeconds: undefined, returnStaleResultOnError: true, }); } async put(data, name) { const response = await fetch(`${this.pinataBaseUrl}/pinJSONToIPFS`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: `Bearer ${this.token}`, }, body: JSON.stringify({ pinataContent: data, pinataOptions: { cidVersion: 1, wrapWithDirectory: false, }, ...(name ? { pinataMetadata: { name } } : undefined), }), }); if (!response.ok) { const statusCode = response.status; const errorResponse = await response.text(); throw new Error(`${statusCode} ${errorResponse}`); } const { IpfsHash: cid } = (await response.json()); // Save time later if we need to retrieve it again await this.cache.put(`ipfs-${cid}`, data); return { cid }; } async getBlob(cid) { return await this.cache.getAndCache(`ipfs-${cid}`, async (_e) => { const response = await (0, http_1.fetchWithRetry)(getCIDUrl(this.ipfsGatewayOptions.baseUrl, cid)); // eslint-disable-next-line no-console console.debug(`Cache miss for ${cid}, fetching from IPFS`); return Buffer.from(await response.arrayBuffer()); }, { staleAfterSeconds: undefined, returnStaleResultOnError: true, isBinary: true, }); } async putBlob(blob, contentType, name) { const formData = new FormData(); formData.append('file', new Blob([blob], { type: contentType }), name ?? 'data'); formData.append('pinataOptions', JSON.stringify({ cidVersion: 1, wrapWithDirectory: false, })); if (name) { formData.append('pinataMetadata', JSON.stringify({ name })); } const response = await fetch(`${this.pinataBaseUrl}/pinFileToIPFS`, { method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${this.token}`, }, body: formData, }); if (!response.ok) { const statusCode = response.status; const errorResponse = await response.text(); throw new Error(`${statusCode} ${errorResponse}`); } const { IpfsHash: cid } = (await response.json()); // Save time later if we need to retrieve it again await this.cache.put(`ipfs-${cid}`, blob); return { cid }; } async getCID(data) { if (data instanceof Uint8Array) { return await generateCID(data); } else { const bytes = raw.encode(Buffer.from(JSON.stringify(data))); const hash = await sha2_1.sha256.digest(bytes); const cid = cid_1.CID.create(1, raw.code, hash); return cid.toString(); } } async updateMetadata(cid, metadata) { const { name, ...keyvalues } = metadata; const response = await fetch(`${this.pinataBaseUrl}/hashMetadata`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: `Bearer ${this.token}`, }, body: JSON.stringify({ ipfsPinHash: cid, ...(name ? { name } : undefined), ...(keyvalues && Object.keys(keyvalues).length > 0 ? { keyvalues } : undefined), }), }); if (!response.ok) { const statusCode = response.status; const errorResponse = await response.text(); throw new Error(`${statusCode} ${errorResponse}`); } } } exports.PinataStorageWithCache = PinataStorageWithCache; class NoOpCache { getAndCache(cacheKey, generator, _options) { return generator(undefined); } async getAndCacheBinary(_cacheKey, generator, options) { return { data: await generator(undefined), mimeType: options?.mimeType ?? 'application/octet-stream', fileExtension: null, }; } put(_cacheKey, _data, _mimeType) { return Promise.resolve(); } putBinary(_cacheKey, _data, _mimeType) { return Promise.resolve(); } clearCache(_cacheKey) { } } class PinataStorage extends PinataStorageWithCache { constructor(pinataToken, ipfsGatewayOptions) { super(pinataToken, new NoOpCache(), ipfsGatewayOptions); } } exports.PinataStorage = PinataStorage; function getCIDUrl(ipfsGatewayBaseUrl, cid) { // The trailing slash is important as it allows us to append the CID segment // using URL instead of concatenating strings ourselves. // Without the trailing slash, the whole path would be replaced. const baseUrl = ipfsGatewayBaseUrl.endsWith('/') ? new URL(ipfsGatewayBaseUrl) : new URL(`${ipfsGatewayBaseUrl}/`); const cidUrl = new URL(cid, baseUrl); return cidUrl.toString(); } //# sourceMappingURL=ipfs.js.map