///
///
///
///
import "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/3b80d99d9f86950c913035044105257b9fd6f3a9/types/tampermonkey/index.d.ts";
const parseHeaders = (rawHeaders: string) =>
new Headers(
rawHeaders
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
.replace(/\r?\n[\t ]+/g, " ")
.split(/\r\n|\r|\n/)
.flatMap((header) => {
const [key, value] = header.split(":").map((text) => text.trim());
if (!key) return [];
return [[key, value]] as [string, string][];
}),
);
/** CORSを突破してfetchする
*
* interfaceは`fetch`と同一
*/
export const GM_fetch = (
input: RequestInfo | URL,
init?: RequestInit,
): Promise =>
new Promise((resolve, reject) => {
const headers = Object.fromEntries(
new Headers(
input instanceof Request ? input.headers : init?.headers,
)
.entries(),
);
if (input instanceof Request) {
headers.Referer = input.referrer;
headers["Referrer-Policy"] = input.referrerPolicy;
}
if (init?.referrer) {
headers.Referer = init.referrer;
}
if (init?.referrerPolicy) {
headers["Referrer-Policy"] = init.referrerPolicy;
}
const request = new Request(input, init);
if (request.signal?.aborted) {
reject(new DOMException("Aborted", "AbortError"));
return;
}
const { abort } = GM_xmlhttpRequest({
// @ts-ignore 多分変な文字列は入ってこないはず
method: request.method,
url: request.url,
headers,
// await request.text() でも同じことができるが、二度手間なのでinitから直接取り出す
...(init?.body ? ({ data: init.body }) : ({})),
anonymous: request.credentials === "omit",
// @ts-ignore streamも指定可能
responseType: "stream",
fetch: true,
onerror: () => {
reject(new TypeError("Network request failed"));
},
ontimeout: () => {
reject(new TypeError("Network request timeout"));
},
onabort: () => {
reject(new DOMException("Aborted", "AbortError"));
},
// fetchはHEADERS_RECEIVEDの段階でresolveするので、それにあわせる
// https://ja.javascript.info/fetch
onreadystatechange: (res) => {
switch (res.readyState) {
case 2: {
const response = new Response(res.response, {
status: res.status,
statusText: res.statusText,
headers: parseHeaders(res.responseHeaders),
});
// urlはconstructor経由で設定できない
Object.defineProperty(response, "url", { value: res.finalUrl });
resolve(response);
break;
}
case 4:
request.signal?.removeEventListener?.("abort", abort);
break;
default:
break;
}
},
});
request.signal?.addEventListener?.("abort", abort);
});