@flagix/js-sdk 1.1.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 +21 -0
- package/LICENSE +21 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +146 -85
- package/dist/index.mjs +146 -85
- package/package.json +2 -2
- package/src/client.ts +119 -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
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
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,26 @@
|
|
|
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
|
+
|
|
14
|
+
## 1.2.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 70d02db: feat: implement reactive context synchronization
|
|
19
|
+
|
|
20
|
+
- Added 'context' prop to FlagixProvider for automatic SDK syncing.
|
|
21
|
+
- Updated setContext to trigger real-time updates for all flag listeners.
|
|
22
|
+
- Improved FlagixProvider stability to prevent unnecessary re-initializations.
|
|
23
|
+
|
|
3
24
|
## 1.1.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flagix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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,15 @@ 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() {
|
|
195
|
+
for (const flagKey of this.localCache.keys()) {
|
|
196
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
197
|
+
}
|
|
177
198
|
}
|
|
178
199
|
async fetchInitialConfig() {
|
|
179
200
|
const url = `${this.apiBaseUrl}/api/flag-config/all`;
|
|
@@ -201,6 +222,9 @@ var FlagixClient = class {
|
|
|
201
222
|
}
|
|
202
223
|
}
|
|
203
224
|
async setupSSEListener() {
|
|
225
|
+
if (this.isConnectingSSE) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
204
228
|
if (this.sseConnection) {
|
|
205
229
|
try {
|
|
206
230
|
this.sseConnection.close();
|
|
@@ -213,71 +237,83 @@ var FlagixClient = class {
|
|
|
213
237
|
}
|
|
214
238
|
this.sseConnection = null;
|
|
215
239
|
}
|
|
240
|
+
this.isConnectingSSE = true;
|
|
216
241
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.sseConnection = source;
|
|
224
|
-
source.onopen = () => {
|
|
225
|
-
this.reconnectAttempts = 0;
|
|
226
|
-
this.isReconnecting = false;
|
|
227
|
-
if (this.reconnectTimeoutId) {
|
|
228
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
229
|
-
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;
|
|
230
248
|
}
|
|
231
|
-
if (this.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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) {
|
|
262
|
+
log(
|
|
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) {
|
|
237
288
|
log(
|
|
238
|
-
"
|
|
239
|
-
"[Flagix SDK]
|
|
289
|
+
"warn",
|
|
290
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
240
291
|
error
|
|
241
292
|
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
"[Flagix SDK]
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
source.addEventListener("connected", () => {
|
|
269
|
-
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
270
|
-
});
|
|
271
|
-
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
272
|
-
try {
|
|
273
|
-
const data = JSON.parse(event.data);
|
|
274
|
-
const { flagKey, type } = data;
|
|
275
|
-
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
276
|
-
this.fetchSingleFlagConfig(flagKey, type);
|
|
277
|
-
} catch (error) {
|
|
278
|
-
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
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
|
+
}
|
|
281
317
|
}
|
|
282
318
|
handleReconnect() {
|
|
283
319
|
if (this.isReconnecting || !this.isInitialized) {
|
|
@@ -319,7 +355,7 @@ var FlagixClient = class {
|
|
|
319
355
|
}
|
|
320
356
|
async fetchSingleFlagConfig(flagKey, type) {
|
|
321
357
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
322
|
-
if (type === "FLAG_DELETED"
|
|
358
|
+
if (type === "FLAG_DELETED") {
|
|
323
359
|
this.localCache.delete(flagKey);
|
|
324
360
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
325
361
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
@@ -457,24 +493,37 @@ var Flagix = {
|
|
|
457
493
|
*/
|
|
458
494
|
async initialize(options) {
|
|
459
495
|
if (clientInstance) {
|
|
460
|
-
|
|
461
|
-
|
|
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
|
+
}
|
|
462
510
|
}
|
|
463
511
|
if (isInitializing && initializationPromise) {
|
|
464
512
|
return initializationPromise;
|
|
465
513
|
}
|
|
466
514
|
isInitializing = true;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
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;
|
|
478
527
|
},
|
|
479
528
|
/**
|
|
480
529
|
* Evaluates a flag based on the local cache and current context.
|
|
@@ -507,6 +556,16 @@ var Flagix = {
|
|
|
507
556
|
}
|
|
508
557
|
clientInstance.track(eventName, properties, contextOverrides);
|
|
509
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
|
+
},
|
|
510
569
|
/**
|
|
511
570
|
* Sets or updates the global evaluation context.
|
|
512
571
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -525,6 +584,8 @@ var Flagix = {
|
|
|
525
584
|
if (clientInstance) {
|
|
526
585
|
clientInstance.close();
|
|
527
586
|
clientInstance = null;
|
|
587
|
+
initializationPromise = null;
|
|
588
|
+
isInitializing = false;
|
|
528
589
|
}
|
|
529
590
|
},
|
|
530
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,15 @@ 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() {
|
|
159
|
+
for (const flagKey of this.localCache.keys()) {
|
|
160
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
161
|
+
}
|
|
141
162
|
}
|
|
142
163
|
async fetchInitialConfig() {
|
|
143
164
|
const url = `${this.apiBaseUrl}/api/flag-config/all`;
|
|
@@ -165,6 +186,9 @@ var FlagixClient = class {
|
|
|
165
186
|
}
|
|
166
187
|
}
|
|
167
188
|
async setupSSEListener() {
|
|
189
|
+
if (this.isConnectingSSE) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
168
192
|
if (this.sseConnection) {
|
|
169
193
|
try {
|
|
170
194
|
this.sseConnection.close();
|
|
@@ -177,71 +201,83 @@ var FlagixClient = class {
|
|
|
177
201
|
}
|
|
178
202
|
this.sseConnection = null;
|
|
179
203
|
}
|
|
204
|
+
this.isConnectingSSE = true;
|
|
180
205
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.sseConnection = source;
|
|
188
|
-
source.onopen = () => {
|
|
189
|
-
this.reconnectAttempts = 0;
|
|
190
|
-
this.isReconnecting = false;
|
|
191
|
-
if (this.reconnectTimeoutId) {
|
|
192
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
193
|
-
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;
|
|
194
212
|
}
|
|
195
|
-
if (this.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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) {
|
|
226
|
+
log(
|
|
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) {
|
|
201
252
|
log(
|
|
202
|
-
"
|
|
203
|
-
"[Flagix SDK]
|
|
253
|
+
"warn",
|
|
254
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
204
255
|
error
|
|
205
256
|
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
"[Flagix SDK]
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
source.addEventListener("connected", () => {
|
|
233
|
-
log("info", "[Flagix SDK] SSE connection confirmed by server.");
|
|
234
|
-
});
|
|
235
|
-
source.addEventListener(EVENT_TO_LISTEN, (event) => {
|
|
236
|
-
try {
|
|
237
|
-
const data = JSON.parse(event.data);
|
|
238
|
-
const { flagKey, type } = data;
|
|
239
|
-
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
240
|
-
this.fetchSingleFlagConfig(flagKey, type);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
log("error", "[Flagix SDK] Failed to parse SSE event data.", error);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
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
|
+
}
|
|
245
281
|
}
|
|
246
282
|
handleReconnect() {
|
|
247
283
|
if (this.isReconnecting || !this.isInitialized) {
|
|
@@ -283,7 +319,7 @@ var FlagixClient = class {
|
|
|
283
319
|
}
|
|
284
320
|
async fetchSingleFlagConfig(flagKey, type) {
|
|
285
321
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
286
|
-
if (type === "FLAG_DELETED"
|
|
322
|
+
if (type === "FLAG_DELETED") {
|
|
287
323
|
this.localCache.delete(flagKey);
|
|
288
324
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
289
325
|
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
@@ -421,24 +457,37 @@ var Flagix = {
|
|
|
421
457
|
*/
|
|
422
458
|
async initialize(options) {
|
|
423
459
|
if (clientInstance) {
|
|
424
|
-
|
|
425
|
-
|
|
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
|
+
}
|
|
426
474
|
}
|
|
427
475
|
if (isInitializing && initializationPromise) {
|
|
428
476
|
return initializationPromise;
|
|
429
477
|
}
|
|
430
478
|
isInitializing = true;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
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;
|
|
442
491
|
},
|
|
443
492
|
/**
|
|
444
493
|
* Evaluates a flag based on the local cache and current context.
|
|
@@ -471,6 +520,16 @@ var Flagix = {
|
|
|
471
520
|
}
|
|
472
521
|
clientInstance.track(eventName, properties, contextOverrides);
|
|
473
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
|
+
},
|
|
474
533
|
/**
|
|
475
534
|
* Sets or updates the global evaluation context.
|
|
476
535
|
* @param newContext New context attributes to merge or replace.
|
|
@@ -489,6 +548,8 @@ var Flagix = {
|
|
|
489
548
|
if (clientInstance) {
|
|
490
549
|
clientInstance.close();
|
|
491
550
|
clientInstance = null;
|
|
551
|
+
initializationPromise = null;
|
|
552
|
+
isInitializing = false;
|
|
492
553
|
}
|
|
493
554
|
},
|
|
494
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,6 +149,16 @@ export class FlagixClient {
|
|
|
135
149
|
"info",
|
|
136
150
|
"[Flagix SDK] Context updated. Evaluations will use the new context."
|
|
137
151
|
);
|
|
152
|
+
this.refreshAllFlags();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Helper to refresh all flags by emitting update events for each cached flag.
|
|
157
|
+
*/
|
|
158
|
+
private refreshAllFlags(): void {
|
|
159
|
+
for (const flagKey of this.localCache.keys()) {
|
|
160
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
161
|
+
}
|
|
138
162
|
}
|
|
139
163
|
|
|
140
164
|
private async fetchInitialConfig(): Promise<void> {
|
|
@@ -168,6 +192,10 @@ export class FlagixClient {
|
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
private async setupSSEListener(): Promise<void> {
|
|
195
|
+
if (this.isConnectingSSE) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
if (this.sseConnection) {
|
|
172
200
|
try {
|
|
173
201
|
this.sseConnection.close();
|
|
@@ -181,89 +209,102 @@ export class FlagixClient {
|
|
|
181
209
|
this.sseConnection = null;
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
this.isConnectingSSE = true;
|
|
184
213
|
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
185
214
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.sseConnection = source;
|
|
194
|
-
|
|
195
|
-
source.onopen = () => {
|
|
196
|
-
this.reconnectAttempts = 0;
|
|
197
|
-
this.isReconnecting = false;
|
|
198
|
-
if (this.reconnectTimeoutId) {
|
|
199
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
200
|
-
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;
|
|
201
221
|
}
|
|
202
222
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
log(
|
|
207
|
-
"info",
|
|
208
|
-
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
209
|
-
);
|
|
210
|
-
this.fetchInitialConfig().catch((error) => {
|
|
211
|
-
log(
|
|
212
|
-
"error",
|
|
213
|
-
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
214
|
-
error
|
|
215
|
-
);
|
|
216
|
-
});
|
|
217
|
-
} else {
|
|
218
|
-
this.hasEstablishedConnection = true;
|
|
223
|
+
if (!this.isInitialized && !this.isReconnecting) {
|
|
224
|
+
source.close();
|
|
225
|
+
return;
|
|
219
226
|
}
|
|
220
227
|
|
|
221
|
-
|
|
222
|
-
};
|
|
228
|
+
this.sseConnection = source;
|
|
223
229
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
source.onopen = () => {
|
|
231
|
+
this.reconnectAttempts = 0;
|
|
232
|
+
this.isReconnecting = false;
|
|
233
|
+
if (this.reconnectTimeoutId) {
|
|
234
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
235
|
+
this.reconnectTimeoutId = null;
|
|
236
|
+
}
|
|
227
237
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
});
|
|
251
286
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
};
|
|
259
294
|
|
|
260
|
-
|
|
295
|
+
log("info", `[Flagix SDK] Received update for ${flagKey} (${type}).`);
|
|
261
296
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
308
|
}
|
|
268
309
|
|
|
269
310
|
private handleReconnect(): void {
|
|
@@ -320,7 +361,7 @@ export class FlagixClient {
|
|
|
320
361
|
): Promise<void> {
|
|
321
362
|
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
322
363
|
|
|
323
|
-
if (type === "FLAG_DELETED"
|
|
364
|
+
if (type === "FLAG_DELETED") {
|
|
324
365
|
this.localCache.delete(flagKey);
|
|
325
366
|
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
326
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
|
|