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