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