UNPKG

wttp-handler

Version:

WTTP handler for fetching data from WTTP sites

301 lines 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WTTPHandler = void 0; exports.getChainId = getChainId; const core_1 = require("@wttp/core"); const ethers_1 = require("ethers"); const _1 = require("."); const MAX_REDIRECTS = 30; function getChainId(alias) { const aliases = { // String aliases "localhost": 31337, "sepolia": 11155111, "testnet": 11155111, "ethereum": 1, "mainnet": 1, "eth": 1, "base": 8453, "polygon": 137, "matic": 137, "arbitrum": 42161, "arb": 42161, }; return aliases[alias] || parseInt(alias) || null; } class WTTPHandler { constructor(signer, defaultChain) { this.visited = []; this.signer = signer; this.defaultChain = getChainId(defaultChain || "") || core_1.config.defaultChain; } // not actually needed for read only operations // public getSite(site: string, chainId?: number, signer?: ethers.Signer): IBaseWTTPSite { // chainId = chainId || this.defaultChain; // signer = this.connectProvider(chainId, signer); // return IBaseWTTPSite__factory.connect(site, signer); // } getGateway(chainId, signer, gateway) { chainId = chainId || this.defaultChain; signer = this.connectProvider(chainId, signer); gateway = gateway || core_1.config.chains[chainId].gateway; return core_1.IWTTPGateway__factory.connect(gateway, signer); } connectProvider(chainId, signer, rpc) { chainId = chainId || this.defaultChain; signer = signer || this.signer || ethers_1.ethers.Wallet.createRandom(); rpc = rpc || core_1.config.chains[chainId].rpcsList[0]; return signer.connect(new ethers_1.ethers.JsonRpcProvider(rpc)); } formatResponse(response, method) { let status; let head; let headers; let structure; let body; if (method === core_1.Method.OPTIONS) { status = Number(response.status); headers = { "Allowed-Methods": (0, core_1.bitmaskToMethods)(Number(response.allow)).join(", "), }; body = ""; return { status, headers, body, }; } else if (method === core_1.Method.HEAD) { head = response; body = ""; } else if (method === core_1.Method.LOCATE) { const locateResponse = response; head = locateResponse.locate.head; structure = locateResponse.structure; body = JSON.stringify(structure, (key, value) => typeof value === 'bigint' ? value.toString() : value); } else if (method === core_1.Method.GET) { const getResponse = response; head = getResponse.head; structure = getResponse.body.sizes; body = head.metadata.properties.charset == "0x7556" || head.metadata.properties.charset == "0x7508" ? ethers_1.ethers.toUtf8String(getResponse.body.data) : ethers_1.ethers.getBytes(getResponse.body.data); } if (head) { status = Number(head.headerInfo.redirect.code || head.status); headers = { "Content-Length": method === core_1.Method.HEAD ? head.metadata.size.toString() : structure?.totalSize.toString() || "0", "Content-Type": `${(0, core_1.decodeMimeType)(head.metadata.properties.mimeType)}; charset=${(0, core_1.decodeCharset)(head.metadata.properties.charset)}` || "", "Content-Encoding": (0, core_1.decodeEncoding)(head.metadata.properties.encoding) || "", "Content-Language": (0, core_1.decodeLanguage)(head.metadata.properties.language) || "", "Content-Version": head.metadata.version.toString(), "Last-Modified": head.metadata.lastModified.toString(), "ETag": head.etag.toString(), "Cache-Control": head.headerInfo.cache.preset.toString(), "Immutable-Flag": head.headerInfo.cache.immutableFlag.toString(), "Cache-Custom": head.headerInfo.cache.custom.toString(), "CORS-Preset": head.headerInfo.cors.preset.toString(), "CORS-Custom": head.headerInfo.cors.custom.toString(), "Allow-Origin": head.headerInfo.cors.origins.toString(), "Allow-Methods": (0, core_1.bitmaskToMethods)(Number(head.headerInfo.cors.methods)).join(", "), "Location": head.headerInfo.redirect.location.toString(), // "Content-Range": we need the request struct to get the response range }; } else { throw new Error("Head not found in response"); } return { status, headers, body: body || "", }; } async fetch(url, options) { const wurl = new core_1.wURL(url); const chainId = getChainId(wurl.alias) || this.defaultChain; const gateway = this.getGateway(chainId, options?.signer, options?.gateway); const fetchHostname = wurl.hostname.endsWith(".contractaddress0x") ? wurl.hostname.replace(".contractaddress0x", "") : wurl.hostname; const siteAddress = await (0, core_1.getHostAddress)(fetchHostname); let response; options = options || {}; if (options.method === undefined) { options.method = core_1.Method.GET; } if (options.redirect === undefined) { options.redirect = "follow"; } if (options.method === core_1.Method.OPTIONS) { try { const optionsResponse = await (0, _1.wttpOPTIONS)(gateway, siteAddress, wurl.pathname); response = this.formatResponse(optionsResponse, core_1.Method.OPTIONS); } catch (error) { if (error instanceof Error) { if (chainId == 11155111 && error.message.includes("execution reverted")) { const simpleResponse = { status: 404, headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else if (error.message.includes(" _4")) { const simpleResponse = { status: Number(error.message.split(" _")[1].slice(0, 2)), headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else { throw new Error("OPTIONS error: " + error.message); // error; } } } } else if (options.method === core_1.Method.HEAD) { try { const headResponse = await (0, _1.wttpHEAD)(gateway, siteAddress, wurl.pathname, options?.headers); response = this.formatResponse(headResponse, core_1.Method.HEAD); } catch (error) { if (error instanceof Error) { if (chainId == 11155111 && error.message.includes("execution reverted")) { const simpleResponse = { status: 404, headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else if (error.message.includes(" _4")) { const simpleResponse = { status: Number(error.message.split(" _")[1].slice(0, 2)), headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else { throw new Error("HEAD error: " + error.message); // error; } } } } else if (options.method === core_1.Method.LOCATE) { try { const locateResponse = await (0, _1.wttpLOCATE)(gateway, siteAddress, wurl.pathname, options?.headers); response = this.formatResponse(locateResponse, core_1.Method.LOCATE); } catch (error) { if (error instanceof Error) { if (chainId == 11155111 && error.message.includes("execution reverted")) { const simpleResponse = { status: 404, headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else if (error.message.includes(" _4")) { const simpleResponse = { status: Number(error.message.split(" _")[1].slice(0, 2)), headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else { throw new Error("LOCATE error: " + error.message); // error; } } } } else if (options.method === core_1.Method.GET) { try { const getResponse = await (0, _1.wttpGET)(gateway, siteAddress, wurl.pathname, options?.headers); response = this.formatResponse(getResponse, core_1.Method.GET); } catch (error) { if (error instanceof Error) { if (chainId == 11155111 && error.message.includes("execution reverted")) { const simpleResponse = { status: 404, headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else if (error.message.includes(" _4")) { const simpleResponse = { status: Number(error.message.split(" _")[1].slice(0, 2)), headers: {}, body: "", }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } else { throw new Error("GET error: " + error.message); // error; } } } } if (!response) { throw new Error(`Unsupported method: ${options?.method}`); } if (this.isRedirect(response.status) && options.redirect === "follow") { this.visited.push(wurl.toString()); const absolutePath = this.getAbsolutePath(response.headers.Location, wurl); if (this.visited.includes(absolutePath)) { const simpleResponse = { status: 508, headers: {}, body: "LOOP_DETECTED: " + this.visited.join(", "), }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } if (this.visited.length > MAX_REDIRECTS) { const simpleResponse = { status: 310, headers: {}, body: "TOO_MANY_REDIRECTS: " + this.visited.join(", "), }; return this.createResponse(simpleResponse.body, simpleResponse.status, simpleResponse.headers); } return await this.fetch(new core_1.wURL(response.headers.Location, wurl), { method: options.method, headers: options?.headers, signer: options?.signer, gateway: gateway.target.toString(), }); } if (this.isRedirect(response.status) && options.redirect === "error") { throw new Error(`Redirect error: ${response.status} Redirect to ${response.headers.Location}`); } return this.createResponse(response.body, response.status, response.headers); } createResponse(body, status, headers) { // Handle null body status codes according to Fetch spec if (status === 204 || status === 304 || (status >= 100 && status < 200)) { return new Response(null, { status, headers }); } return new Response(body, { status, headers }); } isRedirect(status) { return status === 301 || status === 302 || status === 303 || status === 307 || status === 308; } getAbsolutePath(url, base) { return new core_1.wURL(url, base).toString(); } } exports.WTTPHandler = WTTPHandler; //# sourceMappingURL=handler.js.map