wttp-handler
Version:
WTTP handler for fetching data from WTTP sites
301 lines • 13.7 kB
JavaScript
"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