@flagix/js-sdk 1.1.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 +19 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +561 -0
- package/dist/index.mjs +524 -0
- package/package.json +46 -0
- package/src/client.ts +482 -0
- package/src/index.ts +139 -0
- package/src/lib/constants.ts +12 -0
- package/src/lib/emitter.ts +10 -0
- package/src/lib/logger.ts +41 -0
- package/src/sse/create-event-source.ts +33 -0
- package/src/types.ts +22 -0
- package/tsconfig.json +21 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Flagix: () => Flagix
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/client.ts
|
|
38
|
+
var import_evaluation_core = require("@flagix/evaluation-core");
|
|
39
|
+
|
|
40
|
+
// src/lib/constants.ts
|
|
41
|
+
var REMOVE_TRAILING_SLASH = /\/$/;
|
|
42
|
+
var EVENT_TO_LISTEN = "flag-update";
|
|
43
|
+
|
|
44
|
+
// src/lib/emitter.ts
|
|
45
|
+
var import_eventemitter3 = __toESM(require("eventemitter3"));
|
|
46
|
+
var FLAG_UPDATE_EVENT = EVENT_TO_LISTEN;
|
|
47
|
+
var FlagixEventEmitter = class extends import_eventemitter3.default {
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/lib/logger.ts
|
|
51
|
+
var currentLogLevel = "none";
|
|
52
|
+
var LOG_LEVELS = {
|
|
53
|
+
none: 0,
|
|
54
|
+
error: 1,
|
|
55
|
+
warn: 2,
|
|
56
|
+
info: 3
|
|
57
|
+
};
|
|
58
|
+
function setLogLevel(level = "none") {
|
|
59
|
+
currentLogLevel = level;
|
|
60
|
+
}
|
|
61
|
+
function log(level, message, ...args) {
|
|
62
|
+
const currentLevel = LOG_LEVELS[currentLogLevel];
|
|
63
|
+
const requiredLevel = LOG_LEVELS[level];
|
|
64
|
+
if (currentLevel >= requiredLevel) {
|
|
65
|
+
const prefixedMessage = `[Flagix SDK] ${message}`;
|
|
66
|
+
if (level === "error") {
|
|
67
|
+
console.error(prefixedMessage, ...args);
|
|
68
|
+
} else if (level === "warn") {
|
|
69
|
+
console.warn(prefixedMessage, ...args);
|
|
70
|
+
} else {
|
|
71
|
+
console.info(prefixedMessage, ...args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/sse/create-event-source.ts
|
|
77
|
+
async function createEventSource(url, apiKey) {
|
|
78
|
+
if (typeof window !== "undefined" && "EventSource" in window) {
|
|
79
|
+
return new EventSource(`${url}?apiKey=${apiKey}`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const { EventSource: NodeEventSource } = await import("eventsource");
|
|
83
|
+
return new NodeEventSource(url, {
|
|
84
|
+
headers: { "X-Api-Key": apiKey }
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
log("warn", "[Flagix SDK] SSE not supported in this environment.", error);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/client.ts
|
|
93
|
+
var FlagixClient = class {
|
|
94
|
+
constructor(options) {
|
|
95
|
+
this.localCache = /* @__PURE__ */ new Map();
|
|
96
|
+
this.isInitialized = false;
|
|
97
|
+
this.sseConnection = null;
|
|
98
|
+
this.reconnectAttempts = 0;
|
|
99
|
+
this.reconnectTimeoutId = null;
|
|
100
|
+
this.isReconnecting = false;
|
|
101
|
+
this.hasEstablishedConnection = false;
|
|
102
|
+
this.maxReconnectAttempts = Number.POSITIVE_INFINITY;
|
|
103
|
+
this.baseReconnectDelay = 1e3;
|
|
104
|
+
this.maxReconnectDelay = 3e4;
|
|
105
|
+
this.apiKey = options.apiKey;
|
|
106
|
+
this.apiBaseUrl = options.apiBaseUrl.replace(REMOVE_TRAILING_SLASH, "");
|
|
107
|
+
this.context = options.initialContext || {};
|
|
108
|
+
this.emitter = new FlagixEventEmitter();
|
|
109
|
+
setLogLevel(options.logs?.level ?? "none");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Subscribes a listener to a flag update event.
|
|
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);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Fetches all flag configurations from the API, populates the local cache,
|
|
125
|
+
* and sets up the SSE connection for real-time updates.
|
|
126
|
+
*/
|
|
127
|
+
async initialize() {
|
|
128
|
+
if (this.isInitialized) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
log("info", "[Flagix SDK] Starting initialization...");
|
|
132
|
+
await this.fetchInitialConfig();
|
|
133
|
+
this.setupSSEListener();
|
|
134
|
+
this.isInitialized = true;
|
|
135
|
+
log("info", "[Flagix SDK] Initialization complete.");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Returns true if the client has completed initialization
|
|
139
|
+
*/
|
|
140
|
+
getIsInitialized() {
|
|
141
|
+
return this.isInitialized;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Evaluates a flag locally using the cached configuration and current context.
|
|
145
|
+
* @param flagKey The key of the flag to evaluate.
|
|
146
|
+
* @param context Optional, temporary context overrides for this specific evaluation.
|
|
147
|
+
*/
|
|
148
|
+
evaluate(flagKey, contextOverrides) {
|
|
149
|
+
if (!this.isInitialized) {
|
|
150
|
+
log(
|
|
151
|
+
"warn",
|
|
152
|
+
`[Flagix SDK] Not initialized. Cannot evaluate flag: ${flagKey}`
|
|
153
|
+
);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const config = this.localCache.get(flagKey);
|
|
157
|
+
if (!config) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const finalContext = { ...this.context, ...contextOverrides };
|
|
161
|
+
const result = (0, import_evaluation_core.evaluateFlag)(config, finalContext);
|
|
162
|
+
if (result) {
|
|
163
|
+
this.trackEvaluation(flagKey, result, finalContext);
|
|
164
|
+
}
|
|
165
|
+
return result?.value ?? null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Sets or updates the global evaluation context.
|
|
169
|
+
* @param newContext New context attributes to merge or replace.
|
|
170
|
+
*/
|
|
171
|
+
setContext(newContext) {
|
|
172
|
+
this.context = { ...this.context, ...newContext };
|
|
173
|
+
log(
|
|
174
|
+
"info",
|
|
175
|
+
"[Flagix SDK] Context updated. Evaluations will use the new context."
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
async fetchInitialConfig() {
|
|
179
|
+
const url = `${this.apiBaseUrl}/api/flag-config/all`;
|
|
180
|
+
try {
|
|
181
|
+
const response = await fetch(url, {
|
|
182
|
+
headers: { "X-Api-Key": this.apiKey }
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Failed to load initial config: ${response.statusText}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const allFlags = await response.json();
|
|
190
|
+
this.localCache.clear();
|
|
191
|
+
for (const [key, config] of Object.entries(allFlags)) {
|
|
192
|
+
this.localCache.set(key, config);
|
|
193
|
+
}
|
|
194
|
+
log("info", `[Flagix SDK] Loaded ${this.localCache.size} flag configs.`);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
log(
|
|
197
|
+
"error",
|
|
198
|
+
"[Flagix SDK] CRITICAL: Initial configuration fetch failed.",
|
|
199
|
+
error
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async setupSSEListener() {
|
|
204
|
+
if (this.sseConnection) {
|
|
205
|
+
try {
|
|
206
|
+
this.sseConnection.close();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
log(
|
|
209
|
+
"warn",
|
|
210
|
+
"[Flagix SDK] Error closing existing SSE connection",
|
|
211
|
+
error
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
this.sseConnection = null;
|
|
215
|
+
}
|
|
216
|
+
const url = `${this.apiBaseUrl}/api/sse/stream`;
|
|
217
|
+
const source = await createEventSource(url, this.apiKey);
|
|
218
|
+
if (!source) {
|
|
219
|
+
log("warn", "[Flagix SDK] Failed to create EventSource. Retrying...");
|
|
220
|
+
this.scheduleReconnect();
|
|
221
|
+
return;
|
|
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;
|
|
230
|
+
}
|
|
231
|
+
if (this.hasEstablishedConnection && this.isInitialized) {
|
|
232
|
+
log(
|
|
233
|
+
"info",
|
|
234
|
+
"[Flagix SDK] SSE reconnected. Refreshing cache to sync with server..."
|
|
235
|
+
);
|
|
236
|
+
this.fetchInitialConfig().catch((error) => {
|
|
237
|
+
log(
|
|
238
|
+
"error",
|
|
239
|
+
"[Flagix SDK] Failed to refresh cache after reconnection",
|
|
240
|
+
error
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
this.hasEstablishedConnection = true;
|
|
245
|
+
}
|
|
246
|
+
log("info", "[Flagix SDK] SSE connection established.");
|
|
247
|
+
};
|
|
248
|
+
source.onerror = (error) => {
|
|
249
|
+
const eventSource = error.target;
|
|
250
|
+
const readyState = eventSource?.readyState;
|
|
251
|
+
if (readyState === 2) {
|
|
252
|
+
log(
|
|
253
|
+
"warn",
|
|
254
|
+
"[Flagix SDK] SSE connection closed. Attempting to reconnect..."
|
|
255
|
+
);
|
|
256
|
+
this.handleReconnect();
|
|
257
|
+
} else if (readyState === 0) {
|
|
258
|
+
log(
|
|
259
|
+
"warn",
|
|
260
|
+
"[Flagix SDK] SSE connection error (connecting state)",
|
|
261
|
+
error
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
log("error", "[Flagix SDK] SSE error", error);
|
|
265
|
+
this.handleReconnect();
|
|
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
|
+
});
|
|
281
|
+
}
|
|
282
|
+
handleReconnect() {
|
|
283
|
+
if (this.isReconnecting || !this.isInitialized) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
287
|
+
log(
|
|
288
|
+
"error",
|
|
289
|
+
"[Flagix SDK] Max reconnection attempts reached. Stopping reconnection."
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
this.isReconnecting = true;
|
|
294
|
+
this.scheduleReconnect();
|
|
295
|
+
}
|
|
296
|
+
scheduleReconnect() {
|
|
297
|
+
if (this.reconnectTimeoutId) {
|
|
298
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
299
|
+
}
|
|
300
|
+
const delay = Math.min(
|
|
301
|
+
this.baseReconnectDelay * 2 ** this.reconnectAttempts,
|
|
302
|
+
this.maxReconnectDelay
|
|
303
|
+
);
|
|
304
|
+
const jitter = delay * 0.25 * (Math.random() * 2 - 1);
|
|
305
|
+
const finalDelay = Math.max(100, delay + jitter);
|
|
306
|
+
this.reconnectAttempts++;
|
|
307
|
+
log(
|
|
308
|
+
"info",
|
|
309
|
+
`[Flagix SDK] Scheduling SSE reconnection attempt ${this.reconnectAttempts} in ${Math.round(finalDelay)}ms...`
|
|
310
|
+
);
|
|
311
|
+
this.reconnectTimeoutId = setTimeout(() => {
|
|
312
|
+
this.isReconnecting = false;
|
|
313
|
+
this.reconnectTimeoutId = null;
|
|
314
|
+
this.setupSSEListener().catch((error) => {
|
|
315
|
+
log("error", "[Flagix SDK] Failed to reconnect SSE", error);
|
|
316
|
+
this.handleReconnect();
|
|
317
|
+
});
|
|
318
|
+
}, finalDelay);
|
|
319
|
+
}
|
|
320
|
+
async fetchSingleFlagConfig(flagKey, type) {
|
|
321
|
+
const url = `${this.apiBaseUrl}/api/flag-config/${flagKey}`;
|
|
322
|
+
if (type === "FLAG_DELETED" || type === "RULE_DELETED") {
|
|
323
|
+
this.localCache.delete(flagKey);
|
|
324
|
+
log("info", `[Flagix SDK] Flag ${flagKey} deleted from cache.`);
|
|
325
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const response = await fetch(url, {
|
|
330
|
+
headers: { "X-Api-Key": this.apiKey }
|
|
331
|
+
});
|
|
332
|
+
if (response.status === 404) {
|
|
333
|
+
this.localCache.delete(flagKey);
|
|
334
|
+
log(
|
|
335
|
+
"warn",
|
|
336
|
+
`[Flagix SDK] Flag ${flagKey} not found on API, deleted from cache.`
|
|
337
|
+
);
|
|
338
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Failed to fetch update for ${flagKey}: ${response.statusText}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
const config = await response.json();
|
|
347
|
+
this.localCache.set(flagKey, config);
|
|
348
|
+
log("info", `[Flagix SDK] Flag ${flagKey} updated/synced.`);
|
|
349
|
+
this.emitter.emit(FLAG_UPDATE_EVENT, flagKey);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
log(
|
|
352
|
+
"error",
|
|
353
|
+
`[Flagix SDK] Failed to fetch update for ${flagKey}.`,
|
|
354
|
+
error
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Records a custom event (conversion) for analytics and A/B testing.
|
|
360
|
+
*/
|
|
361
|
+
track(eventName, properties, contextOverrides) {
|
|
362
|
+
const url = `${this.apiBaseUrl}/api/track/event`;
|
|
363
|
+
const finalContext = { ...this.context, ...contextOverrides };
|
|
364
|
+
const distinctId = (0, import_evaluation_core.resolveIdentifier)(finalContext);
|
|
365
|
+
const payload = {
|
|
366
|
+
apiKey: this.apiKey,
|
|
367
|
+
event_name: eventName,
|
|
368
|
+
distinctId,
|
|
369
|
+
properties: properties || {},
|
|
370
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
371
|
+
};
|
|
372
|
+
const payloadJson = JSON.stringify(payload);
|
|
373
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
374
|
+
const blob = new Blob([payloadJson], { type: "application/json" });
|
|
375
|
+
if (navigator.sendBeacon(url, blob)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
this.fireAndForgetFetch(url, payloadJson);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Closes the Server-Sent Events (SSE) connection and cleans up resources.
|
|
383
|
+
*/
|
|
384
|
+
close() {
|
|
385
|
+
if (this.reconnectTimeoutId) {
|
|
386
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
387
|
+
this.reconnectTimeoutId = null;
|
|
388
|
+
}
|
|
389
|
+
this.isReconnecting = false;
|
|
390
|
+
this.reconnectAttempts = 0;
|
|
391
|
+
this.hasEstablishedConnection = false;
|
|
392
|
+
if (this.sseConnection) {
|
|
393
|
+
try {
|
|
394
|
+
this.sseConnection.close();
|
|
395
|
+
} catch (error) {
|
|
396
|
+
log("warn", "[Flagix SDK] Error closing SSE connection", error);
|
|
397
|
+
}
|
|
398
|
+
this.sseConnection = null;
|
|
399
|
+
log("info", "[Flagix SDK] SSE connection closed.");
|
|
400
|
+
}
|
|
401
|
+
this.localCache.clear();
|
|
402
|
+
this.isInitialized = false;
|
|
403
|
+
this.emitter.removeAllListeners();
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Asynchronously sends an evaluation event to the backend tracking service.
|
|
407
|
+
*/
|
|
408
|
+
trackEvaluation(flagKey, result, context) {
|
|
409
|
+
const url = `${this.apiBaseUrl}/api/track/evaluation`;
|
|
410
|
+
const distinctId = (0, import_evaluation_core.resolveIdentifier)(context);
|
|
411
|
+
const payload = {
|
|
412
|
+
apiKey: this.apiKey,
|
|
413
|
+
flagKey,
|
|
414
|
+
variationName: result.name,
|
|
415
|
+
variationValue: result.value,
|
|
416
|
+
variationType: result.type,
|
|
417
|
+
distinctId,
|
|
418
|
+
evaluationContext: context,
|
|
419
|
+
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
420
|
+
};
|
|
421
|
+
const payloadJson = JSON.stringify(payload);
|
|
422
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
423
|
+
const blob = new Blob([payloadJson], { type: "application/json" });
|
|
424
|
+
const success = navigator.sendBeacon(url, blob);
|
|
425
|
+
if (success) {
|
|
426
|
+
log("info", `Successfully queued beacon for ${flagKey}.`);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
log("warn", `Beacon queue full for ${flagKey}. Falling back to fetch.`);
|
|
430
|
+
this.fireAndForgetFetch(url, payloadJson);
|
|
431
|
+
} else {
|
|
432
|
+
this.fireAndForgetFetch(url, payloadJson);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
fireAndForgetFetch(url, payloadJson) {
|
|
436
|
+
fetch(url, {
|
|
437
|
+
method: "POST",
|
|
438
|
+
headers: { "Content-Type": "application/json" },
|
|
439
|
+
body: payloadJson,
|
|
440
|
+
keepalive: true
|
|
441
|
+
}).catch((error) => {
|
|
442
|
+
log(
|
|
443
|
+
"error",
|
|
444
|
+
`Critical failure sending impression event via fetch: ${error.message}`
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// src/index.ts
|
|
451
|
+
var clientInstance = null;
|
|
452
|
+
var isInitializing = false;
|
|
453
|
+
var initializationPromise = null;
|
|
454
|
+
var Flagix = {
|
|
455
|
+
/**
|
|
456
|
+
* Initializes the Flagix SDK, fetches all flags, and sets up an SSE connection.
|
|
457
|
+
*/
|
|
458
|
+
async initialize(options) {
|
|
459
|
+
if (clientInstance) {
|
|
460
|
+
log("warn", "Flagix SDK already initialized. Ignoring subsequent call.");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (isInitializing && initializationPromise) {
|
|
464
|
+
return initializationPromise;
|
|
465
|
+
}
|
|
466
|
+
isInitializing = true;
|
|
467
|
+
try {
|
|
468
|
+
clientInstance = new FlagixClient(options);
|
|
469
|
+
initializationPromise = clientInstance.initialize();
|
|
470
|
+
await initializationPromise;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
log("error", "Flagix SDK failed during initialization:", error);
|
|
473
|
+
throw error;
|
|
474
|
+
} finally {
|
|
475
|
+
isInitializing = false;
|
|
476
|
+
initializationPromise = null;
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
/**
|
|
480
|
+
* Evaluates a flag based on the local cache and current context.
|
|
481
|
+
* @param flagKey The key of the flag to evaluate.
|
|
482
|
+
* @param contextOverrides Optional, temporary context overrides.
|
|
483
|
+
*/
|
|
484
|
+
evaluate(flagKey, contextOverrides) {
|
|
485
|
+
if (!clientInstance || !clientInstance.getIsInitialized()) {
|
|
486
|
+
log(
|
|
487
|
+
"error",
|
|
488
|
+
"Flagix SDK not initialized. Call Flagix.initialize() first."
|
|
489
|
+
);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
return clientInstance.evaluate(flagKey, contextOverrides);
|
|
493
|
+
},
|
|
494
|
+
/**
|
|
495
|
+
* Records a custom event for analytics.
|
|
496
|
+
* @param eventName The name of the event.
|
|
497
|
+
* @param properties Optional custom metadata.
|
|
498
|
+
* @param contextOverrides Optional context.
|
|
499
|
+
*/
|
|
500
|
+
track(eventName, properties, contextOverrides) {
|
|
501
|
+
if (!clientInstance) {
|
|
502
|
+
log(
|
|
503
|
+
"error",
|
|
504
|
+
"Flagix SDK not initialized. Call Flagix.initialize() first."
|
|
505
|
+
);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
clientInstance.track(eventName, properties, contextOverrides);
|
|
509
|
+
},
|
|
510
|
+
/**
|
|
511
|
+
* Sets or updates the global evaluation context.
|
|
512
|
+
* @param newContext New context attributes to merge or replace.
|
|
513
|
+
*/
|
|
514
|
+
setContext(newContext) {
|
|
515
|
+
if (!clientInstance) {
|
|
516
|
+
log("error", "Flagix SDK not initialized.");
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
clientInstance.setContext(newContext);
|
|
520
|
+
},
|
|
521
|
+
/**
|
|
522
|
+
* Closes the SSE connection and cleans up resources.
|
|
523
|
+
*/
|
|
524
|
+
close() {
|
|
525
|
+
if (clientInstance) {
|
|
526
|
+
clientInstance.close();
|
|
527
|
+
clientInstance = null;
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
/**
|
|
531
|
+
* checks initialization status
|
|
532
|
+
*/
|
|
533
|
+
isInitialized() {
|
|
534
|
+
return !!clientInstance && clientInstance.getIsInitialized();
|
|
535
|
+
},
|
|
536
|
+
/**
|
|
537
|
+
* Subscribes a listener to updates for any flag.
|
|
538
|
+
* @param listener The callback function (receives the updated flagKey).
|
|
539
|
+
*/
|
|
540
|
+
onFlagUpdate(listener) {
|
|
541
|
+
if (!clientInstance) {
|
|
542
|
+
log("warn", "Flagix SDK not initialized. Cannot subscribe to updates.");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
clientInstance.on(FLAG_UPDATE_EVENT, listener);
|
|
546
|
+
},
|
|
547
|
+
/**
|
|
548
|
+
* Unsubscribes a listener from flag updates.
|
|
549
|
+
* @param listener The callback function to remove.
|
|
550
|
+
*/
|
|
551
|
+
offFlagUpdate(listener) {
|
|
552
|
+
if (!clientInstance) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
clientInstance.off(FLAG_UPDATE_EVENT, listener);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
559
|
+
0 && (module.exports = {
|
|
560
|
+
Flagix
|
|
561
|
+
});
|