diff --git a/CHANGES.txt b/CHANGES.txt index 1e2e1ba..c130bed 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +1.2.0 (November 7, 2025) + - Updated @openfeature/server-sdk to 1.20.0 + - Updated @splitsoftware/splitio to 11.8.0 + 1.1.0 (September 12, 2025) - Updated @openfeature/server-sdk to 1.19.0 - Updated @splitsoftware/splitio to 11.4.1 diff --git a/package-lock.json b/package-lock.json index 1f824ca..bdd786f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "license": "Apache-2.0", "devDependencies": { "@eslint/js": "^9.35.0", - "@openfeature/server-sdk": "^1.19.0", - "@splitsoftware/splitio": "^11.4.1", + "@openfeature/server-sdk": "^1.20.0", + "@splitsoftware/splitio": "^11.8.0", "@types/jest": "^30.0.0", "@types/node": "^24.3.1", "copyfiles": "^2.4.1", @@ -33,8 +33,8 @@ "node": ">=14" }, "peerDependencies": { - "@openfeature/server-sdk": "^1.19.0", - "@splitsoftware/splitio": "^11.4.1" + "@openfeature/server-sdk": "^1.20.0", + "@splitsoftware/splitio": "^11.8.0" } }, "node_modules/@babel/code-frame": { @@ -784,9 +784,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz", - "integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz", + "integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==", "dev": true, "license": "MIT" }, @@ -1746,9 +1746,9 @@ "peer": true }, "node_modules/@openfeature/server-sdk": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@openfeature/server-sdk/-/server-sdk-1.19.0.tgz", - "integrity": "sha512-sxmYKkBCpWWkKX2xJt6bFK15dorfR2lH27lkeKduTFPXRMV+pO7eORUelI9gctXhxvfXbDGK94ybq5fiso3/vg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@openfeature/server-sdk/-/server-sdk-1.20.0.tgz", + "integrity": "sha512-95L9CCaGVKC6+7rNCmDxjthFvUyPLOUkoYyjg9Y/Cmy0G9D63xrJBsmH6fqHtLifzAu4+E7fWAZ3TIBnnCtr7A==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1786,13 +1786,13 @@ } }, "node_modules/@splitsoftware/splitio": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.4.1.tgz", - "integrity": "sha512-wipPwsWwXPRzvEs28VYahILsF8+Lor4tby2GB3CD9kn0C3sQ2Zf3/NaDH4i7acobMlRy2sQ5mu4eeRt15gLJyw==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.8.0.tgz", + "integrity": "sha512-M9ENeH+IEmxwELeCdXgnTbLg+ZP3SRUMM6lImSbv7mD32u1v6ihnUhnhsTJzlQWMDC4H94EAW345q1cO7ovlTQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.4.1", + "@splitsoftware/splitio-commons": "2.8.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -1805,9 +1805,9 @@ } }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.4.1.tgz", - "integrity": "sha512-VcbWpPykfx19LTJ0yeZbV0u3PUIt8MuiZ2a8zqkNf9KnDnhau/XxS/ctoO5jYrg4Nk2rCi0fpt1TkTstqzbaYA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.8.0.tgz", + "integrity": "sha512-QgHUreMOEDwf4GZzVPu4AzkZJvuaeSoHsiJc4tT3CxSIYl2bKMz1SSDlI1tW/oVbIFeWjkrIp2lCYEyUBgcvyA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3809,9 +3809,9 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.30.0.tgz", - "integrity": "sha512-P9F4Eo6zicYsIJbEy/mPJmSxKY0rVcmiy5H8oXPxPDotQRCvCBjBuI5QWoQQanVE9jdeocnum5iqYAHl4pHdLA==", + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.30.1.tgz", + "integrity": "sha512-17Ed70njJ7wT7JZsdTVLb0j/cmwHwfQCFu+AP6jY7nFKd+CA7MBW7nX121mM64eT8S9ekAVtYYt8nGQPmm3euA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 159b116..50dee25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "description": "Split OpenFeature Provider", "files": [ "README.md", @@ -35,13 +35,13 @@ } }, "peerDependencies": { - "@openfeature/server-sdk": "^1.19.0", - "@splitsoftware/splitio": "^11.4.1" + "@openfeature/server-sdk": "^1.20.0", + "@splitsoftware/splitio": "^11.8.0" }, "devDependencies": { "@eslint/js": "^9.35.0", - "@openfeature/server-sdk": "^1.19.0", - "@splitsoftware/splitio": "^11.4.1", + "@openfeature/server-sdk": "^1.20.0", + "@splitsoftware/splitio": "^11.8.0", "@types/jest": "^30.0.0", "@types/node": "^24.3.1", "copyfiles": "^2.4.1", diff --git a/src/lib/js-split-provider.ts b/src/lib/js-split-provider.ts index 9c58d66..f2e8937 100644 --- a/src/lib/js-split-provider.ts +++ b/src/lib/js-split-provider.ts @@ -1,7 +1,6 @@ import { EvaluationContext, FlagNotFoundError, - InvalidContextError, JsonValue, OpenFeatureEventEmitter, ParseError, @@ -20,7 +19,8 @@ type SplitProviderOptions = { } type Consumer = { - key: string | undefined; + targetingKey: string | undefined; + trafficType: string; attributes: SplitIO.Attributes; }; @@ -31,9 +31,9 @@ export class OpenFeatureSplitProvider implements Provider { metadata = { name: 'split', }; - private initialized: Promise; - private client: SplitIO.IClient | SplitIO.IAsyncClient; + private client: SplitIO.IClient | SplitIO.IAsyncClient; + private trafficType: string; public readonly events = new OpenFeatureEventEmitter(); private getSplitClient(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) { @@ -53,27 +53,15 @@ export class OpenFeatureSplitProvider implements Provider { } constructor(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) { - + // Asume 'user' as default traffic type' + this.trafficType = 'user'; this.client = this.getSplitClient(options); - this.client.on(this.client.Event.SDK_UPDATE, () => { - this.events.emit(ProviderEvents.ConfigurationChanged) - }); - this.initialized = new Promise((resolve) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((this.client as any).__getStatus().isReady) { - console.log(`${this.metadata.name} provider initialized`); - resolve(); - } else { - this.client.on(this.client.Event.SDK_READY, () => { - console.log(`${this.metadata.name} provider initialized`); - resolve(); - }); - } - }); + this.events.emit(ProviderEvents.ConfigurationChanged); + }); } - async resolveBooleanEvaluation( + public async resolveBooleanEvaluation( flagKey: string, _: boolean, context: EvaluationContext @@ -95,7 +83,7 @@ export class OpenFeatureSplitProvider implements Provider { throw new ParseError(`Invalid boolean value for ${treatment}`); } - async resolveStringEvaluation( + public async resolveStringEvaluation( flagKey: string, _: string, context: EvaluationContext @@ -107,7 +95,7 @@ export class OpenFeatureSplitProvider implements Provider { return details; } - async resolveNumberEvaluation( + public async resolveNumberEvaluation( flagKey: string, _: number, context: EvaluationContext @@ -119,7 +107,7 @@ export class OpenFeatureSplitProvider implements Provider { return { ...details, value: this.parseValidNumber(details.value) }; } - async resolveObjectEvaluation( + public async resolveObjectEvaluation( flagKey: string, _: U, context: EvaluationContext @@ -135,7 +123,7 @@ export class OpenFeatureSplitProvider implements Provider { flagKey: string, consumer: Consumer ): Promise> { - if (!consumer.key) { + if (!consumer.targetingKey) { throw new TargetingKeyMissingError( 'The Split provider requires a targeting key.' ); @@ -146,9 +134,12 @@ export class OpenFeatureSplitProvider implements Provider { ); } - await this.initialized; + await new Promise((resolve, reject) => { + this.readinessHandler(resolve, reject); + }); + const { treatment: value, config }: SplitIO.TreatmentWithConfig = await this.client.getTreatmentWithConfig( - consumer.key, + consumer.targetingKey, flagKey, consumer.attributes ); @@ -171,23 +162,14 @@ export class OpenFeatureSplitProvider implements Provider { details: TrackingEventDetails ): Promise { - // targetingKey is always required - const { targetingKey } = context; - if (targetingKey == null || targetingKey === '') - throw new TargetingKeyMissingError('Missing targetingKey, required to track'); - // eventName is always required if (trackingEventName == null || trackingEventName === '') throw new ParseError('Missing eventName, required to track'); - // trafficType is always required - const ttVal = context['trafficType']; - const trafficType = - ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== '' - ? ttVal - : null; - if (trafficType == null || trafficType === '') - throw new InvalidContextError('Missing trafficType variable, required to track'); + // targetingKey is always required + const { targetingKey, trafficType } = this.transformContext(context); + if (targetingKey == null || targetingKey === '') + throw new TargetingKeyMissingError('Missing targetingKey, required to track'); let value; let properties: SplitIO.Properties = {}; @@ -203,15 +185,20 @@ export class OpenFeatureSplitProvider implements Provider { this.client.track(targetingKey, trafficType, trackingEventName, value, properties); } - async onClose?(): Promise { + public async onClose?(): Promise { return this.client.destroy(); } //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'. private transformContext(context: EvaluationContext): Consumer { - const { targetingKey, ...attributes } = context; + const { targetingKey, trafficType: ttVal, ...attributes } = context; + const trafficType = + ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== '' + ? ttVal + : this.trafficType; return { - key: targetingKey, + targetingKey, + trafficType, // Stringify context objects include date. attributes: JSON.parse(JSON.stringify(attributes)), }; @@ -247,4 +234,19 @@ export class OpenFeatureSplitProvider implements Provider { throw new ParseError(`Error parsing ${stringValue} as JSON, ${err}`); } } -} + + private async readinessHandler(onSdkReady: (params?: unknown) => void, onSdkTimedOut: () => void): Promise { + + const clientStatus = this.client.getStatus(); + if (clientStatus.isReady) { + onSdkReady(); + } else { + if (clientStatus.hasTimedout) { + onSdkTimedOut(); + } else { + this.client.on(this.client.Event.SDK_READY_TIMED_OUT, onSdkTimedOut); + } + this.client.on(this.client.Event.SDK_READY, onSdkReady); + } + } +} \ No newline at end of file