@splitsoftware/openfeature-js-split-provider 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.txt CHANGED
@@ -1,3 +1,7 @@
1
+ 1.2.0 (November 7, 2025)
2
+ - Updated @openfeature/server-sdk to 1.20.0
3
+ - Updated @splitsoftware/splitio to 11.8.0
4
+
1
5
  1.1.0 (September 12, 2025)
2
6
  - Updated @openfeature/server-sdk to 1.19.0
3
7
  - Updated @splitsoftware/splitio to 11.4.1
@@ -1,4 +1,4 @@
1
- import { FlagNotFoundError, InvalidContextError, OpenFeatureEventEmitter, ParseError, ProviderEvents, StandardResolutionReasons, TargetingKeyMissingError } from '@openfeature/server-sdk';
1
+ import { FlagNotFoundError, OpenFeatureEventEmitter, ParseError, ProviderEvents, StandardResolutionReasons, TargetingKeyMissingError } from '@openfeature/server-sdk';
2
2
  import { SplitFactory } from '@splitsoftware/splitio';
3
3
  const CONTROL_VALUE_ERROR_MESSAGE = 'Received the "control" value from Split.';
4
4
  const CONTROL_TREATMENT = 'control';
@@ -22,23 +22,12 @@ export class OpenFeatureSplitProvider {
22
22
  name: 'split',
23
23
  };
24
24
  this.events = new OpenFeatureEventEmitter();
25
+ // Asume 'user' as default traffic type'
26
+ this.trafficType = 'user';
25
27
  this.client = this.getSplitClient(options);
26
28
  this.client.on(this.client.Event.SDK_UPDATE, () => {
27
29
  this.events.emit(ProviderEvents.ConfigurationChanged);
28
30
  });
29
- this.initialized = new Promise((resolve) => {
30
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
- if (this.client.__getStatus().isReady) {
32
- console.log(`${this.metadata.name} provider initialized`);
33
- resolve();
34
- }
35
- else {
36
- this.client.on(this.client.Event.SDK_READY, () => {
37
- console.log(`${this.metadata.name} provider initialized`);
38
- resolve();
39
- });
40
- }
41
- });
42
31
  }
43
32
  async resolveBooleanEvaluation(flagKey, _, context) {
44
33
  const details = await this.evaluateTreatment(flagKey, this.transformContext(context));
@@ -64,14 +53,16 @@ export class OpenFeatureSplitProvider {
64
53
  return { ...details, value: this.parseValidJsonObject(details.value) };
65
54
  }
66
55
  async evaluateTreatment(flagKey, consumer) {
67
- if (!consumer.key) {
56
+ if (!consumer.targetingKey) {
68
57
  throw new TargetingKeyMissingError('The Split provider requires a targeting key.');
69
58
  }
70
59
  if (flagKey == null || flagKey === '') {
71
60
  throw new FlagNotFoundError('flagKey must be a non-empty string');
72
61
  }
73
- await this.initialized;
74
- const { treatment: value, config } = await this.client.getTreatmentWithConfig(consumer.key, flagKey, consumer.attributes);
62
+ await new Promise((resolve, reject) => {
63
+ this.readinessHandler(resolve, reject);
64
+ });
65
+ const { treatment: value, config } = await this.client.getTreatmentWithConfig(consumer.targetingKey, flagKey, consumer.attributes);
75
66
  if (value === CONTROL_TREATMENT) {
76
67
  throw new FlagNotFoundError(CONTROL_VALUE_ERROR_MESSAGE);
77
68
  }
@@ -85,20 +76,13 @@ export class OpenFeatureSplitProvider {
85
76
  return details;
86
77
  }
87
78
  async track(trackingEventName, context, details) {
88
- // targetingKey is always required
89
- const { targetingKey } = context;
90
- if (targetingKey == null || targetingKey === '')
91
- throw new TargetingKeyMissingError('Missing targetingKey, required to track');
92
79
  // eventName is always required
93
80
  if (trackingEventName == null || trackingEventName === '')
94
81
  throw new ParseError('Missing eventName, required to track');
95
- // trafficType is always required
96
- const ttVal = context['trafficType'];
97
- const trafficType = ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
98
- ? ttVal
99
- : null;
100
- if (trafficType == null || trafficType === '')
101
- throw new InvalidContextError('Missing trafficType variable, required to track');
82
+ // targetingKey is always required
83
+ const { targetingKey, trafficType } = this.transformContext(context);
84
+ if (targetingKey == null || targetingKey === '')
85
+ throw new TargetingKeyMissingError('Missing targetingKey, required to track');
102
86
  let value;
103
87
  let properties = {};
104
88
  if (details != null) {
@@ -116,9 +100,13 @@ export class OpenFeatureSplitProvider {
116
100
  }
117
101
  //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'.
118
102
  transformContext(context) {
119
- const { targetingKey, ...attributes } = context;
103
+ const { targetingKey, trafficType: ttVal, ...attributes } = context;
104
+ const trafficType = ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
105
+ ? ttVal
106
+ : this.trafficType;
120
107
  return {
121
- key: targetingKey,
108
+ targetingKey,
109
+ trafficType,
122
110
  // Stringify context objects include date.
123
111
  attributes: JSON.parse(JSON.stringify(attributes)),
124
112
  };
@@ -149,4 +137,19 @@ export class OpenFeatureSplitProvider {
149
137
  throw new ParseError(`Error parsing ${stringValue} as JSON, ${err}`);
150
138
  }
151
139
  }
140
+ async readinessHandler(onSdkReady, onSdkTimedOut) {
141
+ const clientStatus = this.client.getStatus();
142
+ if (clientStatus.isReady) {
143
+ onSdkReady();
144
+ }
145
+ else {
146
+ if (clientStatus.hasTimedout) {
147
+ onSdkTimedOut();
148
+ }
149
+ else {
150
+ this.client.on(this.client.Event.SDK_READY_TIMED_OUT, onSdkTimedOut);
151
+ }
152
+ this.client.on(this.client.Event.SDK_READY, onSdkReady);
153
+ }
154
+ }
152
155
  }
@@ -25,23 +25,12 @@ class OpenFeatureSplitProvider {
25
25
  name: 'split',
26
26
  };
27
27
  this.events = new server_sdk_1.OpenFeatureEventEmitter();
28
+ // Asume 'user' as default traffic type'
29
+ this.trafficType = 'user';
28
30
  this.client = this.getSplitClient(options);
29
31
  this.client.on(this.client.Event.SDK_UPDATE, () => {
30
32
  this.events.emit(server_sdk_1.ProviderEvents.ConfigurationChanged);
31
33
  });
32
- this.initialized = new Promise((resolve) => {
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- if (this.client.__getStatus().isReady) {
35
- console.log(`${this.metadata.name} provider initialized`);
36
- resolve();
37
- }
38
- else {
39
- this.client.on(this.client.Event.SDK_READY, () => {
40
- console.log(`${this.metadata.name} provider initialized`);
41
- resolve();
42
- });
43
- }
44
- });
45
34
  }
46
35
  async resolveBooleanEvaluation(flagKey, _, context) {
47
36
  const details = await this.evaluateTreatment(flagKey, this.transformContext(context));
@@ -67,14 +56,16 @@ class OpenFeatureSplitProvider {
67
56
  return { ...details, value: this.parseValidJsonObject(details.value) };
68
57
  }
69
58
  async evaluateTreatment(flagKey, consumer) {
70
- if (!consumer.key) {
59
+ if (!consumer.targetingKey) {
71
60
  throw new server_sdk_1.TargetingKeyMissingError('The Split provider requires a targeting key.');
72
61
  }
73
62
  if (flagKey == null || flagKey === '') {
74
63
  throw new server_sdk_1.FlagNotFoundError('flagKey must be a non-empty string');
75
64
  }
76
- await this.initialized;
77
- const { treatment: value, config } = await this.client.getTreatmentWithConfig(consumer.key, flagKey, consumer.attributes);
65
+ await new Promise((resolve, reject) => {
66
+ this.readinessHandler(resolve, reject);
67
+ });
68
+ const { treatment: value, config } = await this.client.getTreatmentWithConfig(consumer.targetingKey, flagKey, consumer.attributes);
78
69
  if (value === CONTROL_TREATMENT) {
79
70
  throw new server_sdk_1.FlagNotFoundError(CONTROL_VALUE_ERROR_MESSAGE);
80
71
  }
@@ -88,20 +79,13 @@ class OpenFeatureSplitProvider {
88
79
  return details;
89
80
  }
90
81
  async track(trackingEventName, context, details) {
91
- // targetingKey is always required
92
- const { targetingKey } = context;
93
- if (targetingKey == null || targetingKey === '')
94
- throw new server_sdk_1.TargetingKeyMissingError('Missing targetingKey, required to track');
95
82
  // eventName is always required
96
83
  if (trackingEventName == null || trackingEventName === '')
97
84
  throw new server_sdk_1.ParseError('Missing eventName, required to track');
98
- // trafficType is always required
99
- const ttVal = context['trafficType'];
100
- const trafficType = ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
101
- ? ttVal
102
- : null;
103
- if (trafficType == null || trafficType === '')
104
- throw new server_sdk_1.InvalidContextError('Missing trafficType variable, required to track');
85
+ // targetingKey is always required
86
+ const { targetingKey, trafficType } = this.transformContext(context);
87
+ if (targetingKey == null || targetingKey === '')
88
+ throw new server_sdk_1.TargetingKeyMissingError('Missing targetingKey, required to track');
105
89
  let value;
106
90
  let properties = {};
107
91
  if (details != null) {
@@ -119,9 +103,13 @@ class OpenFeatureSplitProvider {
119
103
  }
120
104
  //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'.
121
105
  transformContext(context) {
122
- const { targetingKey, ...attributes } = context;
106
+ const { targetingKey, trafficType: ttVal, ...attributes } = context;
107
+ const trafficType = ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
108
+ ? ttVal
109
+ : this.trafficType;
123
110
  return {
124
- key: targetingKey,
111
+ targetingKey,
112
+ trafficType,
125
113
  // Stringify context objects include date.
126
114
  attributes: JSON.parse(JSON.stringify(attributes)),
127
115
  };
@@ -152,5 +140,20 @@ class OpenFeatureSplitProvider {
152
140
  throw new server_sdk_1.ParseError(`Error parsing ${stringValue} as JSON, ${err}`);
153
141
  }
154
142
  }
143
+ async readinessHandler(onSdkReady, onSdkTimedOut) {
144
+ const clientStatus = this.client.getStatus();
145
+ if (clientStatus.isReady) {
146
+ onSdkReady();
147
+ }
148
+ else {
149
+ if (clientStatus.hasTimedout) {
150
+ onSdkTimedOut();
151
+ }
152
+ else {
153
+ this.client.on(this.client.Event.SDK_READY_TIMED_OUT, onSdkTimedOut);
154
+ }
155
+ this.client.on(this.client.Event.SDK_READY, onSdkReady);
156
+ }
157
+ }
155
158
  }
156
159
  exports.OpenFeatureSplitProvider = OpenFeatureSplitProvider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/openfeature-js-split-provider",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Split OpenFeature Provider",
5
5
  "files": [
6
6
  "README.md",
@@ -35,13 +35,13 @@
35
35
  }
36
36
  },
37
37
  "peerDependencies": {
38
- "@openfeature/server-sdk": "^1.19.0",
39
- "@splitsoftware/splitio": "^11.4.1"
38
+ "@openfeature/server-sdk": "^1.20.0",
39
+ "@splitsoftware/splitio": "^11.8.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@eslint/js": "^9.35.0",
43
- "@openfeature/server-sdk": "^1.19.0",
44
- "@splitsoftware/splitio": "^11.4.1",
43
+ "@openfeature/server-sdk": "^1.20.0",
44
+ "@splitsoftware/splitio": "^11.8.0",
45
45
  "@types/jest": "^30.0.0",
46
46
  "@types/node": "^24.3.1",
47
47
  "copyfiles": "^2.4.1",
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  EvaluationContext,
3
3
  FlagNotFoundError,
4
- InvalidContextError,
5
4
  JsonValue,
6
5
  OpenFeatureEventEmitter,
7
6
  ParseError,
@@ -20,7 +19,8 @@ type SplitProviderOptions = {
20
19
  }
21
20
 
22
21
  type Consumer = {
23
- key: string | undefined;
22
+ targetingKey: string | undefined;
23
+ trafficType: string;
24
24
  attributes: SplitIO.Attributes;
25
25
  };
26
26
 
@@ -31,9 +31,9 @@ export class OpenFeatureSplitProvider implements Provider {
31
31
  metadata = {
32
32
  name: 'split',
33
33
  };
34
- private initialized: Promise<void>;
35
- private client: SplitIO.IClient | SplitIO.IAsyncClient;
36
34
 
35
+ private client: SplitIO.IClient | SplitIO.IAsyncClient;
36
+ private trafficType: string;
37
37
  public readonly events = new OpenFeatureEventEmitter();
38
38
 
39
39
  private getSplitClient(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) {
@@ -53,27 +53,15 @@ export class OpenFeatureSplitProvider implements Provider {
53
53
  }
54
54
 
55
55
  constructor(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) {
56
-
56
+ // Asume 'user' as default traffic type'
57
+ this.trafficType = 'user';
57
58
  this.client = this.getSplitClient(options);
58
-
59
59
  this.client.on(this.client.Event.SDK_UPDATE, () => {
60
- this.events.emit(ProviderEvents.ConfigurationChanged)
61
- });
62
- this.initialized = new Promise((resolve) => {
63
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- if ((this.client as any).__getStatus().isReady) {
65
- console.log(`${this.metadata.name} provider initialized`);
66
- resolve();
67
- } else {
68
- this.client.on(this.client.Event.SDK_READY, () => {
69
- console.log(`${this.metadata.name} provider initialized`);
70
- resolve();
71
- });
72
- }
73
- });
60
+ this.events.emit(ProviderEvents.ConfigurationChanged);
61
+ });
74
62
  }
75
63
 
76
- async resolveBooleanEvaluation(
64
+ public async resolveBooleanEvaluation(
77
65
  flagKey: string,
78
66
  _: boolean,
79
67
  context: EvaluationContext
@@ -95,7 +83,7 @@ export class OpenFeatureSplitProvider implements Provider {
95
83
  throw new ParseError(`Invalid boolean value for ${treatment}`);
96
84
  }
97
85
 
98
- async resolveStringEvaluation(
86
+ public async resolveStringEvaluation(
99
87
  flagKey: string,
100
88
  _: string,
101
89
  context: EvaluationContext
@@ -107,7 +95,7 @@ export class OpenFeatureSplitProvider implements Provider {
107
95
  return details;
108
96
  }
109
97
 
110
- async resolveNumberEvaluation(
98
+ public async resolveNumberEvaluation(
111
99
  flagKey: string,
112
100
  _: number,
113
101
  context: EvaluationContext
@@ -119,7 +107,7 @@ export class OpenFeatureSplitProvider implements Provider {
119
107
  return { ...details, value: this.parseValidNumber(details.value) };
120
108
  }
121
109
 
122
- async resolveObjectEvaluation<U extends JsonValue>(
110
+ public async resolveObjectEvaluation<U extends JsonValue>(
123
111
  flagKey: string,
124
112
  _: U,
125
113
  context: EvaluationContext
@@ -135,7 +123,7 @@ export class OpenFeatureSplitProvider implements Provider {
135
123
  flagKey: string,
136
124
  consumer: Consumer
137
125
  ): Promise<ResolutionDetails<string>> {
138
- if (!consumer.key) {
126
+ if (!consumer.targetingKey) {
139
127
  throw new TargetingKeyMissingError(
140
128
  'The Split provider requires a targeting key.'
141
129
  );
@@ -146,9 +134,12 @@ export class OpenFeatureSplitProvider implements Provider {
146
134
  );
147
135
  }
148
136
 
149
- await this.initialized;
137
+ await new Promise((resolve, reject) => {
138
+ this.readinessHandler(resolve, reject);
139
+ });
140
+
150
141
  const { treatment: value, config }: SplitIO.TreatmentWithConfig = await this.client.getTreatmentWithConfig(
151
- consumer.key,
142
+ consumer.targetingKey,
152
143
  flagKey,
153
144
  consumer.attributes
154
145
  );
@@ -171,23 +162,14 @@ export class OpenFeatureSplitProvider implements Provider {
171
162
  details: TrackingEventDetails
172
163
  ): Promise<void> {
173
164
 
174
- // targetingKey is always required
175
- const { targetingKey } = context;
176
- if (targetingKey == null || targetingKey === '')
177
- throw new TargetingKeyMissingError('Missing targetingKey, required to track');
178
-
179
165
  // eventName is always required
180
166
  if (trackingEventName == null || trackingEventName === '')
181
167
  throw new ParseError('Missing eventName, required to track');
182
168
 
183
- // trafficType is always required
184
- const ttVal = context['trafficType'];
185
- const trafficType =
186
- ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
187
- ? ttVal
188
- : null;
189
- if (trafficType == null || trafficType === '')
190
- throw new InvalidContextError('Missing trafficType variable, required to track');
169
+ // targetingKey is always required
170
+ const { targetingKey, trafficType } = this.transformContext(context);
171
+ if (targetingKey == null || targetingKey === '')
172
+ throw new TargetingKeyMissingError('Missing targetingKey, required to track');
191
173
 
192
174
  let value;
193
175
  let properties: SplitIO.Properties = {};
@@ -203,15 +185,20 @@ export class OpenFeatureSplitProvider implements Provider {
203
185
  this.client.track(targetingKey, trafficType, trackingEventName, value, properties);
204
186
  }
205
187
 
206
- async onClose?(): Promise<void> {
188
+ public async onClose?(): Promise<void> {
207
189
  return this.client.destroy();
208
190
  }
209
191
 
210
192
  //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'.
211
193
  private transformContext(context: EvaluationContext): Consumer {
212
- const { targetingKey, ...attributes } = context;
194
+ const { targetingKey, trafficType: ttVal, ...attributes } = context;
195
+ const trafficType =
196
+ ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== ''
197
+ ? ttVal
198
+ : this.trafficType;
213
199
  return {
214
- key: targetingKey,
200
+ targetingKey,
201
+ trafficType,
215
202
  // Stringify context objects include date.
216
203
  attributes: JSON.parse(JSON.stringify(attributes)),
217
204
  };
@@ -247,4 +234,19 @@ export class OpenFeatureSplitProvider implements Provider {
247
234
  throw new ParseError(`Error parsing ${stringValue} as JSON, ${err}`);
248
235
  }
249
236
  }
250
- }
237
+
238
+ private async readinessHandler(onSdkReady: (params?: unknown) => void, onSdkTimedOut: () => void): Promise<void> {
239
+
240
+ const clientStatus = this.client.getStatus();
241
+ if (clientStatus.isReady) {
242
+ onSdkReady();
243
+ } else {
244
+ if (clientStatus.hasTimedout) {
245
+ onSdkTimedOut();
246
+ } else {
247
+ this.client.on(this.client.Event.SDK_READY_TIMED_OUT, onSdkTimedOut);
248
+ }
249
+ this.client.on(this.client.Event.SDK_READY, onSdkReady);
250
+ }
251
+ }
252
+ }
@@ -7,8 +7,8 @@ export declare class OpenFeatureSplitProvider implements Provider {
7
7
  metadata: {
8
8
  name: string;
9
9
  };
10
- private initialized;
11
10
  private client;
11
+ private trafficType;
12
12
  readonly events: OpenFeatureEventEmitter;
13
13
  private getSplitClient;
14
14
  constructor(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK);
@@ -22,5 +22,6 @@ export declare class OpenFeatureSplitProvider implements Provider {
22
22
  private transformContext;
23
23
  private parseValidNumber;
24
24
  private parseValidJsonObject;
25
+ private readinessHandler;
25
26
  }
26
27
  export {};