@flagix/js-sdk 1.2.0 → 1.3.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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +11 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +143 -85
- package/dist/index.mjs +143 -85
- package/package.json +2 -2
- package/src/client.ts +115 -78
- package/src/index.ts +43 -13
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @flagix/js-sdk@1.
|
|
2
|
+
> @flagix/js-sdk@1.3.0 build /home/runner/work/flagix/flagix/sdk/javascript
|
|
3
3
|
> tsup src/index.ts --format cjs,esm --dts --clean
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
[34mCLI[39m Cleaning output folder
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
14
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
15
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m17.07 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 57ms
|
|
14
|
+
[32mCJS[39m [1mdist/index.js [22m[32m18.75 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 58ms
|
|
16
16
|
[34mDTS[39m Build start
|
|
17
|
-
[32mDTS[39m ⚡️ Build success in
|
|
18
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.
|
|
19
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m2.
|
|
17
|
+
[32mDTS[39m ⚡️ Build success in 1849ms
|
|
18
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.41 KB[39m
|
|
19
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m2.41 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @flagix/js-sdk
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4e879d8: Improved SDK stability and lifecycle management. Added identify method for explicit user identity switching, fixed race conditions during initialization and SSE setup, and ensured feature flags gracefully fallback to 'off' variations when disabled. Fixed potential memory leaks in React hooks and added support for runtime API key changes.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [4e879d8]
|
|
12
|
+
- @flagix/evaluation-core@1.2.0
|
|
13
|
+
|
|
3
14
|
## 1.2.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -40,6 +40,10 @@ declare const Flagix: {
|
|
|
40
40
|
* @param contextOverrides Optional context.
|
|
41
41
|
*/
|
|
42
42
|
track(eventName: string, properties?: Record<string, unknown>, contextOverrides?: EvaluationContext): void;
|
|
43
|
+
/**
|
|
44
|
+
* Replaces the global evaluation context.
|
|
45
|
+
*/
|
|
46
|
+
identify(newContext: EvaluationContext): void;
|
|
43
47
|
/**
|
|
44
48
|
* Sets or updates the global evaluation context.
|
|
45
49
|
* @param newContext New context attributes to merge or replace.
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ declare const Flagix: {
|
|
|
40
40
|
* @param contextOverrides Optional context.
|
|
41
41
|
*/
|
|
42
42
|
track(eventName: string, properties?: Record<string, unknown>, contextOverrides?: EvaluationContext): void;
|
|
43
|
+
/**
|
|
44
|
+
* Replaces the global evaluation context.
|
|
45
|
+
*/
|
|
46
|
+
identify(newContext: EvaluationContext): void;
|
|
43
47
|
/**
|
|
44
48
|
* Sets or updates the global evaluation context.
|
|
45
49
|
* @param newContext New context attributes to merge or replace.
|
package/dist/index.js
CHANGED
|
@@ -102,23 +102,27 @@ var FlagixClient = class {
|
|
|
102
102
|
this.maxReconnectAttempts = Number.POSITIVE_INFINITY;
|
|
103
103
|
this.baseReconnectDelay = 1e3;
|
|
104
104
|
this.maxReconnectDelay = 3e4;
|
|
105
|
+
this.isConnectingSSE = false;
|
|
106
|
+
/**
|
|
107
|
+
* Subscribes a listener to a flag update event.
|
|
108
|
+
*/
|
|
109
|
+
this.on = (event, listener) => {
|
|
110
|
+
this.emitter.on(event, listener);
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Unsubscribes a listener from a flag update event.
|
|
114
|
+
*/
|
|
115
|
+
this.off = (event, listener) => {
|
|
116
|
+
this.emitter.off(event, listener);
|
|
117
|
+
};
|
|
105
118
|
this.apiKey = options.apiKey;
|
|
106
119
|
this.apiBaseUrl = options.apiBaseUrl.replace(REMOVE_TRAILING_SLASH, "");
|
|
107
120
|
this.context = options.initialContext || {};
|
|
108
121
|
this.emitter = new FlagixEventEmitter();
|
|
109
122
|
setLogLevel(options.logs?.level ?? "none");
|
|
110
123
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*/
|
|
114
|
-
on(event, listener) {
|
|
115
|
-
this.emitter.on(event, listener);
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Unsubscribes a listener from a flag update event.
|
|
119
|
-
*/
|
|
120
|
-
off(event, listener) {
|
|
121
|
-
this.emitter.off(event, listener);
|
|
124
|
+
getApiKey() {
|
|
125
|
+
return this.apiKey;
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
124
128
|
* Fetches all flag configurations from the API, populates the local cache,
|
|
@@ -164,6 +168,14 @@ var FlagixClient = class {
|
|
|
164
168
|
}
|
|
165
169
|
return result?.value ?? null;
|
|
166
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Replaces the global evaluation context.
|
|
173
|
+
*/
|
|
174
|
+
identify(newContext) {
|
|
175
|
+
this.context = newContext;
|
|
176
|
+
log("info", "[Flagix SDK] Context replaced");
|
|
177
|
+
this.refreshAllFlags();
|
|
178
|
+
}
|
|
167
179
|
/**
|
|
168
180
|
* Sets or updates the global evaluation context.
|
|
169
181
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -174,6 +186,12 @@ var FlagixClient = class {
|
|
|
174
186
|
"info",
|
|
175
187
|
"[Flagix SDK] Context updated. Evaluations will use the new context."
|
|
176
188
|
);
|
|
189
|
+
this.refreshAllFlags();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Helper to refresh all flags by emitting update events for each cached flag.
|
|
193
|
+
*/
|
|
194
|
+
refreshAllFlags() {
|
|
177
195
|
for (const flagKey of this.localCache.keys()) {
|
|
178
196
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
179
197
|
}
|
|
@@ -204,6 +222,9 @@ var FlagixClient = class {
|
|
|
204
222
|
}
|
|
205
223
|
}
|
|
206
224
|
async setupSSEListener() {
|
|
225
|
+
if (this.isConnectingSSE) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
207
228
|
if (this.sseConnection) {
|
|
208
229
|
try {
|
|
209
230
|
this.sseConnection.close();
|
|
@@ -216,71 +237,83 @@ var FlagixClient = class {
|
|
|
216
237
|
}
|
|
217
238
|
this.sseConnection = null;
|
|
218
239
|
}
|
|
240
|
+
this.isConnectingSSE = true;
|
|
219
241
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.sseConnection = source;
|
|
227
|
-
source.onopen = () => {
|
|
228
|
-
this.reconnectAttempts = 0;
|
|
229
|
-
this.isReconnecting = false;
|
|
230
|
-
if (this.reconnectTimeoutId) {
|
|
231
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
232
|
-
this.reconnectTimeoutId = null;
|
|
242
|
+
try {
|
|
243
|
+
const source = await createEventSource(url, this.apiKey);
|
|
244
|
+
if (!source) {
|
|
245
|
+
log("warn", "[Flagix SDK] Failed to create EventSource. Retrying...");
|
|
246
|
+
this.scheduleReconnect();
|
|
247
|
+
return;
|
|
233
248
|
}
|
|
234
|
-
if (this.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
if (!this.isInitialized && !this.isReconnecting) {
|
|
250
|
+
source.close();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.sseConnection = source;
|
|
254
|
+
source.onopen = () => {
|
|
255
|
+
this.reconnectAttempts = 0;
|
|
256
|
+
this.isReconnecting = false;
|
|
257
|
+
if (this.reconnectTimeoutId) {
|
|
258
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
259
|
+
this.reconnectTimeoutId = null;
|
|
260
|
+
}
|
|
261
|
+
if (this.hasEstablishedConnection && this.isInitialized) {
|
|
240
262
|
log(
|
|
241
|
-
"
|
|
242
|
-
"[Flagix SDK]
|
|
263
|
+
"info",
|
|
264
|
+
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
265
|
+
);
|
|
266
|
+
this.fetchInitialConfig().catch((error) => {
|
|
267
|
+
log(
|
|
268
|
+
"error",
|
|
269
|
+
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
270
|
+
error
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
this.hasEstablishedConnection = true;
|
|
275
|
+
}
|
|
276
|
+
log("info", "[Flagix SDK] SSE connection established.");
|
|
277
|
+
};
|
|
278
|
+
source.onerror = (error) => {
|
|
279
|
+
const eventSource = error.target;
|
|
280
|
+
const readyState = eventSource?.readyState;
|
|
281
|
+
if (readyState === 2) {
|
|
282
|
+
log(
|
|
283
|
+
"warn",
|
|
284
|
+
"[Flagix SDK] SSE connection closed. Attempting to reconnect..."
|
|
285
|
+
);
|
|
286
|
+
this.handleReconnect();
|
|
287
|
+
} else if (readyState === 0) {
|
|
288
|
+
log(
|
|
289
|
+
"warn",
|
|
290
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
243
291
|
error
|
|
244
292
|
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"[Flagix SDK]
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
source.addEventListener("connected", () => {
|
|
272
|
-
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
273
|
-
});
|
|
274
|
-
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
275
|
-
try {
|
|
276
|
-
const data = JSON.parse(event.data);
|
|
277
|
-
const { flagKey, type } = data;
|
|
278
|
-
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
279
|
-
this.fetchSingleFlagConfig(flagKey, type);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
293
|
+
} else {
|
|
294
|
+
log("error", "[Flagix SDK] SSE error", error);
|
|
295
|
+
this.handleReconnect();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
source.addEventListener("connected", () => {
|
|
299
|
+
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
300
|
+
});
|
|
301
|
+
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
302
|
+
try {
|
|
303
|
+
const data = JSON.parse(event.data);
|
|
304
|
+
const { flagKey, type } = data;
|
|
305
|
+
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
306
|
+
this.fetchSingleFlagConfig(flagKey, type);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
log("error", "[Flagix SDK] Failed during SSE setup", error);
|
|
313
|
+
this.handleReconnect();
|
|
314
|
+
} finally {
|
|
315
|
+
this.isConnectingSSE = false;
|
|
316
|
+
}
|
|
284
317
|
}
|
|
285
318
|
handleReconnect() {
|
|
286
319
|
if (this.isReconnecting || !this.isInitialized) {
|
|
@@ -322,7 +355,7 @@ var FlagixClient = class {
|
|
|
322
355
|
}
|
|
323
356
|
async fetchSingleFlagConfig(flagKey, type) {
|
|
324
357
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
325
|
-
if (type === "FLAG_DELETED"
|
|
358
|
+
if (type === "FLAG_DELETED") {
|
|
326
359
|
this.localCache.delete(flagKey);
|
|
327
360
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
328
361
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
@@ -460,24 +493,37 @@ var Flagix = {
|
|
|
460
493
|
*/
|
|
461
494
|
async initialize(options) {
|
|
462
495
|
if (clientInstance) {
|
|
463
|
-
|
|
464
|
-
|
|
496
|
+
if (clientInstance.getApiKey() === options.apiKey) {
|
|
497
|
+
if (clientInstance.getIsInitialized()) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (isInitializing && initializationPromise) {
|
|
501
|
+
return initializationPromise;
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
log(
|
|
505
|
+
"info",
|
|
506
|
+
"[Flagix SDK] API Key change detected. Resetting client..."
|
|
507
|
+
);
|
|
508
|
+
this.close();
|
|
509
|
+
}
|
|
465
510
|
}
|
|
466
511
|
if (isInitializing && initializationPromise) {
|
|
467
512
|
return initializationPromise;
|
|
468
513
|
}
|
|
469
514
|
isInitializing = true;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
515
|
+
initializationPromise = (async () => {
|
|
516
|
+
try {
|
|
517
|
+
clientInstance = new FlagixClient(options);
|
|
518
|
+
await clientInstance.initialize();
|
|
519
|
+
} catch (error) {
|
|
520
|
+
clientInstance = null;
|
|
521
|
+
throw error;
|
|
522
|
+
} finally {
|
|
523
|
+
isInitializing = false;
|
|
524
|
+
}
|
|
525
|
+
})();
|
|
526
|
+
return await initializationPromise;
|
|
481
527
|
},
|
|
482
528
|
/**
|
|
483
529
|
* Evaluates a flag based on the local cache and current context.
|
|
@@ -510,6 +556,16 @@ var Flagix = {
|
|
|
510
556
|
}
|
|
511
557
|
clientInstance.track(eventName, properties, contextOverrides);
|
|
512
558
|
},
|
|
559
|
+
/**
|
|
560
|
+
* Replaces the global evaluation context.
|
|
561
|
+
*/
|
|
562
|
+
identify(newContext) {
|
|
563
|
+
if (!clientInstance) {
|
|
564
|
+
log("error", "Flagix SDK not initialized.");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
clientInstance.identify(newContext);
|
|
568
|
+
},
|
|
513
569
|
/**
|
|
514
570
|
* Sets or updates the global evaluation context.
|
|
515
571
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -528,6 +584,8 @@ var Flagix = {
|
|
|
528
584
|
if (clientInstance) {
|
|
529
585
|
clientInstance.close();
|
|
530
586
|
clientInstance = null;
|
|
587
|
+
initializationPromise = null;
|
|
588
|
+
isInitializing = false;
|
|
531
589
|
}
|
|
532
590
|
},
|
|
533
591
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -66,23 +66,27 @@ var FlagixClient = class {
|
|
|
66
66
|
this.maxReconnectAttempts = Number.POSITIVE_INFINITY;
|
|
67
67
|
this.baseReconnectDelay = 1e3;
|
|
68
68
|
this.maxReconnectDelay = 3e4;
|
|
69
|
+
this.isConnectingSSE = false;
|
|
70
|
+
/**
|
|
71
|
+
* Subscribes a listener to a flag update event.
|
|
72
|
+
*/
|
|
73
|
+
this.on = (event, listener) => {
|
|
74
|
+
this.emitter.on(event, listener);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Unsubscribes a listener from a flag update event.
|
|
78
|
+
*/
|
|
79
|
+
this.off = (event, listener) => {
|
|
80
|
+
this.emitter.off(event, listener);
|
|
81
|
+
};
|
|
69
82
|
this.apiKey = options.apiKey;
|
|
70
83
|
this.apiBaseUrl = options.apiBaseUrl.replace(REMOVE_TRAILING_SLASH, "");
|
|
71
84
|
this.context = options.initialContext || {};
|
|
72
85
|
this.emitter = new FlagixEventEmitter();
|
|
73
86
|
setLogLevel(options.logs?.level ?? "none");
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*/
|
|
78
|
-
on(event, listener) {
|
|
79
|
-
this.emitter.on(event, listener);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Unsubscribes a listener from a flag update event.
|
|
83
|
-
*/
|
|
84
|
-
off(event, listener) {
|
|
85
|
-
this.emitter.off(event, listener);
|
|
88
|
+
getApiKey() {
|
|
89
|
+
return this.apiKey;
|
|
86
90
|
}
|
|
87
91
|
/**
|
|
88
92
|
* Fetches all flag configurations from the API, populates the local cache,
|
|
@@ -128,6 +132,14 @@ var FlagixClient = class {
|
|
|
128
132
|
}
|
|
129
133
|
return result?.value ?? null;
|
|
130
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Replaces the global evaluation context.
|
|
137
|
+
*/
|
|
138
|
+
identify(newContext) {
|
|
139
|
+
this.context = newContext;
|
|
140
|
+
log("info", "[Flagix SDK] Context replaced");
|
|
141
|
+
this.refreshAllFlags();
|
|
142
|
+
}
|
|
131
143
|
/**
|
|
132
144
|
* Sets or updates the global evaluation context.
|
|
133
145
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -138,6 +150,12 @@ var FlagixClient = class {
|
|
|
138
150
|
"info",
|
|
139
151
|
"[Flagix SDK] Context updated. Evaluations will use the new context."
|
|
140
152
|
);
|
|
153
|
+
this.refreshAllFlags();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Helper to refresh all flags by emitting update events for each cached flag.
|
|
157
|
+
*/
|
|
158
|
+
refreshAllFlags() {
|
|
141
159
|
for (const flagKey of this.localCache.keys()) {
|
|
142
160
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
143
161
|
}
|
|
@@ -168,6 +186,9 @@ var FlagixClient = class {
|
|
|
168
186
|
}
|
|
169
187
|
}
|
|
170
188
|
async setupSSEListener() {
|
|
189
|
+
if (this.isConnectingSSE) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
171
192
|
if (this.sseConnection) {
|
|
172
193
|
try {
|
|
173
194
|
this.sseConnection.close();
|
|
@@ -180,71 +201,83 @@ var FlagixClient = class {
|
|
|
180
201
|
}
|
|
181
202
|
this.sseConnection = null;
|
|
182
203
|
}
|
|
204
|
+
this.isConnectingSSE = true;
|
|
183
205
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.sseConnection = source;
|
|
191
|
-
source.onopen = () => {
|
|
192
|
-
this.reconnectAttempts = 0;
|
|
193
|
-
this.isReconnecting = false;
|
|
194
|
-
if (this.reconnectTimeoutId) {
|
|
195
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
196
|
-
this.reconnectTimeoutId = null;
|
|
206
|
+
try {
|
|
207
|
+
const source = await createEventSource(url, this.apiKey);
|
|
208
|
+
if (!source) {
|
|
209
|
+
log("warn", "[Flagix SDK] Failed to create EventSource. Retrying...");
|
|
210
|
+
this.scheduleReconnect();
|
|
211
|
+
return;
|
|
197
212
|
}
|
|
198
|
-
if (this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
if (!this.isInitialized && !this.isReconnecting) {
|
|
214
|
+
source.close();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.sseConnection = source;
|
|
218
|
+
source.onopen = () => {
|
|
219
|
+
this.reconnectAttempts = 0;
|
|
220
|
+
this.isReconnecting = false;
|
|
221
|
+
if (this.reconnectTimeoutId) {
|
|
222
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
223
|
+
this.reconnectTimeoutId = null;
|
|
224
|
+
}
|
|
225
|
+
if (this.hasEstablishedConnection && this.isInitialized) {
|
|
204
226
|
log(
|
|
205
|
-
"
|
|
206
|
-
"[Flagix SDK]
|
|
227
|
+
"info",
|
|
228
|
+
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
229
|
+
);
|
|
230
|
+
this.fetchInitialConfig().catch((error) => {
|
|
231
|
+
log(
|
|
232
|
+
"error",
|
|
233
|
+
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
234
|
+
error
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
} else {
|
|
238
|
+
this.hasEstablishedConnection = true;
|
|
239
|
+
}
|
|
240
|
+
log("info", "[Flagix SDK] SSE connection established.");
|
|
241
|
+
};
|
|
242
|
+
source.onerror = (error) => {
|
|
243
|
+
const eventSource = error.target;
|
|
244
|
+
const readyState = eventSource?.readyState;
|
|
245
|
+
if (readyState === 2) {
|
|
246
|
+
log(
|
|
247
|
+
"warn",
|
|
248
|
+
"[Flagix SDK] SSE connection closed. Attempting to reconnect..."
|
|
249
|
+
);
|
|
250
|
+
this.handleReconnect();
|
|
251
|
+
} else if (readyState === 0) {
|
|
252
|
+
log(
|
|
253
|
+
"warn",
|
|
254
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
207
255
|
error
|
|
208
256
|
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"[Flagix SDK]
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
source.addEventListener("connected", () => {
|
|
236
|
-
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
237
|
-
});
|
|
238
|
-
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
239
|
-
try {
|
|
240
|
-
const data = JSON.parse(event.data);
|
|
241
|
-
const { flagKey, type } = data;
|
|
242
|
-
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
243
|
-
this.fetchSingleFlagConfig(flagKey, type);
|
|
244
|
-
} catch (error) {
|
|
245
|
-
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
257
|
+
} else {
|
|
258
|
+
log("error", "[Flagix SDK] SSE error", error);
|
|
259
|
+
this.handleReconnect();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
source.addEventListener("connected", () => {
|
|
263
|
+
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
264
|
+
});
|
|
265
|
+
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
266
|
+
try {
|
|
267
|
+
const data = JSON.parse(event.data);
|
|
268
|
+
const { flagKey, type } = data;
|
|
269
|
+
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
270
|
+
this.fetchSingleFlagConfig(flagKey, type);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
log("error", "[Flagix SDK] Failed during SSE setup", error);
|
|
277
|
+
this.handleReconnect();
|
|
278
|
+
} finally {
|
|
279
|
+
this.isConnectingSSE = false;
|
|
280
|
+
}
|
|
248
281
|
}
|
|
249
282
|
handleReconnect() {
|
|
250
283
|
if (this.isReconnecting || !this.isInitialized) {
|
|
@@ -286,7 +319,7 @@ var FlagixClient = class {
|
|
|
286
319
|
}
|
|
287
320
|
async fetchSingleFlagConfig(flagKey, type) {
|
|
288
321
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
289
|
-
if (type === "FLAG_DELETED"
|
|
322
|
+
if (type === "FLAG_DELETED") {
|
|
290
323
|
this.localCache.delete(flagKey);
|
|
291
324
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
292
325
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
@@ -424,24 +457,37 @@ var Flagix = {
|
|
|
424
457
|
*/
|
|
425
458
|
async initialize(options) {
|
|
426
459
|
if (clientInstance) {
|
|
427
|
-
|
|
428
|
-
|
|
460
|
+
if (clientInstance.getApiKey() === options.apiKey) {
|
|
461
|
+
if (clientInstance.getIsInitialized()) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (isInitializing && initializationPromise) {
|
|
465
|
+
return initializationPromise;
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
log(
|
|
469
|
+
"info",
|
|
470
|
+
"[Flagix SDK] API Key change detected. Resetting client..."
|
|
471
|
+
);
|
|
472
|
+
this.close();
|
|
473
|
+
}
|
|
429
474
|
}
|
|
430
475
|
if (isInitializing && initializationPromise) {
|
|
431
476
|
return initializationPromise;
|
|
432
477
|
}
|
|
433
478
|
isInitializing = true;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
479
|
+
initializationPromise = (async () => {
|
|
480
|
+
try {
|
|
481
|
+
clientInstance = new FlagixClient(options);
|
|
482
|
+
await clientInstance.initialize();
|
|
483
|
+
} catch (error) {
|
|
484
|
+
clientInstance = null;
|
|
485
|
+
throw error;
|
|
486
|
+
} finally {
|
|
487
|
+
isInitializing = false;
|
|
488
|
+
}
|
|
489
|
+
})();
|
|
490
|
+
return await initializationPromise;
|
|
445
491
|
},
|
|
446
492
|
/**
|
|
447
493
|
* Evaluates a flag based on the local cache and current context.
|
|
@@ -474,6 +520,16 @@ var Flagix = {
|
|
|
474
520
|
}
|
|
475
521
|
clientInstance.track(eventName, properties, contextOverrides);
|
|
476
522
|
},
|
|
523
|
+
/**
|
|
524
|
+
* Replaces the global evaluation context.
|
|
525
|
+
*/
|
|
526
|
+
identify(newContext) {
|
|
527
|
+
if (!clientInstance) {
|
|
528
|
+
log("error", "Flagix SDK not initialized.");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
clientInstance.identify(newContext);
|
|
532
|
+
},
|
|
477
533
|
/**
|
|
478
534
|
* Sets or updates the global evaluation context.
|
|
479
535
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -492,6 +548,8 @@ var Flagix = {
|
|
|
492
548
|
if (clientInstance) {
|
|
493
549
|
clientInstance.close();
|
|
494
550
|
clientInstance = null;
|
|
551
|
+
initializationPromise = null;
|
|
552
|
+
isInitializing = false;
|
|
495
553
|
}
|
|
496
554
|
},
|
|
497
555
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flagix/js-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Flagix Javascript SDK",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"eventemitter3": "^5.0.1",
|
|
32
32
|
"eventsource": "^4.1.0",
|
|
33
|
-
"@flagix/evaluation-core": "^1.
|
|
33
|
+
"@flagix/evaluation-core": "^1.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^22.15.3",
|
package/src/client.ts
CHANGED
|
@@ -37,6 +37,7 @@ export class FlagixClient {
|
|
|
37
37
|
private readonly maxReconnectAttempts = Number.POSITIVE_INFINITY;
|
|
38
38
|
private readonly baseReconnectDelay = 1000;
|
|
39
39
|
private readonly maxReconnectDelay = 30_000;
|
|
40
|
+
private isConnectingSSE = false;
|
|
40
41
|
|
|
41
42
|
constructor(options: FlagixClientOptions) {
|
|
42
43
|
this.apiKey = options.apiKey;
|
|
@@ -49,21 +50,25 @@ export class FlagixClient {
|
|
|
49
50
|
/**
|
|
50
51
|
* Subscribes a listener to a flag update event.
|
|
51
52
|
*/
|
|
52
|
-
on(
|
|
53
|
+
on = (
|
|
53
54
|
event: typeof FLAG_UPDATE_EVENT,
|
|
54
55
|
listener: (flagKey: string) => void
|
|
55
|
-
)
|
|
56
|
+
) => {
|
|
56
57
|
this.emitter.on(event, listener);
|
|
57
|
-
}
|
|
58
|
+
};
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Unsubscribes a listener from a flag update event.
|
|
61
62
|
*/
|
|
62
|
-
off(
|
|
63
|
+
off = (
|
|
63
64
|
event: typeof FLAG_UPDATE_EVENT,
|
|
64
65
|
listener: (flagKey: string) => void
|
|
65
|
-
)
|
|
66
|
+
) => {
|
|
66
67
|
this.emitter.off(event, listener);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
getApiKey(): string {
|
|
71
|
+
return this.apiKey;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/**
|
|
@@ -125,6 +130,15 @@ export class FlagixClient {
|
|
|
125
130
|
return (result?.value as T) ?? null;
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Replaces the global evaluation context.
|
|
135
|
+
*/
|
|
136
|
+
identify(newContext: EvaluationContext): void {
|
|
137
|
+
this.context = newContext;
|
|
138
|
+
log("info", "[Flagix SDK] Context replaced");
|
|
139
|
+
this.refreshAllFlags();
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
/**
|
|
129
143
|
* Sets or updates the global evaluation context.
|
|
130
144
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -135,7 +149,13 @@ export class FlagixClient {
|
|
|
135
149
|
"info",
|
|
136
150
|
"[Flagix SDK] Context updated. Evaluations will use the new context."
|
|
137
151
|
);
|
|
152
|
+
this.refreshAllFlags();
|
|
153
|
+
}
|
|
138
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Helper to refresh all flags by emitting update events for each cached flag.
|
|
157
|
+
*/
|
|
158
|
+
private refreshAllFlags(): void {
|
|
139
159
|
for (const flagKey of this.localCache.keys()) {
|
|
140
160
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
141
161
|
}
|
|
@@ -172,6 +192,10 @@ export class FlagixClient {
|
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
private async setupSSEListener(): Promise<void> {
|
|
195
|
+
if (this.isConnectingSSE) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
175
199
|
if (this.sseConnection) {
|
|
176
200
|
try {
|
|
177
201
|
this.sseConnection.close();
|
|
@@ -185,89 +209,102 @@ export class FlagixClient {
|
|
|
185
209
|
this.sseConnection = null;
|
|
186
210
|
}
|
|
187
211
|
|
|
212
|
+
this.isConnectingSSE = true;
|
|
188
213
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
189
214
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.sseConnection = source;
|
|
198
|
-
|
|
199
|
-
source.onopen = () => {
|
|
200
|
-
this.reconnectAttempts = 0;
|
|
201
|
-
this.isReconnecting = false;
|
|
202
|
-
if (this.reconnectTimeoutId) {
|
|
203
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
204
|
-
this.reconnectTimeoutId = null;
|
|
215
|
+
try {
|
|
216
|
+
const source = await createEventSource(url, this.apiKey);
|
|
217
|
+
if (!source) {
|
|
218
|
+
log("warn", "[Flagix SDK] Failed to create EventSource. Retrying...");
|
|
219
|
+
this.scheduleReconnect();
|
|
220
|
+
return;
|
|
205
221
|
}
|
|
206
222
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
log(
|
|
211
|
-
"info",
|
|
212
|
-
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
213
|
-
);
|
|
214
|
-
this.fetchInitialConfig().catch((error) => {
|
|
215
|
-
log(
|
|
216
|
-
"error",
|
|
217
|
-
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
218
|
-
error
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
} else {
|
|
222
|
-
this.hasEstablishedConnection = true;
|
|
223
|
+
if (!this.isInitialized && !this.isReconnecting) {
|
|
224
|
+
source.close();
|
|
225
|
+
return;
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
source.onerror = (error) => {
|
|
229
|
-
const eventSource = error.target as EventSource;
|
|
230
|
-
const readyState = eventSource?.readyState;
|
|
228
|
+
this.sseConnection = source;
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
} else if (readyState === 0) {
|
|
240
|
-
log(
|
|
241
|
-
"warn",
|
|
242
|
-
"[Flagix SDK] SSE connection error (connecting state)",
|
|
243
|
-
error
|
|
244
|
-
);
|
|
245
|
-
} else {
|
|
246
|
-
log("error", "[Flagix SDK] SSE error", error);
|
|
247
|
-
this.handleReconnect();
|
|
248
|
-
}
|
|
249
|
-
};
|
|
230
|
+
source.onopen = () => {
|
|
231
|
+
this.reconnectAttempts = 0;
|
|
232
|
+
this.isReconnecting = false;
|
|
233
|
+
if (this.reconnectTimeoutId) {
|
|
234
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
235
|
+
this.reconnectTimeoutId = null;
|
|
236
|
+
}
|
|
250
237
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
238
|
+
// If this is a reconnection and not the first connection, refresh the cache
|
|
239
|
+
// this ensures we have the latest flag values that may have changed while disconnected
|
|
240
|
+
if (this.hasEstablishedConnection && this.isInitialized) {
|
|
241
|
+
log(
|
|
242
|
+
"info",
|
|
243
|
+
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
244
|
+
);
|
|
245
|
+
this.fetchInitialConfig().catch((error) => {
|
|
246
|
+
log(
|
|
247
|
+
"error",
|
|
248
|
+
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
249
|
+
error
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
this.hasEstablishedConnection = true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
log("info", "[Flagix SDK] SSE connection established.");
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
source.onerror = (error) => {
|
|
260
|
+
const eventSource = error.target as EventSource;
|
|
261
|
+
const readyState = eventSource?.readyState;
|
|
262
|
+
|
|
263
|
+
// EventSource.readyState: 0 = CONNECTING, 1 = OPEN, 2 = CLOSED
|
|
264
|
+
if (readyState === 2) {
|
|
265
|
+
log(
|
|
266
|
+
"warn",
|
|
267
|
+
"[Flagix SDK] SSE connection closed. Attempting to reconnect..."
|
|
268
|
+
);
|
|
269
|
+
this.handleReconnect();
|
|
270
|
+
} else if (readyState === 0) {
|
|
271
|
+
log(
|
|
272
|
+
"warn",
|
|
273
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
274
|
+
error
|
|
275
|
+
);
|
|
276
|
+
} else {
|
|
277
|
+
log("error", "[Flagix SDK] SSE error", error);
|
|
278
|
+
this.handleReconnect();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Listen for the "connected" event from the server
|
|
283
|
+
source.addEventListener("connected", () => {
|
|
284
|
+
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
285
|
+
});
|
|
255
286
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
287
|
+
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
288
|
+
try {
|
|
289
|
+
const data = JSON.parse(event.data);
|
|
290
|
+
const { flagKey, type } = data as {
|
|
291
|
+
flagKey: string;
|
|
292
|
+
type: FlagUpdateType;
|
|
293
|
+
};
|
|
263
294
|
|
|
264
|
-
|
|
295
|
+
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
this.fetchSingleFlagConfig(flagKey, type);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
log("error", "[Flagix SDK] Failed during SSE setup", error);
|
|
304
|
+
this.handleReconnect();
|
|
305
|
+
} finally {
|
|
306
|
+
this.isConnectingSSE = false;
|
|
307
|
+
}
|
|
271
308
|
}
|
|
272
309
|
|
|
273
310
|
private handleReconnect(): void {
|
|
@@ -324,7 +361,7 @@ export class FlagixClient {
|
|
|
324
361
|
): Promise<void> {
|
|
325
362
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
326
363
|
|
|
327
|
-
if (type === "FLAG_DELETED"
|
|
364
|
+
if (type === "FLAG_DELETED") {
|
|
328
365
|
this.localCache.delete(flagKey);
|
|
329
366
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
330
367
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
package/src/index.ts
CHANGED
|
@@ -17,9 +17,24 @@ export const Flagix = {
|
|
|
17
17
|
* Initializes the Flagix SDK, fetches all flags, and sets up an SSE connection.
|
|
18
18
|
*/
|
|
19
19
|
async initialize(options: FlagixClientOptions): Promise<void> {
|
|
20
|
+
// this check ensures that we are able to watch for api key changes and re-initialize accordingly
|
|
21
|
+
// this ensures we dont use stale clients across different api keys
|
|
20
22
|
if (clientInstance) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
if (clientInstance.getApiKey() === options.apiKey) {
|
|
24
|
+
if (clientInstance.getIsInitialized()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isInitializing && initializationPromise) {
|
|
29
|
+
return initializationPromise;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
log(
|
|
33
|
+
"info",
|
|
34
|
+
"[Flagix SDK] API Key change detected. Resetting client..."
|
|
35
|
+
);
|
|
36
|
+
this.close();
|
|
37
|
+
}
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
if (isInitializing && initializationPromise) {
|
|
@@ -28,17 +43,19 @@ export const Flagix = {
|
|
|
28
43
|
|
|
29
44
|
isInitializing = true;
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
46
|
+
initializationPromise = (async () => {
|
|
47
|
+
try {
|
|
48
|
+
clientInstance = new FlagixClient(options);
|
|
49
|
+
await clientInstance.initialize();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
clientInstance = null;
|
|
52
|
+
throw error;
|
|
53
|
+
} finally {
|
|
54
|
+
isInitializing = false;
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
|
|
58
|
+
return await initializationPromise;
|
|
42
59
|
},
|
|
43
60
|
|
|
44
61
|
/**
|
|
@@ -82,6 +99,17 @@ export const Flagix = {
|
|
|
82
99
|
clientInstance.track(eventName, properties, contextOverrides);
|
|
83
100
|
},
|
|
84
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Replaces the global evaluation context.
|
|
104
|
+
*/
|
|
105
|
+
identify(newContext: EvaluationContext): void {
|
|
106
|
+
if (!clientInstance) {
|
|
107
|
+
log("error", "Flagix SDK not initialized.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
clientInstance.identify(newContext);
|
|
111
|
+
},
|
|
112
|
+
|
|
85
113
|
/**
|
|
86
114
|
* Sets or updates the global evaluation context.
|
|
87
115
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -101,6 +129,8 @@ export const Flagix = {
|
|
|
101
129
|
if (clientInstance) {
|
|
102
130
|
clientInstance.close();
|
|
103
131
|
clientInstance = null;
|
|
132
|
+
initializationPromise = null;
|
|
133
|
+
isInitializing = false;
|
|
104
134
|
}
|
|
105
135
|
},
|
|
106
136
|
|