UNPKG

@trim21/gm-fetch

Version:
169 lines (164 loc) 5.7 kB
var GM_fetch = (function () { 'use strict'; function parseRawHeaders(h) { const s = h.trim(); if (!s) { return new Headers(); } const array = s.split("\r\n").map((value) => { let s = value.split(":"); return [s[0].trim(), s[1].trim()]; }); return new Headers(array); } function parseGMResponse(req, res) { // workaround TamperMonkey bug(?) where sometimes response is string despite responseType being "blob" const headers = parseRawHeaders(res.responseHeaders); const body = typeof res.response === "string" ? new Blob([res.response], { type: headers.get("Content-Type") || "text/plain" }) : res.response; return new ResImpl(body, { statusCode: res.status, statusText: res.statusText, headers, finalUrl: res.finalUrl, redirected: res.finalUrl === req.url, }); } class ResImpl { constructor(body, init) { this.rawBody = body; this.init = init; this.body = body.stream(); const { headers, statusCode, statusText, finalUrl, redirected } = init; this.headers = headers; this.status = statusCode; this.statusText = statusText; this.url = finalUrl; this.type = "basic"; this.redirected = redirected; this._bodyUsed = false; } get bodyUsed() { return this._bodyUsed; } get ok() { return this.status < 300; } arrayBuffer() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'arrayBuffer' on 'Response': body stream already read"); } this._bodyUsed = true; return this.rawBody.arrayBuffer(); } blob() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'blob' on 'Response': body stream already read"); } this._bodyUsed = true; // `slice` will use empty string as default value, so need to pass all arguments. return Promise.resolve(this.rawBody.slice(0, this.rawBody.size, this.rawBody.type)); } clone() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'clone' on 'Response': body stream already read"); } return new ResImpl(this.rawBody, this.init); } formData() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'formData' on 'Response': body stream already read"); } this._bodyUsed = true; return this.rawBody.text().then(decode); } async json() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'json' on 'Response': body stream already read"); } this._bodyUsed = true; return JSON.parse(await this.rawBody.text()); } text() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'text' on 'Response': body stream already read"); } this._bodyUsed = true; return this.rawBody.text(); } async bytes() { if (this.bodyUsed) { throw new TypeError("Failed to execute 'bytes' on 'Response': body stream already read"); } this._bodyUsed = true; return new Uint8Array(await this.rawBody.arrayBuffer()); } } function decode(body) { const form = new FormData(); body .trim() .split("&") .forEach(function (bytes) { if (bytes) { const split = bytes.split("="); const name = split.shift()?.replace(/\+/g, " "); const value = split.join("=").replace(/\+/g, " "); form.append(decodeURIComponent(name), decodeURIComponent(value)); } }); return form; } async function GM_fetch(input, init) { const request = new Request(input, init); let data; if (init?.body) { data = await request.text(); } return await XHR(request, init, data); } function XHR(request, init, data) { return new Promise((resolve, reject) => { if (request.signal && request.signal.aborted) { return reject(new DOMException("Aborted", "AbortError")); } GM.xmlHttpRequest({ url: request.url, method: gmXHRMethod(request.method.toUpperCase()), headers: Object.fromEntries(new Headers(init?.headers).entries()), data: data, responseType: "blob", onload(res) { try { resolve(parseGMResponse(request, res)); } catch (e) { reject(e); } }, onabort() { reject(new DOMException("Aborted", "AbortError")); }, ontimeout() { reject(new TypeError("Network request failed, timeout")); }, onerror(err) { reject(new TypeError("Failed to fetch: " + err.finalUrl)); }, }); }); } const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "TRACE", "OPTIONS", "CONNECT"]; // a ts type helper to narrow type function includes(array, element) { return array.includes(element); } function gmXHRMethod(method) { if (includes(httpMethods, method)) { return method; } throw new Error(`unsupported http method ${method}`); } return GM_fetch; })();