@makerx/node-ipfs
Version:
A NodeJS package that makes interacting with IPFS easier
303 lines • 11.7 kB
JavaScript
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
;