From ba001d9d01ad0dc0c4cd2e18473cbea43f23e94d Mon Sep 17 00:00:00 2001 From: OrFrederick Date: Wed, 17 Jun 2026 19:39:11 +0200 Subject: [PATCH 1/3] fix: bind fetch to globalThis to avoid Illegal invocation when bundled --- src/internal/call.test.ts | 15 +++++++++++++++ src/internal/call.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/internal/call.test.ts b/src/internal/call.test.ts index 93d2003..85ba6b2 100644 --- a/src/internal/call.test.ts +++ b/src/internal/call.test.ts @@ -41,3 +41,18 @@ test("Should throw on bad HTTP status", async () => { await expect(call({ method: "Test" })).rejects.toThrow("HTTP status 500"); }); + +test("Should invoke fetch bound to globalThis (guards against Illegal invocation when bundled)", async () => { + let receivedThis: unknown = "unset"; + vi.mocked(fetch).mockImplementation(function (this: unknown) { + receivedThis = this; + return Promise.resolve({ + ok: true, + text: async () => JSON.stringify({ result: "ok" }), + }) as never; + }); + + await call({ method: "Test" }); + + expect(receivedThis).toBe(globalThis); +}); diff --git a/src/internal/call.ts b/src/internal/call.ts index ec4101a..e131128 100644 --- a/src/internal/call.ts +++ b/src/internal/call.ts @@ -10,7 +10,7 @@ export function getCall(options: GeotabOptions) { const parseJSON = options.parseJSON ?? parseJsonWithDates; return async function call({ method, params, signal }: Call) { - const httpResponse = await fetch(url, { + const httpResponse = await fetch.call(globalThis, url, { body: JSON.stringify({ id: nanoid(), method, From f1c6e5121e59e83ebee8a6a26317538bbcd750d3 Mon Sep 17 00:00:00 2001 From: OrFrederick Date: Wed, 17 Jun 2026 19:41:39 +0200 Subject: [PATCH 2/3] fix: strip signal/callbacks from serialized ExecuteMultiCall payload --- src/internal/queue.test.ts | 21 +++++++++++++++++++++ src/internal/queue.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/internal/queue.test.ts b/src/internal/queue.test.ts index b24daec..b8e7d99 100644 --- a/src/internal/queue.test.ts +++ b/src/internal/queue.test.ts @@ -180,6 +180,27 @@ test("First request should define timeout", async () => { await expect(call2).resolves.toBe("test2"); }); +test("Should serialize only method+params into ExecuteMultiCall (no signal/resolve/reject)", async () => { + getResult.mockResolvedValueOnce(["a", "b"]); + const controller = new AbortController(); + + const c1 = callQueued({ method: "Test", params: { x: 1 }, signal: controller.signal }); + const c2 = callQueued({ method: "Test", params: { y: 2 } }); + + vi.advanceTimersToNextTimer(); + await Promise.all([c1, c2]); + + expect(getResult).toHaveBeenCalledWith({ + method: "ExecuteMultiCall", + params: { + calls: [ + { method: "Test", params: { x: 1 } }, + { method: "Test", params: { y: 2 } }, + ], + }, + }); +}); + function noop() { // ignore } diff --git a/src/internal/queue.ts b/src/internal/queue.ts index ace3ce7..666dd5f 100644 --- a/src/internal/queue.ts +++ b/src/internal/queue.ts @@ -92,7 +92,10 @@ export function queue(options: GeotabOptions) { return [await next(calls[0])]; } - return await next({ method: "ExecuteMultiCall", params: { calls } }); + return await next({ + method: "ExecuteMultiCall", + params: { calls: calls.map(({ method, params }) => ({ method, params })) }, + }); } return async function middleware(call: Call) { From 1c121b9a78078c335361b7b3d97dc09417bd4e97 Mon Sep 17 00:00:00 2001 From: OrFrederick Date: Wed, 17 Jun 2026 19:42:46 +0200 Subject: [PATCH 3/3] chore: release 2.4.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f1f4f79 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 2.4.0 + +### Fixed + +- **Illegal invocation on first API call when bundled.** `fetch` is now invoked + with `globalThis` as its receiver (`fetch.call(globalThis, …)`), preventing the + `TypeError: Illegal invocation` thrown by minified production builds on + Authenticate/login. +- **`signal` serialized into `ExecuteMultiCall`.** The batched-call payload no + longer copies queue-entry internals (`signal`, `resolve`, `reject`) into the + JSON-RPC params; only `method` and `params` are sent per call. diff --git a/package.json b/package.json index 3b534e2..865dbf0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "module": "./esm/index.js", "author": "Fairfleet Gmbh", "license": "MIT", - "version": "2.3.0", + "version": "2.4.0", "keywords": [ "geotab", "api",