@wttp/site
Version:
Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools
150 lines • 6.18 kB
JavaScript
// Standalone version of fetchResource - works with standard ethers.js
import { ethers, Contract } from "ethers";
import { ORIGINS_ADMIN_ONLY, normalizePath } from "@wttp/core";
// Import artifact directly to avoid loading tasks
import Web3SiteArtifact from "../../artifacts/contracts/Web3Site.sol/Web3Site.json";
/**
* Fetches a resource directly from a WTTP site contract (standalone version)
*
* @param provider - Ethers.js provider
* @param siteAddress - The address of the WTTP site contract
* @param path - The path to the resource
* @param options - Optional parameters for the request
* @returns The response from the site with full content
*/
export async function fetchResource(provider, siteAddress, path, options = {}) {
// Parameter validation
if (!siteAddress) {
throw new Error("Site address is required");
}
if (!provider) {
throw new Error("Provider is required");
}
try {
path = normalizePath(path || "/");
}
catch (error) {
throw new Error(`Invalid path format: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
const { ifModifiedSince = 0, ifNoneMatch = ethers.ZeroHash, range = { start: 0, end: 0 }, headRequest = false, datapoints = false, // false means fetch content, true means return only datapoint addresses
chainId } = options;
if (process.env.WTTP_SITE_DEBUG)
console.log(`🌐 Connecting to site: ${siteAddress}`);
if (process.env.WTTP_SITE_DEBUG)
console.log(`📄 Requesting resource: ${path}${headRequest ? ' (HEAD only)' : ''}`);
// Get the site contract
const siteContract = new Contract(siteAddress, Web3SiteArtifact.abi, provider);
// Create the request
const headRequest_obj = {
path: path,
ifModifiedSince,
ifNoneMatch
};
let head = {
status: 404n,
headerInfo: {
cache: { immutableFlag: false, preset: 0n, custom: "" },
cors: {
methods: 1n,
origins: ORIGINS_ADMIN_ONLY,
preset: 0n,
custom: ""
},
redirect: { code: 0n, location: "" }
},
metadata: {
properties: {
mimeType: "0x0000",
charset: "0x0000",
encoding: "0x0000",
language: "0x0000"
},
size: 0n,
version: 0n,
lastModified: 0n,
header: ethers.ZeroHash
},
etag: ethers.ZeroHash
};
// If it's a HEAD request, just call HEAD
if (headRequest) {
if (process.env.WTTP_SITE_DEBUG)
console.log(`Sending HEAD request for ${path} from site ${siteAddress}`);
try {
head = await siteContract.HEAD(headRequest_obj);
}
catch (error) {
if (process.env.WTTP_SITE_DEBUG)
console.log("HEAD request failed, assuming file doesn't exist");
}
return { response: { head, resource: { dataPoints: [], totalChunks: 0 } }, content: undefined };
}
else {
// For GET requests, use the site's GET method
if (process.env.WTTP_SITE_DEBUG)
console.log(`Fetching resource at ${path} from site ${siteAddress}`);
let locateResponse;
try {
locateResponse = await siteContract.GET({ head: headRequest_obj, rangeChunks: range });
}
catch (error) {
console.log(error);
locateResponse = {
head: head,
resource: {
dataPoints: [],
totalChunks: 0
}
};
}
if (process.env.WTTP_SITE_DEBUG)
console.log(`Response status: ${locateResponse.head.status}`);
if (locateResponse.head.status === 404n) {
if (process.env.WTTP_SITE_DEBUG)
console.log(`❌ Resource not found at ${path}`);
return { response: locateResponse, content: undefined };
}
// If content is requested (datapoints is false), fetch the actual content
if (!datapoints && locateResponse.resource.dataPoints.length > 0) {
if (process.env.WTTP_SITE_DEBUG)
console.log(`📦 Fetching ${locateResponse.resource.dataPoints.length} data points...`);
// Get DPS address from site
const dpsAddress = await siteContract.DPS();
// Load DPS contract
const dpsAbi = [
"function readDataPoint(bytes32) external view returns (bytes memory)"
];
const dps = new Contract(dpsAddress, dpsAbi, provider);
// Read all data points
const dataPointPromises = locateResponse.resource.dataPoints.map((dp) => dps.readDataPoint(dp));
const dataPointContents = await Promise.all(dataPointPromises);
// Convert bytes to Uint8Array
const chunks = dataPointContents.map((chunk) => {
if (typeof chunk === 'string') {
// If it's a hex string, convert it
return new Uint8Array(ethers.getBytes(chunk));
}
else if (chunk instanceof Uint8Array) {
return chunk;
}
else {
// Assume it's already bytes-like
return new Uint8Array(ethers.getBytes(chunk));
}
});
// Combine all chunks
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combinedContent = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
combinedContent.set(chunk, offset);
offset += chunk.length;
}
if (process.env.WTTP_SITE_DEBUG)
console.log(`✅ Fetched ${combinedContent.length} bytes`);
return { response: locateResponse, content: combinedContent };
}
return { response: locateResponse, content: undefined };
}
}
//# sourceMappingURL=fetchResource.js.map