diff --git a/sdk/package.json b/sdk/package.json index 06e198d5d..7dcbd7d6e 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -51,7 +51,8 @@ "comlink": "^4.4.2", "core-js": "^3.40.0", "mime": "^4.0.6", - "sync-request": "^6.1.0" + "sync-request": "^6.1.0", + "xmlhttprequest-ssl": "^3.1.0" }, "devDependencies": { "@rollup/plugin-replace": "^6.0.2", diff --git a/sdk/rollup.config.js b/sdk/rollup.config.js index edc98b668..90b873407 100644 --- a/sdk/rollup.config.js +++ b/sdk/rollup.config.js @@ -28,6 +28,7 @@ export default networks.map((network) => { "node:crypto", "mime/lite", "sync-request", + "xmlhttprequest-ssl", // Used by the SDK "comlink", diff --git a/sdk/rollup.test.js b/sdk/rollup.test.js index 9d3cc81f8..487707588 100644 --- a/sdk/rollup.test.js +++ b/sdk/rollup.test.js @@ -35,6 +35,7 @@ export default networks.map((network) => { "node:crypto", "mime/lite", "sync-request", + "xmlhttprequest-ssl", // Used by the SDK "comlink", diff --git a/sdk/src/polyfill/fetch.ts b/sdk/src/polyfill/fetch.ts index 5651eeb74..8a26d272a 100644 --- a/sdk/src/polyfill/fetch.ts +++ b/sdk/src/polyfill/fetch.ts @@ -4,13 +4,35 @@ import $mime from "mime/lite"; const oldFetch = globalThis.fetch; + +let supports: Promise | null = null; + +async function checkFetch() { + try { + await oldFetch(new URL("file:")); + return true; + + } catch (e) { + return false; + } +} + +async function supportsFetch(): Promise { + if (supports === null) { + supports = checkFetch(); + } + + return await supports; +} + + // We always polyfill fetch because Node's fetch doesn't support file URLs. (globalThis.fetch as any) = async function (resource: URL | RequestInfo, options: RequestInit | undefined): Promise { const request = new Request(resource, options); const url = new URL(request.url); - if (url.protocol === "file:") { + if (!(await supportsFetch()) && url.protocol === "file:") { const readStream = $fs.createReadStream(url); const headers: HeadersInit = {}; diff --git a/sdk/src/polyfill/worker.ts b/sdk/src/polyfill/worker.ts index 7e05d61a9..8ef0edba5 100644 --- a/sdk/src/polyfill/worker.ts +++ b/sdk/src/polyfill/worker.ts @@ -10,90 +10,92 @@ if (globalThis.navigator == null) { } as Navigator; } -globalThis.Worker = class Worker extends EventTarget { - private _worker: import("node:worker_threads").Worker; +if (globalThis.Worker == null) { + globalThis.Worker = class Worker extends EventTarget { + private _worker: import("node:worker_threads").Worker; - constructor(url: string | URL, options?: WorkerOptions | undefined) { - super(); + constructor(url: string | URL, options?: WorkerOptions | undefined) { + super(); - if (url instanceof URL) { - if (url.protocol !== "file:") { - throw new Error("Worker only supports file: URLs"); + if (url instanceof URL) { + if (url.protocol !== "file:") { + throw new Error("Worker only supports file: URLs"); + } + + url = url.href; + + } else { + throw new Error("Filepaths are unreliable, use `new URL(\"...\", import.meta.url)` instead."); } - url = url.href; + if (!options || options.type !== "module") { + throw new Error("Workers must use \`type: \"module\"\`"); + } - } else { - throw new Error("Filepaths are unreliable, use `new URL(\"...\", import.meta.url)` instead."); + const code = ` + import("node:worker_threads") + .then(({ workerData }) => { + return import(workerData.polyfill) + .then(() => import(workerData.url)) + }) + .catch((e) => { + // TODO maybe it should send a message to the parent? + console.error(e.stack); + }); + `; + + this._worker = new $worker.Worker(code, { + eval: true, + workerData: { + url, + polyfill: new URL("node-polyfill.js", import.meta.url).href, + }, + }); + + this._worker.on("message", (data) => { + this.dispatchEvent(new MessageEvent("message", { data })); + }); + + this._worker.on("messageerror", (error) => { + throw new Error("UNIMPLEMENTED"); + }); + + this._worker.on("error", (error) => { + // TODO attach the error to the event somehow + const event = new Event("error"); + this.dispatchEvent(event); + }); } - if (!options || options.type !== "module") { - throw new Error("Workers must use \`type: \"module\"\`"); + set onmessage(f: () => void) { + throw new Error("UNIMPLEMENTED"); } - const code = ` - import("node:worker_threads") - .then(({ workerData }) => { - return import(workerData.polyfill) - .then(() => import(workerData.url)) - }) - .catch((e) => { - // TODO maybe it should send a message to the parent? - console.error(e.stack); - }); - `; - - this._worker = new $worker.Worker(code, { - eval: true, - workerData: { - url, - polyfill: new URL("node-polyfill.js", import.meta.url).href, - }, - }); - - this._worker.on("message", (data) => { - this.dispatchEvent(new MessageEvent("message", { data })); - }); - - this._worker.on("messageerror", (error) => { + set onmessageerror(f: () => void) { throw new Error("UNIMPLEMENTED"); - }); - - this._worker.on("error", (error) => { - // TODO attach the error to the event somehow - const event = new Event("error"); - this.dispatchEvent(event); - }); - } - - set onmessage(f: () => void) { - throw new Error("UNIMPLEMENTED"); - } - - set onmessageerror(f: () => void) { - throw new Error("UNIMPLEMENTED"); - } + } - set onerror(f: () => void) { - throw new Error("UNIMPLEMENTED"); - } + set onerror(f: () => void) { + throw new Error("UNIMPLEMENTED"); + } - postMessage(message: any, transfer: Array): void; - postMessage(message: any, options?: StructuredSerializeOptions | undefined): void; - postMessage(value: any, transfer: any) { - this._worker.postMessage(value, transfer); - } + postMessage(message: any, transfer: Array): void; + postMessage(message: any, options?: StructuredSerializeOptions | undefined): void; + postMessage(value: any, transfer: any) { + this._worker.postMessage(value, transfer); + } - terminate() { - this._worker.terminate(); - } + terminate() { + this._worker.terminate(); + } - // This is Node-specific, it allows the process to exit - // even if the Worker is still running. - unref() { - this._worker.unref(); - } -}; + // This is Node-specific, it allows the process to exit + // even if the Worker is still running. + unref() { + this._worker.unref(); + } + }; +} if (!$worker.isMainThread) { diff --git a/sdk/src/polyfill/xmlhttprequest.ts b/sdk/src/polyfill/xmlhttprequest.ts index 616d9d91b..8bc6d5f9e 100644 --- a/sdk/src/polyfill/xmlhttprequest.ts +++ b/sdk/src/polyfill/xmlhttprequest.ts @@ -1,154 +1,67 @@ +// @ts-ignore +import $xmlhttprequest from "xmlhttprequest-ssl"; import $request from "sync-request"; - -globalThis.XMLHttpRequest = class extends EventTarget implements XMLHttpRequest { - public static readonly UNSENT = 0; - public static readonly OPENED = 1; - public static readonly HEADERS_RECEIVED = 2; - public static readonly LOADING = 3; - public static readonly DONE = 4; - - public readonly UNSENT = XMLHttpRequest.UNSENT; - public readonly OPENED = XMLHttpRequest.OPENED; - public readonly HEADERS_RECEIVED = XMLHttpRequest.HEADERS_RECEIVED; - public readonly LOADING = XMLHttpRequest.LOADING; - public readonly DONE = XMLHttpRequest.DONE; - - public responseType!: XMLHttpRequestResponseType; - public withCredentials!: boolean; - public timeout!: number; - - public readonly readyState!: number; - public readonly response!: ArrayBuffer | Blob | Document | string | null; - public readonly responseText!: string; - public readonly responseURL!: string; - public readonly responseXML!: Document | null; - public readonly status!: number; - public readonly statusText!: string; - public readonly upload!: XMLHttpRequestUpload; - - private _url!: string | URL | null; - private _mime!: string; - - constructor() { - super(); - - this._reset(); - - this._mime = "text/xml"; - } - - private _reset() { - (this as any).readyState = XMLHttpRequest.UNSENT; - (this as any).response = null; - (this as any).responseText = ""; - (this as any).responseType = ""; - (this as any).responseURL = ""; - (this as any).responseXML = null; - (this as any).status = 0; - (this as any).statusText = ""; - (this as any).timeout = 0; - (this as any).upload = null; - (this as any).withCredentials = false; - - this._url = null; - } - - private _success() { - (this as any).readyState = XMLHttpRequest.DONE; - (this as any).status = 200; - (this as any).statusText = "OK"; - } - - public set onabort(value: () => void) { - throw new Error("Not implemented"); - } - - public set onerror(value: () => void) { - throw new Error("Not implemented"); - } - - public set onreadystatechange(value: () => void) { - throw new Error("Not implemented"); - } - - public set onloadstart(value: () => void) { - throw new Error("Not implemented"); - } - - public set onload(value: () => void) { - throw new Error("Not implemented"); - } - - public set onloadend(value: () => void) { - throw new Error("Not implemented"); - } - - public set onprogress(value: () => void) { - throw new Error("Not implemented"); - } - - public set ontimeout(value: () => void) { - throw new Error("Not implemented"); - } - - public abort() { - throw new Error("Not implemented"); - } - - public overrideMimeType(mime: string) { - this._mime = mime; - } - - public getResponseHeader(): string | null { - throw new Error("Not implemented"); - } - - public getAllResponseHeaders(): string { - throw new Error("Not implemented"); - } - - public setRequestHeader() { - throw new Error("Not implemented"); - } - - public open(method: string, url: string | URL, async: boolean = true, username?: string | null | undefined, password?: string | null | undefined): void { - if (async) { - throw new Error("Async XMLHttpRequest is not implemented yet"); - } - - if (method !== "GET") { - throw new Error("Non-GET requests are not implemented yet"); - } - - this._reset(); - - this._url = url; - } - - public send(body: null = null) { - if (body !== null) { - throw new Error("XMLHttpRequest send body is not implemented yet"); - } - - if (!this._url) { - throw new Error("You must call open before you call send"); - } - - const response = $request("GET", this._url, { - headers: { - "Content-Type": this._mime, +if (globalThis.XMLHttpRequest == null) { + globalThis.XMLHttpRequest = class extends $xmlhttprequest.XMLHttpRequest { + // We have to override the methods inside of the `constructor` + // because `xmlhttprequest-ssl` doesn't use a regular class, + // instead it defines all of the methods inside of the constructor. + constructor(...args: Array) { + super(...args); + + const open = (this as any).open; + const send = (this as any).send; + + let _async: boolean = true; + let _url: null | string = null; + let _mime: string = "text/xml"; + + function reset() { + _async = true; + _url = null; + _mime = "text/xml"; } - }); - const buffer = (response.body as Buffer).buffer as any; - - const responseText = new TextDecoder("iso-8859-5", { fatal: true }).decode(buffer); - - (this as any).response = (this as any).responseText = responseText; - - this._url = null; - - this._success(); - } -}; + (this as any).open = function (method: string, url: string, async: boolean, user?: string, password?: string) { + // Special behavior for synchronous requests + if (method === "GET" && !async && !user && !password) { + _async = false; + _url = url; + + // Default to the normal polyfill for async requests + } else { + reset(); + return open.call(this, method, url, async, user, password); + } + }; + + (this as any).send = function (data: any) { + if (_async) { + return send.call(this, data); + + // Use `sync-request` for synchronous requests. + } else { + const response = $request("GET", _url!, { + headers: { + "Content-Type": _mime, + } + }); + + const buffer = (response.body as Buffer).buffer as any; + + const responseText = new TextDecoder("iso-8859-5", { fatal: true }).decode(buffer); + + (this as any).status = 200; + (this as any).response = (this as any).responseText = responseText; + + reset(); + } + }; + + (this as any).overrideMimeType = function (mime: string) { + _mime = mime; + }; + } + } as any; +} diff --git a/yarn.lock b/yarn.lock index d5daba1ca..40da5b3fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2372,6 +2372,7 @@ __metadata: sinon: "npm:^19.0.2" sync-request: "npm:^6.1.0" typescript: "npm:^5.7.3" + xmlhttprequest-ssl: "npm:^3.1.0" languageName: unknown linkType: soft @@ -14090,6 +14091,13 @@ __metadata: languageName: node linkType: hard +"xmlhttprequest-ssl@npm:^3.1.0": + version: 3.1.0 + resolution: "xmlhttprequest-ssl@npm:3.1.0" + checksum: 10c0/66e8bd30adc90425bf71a3df82bf864ed854aebd0943ade0bf22717d119d47b7c913bae3df1f17c3aac6e72fe7588cd22c50cff9b48837a63b23c99024ebb9f1 + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8"