diff --git a/examples/react18/package.json b/examples/react18/package.json index 2762b61b..c9e1a865 100644 --- a/examples/react18/package.json +++ b/examples/react18/package.json @@ -17,7 +17,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", - "typescript": "^4.4.2", + "typescript": "^5.8.3", "web-vitals": "^2.1.0" }, "devDependencies": { diff --git a/package.json b/package.json index 365361c6..0eb98725 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "rollup": "^4.22.4", "rollup-plugin-dts": "^6.1.1", "ts-jest": "^29.1.0", - "typescript": "^5.1.3" + "typescript": "^5.8.3" }, "config": { "commitizen": { diff --git a/packages/openfeature-server-provider/package.json b/packages/openfeature-server-provider/package.json index f74b2915..2e2637ec 100644 --- a/packages/openfeature-server-provider/package.json +++ b/packages/openfeature-server-provider/package.json @@ -7,7 +7,7 @@ "@openfeature/server-sdk": "^1.13.5", "@spotify-confidence/sdk": "workspace:*", "rollup": "4.24.0", - "typescript": "5.1.6" + "typescript": "^5.8.3" }, "peerDependencies": { "@openfeature/server-sdk": "^1.13.5", diff --git a/packages/openfeature-web-provider/package.json b/packages/openfeature-web-provider/package.json index cf6314c1..220dbeb2 100644 --- a/packages/openfeature-web-provider/package.json +++ b/packages/openfeature-web-provider/package.json @@ -10,7 +10,7 @@ "@openfeature/web-sdk": "^1.0.3", "@spotify-confidence/sdk": "workspace:*", "rollup": "4.24.0", - "typescript": "5.1.6" + "typescript": "^5.8.3" }, "peerDependencies": { "@openfeature/web-sdk": "^1.0.3", diff --git a/packages/openfeature-web-provider/src/ConfidenceWebProvider.e2e.test.ts b/packages/openfeature-web-provider/src/ConfidenceWebProvider.e2e.test.ts index 88fd35b2..82f23796 100644 --- a/packages/openfeature-web-provider/src/ConfidenceWebProvider.e2e.test.ts +++ b/packages/openfeature-web-provider/src/ConfidenceWebProvider.e2e.test.ts @@ -33,7 +33,7 @@ describe('ConfidenceWebProvider E2E tests', () => { errorCode: 'GENERAL', flagKey: 'web-sdk-e2e-flag.str', flagMetadata: {}, - errorMessage: 'Resolve timeout', + errorMessage: 'The operation was aborted due to timeout', reason: 'ERROR', value: 'default', }); diff --git a/packages/react/package.json b/packages/react/package.json index da577a8c..39e7f649 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -29,7 +29,7 @@ "@types/react": "^18", "react": "^19", "rollup": "4.24.0", - "typescript": "5.1.6" + "typescript": "^5.8.3" }, "exports": { ".": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 0be2a132..68867a8a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -25,7 +25,7 @@ "prettier": "*", "rollup": "4.24.0", "ts-proto": "^2.3.0", - "typescript": "5.1.6" + "typescript": "^5.8.3" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/sdk/src/FlagResolverClient.test.ts b/packages/sdk/src/FlagResolverClient.test.ts index 07171ceb..093fb015 100644 --- a/packages/sdk/src/FlagResolverClient.test.ts +++ b/packages/sdk/src/FlagResolverClient.test.ts @@ -79,7 +79,7 @@ describe('Client environment Evaluation', () => { // This is due the request logic that's only used in the client environment expect(flagResolution.evaluate('testflag', {})).toEqual({ errorCode: 'TIMEOUT', - errorMessage: 'Resolve timeout', + errorMessage: 'The operation was aborted due to timeout', reason: 'ERROR', value: {}, }); diff --git a/packages/sdk/src/FlagResolverClient.ts b/packages/sdk/src/FlagResolverClient.ts index 818ef5b6..5dfd8624 100644 --- a/packages/sdk/src/FlagResolverClient.ts +++ b/packages/sdk/src/FlagResolverClient.ts @@ -20,15 +20,11 @@ import { } from './generated/confidence/telemetry/v1/telemetry'; import { Logger } from './logger'; import { WaitUntil } from './types'; +import { Signals } from './signals'; const FLAG_PREFIX = 'flags/'; const retryCodes = new Set([408, 502, 503, 504]); -export class ResolveError extends Error { - constructor(public readonly code: FlagEvaluation.ErrorCode, message: string) { - super(message); - } -} export class PendingResolution extends AccessiblePromise { #context: Context; #controller: AbortController; @@ -192,11 +188,7 @@ export class FetchingFlagResolverClient implements FlagResolverClient { }; return PendingResolution.create(context, signal => { - const signalWithTimeout = withTimeout( - signal, - this.resolveTimeout, - new ResolveError('TIMEOUT', 'Resolve timeout'), - ); + const signalWithTimeout = withTimeout(signal, this.resolveTimeout); const start = performance.now(); return this.cacheReadThrough(context, () => this.resolveFlagsJson(request, signalWithTimeout)) @@ -207,7 +199,8 @@ export class FetchingFlagResolverClient implements FlagResolverClient { }) .catch(error => { const latency = performance.now() - start; - const errorCode: FlagEvaluation.ErrorCode = error instanceof ResolveError ? error.code : 'GENERAL'; + const errorCode: FlagEvaluation.ErrorCode = + error instanceof DOMException && error.name == 'TimeoutError' ? 'TIMEOUT' : 'GENERAL'; this.markLatency(latency, errorCode === 'TIMEOUT' ? TraceStatus.STATUS_TIMEOUT : TraceStatus.STATUS_ERROR); return FlagResolution.failed(context, errorCode, error.message); }); @@ -347,16 +340,8 @@ export function withRequestLogic(fetchBuilder: FetchBuilder, logger: Logger) { .route(url => url.endsWith('flags:apply'), fetchApply); } -function withTimeout(signal: AbortSignal, timeout: number, reason?: any): AbortSignal { - const controller = new AbortController(); - const timeoutId: NodeJS.Timeout | number = setTimeout(() => controller.abort(reason), timeout); - // in Node setTimeout returns an object, with an unref function which will prevent the timeout from keeping the process alive - if (typeof timeoutId === 'object') timeoutId.unref(); - signal.addEventListener('abort', () => { - clearTimeout(timeoutId); - controller.abort(signal.reason); - }); - return controller.signal; +function withTimeout(signal: AbortSignal, timeout: number): AbortSignal { + return Signals.any([Signals.timeout(timeout), signal]); } function resolvablePromise(): [ diff --git a/packages/sdk/src/signals.ts b/packages/sdk/src/signals.ts new file mode 100644 index 00000000..488e06da --- /dev/null +++ b/packages/sdk/src/signals.ts @@ -0,0 +1,33 @@ +export namespace Signals { + const anyPolyfill: (typeof AbortSignal)['any'] = signals => { + const controller = new AbortController(); + const abort = () => { + controller.abort(); + clear(); + }; + const clear = () => { + signals.forEach(signal => { + signal.removeEventListener('abort', abort); + }); + }; + for (const signal of signals) { + if (signal.aborted) { + abort(); + break; + } + signal.addEventListener('abort', abort); + } + return controller.signal; + }; + + const timeoutPolyfill: (typeof AbortSignal)['timeout'] = milliseconds => { + const controller = new AbortController(); + setTimeout(() => { + controller.abort(new DOMException('signal timed out', 'TimeoutError')); + }, milliseconds); + return controller.signal; + }; + + export const any = typeof AbortSignal.any === 'function' ? AbortSignal.any : anyPolyfill; + export const timeout = typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout : timeoutPolyfill; +} diff --git a/yarn.lock b/yarn.lock index 1f366aa9..275f331f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3737,7 +3737,7 @@ __metadata: "@openfeature/server-sdk": "npm:^1.13.5" "@spotify-confidence/sdk": "workspace:*" rollup: "npm:4.24.0" - typescript: "npm:5.1.6" + typescript: "npm:^5.8.3" peerDependencies: "@openfeature/server-sdk": ^1.13.5 "@spotify-confidence/sdk": ">=0.1.4 <0.4.0" @@ -3753,7 +3753,7 @@ __metadata: "@spotify-confidence/sdk": "workspace:*" fast-deep-equal: "npm:^3.1.3" rollup: "npm:4.24.0" - typescript: "npm:5.1.6" + typescript: "npm:^5.8.3" peerDependencies: "@openfeature/web-sdk": ^1.0.3 "@spotify-confidence/sdk": ">=0.1.4 <0.4.0" @@ -3769,7 +3769,7 @@ __metadata: react: "npm:^19" rollup: "npm:4.24.0" server-only: "npm:^0.0.1" - typescript: "npm:5.1.6" + typescript: "npm:^5.8.3" peerDependencies: "@spotify-confidence/sdk": ">=0.3.1 <0.4.0" react: ^18 || ^19 @@ -3785,7 +3785,7 @@ __metadata: prettier: "npm:*" rollup: "npm:4.24.0" ts-proto: "npm:^2.3.0" - typescript: "npm:5.1.6" + typescript: "npm:^5.8.3" web-vitals: "npm:^3.5.2" languageName: unknown linkType: soft @@ -15002,7 +15002,7 @@ __metadata: react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-scripts: "npm:5.0.1" - typescript: "npm:^4.4.2" + typescript: "npm:^5.8.3" web-vitals: "npm:^2.1.0" languageName: unknown linkType: soft @@ -15637,7 +15637,7 @@ __metadata: rollup: "npm:^4.22.4" rollup-plugin-dts: "npm:^6.1.1" ts-jest: "npm:^29.1.0" - typescript: "npm:^5.1.3" + typescript: "npm:^5.8.3" languageName: unknown linkType: soft @@ -17387,16 +17387,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.1.6, typescript@npm:^4.6.4 || ^5.0.0, typescript@npm:^5.1.3": - version: 5.1.6 - resolution: "typescript@npm:5.1.6" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/45ac28e2df8365fd28dac42f5d62edfe69a7203d5ec646732cadc04065331f34f9078f81f150fde42ed9754eed6fa3b06a8f3523c40b821e557b727f1992e025 - languageName: node - linkType: hard - "typescript@npm:5.4.2": version: 5.4.2 resolution: "typescript@npm:5.4.2" @@ -17407,23 +17397,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.4.2": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" +"typescript@npm:^4.6.4 || ^5.0.0": + version: 5.1.6 + resolution: "typescript@npm:5.1.6" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 + checksum: 10c0/45ac28e2df8365fd28dac42f5d62edfe69a7203d5ec646732cadc04065331f34f9078f81f150fde42ed9754eed6fa3b06a8f3523c40b821e557b727f1992e025 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.1.6#optional!builtin, typescript@patch:typescript@npm%3A^4.6.4 || ^5.0.0#optional!builtin, typescript@patch:typescript@npm%3A^5.1.3#optional!builtin": - version: 5.1.6 - resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" +"typescript@npm:^5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/c2bded58ab897a8341fdbb0c1d92ea2362f498cfffebdc8a529d03e15ea2454142dfbf122dabbd9a5cb79b7123790d27def16e11844887d20636226773ed329a + checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 languageName: node linkType: hard @@ -17437,13 +17427,23 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.4.2#optional!builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" +"typescript@patch:typescript@npm%3A^4.6.4 || ^5.0.0#optional!builtin": + version: 5.1.6 + resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/c2bded58ab897a8341fdbb0c1d92ea2362f498cfffebdc8a529d03e15ea2454142dfbf122dabbd9a5cb79b7123790d27def16e11844887d20636226773ed329a + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=d69c25" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 + checksum: 10c0/92ea03509e06598948559ddcdd8a4ae5a7ab475766d5589f1b796f5731b3d631a4c7ddfb86a3bd44d58d10102b132cd4b4994dda9b63e6273c66d77d6a271dbd languageName: node linkType: hard