Repository: ProvableHQ/sdk (language: rust)

Issue:
Global fetch method impacts other SDKs
## Global fetch method impacts other SDKs

### The Problem

The SDK calls `fetch` to load the wasm file, but nodejs' native `fetch` method cannot read the file so the global fetch method has been modified to cater for this. This affects other sdks that run in the same environment as this sdk which creates issues for certain companies to integrate Aleo using this sdk.

### Potential Solution

To get around this issue the [global fetch polyfill](https://github.com/ProvableHQ/sdk/blob/mainnet/sdk/src/polyfill/fetch.ts) can possibly be renamed to something else, for example `new_fetch()`. All the current uses of `fetch()` within the sdk would be renamed to `new_fetch()`. 

Like this the global fetch polyfill would not impact other sdks used within the same environment.




Discussion / hints:
@Pauan here was the original issue mentioned.

Fix patch:
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/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<boolean> | null = null;
+
+async function checkFetch() {
+    try {
+        await oldFetch(new URL("file:"));
+        return true;
+
+    } catch (e) {
+        return false;
+    }
+}
+
+async function supportsFetch(): Promise<boolean> {
+    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<Response> {
     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<Transferable>): void;
-    postMessage(message: any, options?: StructuredSerializeOptions | undefined): void;
-    postMessage(value: any, transfer: any) {
-        this._worker.postMessage(value, transfer);
-    }
+        postMessage(message: any, transfer: Array<Transferable>): 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<any>) {
+            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"


Test patch:
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",