class CORSFetch {
_requestId = 1;
_config = {
include: [],
exclude: [],
request: {
proxy: undefined,
connectTimeout: undefined,
maxRedirections: undefined,
},
};
constructor() {
window.fetchNative = fetch.bind(window);
window.fetch = this.fetchCORS.bind(this);
window.fetchCORS = (input, init) => this.fetchCORS(input, init, true);
}
config(config) {
this._config = {
include: config.include || [],
exclude: config.exclude || [],
request: {
proxy: config.request?.proxy || this._config.request.proxy,
connectTimeout:
config.request?.connectTimeout || this._config.request.connectTimeout,
maxRedirections:
config.request?.maxRedirections ||
this._config.request.maxRedirections,
},
};
}
_matchesPattern(url, patterns) {
if (!patterns || patterns.length === 0) {
return false;
}
return patterns.some((pattern) => {
if (typeof pattern === "string") {
return url.includes(pattern);
} else if (pattern instanceof RegExp) {
return pattern.test(url);
}
return false;
});
}
_shouldUseCORSProxy(url) {
const { include, exclude } = this._config;
const isTauriProtocol = ["ipc", "asset"].some(
(protocol) =>
new RegExp(`^${protocol}://localhost/`).test(url) ||
new RegExp(`^http://${protocol}.localhost/`).test(url)
);
if (isTauriProtocol) {
return false;
}
if (exclude.length > 0 && this._matchesPattern(url, exclude)) {
return false;
}
if (include.length > 0) {
return this._matchesPattern(url, include);
}
return /^https?:\/\//i.test(url);
}
async fetchCORS(input, init, force = false) {
const _url = input instanceof Request ? input.url : input.toString();
if (!this._shouldUseCORSProxy(_url) && !force) {
return window.fetchNative(input, init);
}
return new Promise(async (resolve, reject) => {
const requestId = this._requestId++;
const maxRedirections =
init?.maxRedirections ?? this._config.request.maxRedirections;
const connectTimeout =
init?.connectTimeout ?? this._config.request.connectTimeout;
const proxy = init?.proxy ?? this._config.request.proxy;
if (init) {
delete init.maxRedirections;
delete init.connectTimeout;
delete init.proxy;
}
const signal = init?.signal;
const headers = !init?.headers
? []
: init.headers instanceof Headers
? Array.from(init.headers.entries())
: Array.isArray(init.headers)
? init.headers
: Object.entries(init.headers);
const mappedHeaders = headers.map(([name, val]) => [
name,
typeof val === "string" ? val : val.toString(),
]);
const req = new Request(input, init);
const buffer = await req.arrayBuffer();
const reqData = buffer.byteLength
? Array.from(new Uint8Array(buffer))
: null;
signal?.addEventListener("abort", async (e) => {
const error = e.target.reason;
this._invoke("plugin:cors-fetch|cancel_cors_request", {
requestId,
}).catch(() => {});
reject(error);
});
const {
status,
statusText,
url,
body,
headers: responseHeaders,
} = await this._invoke("plugin:cors-fetch|cors_request", {
request: {
requestId,
method: req.method,
url: req.url,
headers: mappedHeaders,
data: reqData,
maxRedirections,
connectTimeout,
proxy,
},
});
const res = new Response(
body instanceof ArrayBuffer && body.byteLength
? body
: body instanceof Array && body.length
? new Uint8Array(body)
: null,
{
headers: responseHeaders,
status,
statusText,
}
);
Object.defineProperty(res, "url", { value: url });
resolve(res);
});
}
_invoke(cmd, args, options) {
if ("__TAURI__" in window) {
return window.__TAURI_INTERNALS__.invoke(cmd, args, options);
}
}
}
(function () {
window.CORSFetch = new CORSFetch();
})();