diff --git a/src/lib/runtime.ts b/src/lib/runtime.ts index fd519d81e..434386a0b 100644 --- a/src/lib/runtime.ts +++ b/src/lib/runtime.ts @@ -31,7 +31,7 @@ export class BaseAPI { } this.middleware = configuration.middleware || []; - this.fetchApi = configuration.fetch || fetch; + this.fetchApi = configuration.fetch || globalThis.fetch.bind(globalThis); this.parseError = configuration.parseError; this.timeoutDuration = typeof configuration.timeoutDuration === 'number' ? configuration.timeoutDuration : 10000; diff --git a/src/utils.ts b/src/utils.ts index 24e250daa..5ebccec42 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,15 +1,39 @@ import { version } from './version.js'; +/* eslint-disable @typescript-eslint/ban-ts-comment */ +function detectRuntime() { + // Node.js + if (typeof process !== 'undefined' && process.versions?.node) { + return 'node'; + } + + // Cloudflare Workers + if (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') { + return 'cloudflare-workers'; + } + + // Deno + // @ts-ignore + if (typeof Deno !== 'undefined') { + return 'deno'; + } + + return 'unknown'; +} + /** * @private */ -export const generateClientInfo = () => ({ - name: 'node-auth0', - version: version, - env: { - node: process.version.replace('v', ''), - }, -}); +export const generateClientInfo = () => { + const runtime = detectRuntime(); + return { + name: 'node-auth0', + version: version, + env: { + [runtime]: process.version?.replace('v', '') ?? 'unknown', + }, + }; +}; /** * @private diff --git a/test/lib/runtime.test.ts b/test/lib/runtime.test.ts index 495647212..add7b6477 100644 --- a/test/lib/runtime.test.ts +++ b/test/lib/runtime.test.ts @@ -52,6 +52,34 @@ describe('Runtime', () => { clearInterval(interval); }); + it('should use globalThis.fetch bound to globalThis when fetch is not provided in configuration', () => { + // Mock globalThis.fetch to verify it's used + const originalFetch = globalThis.fetch; + let calledWithGlobalThis = false; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - Ignoring type errors for test purposes + globalThis.fetch = async function () { + //This is important for "workerd" the process used by cloudflare workers. + calledWithGlobalThis = this === globalThis; + return new Response(); + }; + + try { + const client = new TestClient({ + baseUrl: URL, + parseError, + }); + + // Call the fetchApi + (client as any).fetchApi('https://example.com'); + + expect(calledWithGlobalThis).toBe(true); + } finally { + // Restore the original fetch + globalThis.fetch = originalFetch; + } + }); + it('should retry 429 until getting a succesful response', async () => { const request = nock(URL, { encodedQueryParams: true }) .get('/clients') @@ -526,6 +554,50 @@ describe('Runtime for ManagementClient', () => { expect(request.isDone()).toBe(true); }); + /* eslint-disable @typescript-eslint/ban-ts-comment */ + it('should add the telemetry in workerd contexts', async () => { + const originalVersion = process.version; + const originalNodeVersion = process.versions.node; + const originalNavigator = globalThis.navigator; + try { + // Simulate a workerd context where process.version is not available + // @ts-ignore + delete process.version; + + // Simulate a workerd context where process.versions.node is not available + // @ts-ignore + delete process.versions.node; + + // @ts-ignore + Object.defineProperty(globalThis, 'navigator', { + value: { userAgent: 'Cloudflare-Workers' }, + configurable: true, + }); + + const clientInfo = utils.generateClientInfo(); + + expect(clientInfo).toEqual({ + name: 'node-auth0', + version: expect.any(String), + env: { + 'cloudflare-workers': 'unknown', + }, + }); + + expect(clientInfo.version).toMatch(/^\d+\.\d+\.\d+(?:-[\w.]+)?$/); + } finally { + // @ts-ignore + process.version = originalVersion; + // @ts-ignore + process.versions.node = originalNodeVersion; + //@ts-ignore + Object.defineProperty(globalThis, 'navigator', { + value: originalNavigator, + configurable: true, + }); + } + }); + it('should add custom telemetry when provided', async () => { const mockClientInfo = { name: 'test', version: '12', env: { node: '16' } };