/// /// /// /// 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 request = new Request(input, init); const headers = Object.fromEntries(request.headers.entries()); if (init?.referrer) { headers.Referer = init.referrer; } 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", 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); });