gm-fetch für Firefox gefixt
This commit is contained in:
		| @@ -14,8 +14,6 @@ module.exports = { | ||||
|   ], | ||||
|   require: [ | ||||
|     'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js', | ||||
|     `https://cdn.jsdelivr.net/npm/axios@${pkg.dependencies.axios}/dist/axios.min.js`, | ||||
|     `https://cdn.jsdelivr.net/npm/axios-userscript-adapter@${pkg.dependencies['axios-userscript-adapter']}/dist/axiosGmxhrAdapter.min.js` | ||||
|   ], | ||||
|   grant: [ | ||||
|     'GM.xmlHttpRequest', | ||||
|   | ||||
							
								
								
									
										11
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,14 +1,13 @@ | ||||
| { | ||||
|   "name": "3cx-tapi", | ||||
|   "version": "9.1.1", | ||||
|   "version": "9.2.0", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "3cx-tapi", | ||||
|       "version": "9.1.1", | ||||
|       "version": "9.2.0", | ||||
|       "dependencies": { | ||||
|         "@trim21/gm-fetch": "^0.1.15", | ||||
|         "chrono-node": "^2.7.7" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
| @@ -1864,12 +1863,6 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@trim21/gm-fetch": { | ||||
|       "version": "0.1.15", | ||||
|       "resolved": "https://registry.npmjs.org/@trim21/gm-fetch/-/gm-fetch-0.1.15.tgz", | ||||
|       "integrity": "sha512-197WkYDd1XY8eDNWMBWSrAV77kCJzvh8EOnKSgIoHDXTb1AFLDN1iFnK/Y2x4XTOUDdOfUQd25rKUlVGWmQYhQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/estree": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", | ||||
|   | ||||
| @@ -24,8 +24,7 @@ | ||||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "chrono-node": "^2.7.7", | ||||
|     "@trim21/gm-fetch": "^0.1.15" | ||||
|     "chrono-node": "^2.7.7" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/greasemonkey": "^4.0.7", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as chrono from 'chrono-node' | ||||
| import { TapiContact } from './tapi-contact' | ||||
| import { extractNumber } from './utils' | ||||
| import GM_fetch from '@trim21/gm-fetch' | ||||
| import GM_fetch from './gm-fetch' | ||||
|  | ||||
| export class CallHistory { | ||||
|   private callerIds: { [number: string]: TapiContact } = {} | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import GM_fetch from '@trim21/gm-fetch' | ||||
| import GM_fetch from './gm-fetch' | ||||
| import { TapiContact } from './tapi-contact' | ||||
| import { extractNumber } from './utils' | ||||
|  | ||||
|   | ||||
							
								
								
									
										55
									
								
								src/gm-fetch/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/gm-fetch/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| import { parseGMResponse } from "./utils"; | ||||
|  | ||||
| export default async function GM_fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> { | ||||
|   const request = new Request(input, init); | ||||
|  | ||||
|   let data: string | undefined; | ||||
|   if (init?.body) { | ||||
|     data = await request.text(); | ||||
|   } | ||||
|  | ||||
|   return await XHR(request, init, data); | ||||
| } | ||||
|  | ||||
| function XHR(request: Request, init: RequestInit | undefined, data: string | undefined): Promise<Response> { | ||||
|   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) { | ||||
|         resolve(parseGMResponse(request, res)); | ||||
|       }, | ||||
|       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"] as const; | ||||
|  | ||||
| // a ts type helper to narrow type | ||||
| function includes<T extends U, U>(array: ReadonlyArray<T>, element: U): element is T { | ||||
|   return array.includes(element as T); | ||||
| } | ||||
|  | ||||
| function gmXHRMethod(method: string): (typeof httpMethods)[number] { | ||||
|   if (includes(httpMethods, method)) { | ||||
|     return method; | ||||
|   } | ||||
|  | ||||
|   throw new Error(`unsupported http method ${method}`); | ||||
| } | ||||
							
								
								
									
										145
									
								
								src/gm-fetch/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/gm-fetch/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| export function parseRawHeaders(h: string): Headers { | ||||
|   const s = h.trim(); | ||||
|   if (!s) { | ||||
|     return new Headers(); | ||||
|   } | ||||
|  | ||||
|   const array: [string, string][] = s.split("\r\n").map((value) => { | ||||
|     let s = value.split(":"); | ||||
|     return [s[0].trim(), s[1].trim()]; | ||||
|   }); | ||||
|  | ||||
|   return new Headers(array); | ||||
| } | ||||
|  | ||||
| export function parseGMResponse(req: Request, res: GM.Response<any>): Response { | ||||
|   return new ResImpl(res.response as Blob, { | ||||
|     statusCode: res.status, | ||||
|     statusText: res.statusText, | ||||
|     headers: parseRawHeaders(res.responseHeaders), | ||||
|     finalUrl: res.finalUrl, | ||||
|     redirected: res.finalUrl === req.url, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| interface ResInit { | ||||
|   redirected: boolean; | ||||
|   headers: Headers; | ||||
|   statusCode: number; | ||||
|   statusText: string; | ||||
|   finalUrl: string; | ||||
| } | ||||
|  | ||||
| class ResImpl implements Response { | ||||
|   private _bodyUsed: boolean; | ||||
|   private readonly rawBody: Blob; | ||||
|   private readonly init: ResInit; | ||||
|  | ||||
|   readonly body: ReadableStream<Uint8Array> | null; | ||||
|   readonly headers: Headers; | ||||
|   readonly redirected: boolean; | ||||
|   readonly status: number; | ||||
|   readonly statusText: string; | ||||
|   readonly type: ResponseType; | ||||
|   readonly url: string; | ||||
|  | ||||
|   constructor(body: Blob, init: ResInit) { | ||||
|     this.rawBody = body; | ||||
|     this.init = init; | ||||
|  | ||||
|     //this.body = toReadableStream(body); | ||||
|     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(): boolean { | ||||
|     return this._bodyUsed; | ||||
|   } | ||||
|  | ||||
|   get ok(): boolean { | ||||
|     return this.status < 300; | ||||
|   } | ||||
|  | ||||
|   arrayBuffer(): Promise<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(): Promise<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(): Response { | ||||
|     if (this.bodyUsed) { | ||||
|       throw new TypeError("Failed to execute 'clone' on 'Response': body stream already read"); | ||||
|     } | ||||
|     return new ResImpl(this.rawBody, this.init); | ||||
|   } | ||||
|  | ||||
|   formData(): Promise<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(): Promise<any> { | ||||
|     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(): Promise<string> { | ||||
|     if (this.bodyUsed) { | ||||
|       throw new TypeError("Failed to execute 'text' on 'Response': body stream already read"); | ||||
|     } | ||||
|     this._bodyUsed = true; | ||||
|     return this.rawBody.text(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function decode(body: string) { | ||||
|   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; | ||||
| } | ||||
|  | ||||
| /* | ||||
| function toReadableStream(value: Blob) { | ||||
|   return new ReadableStream({ | ||||
|     start(controller) { | ||||
|       controller.enqueue(value); | ||||
|       controller.close(); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| */ | ||||
| @@ -2,7 +2,7 @@ import './search.css' | ||||
| import { TapiContact } from './tapi-contact' | ||||
| import { debounce } from './debounce' | ||||
| import { fireChangeEvents } from './utils' | ||||
| import GM_fetch from '@trim21/gm-fetch' | ||||
| import GM_fetch from './gm-fetch' | ||||
|  | ||||
| export class Search { | ||||
|   private currentSearchText = '' | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import GM_fetch from './gm-fetch'; | ||||
| import './status.css'; | ||||
| import { ZcStatus } from './zc-status'; | ||||
| import GM_fetch from "@trim21/gm-fetch"; | ||||
|  | ||||
| declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user