@helia/verified-fetch
Version:
A fetch-like API for obtaining verified & trustless IPFS content on the web
59 lines • 2.92 kB
JavaScript
import { code as dagPbCode } from '@ipld/dag-pb';
import { base32 } from 'multiformats/bases/base32';
import { sha256 } from 'multiformats/hashes/sha2';
import { dirIndexHtml } from '../utils/dir-index-html.js';
import { getETag } from '../utils/get-e-tag.js';
import { getIpfsRoots } from '../utils/response-headers.js';
import { okRangeResponse } from '../utils/responses.js';
import { BasePlugin } from './plugin-base.js';
/**
* Converts a list of directory entries into a small hash that can be used in the etag header.
*
* @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/assets/assets.go#L84
* @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/handler_unixfs_dir.go#L233-L235
*/
async function getAssetHash(directoryEntries) {
const entryDetails = directoryEntries.reduce((acc, entry) => {
return `${acc}${entry.name}${entry.cid.toString()}`;
}, '');
const hashBytes = await sha256.encode(new TextEncoder().encode(entryDetails));
return base32.encode(hashBytes);
}
export class DirIndexHtmlPlugin extends BasePlugin {
id = 'dir-index-html-plugin';
codes = [dagPbCode];
canHandle(context) {
const { cid, pathDetails, directoryEntries } = context;
if (pathDetails == null) {
return false;
}
if (pathDetails.terminalElement?.type !== 'directory') {
return false;
}
if (directoryEntries == null || directoryEntries.length === 0) {
return false;
}
return cid.code === dagPbCode;
}
async handle(context) {
const { resource, pathDetails, directoryEntries } = context;
const { terminalElement, ipfsRoots } = pathDetails;
const gatewayURL = resource;
const htmlResponse = dirIndexHtml(terminalElement, directoryEntries, { gatewayURL, log: this.log });
context.byteRangeContext.setBody(htmlResponse);
const etagPrefix = `DirIndex-${await getAssetHash(directoryEntries)}_CID-`;
const response = okRangeResponse(resource, context.byteRangeContext.getBody('text/html'), { byteRangeContext: context.byteRangeContext, log: this.log }, {
headers: {
'Content-Type': context.byteRangeContext.getContentType() ?? 'text/html',
// see https://github.com/ipfs/gateway-conformance/pull/219
'Cache-Control': 'public, max-age=604800, stale-while-revalidate=2678400',
'X-Ipfs-Roots': getIpfsRoots(ipfsRoots),
// e.g. DirIndex-<asset_hash>_CID-<cid>
Etag: getETag({ cid: terminalElement.cid, reqFormat: context.reqFormat, contentPrefix: etagPrefix })
}
});
return response;
}
}
export const dirIndexHtmlPluginFactory = (opts) => new DirIndexHtmlPlugin(opts);
//# sourceMappingURL=plugin-handle-dir-index-html.js.map