Files
contracts/lib/utils/ipfs.js
T
raucao f24e9a8a0b
Release Drafter / Update release notes draft (pull_request) Failing after 3s
Fix IPFS cat decoding for ipfs-http-client 56 (Uint8Array)
ipfs-http-client 56 yields Uint8Array chunks, whose toString() returns
comma-separated byte values rather than text. Concatenating those turned
every document into an invalid JSON string ("123,10,32,..."), so
JSON.parse failed with "Unexpected non-whitespace character after JSON at
position 3" and no IPFS-backed record could be read.

Decode the chunks explicitly with a streaming TextDecoder, which works in
both Node and browsers. Bump version to 7.5.1.
2026-10-29 20:02:40 +04:00

90 lines
2.8 KiB
JavaScript

const ipfsClient = require('ipfs-http-client');
const multihashes = require('multihashes');
const fetch = require('node-fetch');
class IPFS {
constructor (config) {
if (!config) {
config = { host: 'localhost', port: '5001', protocol: 'http' };
}
this._config = config;
this._ipfsAPI = ipfsClient.create(config);
this._ipfsAPI.id().then(res => {
console.debug('IPFS ID:', res.id);
}).catch(e => {
console.debug('IPFS config:', config);
console.warn('Failed to initialize IPFS:', e.message);
});
}
async catAndMerge (contractData, deserialize) {
let data = {...contractData}; // data from ethers.js is not extensible. this copy the attributes in a new object
// if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) {
return data;
}
// merge ipfsHash (encoded from hashDigest, hashSize, hashFunction)
data.ipfsHash = multihashes.toB58String(this.encodeHash(data));
return this.cat(data.ipfsHash)
.then(deserialize)
.then(attributes => {
return Object.assign({}, data, attributes);
});
}
async add (data) {
return this._ipfsAPI.add(data)
.then(res => {
return this.decodeHash(res.path);
});
}
async cat (hashData) {
let ipfsHash = hashData; // default - if it is a string
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
ipfsHash = this.encodeHash(hashData);
}
if (this._config['gatewayUrl']) {
return fetch(`${this._config['gatewayUrl']}/${ipfsHash}`).then(r => r.text());
} else {
const res = this._ipfsAPI.cat(ipfsHash);
// ipfs-http-client >= 56 yields Uint8Array chunks. Their toString()
// returns comma-separated byte values instead of text, so decode the
// bytes explicitly (TextDecoder works in both Node and browsers).
const decoder = new TextDecoder();
let str = '';
for await (const chunk of res) {
str += decoder.decode(chunk, { stream: true });
}
return Promise.resolve(str + decoder.decode());
}
}
pin (hashData) {
let ipfsHash = hashData; // default - if it is a string
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
ipfsHash = this.encodeHash(hashData);
}
return this._ipfsAPI.pin.add(multihashes.toB58String(ipfsHash));
}
decodeHash (ipfsHash) {
let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash));
return {
hashDigest: '0x' + multihashes.toHexString(multihash.digest),
hashSize: multihash.length,
hashFunction: multihash.code,
ipfsHash: ipfsHash,
};
}
encodeHash (hashData) {
const digest = Buffer.from(hashData.hashDigest.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
}
}
module.exports = IPFS;