@teracrafts/flagkit 1.0.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/README.md +443 -0
- package/dist/index.d.mts +2048 -0
- package/dist/index.d.ts +2048 -0
- package/dist/index.global.js +3 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +73 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2048 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels supported by the SDK
|
|
3
|
+
*/
|
|
4
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
5
|
+
/**
|
|
6
|
+
* Log entry structure
|
|
7
|
+
*/
|
|
8
|
+
interface LogEntry {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
message: string;
|
|
11
|
+
timestamp: Date;
|
|
12
|
+
data?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Logger interface for custom implementations
|
|
16
|
+
*/
|
|
17
|
+
interface Logger {
|
|
18
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
19
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
20
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
21
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Default console logger implementation
|
|
25
|
+
*/
|
|
26
|
+
declare class ConsoleLogger implements Logger {
|
|
27
|
+
private readonly prefix;
|
|
28
|
+
private readonly minLevel;
|
|
29
|
+
constructor(options?: {
|
|
30
|
+
prefix?: string;
|
|
31
|
+
level?: LogLevel;
|
|
32
|
+
});
|
|
33
|
+
private shouldLog;
|
|
34
|
+
private formatMessage;
|
|
35
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
36
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
37
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
38
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* No-op logger that discards all messages
|
|
42
|
+
*/
|
|
43
|
+
declare class NoopLogger implements Logger {
|
|
44
|
+
debug(): void;
|
|
45
|
+
info(): void;
|
|
46
|
+
warn(): void;
|
|
47
|
+
error(): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a logger instance based on configuration
|
|
51
|
+
*/
|
|
52
|
+
declare function createLogger(options?: {
|
|
53
|
+
logger?: Logger;
|
|
54
|
+
debug?: boolean;
|
|
55
|
+
}): Logger;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Flag value types supported by FlagKit
|
|
59
|
+
*/
|
|
60
|
+
type FlagType = 'boolean' | 'string' | 'number' | 'json';
|
|
61
|
+
/**
|
|
62
|
+
* Possible flag value
|
|
63
|
+
*/
|
|
64
|
+
type FlagValue = boolean | string | number | Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* Reasons for an evaluation result
|
|
67
|
+
*/
|
|
68
|
+
type EvaluationReason = 'FLAG_NOT_FOUND' | 'FLAG_DISABLED' | 'ENVIRONMENT_NOT_CONFIGURED' | 'FALLTHROUGH' | 'RULE_MATCH' | 'SEGMENT_MATCH' | 'DEFAULT' | 'EVALUATION_ERROR' | 'CACHED' | 'STALE_CACHE' | 'BOOTSTRAP' | 'OFFLINE';
|
|
69
|
+
/**
|
|
70
|
+
* State of a feature flag returned from the API
|
|
71
|
+
*/
|
|
72
|
+
interface FlagState {
|
|
73
|
+
/** Unique flag key */
|
|
74
|
+
key: string;
|
|
75
|
+
/** Current flag value */
|
|
76
|
+
value: FlagValue;
|
|
77
|
+
/** Whether the flag is enabled */
|
|
78
|
+
enabled: boolean;
|
|
79
|
+
/** Flag version number */
|
|
80
|
+
version: number;
|
|
81
|
+
/** Type of the flag value */
|
|
82
|
+
flagType: FlagType;
|
|
83
|
+
/** Last modification timestamp */
|
|
84
|
+
lastModified: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Result of a flag evaluation
|
|
88
|
+
*/
|
|
89
|
+
interface EvaluationResult<T = FlagValue> {
|
|
90
|
+
/** The flag key that was evaluated */
|
|
91
|
+
flagKey: string;
|
|
92
|
+
/** The resolved flag value */
|
|
93
|
+
value: T;
|
|
94
|
+
/** Whether the flag is enabled */
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
/** Reason for the evaluation result */
|
|
97
|
+
reason: EvaluationReason;
|
|
98
|
+
/** Variation ID if applicable */
|
|
99
|
+
variationId?: string;
|
|
100
|
+
/** Rule ID that matched */
|
|
101
|
+
ruleId?: string;
|
|
102
|
+
/** Segment ID that matched */
|
|
103
|
+
segmentId?: string;
|
|
104
|
+
/** Flag version at time of evaluation */
|
|
105
|
+
version: number;
|
|
106
|
+
/** Timestamp of evaluation */
|
|
107
|
+
timestamp: Date;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* API response for SDK initialization
|
|
111
|
+
*/
|
|
112
|
+
interface InitResponse {
|
|
113
|
+
flags: FlagState[];
|
|
114
|
+
environment: string;
|
|
115
|
+
environmentId: string;
|
|
116
|
+
projectId: string;
|
|
117
|
+
organizationId: string;
|
|
118
|
+
serverTime: string;
|
|
119
|
+
pollingIntervalSeconds: number;
|
|
120
|
+
streamingUrl: string | null;
|
|
121
|
+
metadata: {
|
|
122
|
+
/** Minimum SDK version required (older versions may not work) */
|
|
123
|
+
sdkVersionMin?: string;
|
|
124
|
+
/** Recommended SDK version for optimal experience */
|
|
125
|
+
sdkVersionRecommended?: string;
|
|
126
|
+
/** Latest available SDK version */
|
|
127
|
+
sdkVersionLatest?: string;
|
|
128
|
+
/** Deprecation warning message from server */
|
|
129
|
+
deprecationWarning?: string | null;
|
|
130
|
+
features: {
|
|
131
|
+
streaming: boolean;
|
|
132
|
+
localEval: boolean;
|
|
133
|
+
experiments: boolean;
|
|
134
|
+
segments: boolean;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* API response for flag evaluation
|
|
140
|
+
*/
|
|
141
|
+
interface EvaluateResponse {
|
|
142
|
+
flagKey: string;
|
|
143
|
+
value: FlagValue;
|
|
144
|
+
enabled: boolean;
|
|
145
|
+
reason: EvaluationReason;
|
|
146
|
+
variationId?: string;
|
|
147
|
+
ruleId?: string;
|
|
148
|
+
segmentId?: string;
|
|
149
|
+
version: number;
|
|
150
|
+
timestamp: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* API response for batch evaluation
|
|
154
|
+
*/
|
|
155
|
+
interface BatchEvaluateResponse {
|
|
156
|
+
flags: Record<string, EvaluateResponse>;
|
|
157
|
+
evaluatedAt: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* API response for polling updates
|
|
161
|
+
*/
|
|
162
|
+
interface UpdatesResponse {
|
|
163
|
+
flags: FlagState[];
|
|
164
|
+
checkedAt: string;
|
|
165
|
+
since: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* All error codes with their numeric values and default messages
|
|
170
|
+
*/
|
|
171
|
+
declare const ErrorCodes: {
|
|
172
|
+
readonly INIT_FAILED: {
|
|
173
|
+
readonly code: "INIT_FAILED";
|
|
174
|
+
readonly numericCode: 1000;
|
|
175
|
+
readonly message: "SDK initialization failed";
|
|
176
|
+
readonly recoverable: false;
|
|
177
|
+
};
|
|
178
|
+
readonly INIT_TIMEOUT: {
|
|
179
|
+
readonly code: "INIT_TIMEOUT";
|
|
180
|
+
readonly numericCode: 1001;
|
|
181
|
+
readonly message: "Initialization timed out";
|
|
182
|
+
readonly recoverable: true;
|
|
183
|
+
};
|
|
184
|
+
readonly INIT_INVALID_CONFIG: {
|
|
185
|
+
readonly code: "INIT_INVALID_CONFIG";
|
|
186
|
+
readonly numericCode: 1002;
|
|
187
|
+
readonly message: "Invalid SDK configuration";
|
|
188
|
+
readonly recoverable: false;
|
|
189
|
+
};
|
|
190
|
+
readonly INIT_MISSING_API_KEY: {
|
|
191
|
+
readonly code: "INIT_MISSING_API_KEY";
|
|
192
|
+
readonly numericCode: 1003;
|
|
193
|
+
readonly message: "API key is required";
|
|
194
|
+
readonly recoverable: false;
|
|
195
|
+
};
|
|
196
|
+
readonly INIT_INVALID_BASE_URL: {
|
|
197
|
+
readonly code: "INIT_INVALID_BASE_URL";
|
|
198
|
+
readonly numericCode: 1004;
|
|
199
|
+
readonly message: "Invalid base URL format";
|
|
200
|
+
readonly recoverable: false;
|
|
201
|
+
};
|
|
202
|
+
readonly INIT_ALREADY_INITIALIZED: {
|
|
203
|
+
readonly code: "INIT_ALREADY_INITIALIZED";
|
|
204
|
+
readonly numericCode: 1005;
|
|
205
|
+
readonly message: "SDK already initialized";
|
|
206
|
+
readonly recoverable: false;
|
|
207
|
+
};
|
|
208
|
+
readonly AUTH_INVALID_KEY: {
|
|
209
|
+
readonly code: "AUTH_INVALID_KEY";
|
|
210
|
+
readonly numericCode: 1100;
|
|
211
|
+
readonly message: "Invalid API key";
|
|
212
|
+
readonly recoverable: false;
|
|
213
|
+
};
|
|
214
|
+
readonly AUTH_EXPIRED_KEY: {
|
|
215
|
+
readonly code: "AUTH_EXPIRED_KEY";
|
|
216
|
+
readonly numericCode: 1101;
|
|
217
|
+
readonly message: "API key has expired";
|
|
218
|
+
readonly recoverable: false;
|
|
219
|
+
};
|
|
220
|
+
readonly AUTH_REVOKED_KEY: {
|
|
221
|
+
readonly code: "AUTH_REVOKED_KEY";
|
|
222
|
+
readonly numericCode: 1102;
|
|
223
|
+
readonly message: "API key has been revoked";
|
|
224
|
+
readonly recoverable: false;
|
|
225
|
+
};
|
|
226
|
+
readonly AUTH_INSUFFICIENT_PERMISSIONS: {
|
|
227
|
+
readonly code: "AUTH_INSUFFICIENT_PERMISSIONS";
|
|
228
|
+
readonly numericCode: 1103;
|
|
229
|
+
readonly message: "API key lacks required permissions";
|
|
230
|
+
readonly recoverable: false;
|
|
231
|
+
};
|
|
232
|
+
readonly AUTH_RATE_LIMITED: {
|
|
233
|
+
readonly code: "AUTH_RATE_LIMITED";
|
|
234
|
+
readonly numericCode: 1104;
|
|
235
|
+
readonly message: "Rate limit exceeded";
|
|
236
|
+
readonly recoverable: true;
|
|
237
|
+
};
|
|
238
|
+
readonly AUTH_PROJECT_MISMATCH: {
|
|
239
|
+
readonly code: "AUTH_PROJECT_MISMATCH";
|
|
240
|
+
readonly numericCode: 1105;
|
|
241
|
+
readonly message: "API key not valid for this project";
|
|
242
|
+
readonly recoverable: false;
|
|
243
|
+
};
|
|
244
|
+
readonly AUTH_ENVIRONMENT_MISMATCH: {
|
|
245
|
+
readonly code: "AUTH_ENVIRONMENT_MISMATCH";
|
|
246
|
+
readonly numericCode: 1106;
|
|
247
|
+
readonly message: "API key not valid for this environment";
|
|
248
|
+
readonly recoverable: false;
|
|
249
|
+
};
|
|
250
|
+
readonly AUTH_IP_RESTRICTED: {
|
|
251
|
+
readonly code: "AUTH_IP_RESTRICTED";
|
|
252
|
+
readonly numericCode: 1107;
|
|
253
|
+
readonly message: "IP address not allowed for this API key";
|
|
254
|
+
readonly recoverable: false;
|
|
255
|
+
};
|
|
256
|
+
readonly AUTH_ORGANIZATION_REQUIRED: {
|
|
257
|
+
readonly code: "AUTH_ORGANIZATION_REQUIRED";
|
|
258
|
+
readonly numericCode: 1108;
|
|
259
|
+
readonly message: "Organization context missing from token";
|
|
260
|
+
readonly recoverable: false;
|
|
261
|
+
};
|
|
262
|
+
readonly AUTH_SUBSCRIPTION_SUSPENDED: {
|
|
263
|
+
readonly code: "AUTH_SUBSCRIPTION_SUSPENDED";
|
|
264
|
+
readonly numericCode: 1109;
|
|
265
|
+
readonly message: "Subscription is suspended";
|
|
266
|
+
readonly recoverable: false;
|
|
267
|
+
};
|
|
268
|
+
readonly EVAL_FLAG_NOT_FOUND: {
|
|
269
|
+
readonly code: "EVAL_FLAG_NOT_FOUND";
|
|
270
|
+
readonly numericCode: 1200;
|
|
271
|
+
readonly message: "Flag does not exist";
|
|
272
|
+
readonly recoverable: false;
|
|
273
|
+
};
|
|
274
|
+
readonly EVAL_FLAG_DISABLED: {
|
|
275
|
+
readonly code: "EVAL_FLAG_DISABLED";
|
|
276
|
+
readonly numericCode: 1201;
|
|
277
|
+
readonly message: "Flag is disabled";
|
|
278
|
+
readonly recoverable: false;
|
|
279
|
+
};
|
|
280
|
+
readonly EVAL_TYPE_MISMATCH: {
|
|
281
|
+
readonly code: "EVAL_TYPE_MISMATCH";
|
|
282
|
+
readonly numericCode: 1202;
|
|
283
|
+
readonly message: "Flag value type mismatch";
|
|
284
|
+
readonly recoverable: false;
|
|
285
|
+
};
|
|
286
|
+
readonly EVAL_INVALID_CONTEXT: {
|
|
287
|
+
readonly code: "EVAL_INVALID_CONTEXT";
|
|
288
|
+
readonly numericCode: 1203;
|
|
289
|
+
readonly message: "Invalid evaluation context";
|
|
290
|
+
readonly recoverable: false;
|
|
291
|
+
};
|
|
292
|
+
readonly EVAL_RULE_ERROR: {
|
|
293
|
+
readonly code: "EVAL_RULE_ERROR";
|
|
294
|
+
readonly numericCode: 1204;
|
|
295
|
+
readonly message: "Error evaluating targeting rule";
|
|
296
|
+
readonly recoverable: true;
|
|
297
|
+
};
|
|
298
|
+
readonly EVAL_SEGMENT_ERROR: {
|
|
299
|
+
readonly code: "EVAL_SEGMENT_ERROR";
|
|
300
|
+
readonly numericCode: 1205;
|
|
301
|
+
readonly message: "Error evaluating segment";
|
|
302
|
+
readonly recoverable: true;
|
|
303
|
+
};
|
|
304
|
+
readonly EVAL_DEFAULT_USED: {
|
|
305
|
+
readonly code: "EVAL_DEFAULT_USED";
|
|
306
|
+
readonly numericCode: 1206;
|
|
307
|
+
readonly message: "Using default value";
|
|
308
|
+
readonly recoverable: false;
|
|
309
|
+
};
|
|
310
|
+
readonly EVAL_CACHED_VALUE: {
|
|
311
|
+
readonly code: "EVAL_CACHED_VALUE";
|
|
312
|
+
readonly numericCode: 1207;
|
|
313
|
+
readonly message: "Using cached value";
|
|
314
|
+
readonly recoverable: false;
|
|
315
|
+
};
|
|
316
|
+
readonly EVAL_STALE_VALUE: {
|
|
317
|
+
readonly code: "EVAL_STALE_VALUE";
|
|
318
|
+
readonly numericCode: 1208;
|
|
319
|
+
readonly message: "Using stale cached value";
|
|
320
|
+
readonly recoverable: false;
|
|
321
|
+
};
|
|
322
|
+
readonly NETWORK_ERROR: {
|
|
323
|
+
readonly code: "NETWORK_ERROR";
|
|
324
|
+
readonly numericCode: 1300;
|
|
325
|
+
readonly message: "Network request failed";
|
|
326
|
+
readonly recoverable: true;
|
|
327
|
+
};
|
|
328
|
+
readonly NETWORK_TIMEOUT: {
|
|
329
|
+
readonly code: "NETWORK_TIMEOUT";
|
|
330
|
+
readonly numericCode: 1301;
|
|
331
|
+
readonly message: "Request timed out";
|
|
332
|
+
readonly recoverable: true;
|
|
333
|
+
};
|
|
334
|
+
readonly NETWORK_DNS_ERROR: {
|
|
335
|
+
readonly code: "NETWORK_DNS_ERROR";
|
|
336
|
+
readonly numericCode: 1302;
|
|
337
|
+
readonly message: "DNS resolution failed";
|
|
338
|
+
readonly recoverable: true;
|
|
339
|
+
};
|
|
340
|
+
readonly NETWORK_CONNECTION_REFUSED: {
|
|
341
|
+
readonly code: "NETWORK_CONNECTION_REFUSED";
|
|
342
|
+
readonly numericCode: 1303;
|
|
343
|
+
readonly message: "Connection refused";
|
|
344
|
+
readonly recoverable: true;
|
|
345
|
+
};
|
|
346
|
+
readonly NETWORK_SSL_ERROR: {
|
|
347
|
+
readonly code: "NETWORK_SSL_ERROR";
|
|
348
|
+
readonly numericCode: 1304;
|
|
349
|
+
readonly message: "SSL/TLS error";
|
|
350
|
+
readonly recoverable: false;
|
|
351
|
+
};
|
|
352
|
+
readonly NETWORK_OFFLINE: {
|
|
353
|
+
readonly code: "NETWORK_OFFLINE";
|
|
354
|
+
readonly numericCode: 1305;
|
|
355
|
+
readonly message: "Device is offline";
|
|
356
|
+
readonly recoverable: true;
|
|
357
|
+
};
|
|
358
|
+
readonly NETWORK_SERVER_ERROR: {
|
|
359
|
+
readonly code: "NETWORK_SERVER_ERROR";
|
|
360
|
+
readonly numericCode: 1306;
|
|
361
|
+
readonly message: "Server error";
|
|
362
|
+
readonly recoverable: true;
|
|
363
|
+
};
|
|
364
|
+
readonly NETWORK_INVALID_RESPONSE: {
|
|
365
|
+
readonly code: "NETWORK_INVALID_RESPONSE";
|
|
366
|
+
readonly numericCode: 1307;
|
|
367
|
+
readonly message: "Invalid server response";
|
|
368
|
+
readonly recoverable: true;
|
|
369
|
+
};
|
|
370
|
+
readonly NETWORK_SERVICE_UNAVAILABLE: {
|
|
371
|
+
readonly code: "NETWORK_SERVICE_UNAVAILABLE";
|
|
372
|
+
readonly numericCode: 1308;
|
|
373
|
+
readonly message: "Service unavailable";
|
|
374
|
+
readonly recoverable: true;
|
|
375
|
+
};
|
|
376
|
+
readonly CACHE_READ_ERROR: {
|
|
377
|
+
readonly code: "CACHE_READ_ERROR";
|
|
378
|
+
readonly numericCode: 1400;
|
|
379
|
+
readonly message: "Failed to read from cache";
|
|
380
|
+
readonly recoverable: true;
|
|
381
|
+
};
|
|
382
|
+
readonly CACHE_WRITE_ERROR: {
|
|
383
|
+
readonly code: "CACHE_WRITE_ERROR";
|
|
384
|
+
readonly numericCode: 1401;
|
|
385
|
+
readonly message: "Failed to write to cache";
|
|
386
|
+
readonly recoverable: true;
|
|
387
|
+
};
|
|
388
|
+
readonly CACHE_EXPIRED: {
|
|
389
|
+
readonly code: "CACHE_EXPIRED";
|
|
390
|
+
readonly numericCode: 1402;
|
|
391
|
+
readonly message: "Cache has expired";
|
|
392
|
+
readonly recoverable: true;
|
|
393
|
+
};
|
|
394
|
+
readonly CACHE_CORRUPT: {
|
|
395
|
+
readonly code: "CACHE_CORRUPT";
|
|
396
|
+
readonly numericCode: 1403;
|
|
397
|
+
readonly message: "Cache data is corrupted";
|
|
398
|
+
readonly recoverable: true;
|
|
399
|
+
};
|
|
400
|
+
readonly CACHE_STORAGE_FULL: {
|
|
401
|
+
readonly code: "CACHE_STORAGE_FULL";
|
|
402
|
+
readonly numericCode: 1404;
|
|
403
|
+
readonly message: "Storage quota exceeded";
|
|
404
|
+
readonly recoverable: true;
|
|
405
|
+
};
|
|
406
|
+
readonly EVENT_SEND_FAILED: {
|
|
407
|
+
readonly code: "EVENT_SEND_FAILED";
|
|
408
|
+
readonly numericCode: 1500;
|
|
409
|
+
readonly message: "Failed to send event";
|
|
410
|
+
readonly recoverable: true;
|
|
411
|
+
};
|
|
412
|
+
readonly EVENT_INVALID_TYPE: {
|
|
413
|
+
readonly code: "EVENT_INVALID_TYPE";
|
|
414
|
+
readonly numericCode: 1501;
|
|
415
|
+
readonly message: "Invalid event type";
|
|
416
|
+
readonly recoverable: false;
|
|
417
|
+
};
|
|
418
|
+
readonly EVENT_INVALID_DATA: {
|
|
419
|
+
readonly code: "EVENT_INVALID_DATA";
|
|
420
|
+
readonly numericCode: 1502;
|
|
421
|
+
readonly message: "Invalid event data";
|
|
422
|
+
readonly recoverable: false;
|
|
423
|
+
};
|
|
424
|
+
readonly EVENT_QUEUE_FULL: {
|
|
425
|
+
readonly code: "EVENT_QUEUE_FULL";
|
|
426
|
+
readonly numericCode: 1503;
|
|
427
|
+
readonly message: "Event queue is full";
|
|
428
|
+
readonly recoverable: true;
|
|
429
|
+
};
|
|
430
|
+
readonly EVENT_BATCH_PARTIAL: {
|
|
431
|
+
readonly code: "EVENT_BATCH_PARTIAL";
|
|
432
|
+
readonly numericCode: 1504;
|
|
433
|
+
readonly message: "Some events failed to send";
|
|
434
|
+
readonly recoverable: true;
|
|
435
|
+
};
|
|
436
|
+
readonly CONFIG_INVALID_OPTION: {
|
|
437
|
+
readonly code: "CONFIG_INVALID_OPTION";
|
|
438
|
+
readonly numericCode: 1600;
|
|
439
|
+
readonly message: "Invalid configuration option";
|
|
440
|
+
readonly recoverable: false;
|
|
441
|
+
};
|
|
442
|
+
readonly CONFIG_MISSING_REQUIRED: {
|
|
443
|
+
readonly code: "CONFIG_MISSING_REQUIRED";
|
|
444
|
+
readonly numericCode: 1601;
|
|
445
|
+
readonly message: "Missing required configuration";
|
|
446
|
+
readonly recoverable: false;
|
|
447
|
+
};
|
|
448
|
+
readonly CONFIG_TYPE_ERROR: {
|
|
449
|
+
readonly code: "CONFIG_TYPE_ERROR";
|
|
450
|
+
readonly numericCode: 1602;
|
|
451
|
+
readonly message: "Configuration type mismatch";
|
|
452
|
+
readonly recoverable: false;
|
|
453
|
+
};
|
|
454
|
+
readonly CONFIG_DEPRECATED: {
|
|
455
|
+
readonly code: "CONFIG_DEPRECATED";
|
|
456
|
+
readonly numericCode: 1603;
|
|
457
|
+
readonly message: "Configuration option deprecated";
|
|
458
|
+
readonly recoverable: false;
|
|
459
|
+
};
|
|
460
|
+
readonly SECURITY_ERROR: {
|
|
461
|
+
readonly code: "SECURITY_ERROR";
|
|
462
|
+
readonly numericCode: 1700;
|
|
463
|
+
readonly message: "Security violation detected";
|
|
464
|
+
readonly recoverable: false;
|
|
465
|
+
};
|
|
466
|
+
readonly SECURITY_PII_DETECTED: {
|
|
467
|
+
readonly code: "SECURITY_PII_DETECTED";
|
|
468
|
+
readonly numericCode: 1701;
|
|
469
|
+
readonly message: "PII detected in data without privateAttributes";
|
|
470
|
+
readonly recoverable: false;
|
|
471
|
+
};
|
|
472
|
+
readonly SECURITY_LOCAL_PORT_IN_PRODUCTION: {
|
|
473
|
+
readonly code: "SECURITY_LOCAL_PORT_IN_PRODUCTION";
|
|
474
|
+
readonly numericCode: 1702;
|
|
475
|
+
readonly message: "localPort cannot be used in production";
|
|
476
|
+
readonly recoverable: false;
|
|
477
|
+
};
|
|
478
|
+
readonly SECURITY_KEY_ROTATION_FAILED: {
|
|
479
|
+
readonly code: "SECURITY_KEY_ROTATION_FAILED";
|
|
480
|
+
readonly numericCode: 1703;
|
|
481
|
+
readonly message: "Key rotation failed - both primary and secondary keys rejected";
|
|
482
|
+
readonly recoverable: false;
|
|
483
|
+
};
|
|
484
|
+
readonly SECURITY_BOOTSTRAP_VERIFICATION_FAILED: {
|
|
485
|
+
readonly code: "SECURITY_BOOTSTRAP_VERIFICATION_FAILED";
|
|
486
|
+
readonly numericCode: 1704;
|
|
487
|
+
readonly message: "Bootstrap data signature verification failed";
|
|
488
|
+
readonly recoverable: false;
|
|
489
|
+
};
|
|
490
|
+
readonly STREAMING_TOKEN_INVALID: {
|
|
491
|
+
readonly code: "STREAMING_TOKEN_INVALID";
|
|
492
|
+
readonly numericCode: 1800;
|
|
493
|
+
readonly message: "Stream token is invalid";
|
|
494
|
+
readonly recoverable: true;
|
|
495
|
+
};
|
|
496
|
+
readonly STREAMING_TOKEN_EXPIRED: {
|
|
497
|
+
readonly code: "STREAMING_TOKEN_EXPIRED";
|
|
498
|
+
readonly numericCode: 1801;
|
|
499
|
+
readonly message: "Stream token has expired";
|
|
500
|
+
readonly recoverable: true;
|
|
501
|
+
};
|
|
502
|
+
readonly STREAMING_SUBSCRIPTION_SUSPENDED: {
|
|
503
|
+
readonly code: "STREAMING_SUBSCRIPTION_SUSPENDED";
|
|
504
|
+
readonly numericCode: 1802;
|
|
505
|
+
readonly message: "Organization subscription suspended";
|
|
506
|
+
readonly recoverable: false;
|
|
507
|
+
};
|
|
508
|
+
readonly STREAMING_CONNECTION_LIMIT: {
|
|
509
|
+
readonly code: "STREAMING_CONNECTION_LIMIT";
|
|
510
|
+
readonly numericCode: 1803;
|
|
511
|
+
readonly message: "Too many concurrent streaming connections";
|
|
512
|
+
readonly recoverable: true;
|
|
513
|
+
};
|
|
514
|
+
readonly STREAMING_UNAVAILABLE: {
|
|
515
|
+
readonly code: "STREAMING_UNAVAILABLE";
|
|
516
|
+
readonly numericCode: 1804;
|
|
517
|
+
readonly message: "Streaming service not available";
|
|
518
|
+
readonly recoverable: true;
|
|
519
|
+
};
|
|
520
|
+
readonly INTERNAL_ERROR: {
|
|
521
|
+
readonly code: "INTERNAL_ERROR";
|
|
522
|
+
readonly numericCode: 1900;
|
|
523
|
+
readonly message: "Internal SDK error";
|
|
524
|
+
readonly recoverable: false;
|
|
525
|
+
};
|
|
526
|
+
readonly INTERNAL_STATE_ERROR: {
|
|
527
|
+
readonly code: "INTERNAL_STATE_ERROR";
|
|
528
|
+
readonly numericCode: 1901;
|
|
529
|
+
readonly message: "Invalid internal state";
|
|
530
|
+
readonly recoverable: false;
|
|
531
|
+
};
|
|
532
|
+
readonly UNKNOWN_ERROR: {
|
|
533
|
+
readonly code: "UNKNOWN_ERROR";
|
|
534
|
+
readonly numericCode: 1999;
|
|
535
|
+
readonly message: "Unknown error occurred";
|
|
536
|
+
readonly recoverable: false;
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
type ErrorCode = keyof typeof ErrorCodes;
|
|
540
|
+
/**
|
|
541
|
+
* Check if an error code is retryable
|
|
542
|
+
*/
|
|
543
|
+
declare function isRetryableCode(code: ErrorCode): boolean;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Error details for additional context
|
|
547
|
+
*/
|
|
548
|
+
interface ErrorDetails {
|
|
549
|
+
[key: string]: unknown;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Extended options for FlagKitError constructor
|
|
553
|
+
*/
|
|
554
|
+
interface FlagKitErrorOptions {
|
|
555
|
+
statusCode?: number;
|
|
556
|
+
details?: ErrorDetails;
|
|
557
|
+
retryAfter?: number;
|
|
558
|
+
requestId?: string;
|
|
559
|
+
cause?: Error;
|
|
560
|
+
/** Override sanitization for this specific error */
|
|
561
|
+
sanitization?: ErrorSanitizationOptions;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Base error class for all FlagKit SDK errors
|
|
565
|
+
*/
|
|
566
|
+
declare class FlagKitError extends Error {
|
|
567
|
+
/** Error code string (e.g., "NETWORK_TIMEOUT") */
|
|
568
|
+
readonly code: ErrorCode;
|
|
569
|
+
/** Numeric error code (e.g., 1301) */
|
|
570
|
+
readonly numericCode: number;
|
|
571
|
+
/** HTTP status code if applicable */
|
|
572
|
+
readonly statusCode?: number;
|
|
573
|
+
/** Additional error details */
|
|
574
|
+
readonly details?: ErrorDetails;
|
|
575
|
+
/** Whether the operation can be retried */
|
|
576
|
+
readonly recoverable: boolean;
|
|
577
|
+
/** Seconds to wait before retrying (from Retry-After header) */
|
|
578
|
+
readonly retryAfter?: number;
|
|
579
|
+
/** Server request ID for debugging */
|
|
580
|
+
readonly requestId?: string;
|
|
581
|
+
/** Timestamp when error occurred */
|
|
582
|
+
readonly timestamp: Date;
|
|
583
|
+
/** Original unsanitized message (only set when preserveOriginal is true) */
|
|
584
|
+
readonly originalMessage?: string;
|
|
585
|
+
constructor(code: ErrorCode, message?: string, options?: FlagKitErrorOptions);
|
|
586
|
+
/**
|
|
587
|
+
* Create a string representation of the error
|
|
588
|
+
*/
|
|
589
|
+
toString(): string;
|
|
590
|
+
/**
|
|
591
|
+
* Convert error to JSON for logging/serialization
|
|
592
|
+
*/
|
|
593
|
+
toJSON(): Record<string, unknown>;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Initialization error - SDK cannot start
|
|
597
|
+
*/
|
|
598
|
+
declare class InitializationError extends FlagKitError {
|
|
599
|
+
constructor(code: Extract<ErrorCode, 'INIT_FAILED' | 'INIT_TIMEOUT' | 'INIT_INVALID_CONFIG' | 'INIT_MISSING_API_KEY' | 'INIT_INVALID_BASE_URL' | 'INIT_ALREADY_INITIALIZED'>, message?: string, options?: {
|
|
600
|
+
details?: ErrorDetails;
|
|
601
|
+
cause?: Error;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Authentication error - API key issues
|
|
606
|
+
*/
|
|
607
|
+
declare class AuthenticationError extends FlagKitError {
|
|
608
|
+
constructor(code: Extract<ErrorCode, 'AUTH_INVALID_KEY' | 'AUTH_EXPIRED_KEY' | 'AUTH_REVOKED_KEY' | 'AUTH_INSUFFICIENT_PERMISSIONS' | 'AUTH_RATE_LIMITED' | 'AUTH_PROJECT_MISMATCH' | 'AUTH_ENVIRONMENT_MISMATCH'>, message?: string, options?: {
|
|
609
|
+
statusCode?: number;
|
|
610
|
+
details?: ErrorDetails;
|
|
611
|
+
retryAfter?: number;
|
|
612
|
+
requestId?: string;
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Network error - connectivity issues
|
|
617
|
+
*/
|
|
618
|
+
declare class NetworkError extends FlagKitError {
|
|
619
|
+
constructor(code: Extract<ErrorCode, 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'NETWORK_DNS_ERROR' | 'NETWORK_CONNECTION_REFUSED' | 'NETWORK_SSL_ERROR' | 'NETWORK_OFFLINE' | 'NETWORK_SERVER_ERROR' | 'NETWORK_INVALID_RESPONSE'>, message?: string, options?: {
|
|
620
|
+
statusCode?: number;
|
|
621
|
+
details?: ErrorDetails;
|
|
622
|
+
retryAfter?: number;
|
|
623
|
+
requestId?: string;
|
|
624
|
+
cause?: Error;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Evaluation error - flag evaluation issues
|
|
629
|
+
*/
|
|
630
|
+
declare class EvaluationError extends FlagKitError {
|
|
631
|
+
constructor(code: Extract<ErrorCode, 'EVAL_FLAG_NOT_FOUND' | 'EVAL_FLAG_DISABLED' | 'EVAL_TYPE_MISMATCH' | 'EVAL_INVALID_CONTEXT' | 'EVAL_RULE_ERROR' | 'EVAL_SEGMENT_ERROR' | 'EVAL_DEFAULT_USED' | 'EVAL_CACHED_VALUE' | 'EVAL_STALE_VALUE'>, message?: string, options?: {
|
|
632
|
+
details?: ErrorDetails;
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Security error - security violations
|
|
637
|
+
*/
|
|
638
|
+
declare class SecurityError extends FlagKitError {
|
|
639
|
+
constructor(code: Extract<ErrorCode, 'SECURITY_ERROR' | 'SECURITY_PII_DETECTED' | 'SECURITY_LOCAL_PORT_IN_PRODUCTION' | 'SECURITY_KEY_ROTATION_FAILED' | 'SECURITY_BOOTSTRAP_VERIFICATION_FAILED'>, message?: string, options?: {
|
|
640
|
+
details?: ErrorDetails;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Create an error from an HTTP response
|
|
645
|
+
*/
|
|
646
|
+
declare function createErrorFromResponse(statusCode: number, body?: {
|
|
647
|
+
error?: string;
|
|
648
|
+
message?: string;
|
|
649
|
+
code?: string;
|
|
650
|
+
requestId?: string;
|
|
651
|
+
}, retryAfter?: number): FlagKitError;
|
|
652
|
+
/**
|
|
653
|
+
* Check if an error is a FlagKitError
|
|
654
|
+
*/
|
|
655
|
+
declare function isFlagKitError(error: unknown): error is FlagKitError;
|
|
656
|
+
/**
|
|
657
|
+
* Check if an error is retryable
|
|
658
|
+
*/
|
|
659
|
+
declare function isRetryableError(error: unknown): boolean;
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Circuit breaker states
|
|
663
|
+
*/
|
|
664
|
+
declare enum CircuitState {
|
|
665
|
+
/** Normal operation - requests are allowed */
|
|
666
|
+
CLOSED = "CLOSED",
|
|
667
|
+
/** Circuit is open - requests are rejected */
|
|
668
|
+
OPEN = "OPEN",
|
|
669
|
+
/** Testing recovery - limited requests allowed */
|
|
670
|
+
HALF_OPEN = "HALF_OPEN"
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Circuit breaker configuration
|
|
674
|
+
*/
|
|
675
|
+
interface CircuitBreakerConfig {
|
|
676
|
+
/** Number of failures before opening circuit. Default: 5 */
|
|
677
|
+
failureThreshold: number;
|
|
678
|
+
/** Time in ms before attempting recovery. Default: 30000 */
|
|
679
|
+
resetTimeout: number;
|
|
680
|
+
/** Number of successful requests in half-open to close circuit. Default: 1 */
|
|
681
|
+
successThreshold: number;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Error thrown when circuit is open
|
|
685
|
+
*/
|
|
686
|
+
declare class CircuitOpenError extends Error {
|
|
687
|
+
readonly timeUntilReset: number;
|
|
688
|
+
constructor(timeUntilReset: number);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Circuit breaker for protecting against cascading failures
|
|
692
|
+
*
|
|
693
|
+
* State transitions:
|
|
694
|
+
* - CLOSED -> OPEN: When failures >= failureThreshold
|
|
695
|
+
* - OPEN -> HALF_OPEN: After resetTimeout
|
|
696
|
+
* - HALF_OPEN -> CLOSED: After successThreshold successes
|
|
697
|
+
* - HALF_OPEN -> OPEN: On any failure
|
|
698
|
+
*/
|
|
699
|
+
declare class CircuitBreaker {
|
|
700
|
+
private state;
|
|
701
|
+
private failures;
|
|
702
|
+
private successes;
|
|
703
|
+
private lastFailureTime;
|
|
704
|
+
private readonly config;
|
|
705
|
+
private readonly logger?;
|
|
706
|
+
constructor(config?: Partial<CircuitBreakerConfig>, logger?: Logger);
|
|
707
|
+
/**
|
|
708
|
+
* Get current circuit state
|
|
709
|
+
*/
|
|
710
|
+
getState(): CircuitState;
|
|
711
|
+
/**
|
|
712
|
+
* Execute an operation through the circuit breaker
|
|
713
|
+
*/
|
|
714
|
+
execute<T>(operation: () => Promise<T>): Promise<T>;
|
|
715
|
+
/**
|
|
716
|
+
* Record a success
|
|
717
|
+
*/
|
|
718
|
+
onSuccess(): void;
|
|
719
|
+
/**
|
|
720
|
+
* Record a failure
|
|
721
|
+
*/
|
|
722
|
+
onFailure(): void;
|
|
723
|
+
/**
|
|
724
|
+
* Manually reset the circuit breaker
|
|
725
|
+
*/
|
|
726
|
+
reset(): void;
|
|
727
|
+
/**
|
|
728
|
+
* Check if state should transition from OPEN to HALF_OPEN
|
|
729
|
+
*/
|
|
730
|
+
private checkStateTransition;
|
|
731
|
+
/**
|
|
732
|
+
* Transition to a new state
|
|
733
|
+
*/
|
|
734
|
+
private transitionTo;
|
|
735
|
+
/**
|
|
736
|
+
* Get circuit breaker statistics
|
|
737
|
+
*/
|
|
738
|
+
getStats(): {
|
|
739
|
+
state: CircuitState;
|
|
740
|
+
failures: number;
|
|
741
|
+
successes: number;
|
|
742
|
+
lastFailureTime: number;
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Retry configuration
|
|
748
|
+
*/
|
|
749
|
+
interface RetryConfig {
|
|
750
|
+
/** Maximum number of retry attempts. Default: 3 */
|
|
751
|
+
maxAttempts: number;
|
|
752
|
+
/** Base delay in milliseconds. Default: 1000 */
|
|
753
|
+
baseDelay: number;
|
|
754
|
+
/** Maximum delay in milliseconds. Default: 30000 */
|
|
755
|
+
maxDelay: number;
|
|
756
|
+
/** Backoff multiplier. Default: 2 */
|
|
757
|
+
backoffMultiplier: number;
|
|
758
|
+
/** Maximum jitter in milliseconds. Default: 100 */
|
|
759
|
+
jitter: number;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Calculate backoff delay with exponential backoff and jitter
|
|
763
|
+
*/
|
|
764
|
+
declare function calculateBackoff(attempt: number, config: RetryConfig): number;
|
|
765
|
+
/**
|
|
766
|
+
* Execute an operation with retry logic
|
|
767
|
+
*/
|
|
768
|
+
declare function withRetry<T>(operation: () => Promise<T>, config?: Partial<RetryConfig>, options?: {
|
|
769
|
+
logger?: Logger;
|
|
770
|
+
operationName?: string;
|
|
771
|
+
shouldRetry?: (error: unknown) => boolean;
|
|
772
|
+
onRetry?: (attempt: number, error: unknown, delay: number) => void;
|
|
773
|
+
}): Promise<T>;
|
|
774
|
+
/**
|
|
775
|
+
* Parse Retry-After header value
|
|
776
|
+
* Can be either a number of seconds or an HTTP date
|
|
777
|
+
*/
|
|
778
|
+
declare function parseRetryAfter(value: string | null): number | undefined;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* SDK version - should be updated with releases
|
|
782
|
+
*/
|
|
783
|
+
declare const SDK_VERSION = "1.0.0";
|
|
784
|
+
/**
|
|
785
|
+
* HTTP request options
|
|
786
|
+
*/
|
|
787
|
+
interface RequestOptions {
|
|
788
|
+
/** HTTP method */
|
|
789
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
790
|
+
/** Request headers */
|
|
791
|
+
headers?: Record<string, string>;
|
|
792
|
+
/** Request body (will be JSON serialized) */
|
|
793
|
+
body?: unknown;
|
|
794
|
+
/** Request timeout in ms */
|
|
795
|
+
timeout?: number;
|
|
796
|
+
/** Skip retry logic */
|
|
797
|
+
skipRetry?: boolean;
|
|
798
|
+
/** Skip circuit breaker */
|
|
799
|
+
skipCircuitBreaker?: boolean;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* HTTP response wrapper
|
|
803
|
+
*/
|
|
804
|
+
interface HttpResponse<T = unknown> {
|
|
805
|
+
/** Response status code */
|
|
806
|
+
status: number;
|
|
807
|
+
/** Response headers */
|
|
808
|
+
headers: Record<string, string>;
|
|
809
|
+
/** Parsed response body */
|
|
810
|
+
data: T;
|
|
811
|
+
/** Server request ID if available */
|
|
812
|
+
requestId?: string;
|
|
813
|
+
/** Usage metrics from response headers */
|
|
814
|
+
usageMetrics?: UsageMetrics;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Usage metrics extracted from response headers
|
|
818
|
+
*/
|
|
819
|
+
interface UsageMetrics {
|
|
820
|
+
/** Percentage of API call limit used this period (0-150+) */
|
|
821
|
+
apiUsagePercent?: number;
|
|
822
|
+
/** Percentage of evaluation limit used (0-150+) */
|
|
823
|
+
evaluationUsagePercent?: number;
|
|
824
|
+
/** Whether approaching rate limit threshold */
|
|
825
|
+
rateLimitWarning: boolean;
|
|
826
|
+
/** Current subscription status */
|
|
827
|
+
subscriptionStatus?: 'active' | 'trial' | 'past_due' | 'suspended' | 'cancelled';
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Usage update callback type
|
|
831
|
+
*/
|
|
832
|
+
type UsageUpdateCallback = (metrics: UsageMetrics) => void;
|
|
833
|
+
/**
|
|
834
|
+
* HTTP client configuration
|
|
835
|
+
*/
|
|
836
|
+
interface HttpClientConfig {
|
|
837
|
+
/** Base URL for all requests */
|
|
838
|
+
baseUrl: string;
|
|
839
|
+
/** API key for authentication */
|
|
840
|
+
apiKey: string;
|
|
841
|
+
/** Secondary API key for key rotation */
|
|
842
|
+
secondaryApiKey?: string;
|
|
843
|
+
/** Grace period for key rotation in ms. Default: 300000 (5 minutes) */
|
|
844
|
+
keyRotationGracePeriod?: number;
|
|
845
|
+
/** Default request timeout in ms */
|
|
846
|
+
timeout: number;
|
|
847
|
+
/** Retry configuration */
|
|
848
|
+
retry: Partial<RetryConfig>;
|
|
849
|
+
/** Circuit breaker configuration */
|
|
850
|
+
circuitBreaker: Partial<CircuitBreakerConfig>;
|
|
851
|
+
/** Logger instance */
|
|
852
|
+
logger?: Logger;
|
|
853
|
+
/** Enable request signing for POST requests. Default: true */
|
|
854
|
+
enableRequestSigning?: boolean;
|
|
855
|
+
/** Callback for usage metrics updates */
|
|
856
|
+
onUsageUpdate?: UsageUpdateCallback;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* HTTP client for FlagKit API communication
|
|
860
|
+
*
|
|
861
|
+
* Features:
|
|
862
|
+
* - Automatic retry with exponential backoff
|
|
863
|
+
* - Circuit breaker for failure protection
|
|
864
|
+
* - Request timeout handling
|
|
865
|
+
* - Rate limit header parsing
|
|
866
|
+
* - HMAC-SHA256 request signing for POST requests
|
|
867
|
+
* - Signed beacon payloads for page unload events
|
|
868
|
+
* - Key rotation support with automatic failover
|
|
869
|
+
*/
|
|
870
|
+
declare class HttpClient {
|
|
871
|
+
private readonly config;
|
|
872
|
+
private readonly circuitBreaker;
|
|
873
|
+
private readonly logger?;
|
|
874
|
+
private currentApiKey;
|
|
875
|
+
private keyRotationTimestamp;
|
|
876
|
+
constructor(config: HttpClientConfig);
|
|
877
|
+
/**
|
|
878
|
+
* Get the current active API key
|
|
879
|
+
*/
|
|
880
|
+
getActiveApiKey(): string;
|
|
881
|
+
/**
|
|
882
|
+
* Get the key identifier (first 8 chars) for the current key
|
|
883
|
+
*/
|
|
884
|
+
getKeyId(): string;
|
|
885
|
+
/**
|
|
886
|
+
* Check if key rotation is currently active
|
|
887
|
+
*/
|
|
888
|
+
isInKeyRotation(): boolean;
|
|
889
|
+
/**
|
|
890
|
+
* Rotate to secondary API key
|
|
891
|
+
*/
|
|
892
|
+
private rotateToSecondaryKey;
|
|
893
|
+
/**
|
|
894
|
+
* Handle key rotation on authentication failure
|
|
895
|
+
* Returns true if rotation was performed and request should be retried
|
|
896
|
+
*/
|
|
897
|
+
private handleAuthFailure;
|
|
898
|
+
/**
|
|
899
|
+
* Make a GET request
|
|
900
|
+
*/
|
|
901
|
+
get<T>(path: string, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<HttpResponse<T>>;
|
|
902
|
+
/**
|
|
903
|
+
* Make a POST request with automatic signing
|
|
904
|
+
*/
|
|
905
|
+
post<T>(path: string, body?: unknown, options?: Omit<RequestOptions, 'method'>): Promise<HttpResponse<T>>;
|
|
906
|
+
/**
|
|
907
|
+
* Make an HTTP request
|
|
908
|
+
*/
|
|
909
|
+
request<T>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
910
|
+
/**
|
|
911
|
+
* Execute a single HTTP request
|
|
912
|
+
*/
|
|
913
|
+
private executeRequest;
|
|
914
|
+
/**
|
|
915
|
+
* Build full URL from path
|
|
916
|
+
*/
|
|
917
|
+
private buildUrl;
|
|
918
|
+
/**
|
|
919
|
+
* Build request headers
|
|
920
|
+
*/
|
|
921
|
+
private buildHeaders;
|
|
922
|
+
/**
|
|
923
|
+
* Extract usage metrics from response headers
|
|
924
|
+
*/
|
|
925
|
+
private extractUsageMetrics;
|
|
926
|
+
/**
|
|
927
|
+
* Send events using beacon API (for page unload)
|
|
928
|
+
* Only available in browser environments
|
|
929
|
+
*
|
|
930
|
+
* VULN-025: Uses HMAC-SHA256 signed payload for beacon authentication.
|
|
931
|
+
* Beacon requests cannot include Authorization headers, so authentication
|
|
932
|
+
* is done via payload-based HMAC signatures.
|
|
933
|
+
*
|
|
934
|
+
* Payload format:
|
|
935
|
+
* {
|
|
936
|
+
* data: object, // Event data
|
|
937
|
+
* signature: string, // HMAC-SHA256 signature of data
|
|
938
|
+
* timestamp: number, // Unix timestamp in milliseconds
|
|
939
|
+
* apiKeyId: string // Last 8 characters of API key
|
|
940
|
+
* }
|
|
941
|
+
*
|
|
942
|
+
* Note: This method uses a pre-computed signature approach since
|
|
943
|
+
* sendBeacon must complete synchronously. For better security,
|
|
944
|
+
* use sendSignedBeacon when you have time (e.g., visibilitychange).
|
|
945
|
+
*/
|
|
946
|
+
sendBeacon(path: string, data: unknown): boolean;
|
|
947
|
+
/**
|
|
948
|
+
* Send events using beacon API with HMAC-SHA256 signing
|
|
949
|
+
* This is the preferred method when you have time before page unload
|
|
950
|
+
* (e.g., during visibilitychange event)
|
|
951
|
+
*
|
|
952
|
+
* VULN-025: Computes full HMAC-SHA256 signature for authenticated beacons.
|
|
953
|
+
*/
|
|
954
|
+
sendSignedBeacon(path: string, data: unknown): Promise<boolean>;
|
|
955
|
+
/**
|
|
956
|
+
* Get circuit breaker state
|
|
957
|
+
*/
|
|
958
|
+
getCircuitState(): CircuitState;
|
|
959
|
+
/**
|
|
960
|
+
* Reset circuit breaker
|
|
961
|
+
*/
|
|
962
|
+
resetCircuit(): void;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Bootstrap configuration with optional signature verification
|
|
967
|
+
*/
|
|
968
|
+
interface BootstrapConfig {
|
|
969
|
+
/** Flag values for offline/fallback */
|
|
970
|
+
flags: Record<string, unknown>;
|
|
971
|
+
/** HMAC-SHA256 signature of canonical flags JSON */
|
|
972
|
+
signature?: string;
|
|
973
|
+
/** Unix timestamp in milliseconds when bootstrap was generated */
|
|
974
|
+
timestamp?: number;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Options for bootstrap signature verification
|
|
978
|
+
*/
|
|
979
|
+
interface BootstrapVerificationOptions {
|
|
980
|
+
/** Enable signature verification. Default: true (verify if signature present) */
|
|
981
|
+
enabled?: boolean;
|
|
982
|
+
/** Maximum age in milliseconds. Default: 86400000 (24 hours) */
|
|
983
|
+
maxAge?: number;
|
|
984
|
+
/** Behavior on verification failure. Default: 'warn' */
|
|
985
|
+
onFailure?: 'warn' | 'error' | 'ignore';
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Bootstrap data - supports both legacy format (Record) and new format (BootstrapConfig)
|
|
989
|
+
*/
|
|
990
|
+
type BootstrapData = BootstrapConfig | Record<string, unknown>;
|
|
991
|
+
/**
|
|
992
|
+
* Options for error message sanitization to prevent sensitive data leakage
|
|
993
|
+
*/
|
|
994
|
+
interface ErrorSanitizationOptions {
|
|
995
|
+
/** Enable error message sanitization. Default: true */
|
|
996
|
+
enabled?: boolean;
|
|
997
|
+
/** Preserve original message (for debugging). Default: false (true in debug mode) */
|
|
998
|
+
preserveOriginal?: boolean;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Configuration options for initializing the FlagKit SDK
|
|
1002
|
+
*/
|
|
1003
|
+
interface FlagKitOptions {
|
|
1004
|
+
/** Required: API key for authentication */
|
|
1005
|
+
apiKey: string;
|
|
1006
|
+
/** Polling interval in milliseconds. Default: 30000 */
|
|
1007
|
+
pollingInterval?: number;
|
|
1008
|
+
/** Enable background polling for flag updates. Default: true */
|
|
1009
|
+
enablePolling?: boolean;
|
|
1010
|
+
/** Enable in-memory caching. Default: true */
|
|
1011
|
+
cacheEnabled?: boolean;
|
|
1012
|
+
/** Cache TTL in milliseconds. Default: 300000 (5 minutes) */
|
|
1013
|
+
cacheTTL?: number;
|
|
1014
|
+
/** Enable offline mode. Default: false */
|
|
1015
|
+
offline?: boolean;
|
|
1016
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
1017
|
+
timeout?: number;
|
|
1018
|
+
/** Number of retry attempts. Default: 3 */
|
|
1019
|
+
retries?: number;
|
|
1020
|
+
/** Custom logger implementation */
|
|
1021
|
+
logger?: Logger;
|
|
1022
|
+
/** Bootstrap flag values for offline/fallback */
|
|
1023
|
+
bootstrap?: BootstrapData;
|
|
1024
|
+
/** Options for bootstrap signature verification */
|
|
1025
|
+
bootstrapVerification?: BootstrapVerificationOptions;
|
|
1026
|
+
/** Persist cache to storage. Default: false */
|
|
1027
|
+
persistCache?: boolean;
|
|
1028
|
+
/** Storage key for persisted cache. Default: 'flagkit_cache' */
|
|
1029
|
+
cacheStorageKey?: string;
|
|
1030
|
+
/** Called when SDK is initialized and ready */
|
|
1031
|
+
onReady?: () => void;
|
|
1032
|
+
/** Called when an error occurs */
|
|
1033
|
+
onError?: (error: FlagKitError) => void;
|
|
1034
|
+
/** Called when flags are updated */
|
|
1035
|
+
onUpdate?: (flags: FlagState[]) => void;
|
|
1036
|
+
/**
|
|
1037
|
+
* Called when usage metrics are received from API responses.
|
|
1038
|
+
* Provides visibility into API usage, rate limits, and subscription status.
|
|
1039
|
+
*/
|
|
1040
|
+
onUsageUpdate?: (metrics: UsageMetrics) => void;
|
|
1041
|
+
/** Enable debug logging. Default: false */
|
|
1042
|
+
debug?: boolean;
|
|
1043
|
+
/** Local development port. When set, uses http://localhost:{port}/api/v1 */
|
|
1044
|
+
localPort?: number;
|
|
1045
|
+
/**
|
|
1046
|
+
* Secondary API key for key rotation support.
|
|
1047
|
+
* On 401 errors, SDK will automatically retry with this key.
|
|
1048
|
+
*/
|
|
1049
|
+
secondaryApiKey?: string;
|
|
1050
|
+
/**
|
|
1051
|
+
* Grace period in milliseconds during key rotation.
|
|
1052
|
+
* Default: 300000 (5 minutes)
|
|
1053
|
+
*/
|
|
1054
|
+
keyRotationGracePeriod?: number;
|
|
1055
|
+
/**
|
|
1056
|
+
* Strict PII mode - throws SecurityError instead of warning when PII detected
|
|
1057
|
+
* without privateAttributes. Default: false
|
|
1058
|
+
*/
|
|
1059
|
+
strictPIIMode?: boolean;
|
|
1060
|
+
/**
|
|
1061
|
+
* Evaluation jitter configuration for cache timing attack protection.
|
|
1062
|
+
* Adds a random delay at the start of flag evaluation to prevent
|
|
1063
|
+
* attackers from inferring flag values based on response timing.
|
|
1064
|
+
*/
|
|
1065
|
+
evaluationJitter?: {
|
|
1066
|
+
/** Enable evaluation jitter. Default: false */
|
|
1067
|
+
enabled?: boolean;
|
|
1068
|
+
/** Minimum jitter delay in milliseconds. Default: 5 */
|
|
1069
|
+
minMs?: number;
|
|
1070
|
+
/** Maximum jitter delay in milliseconds. Default: 15 */
|
|
1071
|
+
maxMs?: number;
|
|
1072
|
+
};
|
|
1073
|
+
/**
|
|
1074
|
+
* Error message sanitization options to prevent sensitive data leakage.
|
|
1075
|
+
* Removes paths, IPs, API keys, emails, and connection strings from error messages.
|
|
1076
|
+
*/
|
|
1077
|
+
errorSanitization?: ErrorSanitizationOptions;
|
|
1078
|
+
/**
|
|
1079
|
+
* Real-time streaming configuration for SSE-based flag updates.
|
|
1080
|
+
* When enabled, provides near-instant flag updates (~200ms) instead of polling (~30s).
|
|
1081
|
+
*/
|
|
1082
|
+
streaming?: {
|
|
1083
|
+
/** Enable SSE streaming for real-time updates. Default: true */
|
|
1084
|
+
enabled?: boolean;
|
|
1085
|
+
/** Reconnection interval in ms after connection failure. Default: 3000 */
|
|
1086
|
+
reconnectInterval?: number;
|
|
1087
|
+
/** Maximum reconnection attempts before falling back to polling. Default: 3 */
|
|
1088
|
+
maxReconnectAttempts?: number;
|
|
1089
|
+
/** Expected heartbeat interval in ms. Default: 30000 */
|
|
1090
|
+
heartbeatInterval?: number;
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Resolved configuration with all defaults applied
|
|
1095
|
+
*/
|
|
1096
|
+
interface ResolvedConfig {
|
|
1097
|
+
apiKey: string;
|
|
1098
|
+
pollingInterval: number;
|
|
1099
|
+
enablePolling: boolean;
|
|
1100
|
+
cacheEnabled: boolean;
|
|
1101
|
+
cacheTTL: number;
|
|
1102
|
+
offline: boolean;
|
|
1103
|
+
timeout: number;
|
|
1104
|
+
retries: number;
|
|
1105
|
+
logger: Logger;
|
|
1106
|
+
bootstrap: BootstrapData;
|
|
1107
|
+
bootstrapVerification: Required<BootstrapVerificationOptions>;
|
|
1108
|
+
persistCache: boolean;
|
|
1109
|
+
cacheStorageKey: string;
|
|
1110
|
+
onReady?: () => void;
|
|
1111
|
+
onError?: (error: FlagKitError) => void;
|
|
1112
|
+
onUpdate?: (flags: FlagState[]) => void;
|
|
1113
|
+
onUsageUpdate?: (metrics: UsageMetrics) => void;
|
|
1114
|
+
debug: boolean;
|
|
1115
|
+
localPort?: number;
|
|
1116
|
+
secondaryApiKey?: string;
|
|
1117
|
+
keyRotationGracePeriod: number;
|
|
1118
|
+
strictPIIMode: boolean;
|
|
1119
|
+
evaluationJitter: {
|
|
1120
|
+
enabled: boolean;
|
|
1121
|
+
minMs: number;
|
|
1122
|
+
maxMs: number;
|
|
1123
|
+
};
|
|
1124
|
+
errorSanitization: Required<ErrorSanitizationOptions>;
|
|
1125
|
+
streaming: {
|
|
1126
|
+
enabled: boolean;
|
|
1127
|
+
reconnectInterval: number;
|
|
1128
|
+
maxReconnectAttempts: number;
|
|
1129
|
+
heartbeatInterval: number;
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Evaluation context containing user and environment attributes
|
|
1135
|
+
* Used for targeting rules and personalization
|
|
1136
|
+
*/
|
|
1137
|
+
interface EvaluationContext {
|
|
1138
|
+
/** Unique user identifier */
|
|
1139
|
+
userId?: string;
|
|
1140
|
+
/** Alternative user key (e.g., email hash) */
|
|
1141
|
+
userKey?: string;
|
|
1142
|
+
/** User email address */
|
|
1143
|
+
email?: string;
|
|
1144
|
+
/** User display name */
|
|
1145
|
+
name?: string;
|
|
1146
|
+
/** Whether the user is anonymous */
|
|
1147
|
+
anonymous?: boolean;
|
|
1148
|
+
/** ISO country code (e.g., "US", "GB") */
|
|
1149
|
+
country?: string;
|
|
1150
|
+
/** User IP address */
|
|
1151
|
+
ip?: string;
|
|
1152
|
+
/** Browser/device user agent string */
|
|
1153
|
+
userAgent?: string;
|
|
1154
|
+
/** Custom attributes for targeting */
|
|
1155
|
+
custom?: Record<string, unknown>;
|
|
1156
|
+
/** List of attribute names that should not be sent to the server */
|
|
1157
|
+
privateAttributes?: string[];
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Context with all attributes resolved for sending to API
|
|
1161
|
+
*/
|
|
1162
|
+
interface ResolvedContext {
|
|
1163
|
+
userId?: string;
|
|
1164
|
+
userKey?: string;
|
|
1165
|
+
email?: string;
|
|
1166
|
+
name?: string;
|
|
1167
|
+
anonymous?: boolean;
|
|
1168
|
+
country?: string;
|
|
1169
|
+
ip?: string;
|
|
1170
|
+
userAgent?: string;
|
|
1171
|
+
custom?: Record<string, unknown>;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* FlagKit SDK Client
|
|
1176
|
+
*
|
|
1177
|
+
* Main interface for feature flag evaluation and event tracking.
|
|
1178
|
+
*/
|
|
1179
|
+
declare class FlagKitClient {
|
|
1180
|
+
private readonly config;
|
|
1181
|
+
private readonly cache;
|
|
1182
|
+
private readonly contextManager;
|
|
1183
|
+
private readonly httpClient;
|
|
1184
|
+
private readonly eventQueue;
|
|
1185
|
+
private readonly logger;
|
|
1186
|
+
private readonly sessionId;
|
|
1187
|
+
private pollingManager;
|
|
1188
|
+
private streamingManager;
|
|
1189
|
+
private readyPromise;
|
|
1190
|
+
private isReadyFlag;
|
|
1191
|
+
private isClosedFlag;
|
|
1192
|
+
private lastUpdateTime;
|
|
1193
|
+
private isStreamingActive;
|
|
1194
|
+
constructor(options: FlagKitOptions);
|
|
1195
|
+
/**
|
|
1196
|
+
* Initialize the SDK by fetching flag configurations
|
|
1197
|
+
*/
|
|
1198
|
+
initialize(): Promise<void>;
|
|
1199
|
+
private doInitialize;
|
|
1200
|
+
/**
|
|
1201
|
+
* Check if SDK is ready
|
|
1202
|
+
*/
|
|
1203
|
+
isReady(): boolean;
|
|
1204
|
+
/**
|
|
1205
|
+
* Wait for SDK to be ready
|
|
1206
|
+
*/
|
|
1207
|
+
waitForReady(): Promise<void>;
|
|
1208
|
+
/**
|
|
1209
|
+
* Evaluate a boolean flag
|
|
1210
|
+
*/
|
|
1211
|
+
getBooleanValue(key: string, defaultValue: boolean, context?: EvaluationContext): Promise<boolean>;
|
|
1212
|
+
/**
|
|
1213
|
+
* Evaluate a string flag
|
|
1214
|
+
*/
|
|
1215
|
+
getStringValue(key: string, defaultValue: string, context?: EvaluationContext): Promise<string>;
|
|
1216
|
+
/**
|
|
1217
|
+
* Evaluate a number flag
|
|
1218
|
+
*/
|
|
1219
|
+
getNumberValue(key: string, defaultValue: number, context?: EvaluationContext): Promise<number>;
|
|
1220
|
+
/**
|
|
1221
|
+
* Evaluate a JSON flag
|
|
1222
|
+
*/
|
|
1223
|
+
getJsonValue<T extends Record<string, unknown>>(key: string, defaultValue: T, context?: EvaluationContext): Promise<T>;
|
|
1224
|
+
/**
|
|
1225
|
+
* Evaluate a flag and return full result details
|
|
1226
|
+
*/
|
|
1227
|
+
evaluate(key: string, context?: EvaluationContext): Promise<EvaluationResult>;
|
|
1228
|
+
/**
|
|
1229
|
+
* Synchronously get a boolean flag value from cache
|
|
1230
|
+
* Returns the default value if not cached
|
|
1231
|
+
*/
|
|
1232
|
+
getBooleanValueSync(key: string, defaultValue: boolean): boolean;
|
|
1233
|
+
/**
|
|
1234
|
+
* Synchronously get a string flag value from cache
|
|
1235
|
+
* Returns the default value if not cached
|
|
1236
|
+
*/
|
|
1237
|
+
getStringValueSync(key: string, defaultValue: string): string;
|
|
1238
|
+
/**
|
|
1239
|
+
* Synchronously get a number flag value from cache
|
|
1240
|
+
* Returns the default value if not cached
|
|
1241
|
+
*/
|
|
1242
|
+
getNumberValueSync(key: string, defaultValue: number): number;
|
|
1243
|
+
/**
|
|
1244
|
+
* Synchronously get a JSON flag value from cache
|
|
1245
|
+
* Returns the default value if not cached
|
|
1246
|
+
*/
|
|
1247
|
+
getJsonValueSync<T extends Record<string, unknown>>(key: string, defaultValue: T): T;
|
|
1248
|
+
/**
|
|
1249
|
+
* Synchronously evaluate a flag from cache and return full result details
|
|
1250
|
+
* Returns a default result if not cached
|
|
1251
|
+
*/
|
|
1252
|
+
evaluateSync(key: string, defaultValue?: FlagValue): EvaluationResult;
|
|
1253
|
+
/**
|
|
1254
|
+
* Evaluate all flags
|
|
1255
|
+
*/
|
|
1256
|
+
evaluateAll(context?: EvaluationContext): Promise<Record<string, EvaluationResult>>;
|
|
1257
|
+
/**
|
|
1258
|
+
* Get bootstrap flags (handles both legacy and new format)
|
|
1259
|
+
*/
|
|
1260
|
+
private getBootstrapFlags;
|
|
1261
|
+
/**
|
|
1262
|
+
* Check if a flag exists
|
|
1263
|
+
*/
|
|
1264
|
+
hasFlag(key: string): boolean;
|
|
1265
|
+
/**
|
|
1266
|
+
* Get all flag keys
|
|
1267
|
+
*/
|
|
1268
|
+
getAllFlagKeys(): string[];
|
|
1269
|
+
/**
|
|
1270
|
+
* Set global evaluation context
|
|
1271
|
+
* @throws {SecurityError} If strictPIIMode is enabled and PII is detected without privateAttributes
|
|
1272
|
+
*/
|
|
1273
|
+
setContext(context: EvaluationContext): void;
|
|
1274
|
+
/**
|
|
1275
|
+
* Get current global context
|
|
1276
|
+
*/
|
|
1277
|
+
getContext(): EvaluationContext | null;
|
|
1278
|
+
/**
|
|
1279
|
+
* Clear global context
|
|
1280
|
+
*/
|
|
1281
|
+
clearContext(): void;
|
|
1282
|
+
/**
|
|
1283
|
+
* Identify a user
|
|
1284
|
+
* @throws {SecurityError} If strictPIIMode is enabled and PII is detected without privateAttributes
|
|
1285
|
+
*/
|
|
1286
|
+
identify(userId: string, attributes?: Partial<EvaluationContext>): void;
|
|
1287
|
+
/**
|
|
1288
|
+
* Reset to anonymous user
|
|
1289
|
+
*/
|
|
1290
|
+
reset(): void;
|
|
1291
|
+
/**
|
|
1292
|
+
* Track a custom event
|
|
1293
|
+
* @throws {SecurityError} If strictPIIMode is enabled and PII is detected in event data
|
|
1294
|
+
*/
|
|
1295
|
+
track(eventType: string, eventData?: Record<string, unknown>): void;
|
|
1296
|
+
/**
|
|
1297
|
+
* Flush pending events
|
|
1298
|
+
*/
|
|
1299
|
+
flush(): Promise<void>;
|
|
1300
|
+
/**
|
|
1301
|
+
* Force refresh flags from server
|
|
1302
|
+
*/
|
|
1303
|
+
refresh(): Promise<void>;
|
|
1304
|
+
/**
|
|
1305
|
+
* Close the SDK and cleanup resources
|
|
1306
|
+
*/
|
|
1307
|
+
close(): Promise<void>;
|
|
1308
|
+
/**
|
|
1309
|
+
* Synchronous flag evaluation from cache only
|
|
1310
|
+
* Used by React hooks for immediate value access
|
|
1311
|
+
*/
|
|
1312
|
+
private evaluateFlagSync;
|
|
1313
|
+
/**
|
|
1314
|
+
* Internal flag evaluation logic
|
|
1315
|
+
*/
|
|
1316
|
+
private evaluateFlag;
|
|
1317
|
+
/**
|
|
1318
|
+
* Check if bootstrap data is in the new BootstrapConfig format
|
|
1319
|
+
*/
|
|
1320
|
+
private isBootstrapConfig;
|
|
1321
|
+
/**
|
|
1322
|
+
* Apply bootstrap values to cache
|
|
1323
|
+
*/
|
|
1324
|
+
private applyBootstrap;
|
|
1325
|
+
/**
|
|
1326
|
+
* Apply bootstrap values to cache with async verification
|
|
1327
|
+
*/
|
|
1328
|
+
private applyBootstrapAsync;
|
|
1329
|
+
/**
|
|
1330
|
+
* Infer flag type from value
|
|
1331
|
+
*/
|
|
1332
|
+
private inferFlagType;
|
|
1333
|
+
/**
|
|
1334
|
+
* Start background polling
|
|
1335
|
+
*/
|
|
1336
|
+
private startPolling;
|
|
1337
|
+
/**
|
|
1338
|
+
* Start SSE streaming for real-time flag updates
|
|
1339
|
+
*/
|
|
1340
|
+
private startStreaming;
|
|
1341
|
+
/**
|
|
1342
|
+
* Handle flag update from streaming
|
|
1343
|
+
*/
|
|
1344
|
+
private handleStreamFlagUpdate;
|
|
1345
|
+
/**
|
|
1346
|
+
* Handle flag deletion from streaming
|
|
1347
|
+
*/
|
|
1348
|
+
private handleStreamFlagDelete;
|
|
1349
|
+
/**
|
|
1350
|
+
* Handle flags reset from streaming
|
|
1351
|
+
*/
|
|
1352
|
+
private handleStreamFlagsReset;
|
|
1353
|
+
/**
|
|
1354
|
+
* Handle streaming fallback to polling
|
|
1355
|
+
*/
|
|
1356
|
+
private handleStreamingFallback;
|
|
1357
|
+
/**
|
|
1358
|
+
* Check if streaming is currently active
|
|
1359
|
+
*/
|
|
1360
|
+
isStreaming(): boolean;
|
|
1361
|
+
/**
|
|
1362
|
+
* Check SDK version metadata from init response and emit appropriate warnings.
|
|
1363
|
+
*
|
|
1364
|
+
* Per spec, the SDK should parse and surface:
|
|
1365
|
+
* - sdkVersionMin: Minimum required version (older may not work)
|
|
1366
|
+
* - sdkVersionRecommended: Recommended version for optimal experience
|
|
1367
|
+
* - sdkVersionLatest: Latest available version
|
|
1368
|
+
* - deprecationWarning: Server-provided deprecation message
|
|
1369
|
+
*/
|
|
1370
|
+
private checkVersionMetadata;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* FlagKit SDK static factory
|
|
1375
|
+
*
|
|
1376
|
+
* Provides a singleton interface for SDK initialization and access.
|
|
1377
|
+
*/
|
|
1378
|
+
declare class FlagKit {
|
|
1379
|
+
private static instance;
|
|
1380
|
+
private static initPromise;
|
|
1381
|
+
/**
|
|
1382
|
+
* Initialize the FlagKit SDK
|
|
1383
|
+
*
|
|
1384
|
+
* @param options SDK configuration options
|
|
1385
|
+
* @returns Promise resolving to the FlagKit client
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* ```typescript
|
|
1389
|
+
* const client = await FlagKit.initialize({
|
|
1390
|
+
* apiKey: 'sdk_your_api_key',
|
|
1391
|
+
* onReady: () => console.log('FlagKit ready!'),
|
|
1392
|
+
* });
|
|
1393
|
+
*
|
|
1394
|
+
* const darkMode = client.getBooleanValue('dark-mode', false);
|
|
1395
|
+
* ```
|
|
1396
|
+
*/
|
|
1397
|
+
static initialize(options: FlagKitOptions): Promise<FlagKitClient>;
|
|
1398
|
+
/**
|
|
1399
|
+
* Get the current FlagKit client instance
|
|
1400
|
+
*
|
|
1401
|
+
* @throws Error if SDK is not initialized
|
|
1402
|
+
*/
|
|
1403
|
+
static getInstance(): FlagKitClient;
|
|
1404
|
+
/**
|
|
1405
|
+
* Check if SDK is initialized
|
|
1406
|
+
*/
|
|
1407
|
+
static isInitialized(): boolean;
|
|
1408
|
+
/**
|
|
1409
|
+
* Reset the SDK (for testing or reinitialization)
|
|
1410
|
+
*/
|
|
1411
|
+
static reset(): Promise<void>;
|
|
1412
|
+
/**
|
|
1413
|
+
* Internal initialization logic
|
|
1414
|
+
*/
|
|
1415
|
+
private static doInitialize;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Base event structure for all FlagKit events
|
|
1420
|
+
*/
|
|
1421
|
+
interface BaseEvent {
|
|
1422
|
+
/** Event type identifier */
|
|
1423
|
+
eventType: string;
|
|
1424
|
+
/** ISO 8601 timestamp */
|
|
1425
|
+
timestamp: string;
|
|
1426
|
+
/** SDK version */
|
|
1427
|
+
sdkVersion: string;
|
|
1428
|
+
/** SDK language identifier */
|
|
1429
|
+
sdkLanguage: string;
|
|
1430
|
+
/** Session identifier */
|
|
1431
|
+
sessionId: string;
|
|
1432
|
+
/** Environment UUID */
|
|
1433
|
+
environmentId: string;
|
|
1434
|
+
/** User identifier */
|
|
1435
|
+
userId?: string;
|
|
1436
|
+
/** Alternative user key */
|
|
1437
|
+
userKey?: string;
|
|
1438
|
+
/** Whether the user is anonymous */
|
|
1439
|
+
anonymous?: boolean;
|
|
1440
|
+
/** Custom event data */
|
|
1441
|
+
eventData?: Record<string, unknown>;
|
|
1442
|
+
/** Event metadata */
|
|
1443
|
+
metadata?: EventMetadata;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Optional metadata for events
|
|
1447
|
+
*/
|
|
1448
|
+
interface EventMetadata {
|
|
1449
|
+
/** Event source (sdk, api, webhook) */
|
|
1450
|
+
source?: string;
|
|
1451
|
+
/** Platform (web, ios, android, server) */
|
|
1452
|
+
platform?: string;
|
|
1453
|
+
/** Device identifier */
|
|
1454
|
+
deviceId?: string;
|
|
1455
|
+
/** Application version */
|
|
1456
|
+
appVersion?: string;
|
|
1457
|
+
/** User locale (e.g., en-US) */
|
|
1458
|
+
locale?: string;
|
|
1459
|
+
/** User timezone */
|
|
1460
|
+
timezone?: string;
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Flag evaluation event (automatic, opt-in)
|
|
1464
|
+
*/
|
|
1465
|
+
interface FlagEvaluatedEvent extends BaseEvent {
|
|
1466
|
+
eventType: 'flag.evaluated';
|
|
1467
|
+
eventData: {
|
|
1468
|
+
flagKey: string;
|
|
1469
|
+
value: unknown;
|
|
1470
|
+
defaultValue: unknown;
|
|
1471
|
+
reason: EvaluationReason;
|
|
1472
|
+
variationId?: string;
|
|
1473
|
+
ruleId?: string;
|
|
1474
|
+
segmentId?: string;
|
|
1475
|
+
version: number;
|
|
1476
|
+
evaluationTimeMs: number;
|
|
1477
|
+
fromCache: boolean;
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Custom event tracked by user code
|
|
1482
|
+
*/
|
|
1483
|
+
interface CustomEvent extends BaseEvent {
|
|
1484
|
+
eventType: string;
|
|
1485
|
+
eventData: Record<string, unknown>;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* SDK system event types
|
|
1489
|
+
*/
|
|
1490
|
+
type SystemEventType = 'sdk.initialized' | 'sdk.ready' | 'sdk.error' | 'flag.evaluated' | 'context.identified' | 'context.reset';
|
|
1491
|
+
/**
|
|
1492
|
+
* Event queue configuration
|
|
1493
|
+
*/
|
|
1494
|
+
interface EventQueueConfig {
|
|
1495
|
+
/** Maximum events to queue. Default: 100 */
|
|
1496
|
+
maxQueueSize: number;
|
|
1497
|
+
/** Batch size for sending. Default: 10 */
|
|
1498
|
+
batchSize: number;
|
|
1499
|
+
/** Flush interval in ms. Default: 30000 */
|
|
1500
|
+
flushInterval: number;
|
|
1501
|
+
/** Maximum retry attempts. Default: 3 */
|
|
1502
|
+
maxRetries: number;
|
|
1503
|
+
/** Base retry delay in ms. Default: 1000 */
|
|
1504
|
+
retryDelay: number;
|
|
1505
|
+
/** Retry backoff multiplier. Default: 2 */
|
|
1506
|
+
retryBackoff: number;
|
|
1507
|
+
/** Persist queue to storage. Default: false */
|
|
1508
|
+
persistQueue: boolean;
|
|
1509
|
+
/** Storage key for persistence. Default: 'flagkit_events' */
|
|
1510
|
+
storageKey: string;
|
|
1511
|
+
/** Enabled event types. Default: ['*'] (all) */
|
|
1512
|
+
enabledEventTypes: string[];
|
|
1513
|
+
/** Disabled event types. Default: [] */
|
|
1514
|
+
disabledEventTypes: string[];
|
|
1515
|
+
/** Event sampling rate (0.0 - 1.0). Default: 1.0 */
|
|
1516
|
+
sampleRate: number;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* API response for batch events
|
|
1520
|
+
*/
|
|
1521
|
+
interface BatchEventsResponse {
|
|
1522
|
+
success: boolean;
|
|
1523
|
+
message: string;
|
|
1524
|
+
recorded: number;
|
|
1525
|
+
errors: number;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Security utilities for FlagKit SDK
|
|
1530
|
+
*/
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Check if a field name potentially contains PII
|
|
1534
|
+
*/
|
|
1535
|
+
declare function isPotentialPIIField(fieldName: string): boolean;
|
|
1536
|
+
/**
|
|
1537
|
+
* Detect potential PII in an object and return the field names
|
|
1538
|
+
*/
|
|
1539
|
+
declare function detectPotentialPII(data: Record<string, unknown>, prefix?: string): string[];
|
|
1540
|
+
/**
|
|
1541
|
+
* PII detection result
|
|
1542
|
+
*/
|
|
1543
|
+
interface PIIDetectionResult {
|
|
1544
|
+
hasPII: boolean;
|
|
1545
|
+
fields: string[];
|
|
1546
|
+
message: string;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Check for potential PII in data and return detailed result
|
|
1550
|
+
*/
|
|
1551
|
+
declare function checkForPotentialPII(data: Record<string, unknown> | undefined, dataType: 'context' | 'event'): PIIDetectionResult;
|
|
1552
|
+
/**
|
|
1553
|
+
* Warn about potential PII in data
|
|
1554
|
+
*/
|
|
1555
|
+
declare function warnIfPotentialPII(data: Record<string, unknown> | undefined, dataType: 'context' | 'event', logger?: Logger): void;
|
|
1556
|
+
/**
|
|
1557
|
+
* Check if an API key is a server key
|
|
1558
|
+
*/
|
|
1559
|
+
declare function isServerKey(apiKey: string): boolean;
|
|
1560
|
+
/**
|
|
1561
|
+
* Check if an API key is a client/SDK key
|
|
1562
|
+
*/
|
|
1563
|
+
declare function isClientKey(apiKey: string): boolean;
|
|
1564
|
+
/**
|
|
1565
|
+
* Warn if server key is used in browser environment
|
|
1566
|
+
*/
|
|
1567
|
+
declare function warnIfServerKeyInBrowser(apiKey: string, logger?: Logger): void;
|
|
1568
|
+
/**
|
|
1569
|
+
* Security configuration options
|
|
1570
|
+
*/
|
|
1571
|
+
interface SecurityConfig {
|
|
1572
|
+
/** Warn about potential PII in context/events. Default: true in development */
|
|
1573
|
+
warnOnPotentialPII?: boolean;
|
|
1574
|
+
/** Warn when server keys are used in browser. Default: true */
|
|
1575
|
+
warnOnServerKeyInBrowser?: boolean;
|
|
1576
|
+
/** Custom PII patterns to detect */
|
|
1577
|
+
additionalPIIPatterns?: string[];
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Default security configuration
|
|
1581
|
+
*/
|
|
1582
|
+
declare const DEFAULT_SECURITY_CONFIG: Required<SecurityConfig>;
|
|
1583
|
+
/**
|
|
1584
|
+
* Signed payload structure for beacon requests
|
|
1585
|
+
*/
|
|
1586
|
+
interface SignedPayload<T = unknown> {
|
|
1587
|
+
data: T;
|
|
1588
|
+
signature: string;
|
|
1589
|
+
timestamp: number;
|
|
1590
|
+
keyId: string;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Get the first 8 characters of an API key for identification
|
|
1594
|
+
* This is safe to expose as it doesn't reveal the full key
|
|
1595
|
+
*/
|
|
1596
|
+
declare function getKeyId(apiKey: string): string;
|
|
1597
|
+
/**
|
|
1598
|
+
* Generate HMAC-SHA256 signature
|
|
1599
|
+
* Uses Web Crypto API in browser, Node.js crypto in server
|
|
1600
|
+
*/
|
|
1601
|
+
declare function generateHMACSHA256(message: string, key: string): Promise<string>;
|
|
1602
|
+
/**
|
|
1603
|
+
* Sign a payload with HMAC-SHA256
|
|
1604
|
+
*/
|
|
1605
|
+
declare function signPayload<T>(data: T, apiKey: string, timestamp?: number): Promise<SignedPayload<T>>;
|
|
1606
|
+
/**
|
|
1607
|
+
* Create signature string for request headers
|
|
1608
|
+
* Format: timestamp.signature
|
|
1609
|
+
*/
|
|
1610
|
+
declare function createRequestSignature(body: string, apiKey: string, timestamp?: number): Promise<{
|
|
1611
|
+
signature: string;
|
|
1612
|
+
timestamp: number;
|
|
1613
|
+
}>;
|
|
1614
|
+
/**
|
|
1615
|
+
* Verify a signed payload (for testing/debugging)
|
|
1616
|
+
*/
|
|
1617
|
+
declare function verifySignedPayload<T>(signedPayload: SignedPayload<T>, apiKey: string, maxAgeMs?: number): Promise<boolean>;
|
|
1618
|
+
/**
|
|
1619
|
+
* Check if environment is production
|
|
1620
|
+
*/
|
|
1621
|
+
declare function isProductionEnvironment(): boolean;
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Storage interface for cache persistence
|
|
1625
|
+
*/
|
|
1626
|
+
interface Storage {
|
|
1627
|
+
/**
|
|
1628
|
+
* Get an item from storage
|
|
1629
|
+
* @param key The storage key
|
|
1630
|
+
* @returns The stored value or null if not found
|
|
1631
|
+
*/
|
|
1632
|
+
get(key: string): string | null;
|
|
1633
|
+
/**
|
|
1634
|
+
* Set an item in storage
|
|
1635
|
+
* @param key The storage key
|
|
1636
|
+
* @param value The value to store
|
|
1637
|
+
*/
|
|
1638
|
+
set(key: string, value: string): void;
|
|
1639
|
+
/**
|
|
1640
|
+
* Remove an item from storage
|
|
1641
|
+
* @param key The storage key
|
|
1642
|
+
*/
|
|
1643
|
+
remove(key: string): void;
|
|
1644
|
+
/**
|
|
1645
|
+
* Clear all items from storage
|
|
1646
|
+
*/
|
|
1647
|
+
clear(): void;
|
|
1648
|
+
/**
|
|
1649
|
+
* Check if storage is available
|
|
1650
|
+
*/
|
|
1651
|
+
isAvailable(): boolean;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Browser localStorage adapter
|
|
1656
|
+
*/
|
|
1657
|
+
declare class LocalStorage implements Storage {
|
|
1658
|
+
private readonly prefix;
|
|
1659
|
+
private available;
|
|
1660
|
+
constructor(prefix?: string);
|
|
1661
|
+
/**
|
|
1662
|
+
* Get an item from localStorage
|
|
1663
|
+
*/
|
|
1664
|
+
get(key: string): string | null;
|
|
1665
|
+
/**
|
|
1666
|
+
* Set an item in localStorage
|
|
1667
|
+
*/
|
|
1668
|
+
set(key: string, value: string): void;
|
|
1669
|
+
/**
|
|
1670
|
+
* Remove an item from localStorage
|
|
1671
|
+
*/
|
|
1672
|
+
remove(key: string): void;
|
|
1673
|
+
/**
|
|
1674
|
+
* Clear all FlagKit items from localStorage
|
|
1675
|
+
*/
|
|
1676
|
+
clear(): void;
|
|
1677
|
+
/**
|
|
1678
|
+
* Check if localStorage is available
|
|
1679
|
+
*/
|
|
1680
|
+
isAvailable(): boolean;
|
|
1681
|
+
/**
|
|
1682
|
+
* Add prefix to key
|
|
1683
|
+
*/
|
|
1684
|
+
private prefixKey;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* In-memory storage adapter
|
|
1689
|
+
* Used as fallback when localStorage is not available
|
|
1690
|
+
*/
|
|
1691
|
+
declare class MemoryStorage implements Storage {
|
|
1692
|
+
private readonly store;
|
|
1693
|
+
/**
|
|
1694
|
+
* Get an item from memory
|
|
1695
|
+
*/
|
|
1696
|
+
get(key: string): string | null;
|
|
1697
|
+
/**
|
|
1698
|
+
* Set an item in memory
|
|
1699
|
+
*/
|
|
1700
|
+
set(key: string, value: string): void;
|
|
1701
|
+
/**
|
|
1702
|
+
* Remove an item from memory
|
|
1703
|
+
*/
|
|
1704
|
+
remove(key: string): void;
|
|
1705
|
+
/**
|
|
1706
|
+
* Clear all items from memory
|
|
1707
|
+
*/
|
|
1708
|
+
clear(): void;
|
|
1709
|
+
/**
|
|
1710
|
+
* Memory storage is always available
|
|
1711
|
+
*/
|
|
1712
|
+
isAvailable(): boolean;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* Encrypted storage wrapper using AES-256-GCM
|
|
1717
|
+
*
|
|
1718
|
+
* Wraps another storage implementation and encrypts/decrypts data automatically.
|
|
1719
|
+
* Uses Web Crypto API in browser and Node.js crypto in server environments.
|
|
1720
|
+
*/
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Configuration for encrypted storage
|
|
1724
|
+
*/
|
|
1725
|
+
interface EncryptedStorageConfig {
|
|
1726
|
+
/** The underlying storage to wrap */
|
|
1727
|
+
storage: Storage;
|
|
1728
|
+
/** API key used to derive encryption key */
|
|
1729
|
+
apiKey: string;
|
|
1730
|
+
/** Optional logger for debugging */
|
|
1731
|
+
logger?: Logger;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Encrypted storage wrapper
|
|
1735
|
+
*
|
|
1736
|
+
* Provides transparent encryption for cached data using AES-256-GCM.
|
|
1737
|
+
* Falls back to unencrypted storage if encryption is unavailable.
|
|
1738
|
+
*/
|
|
1739
|
+
declare class EncryptedStorage implements Storage {
|
|
1740
|
+
private readonly storage;
|
|
1741
|
+
private readonly apiKey;
|
|
1742
|
+
private readonly logger?;
|
|
1743
|
+
private derivedKey;
|
|
1744
|
+
private keyDerivationPromise;
|
|
1745
|
+
private cryptoAvailable;
|
|
1746
|
+
constructor(config: EncryptedStorageConfig);
|
|
1747
|
+
/**
|
|
1748
|
+
* Check if crypto is available
|
|
1749
|
+
*/
|
|
1750
|
+
private isCryptoAvailable;
|
|
1751
|
+
/**
|
|
1752
|
+
* Derive encryption key from API key
|
|
1753
|
+
*/
|
|
1754
|
+
private deriveKey;
|
|
1755
|
+
/**
|
|
1756
|
+
* Derive key using Web Crypto API
|
|
1757
|
+
*/
|
|
1758
|
+
private deriveKeyWebCrypto;
|
|
1759
|
+
/**
|
|
1760
|
+
* Derive key using Node.js crypto
|
|
1761
|
+
*/
|
|
1762
|
+
private deriveKeyNode;
|
|
1763
|
+
/**
|
|
1764
|
+
* Wait for key derivation to complete
|
|
1765
|
+
*/
|
|
1766
|
+
private ensureKeyReady;
|
|
1767
|
+
/**
|
|
1768
|
+
* Encrypt data using AES-256-GCM
|
|
1769
|
+
*/
|
|
1770
|
+
private encrypt;
|
|
1771
|
+
/**
|
|
1772
|
+
* Encrypt using Web Crypto API
|
|
1773
|
+
*/
|
|
1774
|
+
private encryptWebCrypto;
|
|
1775
|
+
/**
|
|
1776
|
+
* Encrypt using Node.js crypto
|
|
1777
|
+
*/
|
|
1778
|
+
private encryptNode;
|
|
1779
|
+
/**
|
|
1780
|
+
* Decrypt data using AES-256-GCM
|
|
1781
|
+
*/
|
|
1782
|
+
private decrypt;
|
|
1783
|
+
/**
|
|
1784
|
+
* Decrypt using Web Crypto API
|
|
1785
|
+
*/
|
|
1786
|
+
private decryptWebCrypto;
|
|
1787
|
+
/**
|
|
1788
|
+
* Decrypt using Node.js crypto
|
|
1789
|
+
*/
|
|
1790
|
+
private decryptNode;
|
|
1791
|
+
/**
|
|
1792
|
+
* Convert Uint8Array to Base64
|
|
1793
|
+
*/
|
|
1794
|
+
private arrayToBase64;
|
|
1795
|
+
/**
|
|
1796
|
+
* Convert Base64 to Uint8Array
|
|
1797
|
+
*/
|
|
1798
|
+
private base64ToArray;
|
|
1799
|
+
/**
|
|
1800
|
+
* Get an item from storage (decrypted)
|
|
1801
|
+
*/
|
|
1802
|
+
get(key: string): string | null;
|
|
1803
|
+
/**
|
|
1804
|
+
* Check if data looks like encrypted format
|
|
1805
|
+
*/
|
|
1806
|
+
private isEncryptedFormat;
|
|
1807
|
+
/**
|
|
1808
|
+
* Get an item from storage (async, with decryption)
|
|
1809
|
+
*/
|
|
1810
|
+
getAsync(key: string): Promise<string | null>;
|
|
1811
|
+
/**
|
|
1812
|
+
* Set an item in storage (encrypted)
|
|
1813
|
+
*/
|
|
1814
|
+
set(key: string, value: string): void;
|
|
1815
|
+
/**
|
|
1816
|
+
* Set an item in storage (async, with encryption)
|
|
1817
|
+
*/
|
|
1818
|
+
setAsync(key: string, value: string): Promise<void>;
|
|
1819
|
+
/**
|
|
1820
|
+
* Remove an item from storage
|
|
1821
|
+
*/
|
|
1822
|
+
remove(key: string): void;
|
|
1823
|
+
/**
|
|
1824
|
+
* Clear all items from storage
|
|
1825
|
+
*/
|
|
1826
|
+
clear(): void;
|
|
1827
|
+
/**
|
|
1828
|
+
* Check if storage is available
|
|
1829
|
+
*/
|
|
1830
|
+
isAvailable(): boolean;
|
|
1831
|
+
/**
|
|
1832
|
+
* Check if encryption is available
|
|
1833
|
+
*/
|
|
1834
|
+
isEncryptionAvailable(): boolean;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/**
|
|
1838
|
+
* Stream event types from server
|
|
1839
|
+
*/
|
|
1840
|
+
type StreamEventType = 'flag_updated' | 'flag_deleted' | 'flags_reset' | 'heartbeat' | 'error';
|
|
1841
|
+
interface StreamEvent {
|
|
1842
|
+
type: StreamEventType;
|
|
1843
|
+
data: FlagState | {
|
|
1844
|
+
key: string;
|
|
1845
|
+
} | FlagState[] | {
|
|
1846
|
+
timestamp: string;
|
|
1847
|
+
} | StreamErrorData;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* SSE error event data structure
|
|
1851
|
+
*/
|
|
1852
|
+
interface StreamErrorData {
|
|
1853
|
+
code: StreamErrorCode;
|
|
1854
|
+
message: string;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* SSE error codes from server
|
|
1858
|
+
*/
|
|
1859
|
+
type StreamErrorCode = 'TOKEN_INVALID' | 'TOKEN_EXPIRED' | 'SUBSCRIPTION_SUSPENDED' | 'CONNECTION_LIMIT' | 'STREAMING_UNAVAILABLE';
|
|
1860
|
+
/**
|
|
1861
|
+
* Response from the stream token endpoint
|
|
1862
|
+
*/
|
|
1863
|
+
interface StreamTokenResponse {
|
|
1864
|
+
/** Short-lived token for SSE connection */
|
|
1865
|
+
token: string;
|
|
1866
|
+
/** Token expiry time in seconds */
|
|
1867
|
+
expiresIn: number;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Streaming configuration
|
|
1871
|
+
*/
|
|
1872
|
+
interface StreamingConfig {
|
|
1873
|
+
/** Base URL for API endpoints */
|
|
1874
|
+
baseUrl: string;
|
|
1875
|
+
/** Reconnection interval in ms. Default: 3000 */
|
|
1876
|
+
reconnectInterval: number;
|
|
1877
|
+
/** Maximum reconnection attempts before fallback. Default: 3 */
|
|
1878
|
+
maxReconnectAttempts: number;
|
|
1879
|
+
/** Expected heartbeat interval in ms. Default: 30000 */
|
|
1880
|
+
heartbeatInterval: number;
|
|
1881
|
+
/** API key for authentication (used to fetch stream token) */
|
|
1882
|
+
apiKey: string;
|
|
1883
|
+
/** Callback when flag is updated */
|
|
1884
|
+
onFlagUpdate: (flag: FlagState) => void;
|
|
1885
|
+
/** Callback when flag is deleted */
|
|
1886
|
+
onFlagDelete: (key: string) => void;
|
|
1887
|
+
/** Callback when all flags are reset */
|
|
1888
|
+
onFlagsReset: (flags: FlagState[]) => void;
|
|
1889
|
+
/** Callback when streaming fails and falls back to polling */
|
|
1890
|
+
onFallbackToPolling: () => void;
|
|
1891
|
+
/** Callback when subscription error occurs (e.g., suspended) */
|
|
1892
|
+
onSubscriptionError?: (message: string) => void;
|
|
1893
|
+
/** Callback when connection limit is reached */
|
|
1894
|
+
onConnectionLimitError?: () => void;
|
|
1895
|
+
/** Logger instance */
|
|
1896
|
+
logger?: Logger;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Connection states for streaming
|
|
1900
|
+
*/
|
|
1901
|
+
type StreamingState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'failed';
|
|
1902
|
+
/**
|
|
1903
|
+
* Manages Server-Sent Events (SSE) connection for real-time flag updates
|
|
1904
|
+
*
|
|
1905
|
+
* Security: Uses token exchange pattern to avoid exposing API keys in URLs.
|
|
1906
|
+
* 1. Fetches short-lived token via POST with API key in header
|
|
1907
|
+
* 2. Connects to SSE endpoint with disposable token in URL
|
|
1908
|
+
*
|
|
1909
|
+
* Features:
|
|
1910
|
+
* - Secure token-based authentication
|
|
1911
|
+
* - Automatic token refresh before expiry
|
|
1912
|
+
* - Automatic reconnection with exponential backoff
|
|
1913
|
+
* - Graceful degradation to polling after max failures
|
|
1914
|
+
* - Heartbeat monitoring for connection health
|
|
1915
|
+
* - Event parsing and dispatch
|
|
1916
|
+
*/
|
|
1917
|
+
declare class StreamingManager {
|
|
1918
|
+
private readonly config;
|
|
1919
|
+
private readonly logger?;
|
|
1920
|
+
private eventSource;
|
|
1921
|
+
private state;
|
|
1922
|
+
private consecutiveFailures;
|
|
1923
|
+
private reconnectTimer;
|
|
1924
|
+
private heartbeatTimer;
|
|
1925
|
+
private tokenRefreshTimer;
|
|
1926
|
+
private lastHeartbeat;
|
|
1927
|
+
private retryStreamingTimer;
|
|
1928
|
+
constructor(config: Partial<StreamingConfig> & Pick<StreamingConfig, 'baseUrl' | 'apiKey' | 'onFlagUpdate' | 'onFlagDelete' | 'onFlagsReset' | 'onFallbackToPolling'>);
|
|
1929
|
+
/**
|
|
1930
|
+
* Get current connection state
|
|
1931
|
+
*/
|
|
1932
|
+
getState(): StreamingState;
|
|
1933
|
+
/**
|
|
1934
|
+
* Check if streaming is active
|
|
1935
|
+
*/
|
|
1936
|
+
isConnected(): boolean;
|
|
1937
|
+
/**
|
|
1938
|
+
* Start streaming connection
|
|
1939
|
+
*/
|
|
1940
|
+
connect(): void;
|
|
1941
|
+
/**
|
|
1942
|
+
* Stop streaming connection
|
|
1943
|
+
*/
|
|
1944
|
+
disconnect(): void;
|
|
1945
|
+
/**
|
|
1946
|
+
* Retry streaming connection (called periodically after fallback to polling)
|
|
1947
|
+
*/
|
|
1948
|
+
retryConnection(): void;
|
|
1949
|
+
/**
|
|
1950
|
+
* Initiate connection by first fetching a stream token
|
|
1951
|
+
*/
|
|
1952
|
+
private initiateConnection;
|
|
1953
|
+
/**
|
|
1954
|
+
* Fetch a short-lived stream token from the API
|
|
1955
|
+
* Token is fetched via POST with API key in header (secure)
|
|
1956
|
+
*/
|
|
1957
|
+
private fetchStreamToken;
|
|
1958
|
+
/**
|
|
1959
|
+
* Schedule token refresh before expiry
|
|
1960
|
+
*/
|
|
1961
|
+
private scheduleTokenRefresh;
|
|
1962
|
+
/**
|
|
1963
|
+
* Clear token refresh timer
|
|
1964
|
+
*/
|
|
1965
|
+
private clearTokenRefreshTimer;
|
|
1966
|
+
/**
|
|
1967
|
+
* Create SSE connection with token
|
|
1968
|
+
*/
|
|
1969
|
+
private createConnection;
|
|
1970
|
+
/**
|
|
1971
|
+
* Handle successful connection
|
|
1972
|
+
*/
|
|
1973
|
+
private handleOpen;
|
|
1974
|
+
/**
|
|
1975
|
+
* Handle generic message (fallback for unnamed events)
|
|
1976
|
+
*/
|
|
1977
|
+
private handleMessage;
|
|
1978
|
+
/**
|
|
1979
|
+
* Handle flag_updated event
|
|
1980
|
+
*/
|
|
1981
|
+
private handleFlagUpdated;
|
|
1982
|
+
/**
|
|
1983
|
+
* Handle flag_deleted event
|
|
1984
|
+
*/
|
|
1985
|
+
private handleFlagDeleted;
|
|
1986
|
+
/**
|
|
1987
|
+
* Handle flags_reset event
|
|
1988
|
+
*/
|
|
1989
|
+
private handleFlagsReset;
|
|
1990
|
+
/**
|
|
1991
|
+
* Handle heartbeat event
|
|
1992
|
+
*/
|
|
1993
|
+
private handleHeartbeat;
|
|
1994
|
+
/**
|
|
1995
|
+
* Handle SSE error event from server
|
|
1996
|
+
* These are application-level errors sent as SSE events, not connection errors
|
|
1997
|
+
*
|
|
1998
|
+
* Error codes:
|
|
1999
|
+
* - TOKEN_INVALID: Re-authenticate completely
|
|
2000
|
+
* - TOKEN_EXPIRED: Refresh token and reconnect
|
|
2001
|
+
* - SUBSCRIPTION_SUSPENDED: Notify user, fall back to cached values
|
|
2002
|
+
* - CONNECTION_LIMIT: Implement backoff or close other connections
|
|
2003
|
+
* - STREAMING_UNAVAILABLE: Fall back to polling
|
|
2004
|
+
*/
|
|
2005
|
+
private handleStreamError;
|
|
2006
|
+
/**
|
|
2007
|
+
* Dispatch parsed event to appropriate handler
|
|
2008
|
+
*/
|
|
2009
|
+
private dispatchEvent;
|
|
2010
|
+
/**
|
|
2011
|
+
* Handle connection error
|
|
2012
|
+
*/
|
|
2013
|
+
private handleError;
|
|
2014
|
+
/**
|
|
2015
|
+
* Handle connection failure
|
|
2016
|
+
*/
|
|
2017
|
+
private handleConnectionFailure;
|
|
2018
|
+
/**
|
|
2019
|
+
* Schedule reconnection attempt
|
|
2020
|
+
*/
|
|
2021
|
+
private scheduleReconnect;
|
|
2022
|
+
/**
|
|
2023
|
+
* Get reconnection delay with exponential backoff
|
|
2024
|
+
*/
|
|
2025
|
+
private getReconnectDelay;
|
|
2026
|
+
/**
|
|
2027
|
+
* Schedule retry of streaming after fallback to polling
|
|
2028
|
+
*/
|
|
2029
|
+
private scheduleStreamingRetry;
|
|
2030
|
+
/**
|
|
2031
|
+
* Start heartbeat monitoring
|
|
2032
|
+
*/
|
|
2033
|
+
private startHeartbeatMonitor;
|
|
2034
|
+
/**
|
|
2035
|
+
* Stop heartbeat monitoring
|
|
2036
|
+
*/
|
|
2037
|
+
private stopHeartbeatMonitor;
|
|
2038
|
+
/**
|
|
2039
|
+
* Set connection state
|
|
2040
|
+
*/
|
|
2041
|
+
private setState;
|
|
2042
|
+
/**
|
|
2043
|
+
* Cleanup resources
|
|
2044
|
+
*/
|
|
2045
|
+
private cleanup;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
export { AuthenticationError, type BaseEvent, type BatchEvaluateResponse, type BatchEventsResponse, CircuitBreaker, type CircuitBreakerConfig, CircuitOpenError, CircuitState, ConsoleLogger, type CustomEvent, DEFAULT_SECURITY_CONFIG, EncryptedStorage, type EncryptedStorageConfig, type ErrorCode, ErrorCodes, type EvaluateResponse, type EvaluationContext, EvaluationError, type EvaluationReason, type EvaluationResult, type EventMetadata, type EventQueueConfig, type FlagEvaluatedEvent, FlagKit, FlagKitClient, FlagKitError, type FlagKitOptions, type FlagState, type FlagType, type FlagValue, HttpClient, type HttpResponse, type InitResponse, InitializationError, LocalStorage, type LogEntry, type LogLevel, type Logger, MemoryStorage, NetworkError, NoopLogger, type PIIDetectionResult, type ResolvedConfig, type ResolvedContext, type RetryConfig, SDK_VERSION, type SecurityConfig, SecurityError, type SignedPayload, type Storage, type StreamErrorCode, type StreamErrorData, type StreamEvent, type StreamEventType, type StreamTokenResponse, type StreamingConfig, StreamingManager, type StreamingState, type SystemEventType, type UpdatesResponse, type UsageMetrics, type UsageUpdateCallback, calculateBackoff, checkForPotentialPII, createErrorFromResponse, createLogger, createRequestSignature, FlagKit as default, detectPotentialPII, generateHMACSHA256, getKeyId, isClientKey, isFlagKitError, isPotentialPIIField, isProductionEnvironment, isRetryableCode, isRetryableError, isServerKey, parseRetryAfter, signPayload, verifySignedPayload, warnIfPotentialPII, warnIfServerKeyInBrowser, withRetry };
|