flipflag-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.
Files changed (97) hide show
  1. package/README.md +713 -0
  2. package/dist/core/FlipFlagSDK.js +379 -0
  3. package/dist/index.js +384 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +382 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/js/featureFlagManager.js +172 -0
  8. package/dist/js/identifyUser.js +15 -0
  9. package/dist/js/index.js +3 -0
  10. package/dist/js.js +565 -0
  11. package/dist/js.js.map +1 -0
  12. package/dist/js.mjs +561 -0
  13. package/dist/js.mjs.map +1 -0
  14. package/dist/next/FlipflagProvider.js +27 -0
  15. package/dist/next/identifyUser.js +9 -0
  16. package/dist/next/index.js +6 -0
  17. package/dist/next/server-utils.js +70 -0
  18. package/dist/next/types.js +1 -0
  19. package/dist/next/useABTest.js +53 -0
  20. package/dist/next/useFeatureFlag.js +55 -0
  21. package/dist/next/useFeatureFlags.js +69 -0
  22. package/dist/next.js +657 -0
  23. package/dist/next.js.map +1 -0
  24. package/dist/next.mjs +650 -0
  25. package/dist/next.mjs.map +1 -0
  26. package/dist/react/FeatureFlagProvider.js +19 -0
  27. package/dist/react/identifyUser.js +9 -0
  28. package/dist/react/index.js +5 -0
  29. package/dist/react/types.js +1 -0
  30. package/dist/react/useABTest.js +64 -0
  31. package/dist/react/useFeatureFlag.js +84 -0
  32. package/dist/react/useFeatureFlags.js +74 -0
  33. package/dist/react-native/FeatureFlagProvider.js +19 -0
  34. package/dist/react-native/identifyUser.js +9 -0
  35. package/dist/react-native/index.js +8 -0
  36. package/dist/react-native/offlineStorage.js +39 -0
  37. package/dist/react-native/types.js +1 -0
  38. package/dist/react-native/useABTest.js +87 -0
  39. package/dist/react-native/useFeatureFlag.js +82 -0
  40. package/dist/react-native/useFeatureFlags.js +115 -0
  41. package/dist/react-native.js +732 -0
  42. package/dist/react-native.js.map +1 -0
  43. package/dist/react-native.mjs +723 -0
  44. package/dist/react-native.mjs.map +1 -0
  45. package/dist/react.js +634 -0
  46. package/dist/react.js.map +1 -0
  47. package/dist/react.mjs +627 -0
  48. package/dist/react.mjs.map +1 -0
  49. package/dist/types/core/FlipFlagSDK.d.ts +94 -0
  50. package/dist/types/index.d.ts +2 -0
  51. package/dist/types/index.js +1 -0
  52. package/dist/types/js/featureFlagManager.d.ts +59 -0
  53. package/dist/types/js/identifyUser.d.ts +11 -0
  54. package/dist/types/js/index.d.ts +4 -0
  55. package/dist/types/next/FlipflagProvider.d.ts +18 -0
  56. package/dist/types/next/identifyUser.d.ts +6 -0
  57. package/dist/types/next/index.d.ts +7 -0
  58. package/dist/types/next/server-utils.d.ts +15 -0
  59. package/dist/types/next/types.d.ts +24 -0
  60. package/dist/types/next/useABTest.d.ts +10 -0
  61. package/dist/types/next/useFeatureFlag.d.ts +10 -0
  62. package/dist/types/next/useFeatureFlags.d.ts +10 -0
  63. package/dist/types/react/FeatureFlagProvider.d.ts +14 -0
  64. package/dist/types/react/identifyUser.d.ts +6 -0
  65. package/dist/types/react/index.d.ts +6 -0
  66. package/dist/types/react/types.d.ts +20 -0
  67. package/dist/types/react/useABTest.d.ts +10 -0
  68. package/dist/types/react/useFeatureFlag.d.ts +10 -0
  69. package/dist/types/react/useFeatureFlags.d.ts +10 -0
  70. package/dist/types/react-native/FeatureFlagProvider.d.ts +14 -0
  71. package/dist/types/react-native/identifyUser.d.ts +6 -0
  72. package/dist/types/react-native/index.d.ts +9 -0
  73. package/dist/types/react-native/offlineStorage.d.ts +9 -0
  74. package/dist/types/react-native/types.d.ts +25 -0
  75. package/dist/types/react-native/useABTest.d.ts +10 -0
  76. package/dist/types/react-native/useFeatureFlag.d.ts +10 -0
  77. package/dist/types/react-native/useFeatureFlags.d.ts +10 -0
  78. package/dist/types/types/index.d.ts +86 -0
  79. package/dist/types/vue/identifyUser.d.ts +6 -0
  80. package/dist/types/vue/index.d.ts +8 -0
  81. package/dist/types/vue/plugin.d.ts +19 -0
  82. package/dist/types/vue/types.d.ts +20 -0
  83. package/dist/types/vue/useABTest.d.ts +10 -0
  84. package/dist/types/vue/useFeatureFlag.d.ts +10 -0
  85. package/dist/types/vue/useFeatureFlags.d.ts +10 -0
  86. package/dist/vue/identifyUser.js +13 -0
  87. package/dist/vue/index.js +7 -0
  88. package/dist/vue/plugin.js +30 -0
  89. package/dist/vue/types.js +1 -0
  90. package/dist/vue/useABTest.js +93 -0
  91. package/dist/vue/useFeatureFlag.js +87 -0
  92. package/dist/vue/useFeatureFlags.js +120 -0
  93. package/dist/vue.js +718 -0
  94. package/dist/vue.js.map +1 -0
  95. package/dist/vue.mjs +710 -0
  96. package/dist/vue.mjs.map +1 -0
  97. package/package.json +125 -0
package/dist/react.mjs ADDED
@@ -0,0 +1,627 @@
1
+ import { createContext, useContext, useState, useCallback, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ class FlipFlagSDK {
5
+ constructor(config) {
6
+ this.cache = new Map();
7
+ this.defaultBaseUrl = "https://app.flipflag.ru/api";
8
+ this.stateListeners = new Set();
9
+ this.config = {
10
+ baseUrl: this.defaultBaseUrl,
11
+ cacheTimeout: 30000, // 30 seconds
12
+ retryAttempts: 3,
13
+ retryDelay: 1000, // 1 second
14
+ pullInterval: 60000, // 1 minute
15
+ ...config,
16
+ };
17
+ this.state = {
18
+ flags: {},
19
+ abTests: {},
20
+ lastFetch: null,
21
+ isLoading: false,
22
+ error: null,
23
+ };
24
+ // Start automatic pulling if pullInterval is set
25
+ if (this.config.pullInterval && this.config.pullInterval > 0) {
26
+ this.startPulling();
27
+ }
28
+ }
29
+ /**
30
+ * Identify user for A/B testing and user-specific flags
31
+ */
32
+ identifyUser(options) {
33
+ this.config.userId = options.userId;
34
+ // Clear A/B test cache since user changed
35
+ this.clearABTestCache();
36
+ // If we have a user, we might want to refresh immediately
37
+ if (this.config.pullInterval && this.config.pullInterval > 0) {
38
+ this.pullData();
39
+ }
40
+ }
41
+ /**
42
+ * Get current SDK state
43
+ */
44
+ getState() {
45
+ return { ...this.state };
46
+ }
47
+ /**
48
+ * Subscribe to state changes
49
+ */
50
+ subscribe(callback) {
51
+ this.stateListeners.add(callback);
52
+ // Return unsubscribe function
53
+ return () => {
54
+ this.stateListeners.delete(callback);
55
+ };
56
+ }
57
+ /**
58
+ * Get a feature flag value from current state
59
+ */
60
+ getFlagValue(flagName) {
61
+ var _a;
62
+ return (_a = this.state.flags[flagName]) !== null && _a !== void 0 ? _a : false;
63
+ }
64
+ /**
65
+ * Get A/B test variant from current state
66
+ */
67
+ getABTestVariant(testName) {
68
+ return this.state.abTests[testName] || null;
69
+ }
70
+ /**
71
+ * Start automatic data pulling
72
+ */
73
+ startPulling() {
74
+ if (this.pullIntervalId) {
75
+ clearInterval(this.pullIntervalId);
76
+ }
77
+ this.pullIntervalId = setInterval(() => {
78
+ this.pullData();
79
+ }, this.config.pullInterval);
80
+ // Initial pull
81
+ this.pullData();
82
+ }
83
+ /**
84
+ * Stop automatic data pulling
85
+ */
86
+ stopPulling() {
87
+ if (this.pullIntervalId) {
88
+ clearInterval(this.pullIntervalId);
89
+ this.pullIntervalId = undefined;
90
+ }
91
+ }
92
+ /**
93
+ * Pull fresh data from API
94
+ */
95
+ async pullData() {
96
+ try {
97
+ this.updateState({ isLoading: true, error: null });
98
+ // Pull flags
99
+ const flagsResponse = await this.fetchAllFlags();
100
+ // Pull A/B tests if user is identified
101
+ let abTestsResponse = null;
102
+ if (this.config.userId) {
103
+ try {
104
+ abTestsResponse = await this.fetchAllABTests();
105
+ }
106
+ catch (error) {
107
+ console.warn("Failed to fetch A/B tests:", error);
108
+ }
109
+ }
110
+ const newState = {
111
+ flags: (flagsResponse === null || flagsResponse === void 0 ? void 0 : flagsResponse.flags) || {},
112
+ lastFetch: new Date(),
113
+ isLoading: false,
114
+ error: null,
115
+ };
116
+ if (abTestsResponse) {
117
+ const abTestsMap = {};
118
+ abTestsResponse.abTests.forEach((test) => {
119
+ // For each test, get the variant for current user
120
+ if (this.config.userId) {
121
+ // This would normally fetch the variant, but for demo we'll simulate
122
+ abTestsMap[test.name] = {
123
+ testName: test.name,
124
+ testId: test.id,
125
+ variantId: "variant_a", // Default variant
126
+ variant: test.variants[0] || {
127
+ id: "default",
128
+ name: "Default",
129
+ value: null,
130
+ weight: 100,
131
+ },
132
+ timestamp: new Date().toISOString(),
133
+ };
134
+ }
135
+ });
136
+ newState.abTests = abTestsMap;
137
+ }
138
+ this.updateState(newState);
139
+ }
140
+ catch (error) {
141
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
142
+ this.updateState({
143
+ isLoading: false,
144
+ error: errorMessage,
145
+ });
146
+ console.error("Failed to pull data:", error);
147
+ }
148
+ }
149
+ /**
150
+ * Update state and notify listeners
151
+ */
152
+ updateState(newState) {
153
+ this.state = { ...this.state, ...newState };
154
+ // Notify all listeners
155
+ this.stateListeners.forEach((callback) => {
156
+ try {
157
+ callback(this.state);
158
+ }
159
+ catch (error) {
160
+ console.error("State listener error:", error);
161
+ }
162
+ });
163
+ }
164
+ /**
165
+ * Fetch all flags from API
166
+ */
167
+ async fetchAllFlags() {
168
+ try {
169
+ return await this.makeRequest(`/sdk/flags/${this.config.projectId}`);
170
+ }
171
+ catch (error) {
172
+ console.error("Failed to fetch flags:", error);
173
+ return null;
174
+ }
175
+ }
176
+ /**
177
+ * Fetch all A/B tests from API
178
+ */
179
+ async fetchAllABTests() {
180
+ try {
181
+ return await this.makeRequest(`/sdk/ab-tests/${this.config.projectId}`);
182
+ }
183
+ catch (error) {
184
+ console.error("Failed to fetch A/B tests:", error);
185
+ return null;
186
+ }
187
+ }
188
+ /**
189
+ * Clear A/B test cache
190
+ */
191
+ clearABTestCache() {
192
+ const abTestKeys = Array.from(this.cache.keys()).filter((key) => key.startsWith("abtest:"));
193
+ abTestKeys.forEach((key) => this.cache.delete(key));
194
+ }
195
+ /**
196
+ * Get all feature flags for a project
197
+ */
198
+ async getFlags(projectId = this.config.projectId, environment) {
199
+ const cacheKey = `flags:${projectId}:${environment || "all"}`;
200
+ const cached = this.getFromCache(cacheKey);
201
+ if (cached) {
202
+ return cached;
203
+ }
204
+ const url = new URL(`/sdk/flags/${projectId}`, this.config.baseUrl);
205
+ if (environment || this.config.environment) {
206
+ url.searchParams.set("environment", environment || this.config.environment);
207
+ }
208
+ const response = await this.makeRequest(url.toString());
209
+ this.setCache(cacheKey, response, this.config.cacheTimeout);
210
+ return response;
211
+ }
212
+ /**
213
+ * Get a specific feature flag value
214
+ */
215
+ async getFlag(flagName, projectId = this.config.projectId, environment) {
216
+ var _a;
217
+ // If we have the flag in state, return it immediately
218
+ if (this.state.flags[flagName] !== undefined) {
219
+ return {
220
+ projectId: this.config.projectId,
221
+ flagName,
222
+ environment: this.config.environment || "all",
223
+ value: this.state.flags[flagName],
224
+ timestamp: ((_a = this.state.lastFetch) === null || _a === void 0 ? void 0 : _a.toISOString()) || new Date().toISOString(),
225
+ };
226
+ }
227
+ const cacheKey = `flag:${projectId}:${flagName}:${environment || "all"}`;
228
+ const cached = this.getFromCache(cacheKey);
229
+ if (cached) {
230
+ return cached;
231
+ }
232
+ const url = new URL(`/sdk/flags/${projectId}/${flagName}`, this.config.baseUrl);
233
+ if (environment || this.config.environment) {
234
+ url.searchParams.set("environment", environment || this.config.environment);
235
+ }
236
+ const response = await this.makeRequest(url.toString());
237
+ this.setCache(cacheKey, response, this.config.cacheTimeout);
238
+ return response;
239
+ }
240
+ /**
241
+ * Get A/B test variant for a user
242
+ */
243
+ async getAbTestVariant(testName, userId, projectId = this.config.projectId) {
244
+ // If we have the A/B test in state, return it immediately
245
+ const stateVariant = this.state.abTests[testName];
246
+ if (stateVariant && stateVariant.testName === testName) {
247
+ return stateVariant;
248
+ }
249
+ const cacheKey = `abtest:${projectId}:${testName}:${userId}`;
250
+ const cached = this.getFromCache(cacheKey);
251
+ if (cached) {
252
+ return cached;
253
+ }
254
+ const url = `${this.config.baseUrl}/sdk/ab-test/${projectId}/${testName}`;
255
+ const response = await this.makeRequest(url, {
256
+ headers: {
257
+ "X-User-ID": userId,
258
+ },
259
+ });
260
+ // Cache A/B test variants for longer since they shouldn't change frequently for a user
261
+ this.setCache(cacheKey, response, this.config.cacheTimeout * 10);
262
+ return response;
263
+ }
264
+ /**
265
+ * Record an A/B test event
266
+ */
267
+ async recordAbTestEvent(testName, userId, event, variantId, eventData, projectId = this.config.projectId) {
268
+ const url = `${this.config.baseUrl}/sdk/ab-test/${projectId}/${testName}/event`;
269
+ return this.makeRequest(url, {
270
+ method: "POST",
271
+ headers: {
272
+ "X-User-ID": userId,
273
+ "Content-Type": "application/json",
274
+ },
275
+ body: JSON.stringify({
276
+ event,
277
+ variantId,
278
+ eventData,
279
+ }),
280
+ });
281
+ }
282
+ /**
283
+ * Get all A/B tests for a project
284
+ */
285
+ async getAbTests(projectId = this.config.projectId) {
286
+ const cacheKey = `abtests:${projectId}`;
287
+ const cached = this.getFromCache(cacheKey);
288
+ if (cached) {
289
+ return cached;
290
+ }
291
+ const url = `${this.config.baseUrl}/sdk/ab-tests/${projectId}`;
292
+ const response = await this.makeRequest(url);
293
+ this.setCache(cacheKey, response, this.config.cacheTimeout);
294
+ return response;
295
+ }
296
+ /**
297
+ * Clear cache for specific key or all cache
298
+ */
299
+ clearCache(key) {
300
+ if (key) {
301
+ this.cache.delete(key);
302
+ }
303
+ else {
304
+ this.cache.clear();
305
+ }
306
+ }
307
+ /**
308
+ * Update configuration
309
+ */
310
+ updateConfig(newConfig) {
311
+ this.config = { ...this.config, ...newConfig };
312
+ }
313
+ async makeRequest(url, options = {}) {
314
+ const headers = {
315
+ "X-API-Key": this.config.apiKey,
316
+ "Content-Type": "application/json",
317
+ ...options.headers,
318
+ };
319
+ const requestOptions = {
320
+ ...options,
321
+ headers,
322
+ };
323
+ return this.makeRequestWithRetry(url, requestOptions);
324
+ }
325
+ async makeRequestWithRetry(url, options, retryOptions = {
326
+ attempts: this.config.retryAttempts,
327
+ delay: this.config.retryDelay,
328
+ backoff: 2,
329
+ }) {
330
+ let lastError;
331
+ for (let attempt = 1; attempt <= retryOptions.attempts; attempt++) {
332
+ try {
333
+ const response = await fetch(url, options);
334
+ if (!response.ok) {
335
+ const errorData = await response.json().catch(() => ({}));
336
+ throw new Error(`HTTP ${response.status}: ${errorData.error || response.statusText}`);
337
+ }
338
+ return await response.json();
339
+ }
340
+ catch (error) {
341
+ lastError = error;
342
+ // Don't retry on client errors (4xx) except 429 (rate limit)
343
+ if (error instanceof Error && error.message.includes("HTTP 4")) {
344
+ if (!error.message.includes("HTTP 429")) {
345
+ throw error;
346
+ }
347
+ }
348
+ if (attempt < retryOptions.attempts) {
349
+ const delay = retryOptions.delay * Math.pow(retryOptions.backoff, attempt - 1);
350
+ await new Promise((resolve) => setTimeout(resolve, delay));
351
+ }
352
+ }
353
+ }
354
+ throw lastError;
355
+ }
356
+ getFromCache(key) {
357
+ const entry = this.cache.get(key);
358
+ if (!entry) {
359
+ return null;
360
+ }
361
+ if (Date.now() - entry.timestamp > entry.ttl) {
362
+ this.cache.delete(key);
363
+ return null;
364
+ }
365
+ return entry.data;
366
+ }
367
+ setCache(key, data, ttl) {
368
+ this.cache.set(key, {
369
+ data,
370
+ timestamp: Date.now(),
371
+ ttl,
372
+ });
373
+ }
374
+ /**
375
+ * Cleanup method - should be called when SDK is no longer needed
376
+ */
377
+ destroy() {
378
+ this.stopPulling();
379
+ this.stateListeners.clear();
380
+ this.cache.clear();
381
+ }
382
+ }
383
+
384
+ const FeatureFlagContext = createContext(null);
385
+ function FeatureFlagProvider({ children, config, }) {
386
+ const sdk = new FlipFlagSDK(config);
387
+ const contextValue = {
388
+ sdk,
389
+ config,
390
+ };
391
+ return (jsx(FeatureFlagContext.Provider, { value: contextValue, children: children }));
392
+ }
393
+ function useFeatureFlagContext() {
394
+ const context = useContext(FeatureFlagContext);
395
+ if (!context) {
396
+ throw new Error("useFeatureFlagContext must be used within a FeatureFlagProvider");
397
+ }
398
+ return context;
399
+ }
400
+
401
+ function useFeatureFlag(flagName, options = {}) {
402
+ var _a;
403
+ const [value, setValue] = useState((_a = options.fallbackValue) !== null && _a !== void 0 ? _a : false);
404
+ const [loading, setLoading] = useState(true);
405
+ const [error, setError] = useState(null);
406
+ // Try to get SDK from context, otherwise create new instance
407
+ let sdk;
408
+ try {
409
+ const context = useFeatureFlagContext();
410
+ sdk = context.sdk;
411
+ }
412
+ catch (_b) {
413
+ if (!options.config) {
414
+ throw new Error("useFeatureFlag must be used within FeatureFlagProvider or config must be provided");
415
+ }
416
+ sdk = new FlipFlagSDK(options.config);
417
+ }
418
+ const updateFromState = useCallback(() => {
419
+ if (!options.enabled && options.enabled !== undefined) {
420
+ return;
421
+ }
422
+ try {
423
+ const flagValue = sdk.getFlagValue(flagName);
424
+ setValue(flagValue);
425
+ setLoading(false);
426
+ setError(null);
427
+ }
428
+ catch (err) {
429
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
430
+ setError(errorMessage);
431
+ // Use fallback value if provided
432
+ if (options.fallbackValue !== undefined) {
433
+ setValue(options.fallbackValue);
434
+ }
435
+ setLoading(false);
436
+ }
437
+ }, [sdk, flagName, options.fallbackValue, options.enabled]);
438
+ const refetch = useCallback(async () => {
439
+ // Clear cache for this specific flag and force refresh
440
+ sdk.clearCache(`flag:${sdk["config"].projectId}:${flagName}:${sdk["config"].environment || "all"}`);
441
+ setLoading(true);
442
+ try {
443
+ const response = await sdk.getFlag(flagName);
444
+ setValue(response.value);
445
+ setError(null);
446
+ }
447
+ catch (err) {
448
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
449
+ setError(errorMessage);
450
+ if (options.fallbackValue !== undefined) {
451
+ setValue(options.fallbackValue);
452
+ }
453
+ }
454
+ finally {
455
+ setLoading(false);
456
+ }
457
+ }, [sdk, flagName, options.fallbackValue]);
458
+ useEffect(() => {
459
+ // Subscribe to state changes
460
+ const unsubscribe = sdk.subscribe((state) => {
461
+ if (!options.enabled && options.enabled !== undefined) {
462
+ return;
463
+ }
464
+ const flagValue = state.flags[flagName];
465
+ if (flagValue !== undefined) {
466
+ setValue(flagValue);
467
+ setLoading(false);
468
+ setError(state.error);
469
+ }
470
+ });
471
+ // Initial update from current state
472
+ updateFromState();
473
+ return unsubscribe;
474
+ }, [updateFromState, options.enabled]);
475
+ return {
476
+ value,
477
+ loading,
478
+ error,
479
+ refetch,
480
+ };
481
+ }
482
+
483
+ function useFeatureFlags(flagNames, options = {}) {
484
+ const [flags, setFlags] = useState(() => {
485
+ // Initialize with fallback values
486
+ const initialFlags = {};
487
+ flagNames.forEach((flagName) => {
488
+ var _a, _b;
489
+ initialFlags[flagName] = (_b = (_a = options.fallbackValues) === null || _a === void 0 ? void 0 : _a[flagName]) !== null && _b !== void 0 ? _b : false;
490
+ });
491
+ return initialFlags;
492
+ });
493
+ const [loading, setLoading] = useState(true);
494
+ const [error, setError] = useState(null);
495
+ // Try to get SDK from context, otherwise create new instance
496
+ let sdk;
497
+ try {
498
+ const context = useFeatureFlagContext();
499
+ sdk = context.sdk;
500
+ }
501
+ catch (_a) {
502
+ if (!options.config) {
503
+ throw new Error("useFeatureFlags must be used within FeatureFlagProvider or config must be provided");
504
+ }
505
+ sdk = new FlipFlagSDK(options.config);
506
+ }
507
+ const fetchFlags = useCallback(async () => {
508
+ if (!options.enabled && options.enabled !== undefined) {
509
+ return;
510
+ }
511
+ try {
512
+ setLoading(true);
513
+ setError(null);
514
+ const response = await sdk.getFlags();
515
+ const newFlags = { ...flags };
516
+ flagNames.forEach((flagName) => {
517
+ var _a, _b, _c;
518
+ // Use API value if available, otherwise fallback
519
+ newFlags[flagName] =
520
+ (_c = (_a = response.flags[flagName]) !== null && _a !== void 0 ? _a : (_b = options.fallbackValues) === null || _b === void 0 ? void 0 : _b[flagName]) !== null && _c !== void 0 ? _c : false;
521
+ });
522
+ setFlags(newFlags);
523
+ }
524
+ catch (err) {
525
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
526
+ setError(errorMessage);
527
+ // Use fallback values
528
+ const fallbackFlags = {};
529
+ flagNames.forEach((flagName) => {
530
+ var _a, _b;
531
+ fallbackFlags[flagName] = (_b = (_a = options.fallbackValues) === null || _a === void 0 ? void 0 : _a[flagName]) !== null && _b !== void 0 ? _b : false;
532
+ });
533
+ setFlags(fallbackFlags);
534
+ }
535
+ finally {
536
+ setLoading(false);
537
+ }
538
+ }, [sdk, flagNames, options.fallbackValues, options.enabled, flags]);
539
+ const refetch = useCallback(async () => {
540
+ // Clear cache for flags
541
+ sdk.clearCache(`flags:${sdk["config"].projectId}:${sdk["config"].environment || "all"}`);
542
+ await fetchFlags();
543
+ }, [sdk, fetchFlags]);
544
+ useEffect(() => {
545
+ fetchFlags();
546
+ }, [fetchFlags]);
547
+ return {
548
+ flags,
549
+ loading,
550
+ error,
551
+ refetch,
552
+ };
553
+ }
554
+
555
+ function useABTest(testName, options) {
556
+ const [variant, setVariant] = useState(null);
557
+ const [variantId, setVariantId] = useState(null);
558
+ const [loading, setLoading] = useState(true);
559
+ const [error, setError] = useState(null);
560
+ // Try to get SDK from context, otherwise create new instance
561
+ let sdk;
562
+ try {
563
+ const context = useFeatureFlagContext();
564
+ sdk = context.sdk;
565
+ }
566
+ catch (_a) {
567
+ if (!options.config) {
568
+ throw new Error("useABTest must be used within FeatureFlagProvider or config must be provided");
569
+ }
570
+ sdk = new FlipFlagSDK(options.config);
571
+ }
572
+ const fetchVariant = useCallback(async () => {
573
+ if (!options.enabled && options.enabled !== undefined) {
574
+ return;
575
+ }
576
+ try {
577
+ setLoading(true);
578
+ setError(null);
579
+ const response = await sdk.getAbTestVariant(testName, options.userId);
580
+ setVariant(response.variant.value);
581
+ setVariantId(response.variantId);
582
+ }
583
+ catch (err) {
584
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
585
+ setError(errorMessage);
586
+ setVariant(null);
587
+ setVariantId(null);
588
+ }
589
+ finally {
590
+ setLoading(false);
591
+ }
592
+ }, [sdk, testName, options.userId, options.enabled]);
593
+ const recordEvent = useCallback(async (event, eventData) => {
594
+ if (!variantId) {
595
+ console.warn("Cannot record event: no variant assigned yet");
596
+ return;
597
+ }
598
+ try {
599
+ await sdk.recordAbTestEvent(testName, options.userId, event, variantId, eventData);
600
+ }
601
+ catch (err) {
602
+ console.error("Failed to record A/B test event:", err);
603
+ }
604
+ }, [sdk, testName, options.userId, variantId]);
605
+ useEffect(() => {
606
+ fetchVariant();
607
+ }, [fetchVariant]);
608
+ return {
609
+ variant,
610
+ variantId,
611
+ loading,
612
+ error,
613
+ recordEvent,
614
+ };
615
+ }
616
+
617
+ /**
618
+ * Identify user for A/B testing and user-specific flags
619
+ * Must be called within FeatureFlagProvider
620
+ */
621
+ function identifyUser(options) {
622
+ const context = useFeatureFlagContext();
623
+ context.sdk.identifyUser(options);
624
+ }
625
+
626
+ export { FeatureFlagProvider, identifyUser, useABTest, useFeatureFlag, useFeatureFlagContext, useFeatureFlags };
627
+ //# sourceMappingURL=react.mjs.map