@flipswitch-io/sdk 0.1.4 → 0.1.6

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 CHANGED
@@ -5,15 +5,26 @@
5
5
 
6
6
  Flipswitch SDK for JavaScript/TypeScript with real-time SSE support for OpenFeature.
7
7
 
8
- This SDK provides an OpenFeature-compatible provider that wraps OFREP flag evaluation with automatic cache invalidation
9
- via Server-Sent Events (SSE). When flags change in your Flipswitch dashboard, connected clients receive updates in
10
- real-time.
8
+ This SDK provides an OpenFeature-compatible provider that wraps OFREP flag evaluation with automatic cache invalidation via Server-Sent Events (SSE). When flags change in your Flipswitch dashboard, connected clients receive updates in real-time.
9
+
10
+ ## Overview
11
+
12
+ - **OpenFeature Compatible**: Works with the OpenFeature standard for feature flags
13
+ - **Real-Time Updates**: SSE connection delivers instant flag changes
14
+ - **Browser Optimized**: Visibility API integration, localStorage persistence, offline support
15
+ - **Polling Fallback**: Automatic fallback when SSE connection fails
16
+ - **TypeScript First**: Full type definitions included
17
+
18
+ ## Requirements
19
+
20
+ - Node.js 18+ or modern browsers (Chrome, Firefox, Safari, Edge)
21
+ - OpenFeature SDK (`@openfeature/web-sdk` or `@openfeature/server-sdk`)
11
22
 
12
23
  ## Installation
13
24
 
14
25
  ```bash
15
26
  npm install @flipswitch-io/sdk @openfeature/web-sdk
16
- # or
27
+ # or for server
17
28
  npm install @flipswitch-io/sdk @openfeature/server-sdk
18
29
  ```
19
30
 
@@ -25,18 +36,14 @@ npm install @flipswitch-io/sdk @openfeature/server-sdk
25
36
  import { FlipswitchProvider } from '@flipswitch-io/sdk';
26
37
  import { OpenFeature } from '@openfeature/web-sdk';
27
38
 
28
- // Only API key is required
29
39
  const provider = new FlipswitchProvider({
30
40
  apiKey: 'your-environment-api-key'
31
41
  });
32
42
 
33
- // Register with OpenFeature
34
43
  await OpenFeature.setProviderAndWait(provider);
35
-
36
- // Get a client and evaluate flags
37
44
  const client = OpenFeature.getClient();
45
+
38
46
  const darkMode = await client.getBooleanValue('dark-mode', false);
39
- const welcomeMessage = await client.getStringValue('welcome-message', 'Hello!');
40
47
  ```
41
48
 
42
49
  ### Node.js (Server)
@@ -52,7 +59,6 @@ const provider = new FlipswitchProvider({
52
59
  await OpenFeature.setProviderAndWait(provider);
53
60
  const client = OpenFeature.getClient();
54
61
 
55
- // Evaluate with context
56
62
  const context = {
57
63
  targetingKey: 'user-123',
58
64
  email: 'user@example.com',
@@ -69,57 +75,174 @@ const showFeature = await client.getBooleanValue('new-feature', false, context);
69
75
  | `apiKey` | `string` | *required* | Environment API key from dashboard |
70
76
  | `baseUrl` | `string` | `https://api.flipswitch.io` | Your Flipswitch server URL |
71
77
  | `enableRealtime` | `boolean` | `true` | Enable SSE for real-time flag updates |
72
- | `pollingInterval` | `number` | `30000` | Cache TTL in milliseconds |
73
78
  | `fetchImplementation` | `typeof fetch` | `fetch` | Custom fetch function |
79
+ | `persistCache` | `boolean` | `true` (browser) | Persist flag values to localStorage |
80
+ | `offlineMode` | `boolean` | `true` (browser) | Enable offline support with cached values |
81
+ | `enableVisibilityHandling` | `boolean` | `true` (browser) | Pause SSE when tab is hidden |
82
+ | `enablePollingFallback` | `boolean` | `true` | Fall back to polling when SSE fails |
83
+ | `pollingInterval` | `number` | `30000` | Polling interval in ms (fallback mode) |
84
+ | `maxSseRetries` | `number` | `5` | Max SSE retries before polling fallback |
85
+
86
+ ## Usage Examples
87
+
88
+ ### Basic Flag Evaluation
89
+
90
+ ```typescript
91
+ const client = OpenFeature.getClient();
92
+
93
+ // Boolean flag
94
+ const darkMode = await client.getBooleanValue('dark-mode', false);
95
+
96
+ // String flag
97
+ const welcomeMessage = await client.getStringValue('welcome-message', 'Hello!');
98
+
99
+ // Number flag
100
+ const maxItems = await client.getNumberValue('max-items', 10);
74
101
 
75
- ## Real-Time Updates
102
+ // Object flag
103
+ const config = await client.getObjectValue('feature-config', { enabled: false });
104
+ ```
76
105
 
77
- When `enableRealtime` is enabled, the SDK maintains a Server-Sent Events (SSE) connection to receive instant flag change notifications. When a flag changes:
106
+ ### Evaluation Context
78
107
 
79
- 1. The SSE client receives a `flag-change` event
80
- 2. The local cache is immediately invalidated
81
- 3. Next flag evaluation fetches the fresh value
82
- 4. OpenFeature emits a `PROVIDER_CONFIGURATION_CHANGED` event
108
+ Target specific users or segments:
83
109
 
84
- ### Event Handlers
110
+ ```typescript
111
+ const context = {
112
+ targetingKey: 'user-123', // Unique user identifier
113
+ email: 'user@example.com',
114
+ plan: 'premium',
115
+ country: 'US',
116
+ betaUser: true,
117
+ };
118
+
119
+ const showFeature = await client.getBooleanValue('new-feature', false, context);
120
+ ```
121
+
122
+ ### Real-Time Updates (SSE)
123
+
124
+ The SDK automatically listens for flag changes via SSE:
85
125
 
86
126
  ```typescript
87
127
  const provider = new FlipswitchProvider(
88
128
  { apiKey: 'your-api-key' },
89
129
  {
90
130
  onFlagChange: (event) => {
91
- console.log(`Flag changed: ${event.flagKey}`);
131
+ console.log(`Flag changed: ${event.flagKey ?? 'all flags'}`);
132
+ // event.flagKey is null for bulk invalidation
92
133
  },
93
134
  onConnectionStatusChange: (status) => {
94
135
  console.log(`SSE status: ${status}`);
136
+ // 'connecting' | 'connected' | 'disconnected' | 'error'
95
137
  },
96
138
  }
97
139
  );
98
- ```
99
-
100
- ### Connection Status
101
140
 
102
- ```typescript
103
- // Check current SSE status
141
+ // Check current status
104
142
  const status = provider.getSseStatus();
105
- // 'connecting' | 'connected' | 'disconnected' | 'error'
106
143
 
107
144
  // Force reconnect
108
145
  provider.reconnectSse();
109
146
  ```
110
147
 
111
- ## React Integration
148
+ ### Bulk Flag Evaluation
112
149
 
113
- ```tsx
114
- import { OpenFeature, OpenFeatureProvider, useFlag } from '@openfeature/react-sdk';
115
- import { FlipswitchProvider } from '@flipswitch-io/sdk';
150
+ Evaluate all flags at once:
151
+
152
+ ```typescript
153
+ const flags = await provider.evaluateAllFlags(context);
154
+ for (const flag of flags) {
155
+ console.log(`${flag.key} (${flag.valueType}): ${flag.value}`);
156
+ console.log(` Reason: ${flag.reason}, Variant: ${flag.variant}`);
157
+ }
158
+
159
+ // Single flag with full details
160
+ const flag = await provider.evaluateFlag('dark-mode', context);
161
+ if (flag) {
162
+ console.log(`Value: ${flag.value}`);
163
+ console.log(`Reason: ${flag.reason}`);
164
+ console.log(`Variant: ${flag.variant}`);
165
+ }
166
+ ```
167
+
168
+ ## Advanced Features
169
+
170
+ ### Offline Support (Browser)
171
+
172
+ When offline mode is enabled, the SDK:
173
+ - Detects online/offline state via `navigator.onLine`
174
+ - Serves cached values when offline
175
+ - Automatically refreshes when connection is restored
176
+
177
+ ```typescript
178
+ const provider = new FlipswitchProvider({
179
+ apiKey: 'your-api-key',
180
+ offlineMode: true, // Enable offline support
181
+ persistCache: true, // Persist to localStorage
182
+ });
183
+
184
+ // Check online status
185
+ if (provider.isOnline()) {
186
+ console.log('Online - flags are fresh');
187
+ } else {
188
+ console.log('Offline - serving cached values');
189
+ }
190
+ ```
191
+
192
+ ### Visibility API Integration (Browser)
193
+
194
+ The SDK automatically pauses SSE when the browser tab is hidden to save resources:
195
+
196
+ ```typescript
197
+ const provider = new FlipswitchProvider({
198
+ apiKey: 'your-api-key',
199
+ enableVisibilityHandling: true, // default: true in browsers
200
+ });
201
+
202
+ // Connection is paused when tab is hidden
203
+ // Connection resumes when tab becomes visible
204
+ ```
205
+
206
+ ### Polling Fallback
207
+
208
+ When SSE connection fails repeatedly, the SDK falls back to polling:
209
+
210
+ ```typescript
211
+ const provider = new FlipswitchProvider({
212
+ apiKey: 'your-api-key',
213
+ enablePollingFallback: true, // default: true
214
+ pollingInterval: 30000, // Poll every 30 seconds
215
+ maxSseRetries: 5, // Fall back after 5 failed SSE attempts
216
+ });
217
+
218
+ // Check if polling is active
219
+ if (provider.isPollingActive()) {
220
+ console.log('Polling fallback is active');
221
+ }
222
+ ```
223
+
224
+ ### Custom HTTP Client
225
+
226
+ Provide a custom fetch implementation for testing or special requirements:
227
+
228
+ ```typescript
229
+ import nodeFetch from 'node-fetch';
116
230
 
117
- // Initialize provider
118
231
  const provider = new FlipswitchProvider({
119
- baseUrl: 'https://api.flipswitch.io',
120
232
  apiKey: 'your-api-key',
233
+ fetchImplementation: nodeFetch as unknown as typeof fetch,
121
234
  });
235
+ ```
236
+
237
+ ## Framework Integration
238
+
239
+ ### React
240
+
241
+ ```tsx
242
+ import { OpenFeature, OpenFeatureProvider, useFlag } from '@openfeature/react-sdk';
243
+ import { FlipswitchProvider } from '@flipswitch-io/sdk';
122
244
 
245
+ const provider = new FlipswitchProvider({ apiKey: 'your-api-key' });
123
246
  await OpenFeature.setProviderAndWait(provider);
124
247
 
125
248
  function App() {
@@ -131,7 +254,9 @@ function App() {
131
254
  }
132
255
 
133
256
  function MyComponent() {
134
- const { value: darkMode } = useFlag('dark-mode', false);
257
+ const { value: darkMode, isLoading } = useFlag('dark-mode', false);
258
+
259
+ if (isLoading) return <div>Loading...</div>;
135
260
 
136
261
  return (
137
262
  <div className={darkMode ? 'dark' : 'light'}>
@@ -141,48 +266,169 @@ function MyComponent() {
141
266
  }
142
267
  ```
143
268
 
144
- ## Bulk Flag Evaluation
269
+ ### Vue 3
145
270
 
146
- Evaluate all flags at once or get detailed evaluation results:
271
+ ```vue
272
+ <script setup>
273
+ import { ref, onMounted } from 'vue';
274
+ import { OpenFeature } from '@openfeature/web-sdk';
275
+ import { FlipswitchProvider } from '@flipswitch-io/sdk';
276
+
277
+ const darkMode = ref(false);
278
+ const client = OpenFeature.getClient();
279
+
280
+ onMounted(async () => {
281
+ const provider = new FlipswitchProvider({ apiKey: 'your-api-key' });
282
+ await OpenFeature.setProviderAndWait(provider);
283
+ darkMode.value = await client.getBooleanValue('dark-mode', false);
284
+ });
285
+ </script>
286
+
287
+ <template>
288
+ <div :class="darkMode ? 'dark' : 'light'">
289
+ Dark mode is {{ darkMode ? 'enabled' : 'disabled' }}
290
+ </div>
291
+ </template>
292
+ ```
293
+
294
+ ## Error Handling
295
+
296
+ The SDK handles errors gracefully and provides fallback behavior:
147
297
 
148
298
  ```typescript
149
- // Evaluate all flags
150
- const flags = await provider.evaluateAllFlags(context);
151
- for (const flag of flags) {
152
- console.log(`${flag.key} (${flag.valueType}): ${flag.value}`);
299
+ try {
300
+ const provider = new FlipswitchProvider({ apiKey: 'your-api-key' });
301
+ await OpenFeature.setProviderAndWait(provider);
302
+ } catch (error) {
303
+ console.error('Failed to initialize provider:', error);
304
+ // Provider will use default values
153
305
  }
154
306
 
155
- // Evaluate a single flag with full details
156
- const flag = await provider.evaluateFlag('dark-mode', context);
157
- if (flag) {
158
- console.log(`Value: ${flag.value}, Reason: ${flag.reason}, Variant: ${flag.variant}`);
307
+ // Flag evaluation never throws - returns default value on error
308
+ const value = await client.getBooleanValue('my-flag', false);
309
+ ```
310
+
311
+ ## Logging & Debugging
312
+
313
+ Enable console logging to debug issues:
314
+
315
+ ```typescript
316
+ // The SDK logs to console with [Flipswitch] prefix
317
+ // Look for messages like:
318
+ // [Flipswitch] SSE connection established
319
+ // [Flipswitch] Flag changed: my-flag
320
+ // [Flipswitch] SSE connection error, retrying...
321
+ // [Flipswitch] Starting polling fallback
322
+ ```
323
+
324
+ ## Testing
325
+
326
+ Mock the provider in your tests:
327
+
328
+ ```typescript
329
+ import { OpenFeature, InMemoryProvider } from '@openfeature/web-sdk';
330
+
331
+ // Use InMemoryProvider for testing
332
+ const testProvider = new InMemoryProvider({
333
+ 'dark-mode': { defaultVariant: 'on', variants: { on: true, off: false } },
334
+ 'max-items': { defaultVariant: 'default', variants: { default: 10 } },
335
+ });
336
+
337
+ await OpenFeature.setProviderAndWait(testProvider);
338
+
339
+ // Your tests can now evaluate flags without network calls
340
+ ```
341
+
342
+ ## API Reference
343
+
344
+ ### FlipswitchProvider
345
+
346
+ ```typescript
347
+ class FlipswitchProvider {
348
+ constructor(options: FlipswitchOptions, eventHandlers?: FlipswitchEventHandlers);
349
+
350
+ // OpenFeature Provider interface
351
+ initialize(context?: EvaluationContext): Promise<void>;
352
+ onClose(): Promise<void>;
353
+ resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: EvaluationContext): ResolutionDetails<boolean>;
354
+ resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext): ResolutionDetails<string>;
355
+ resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext): ResolutionDetails<number>;
356
+ resolveObjectEvaluation<T>(flagKey: string, defaultValue: T, context: EvaluationContext): ResolutionDetails<T>;
357
+
358
+ // Flipswitch-specific methods
359
+ getSseStatus(): SseConnectionStatus;
360
+ reconnectSse(): void;
361
+ isOnline(): boolean;
362
+ isPollingActive(): boolean;
363
+ evaluateAllFlags(context: EvaluationContext): Promise<FlagEvaluation[]>;
364
+ evaluateFlag(flagKey: string, context: EvaluationContext): Promise<FlagEvaluation | null>;
159
365
  }
160
366
  ```
161
367
 
162
- ## Reconnection Strategy
368
+ ### Types
369
+
370
+ ```typescript
371
+ interface FlipswitchOptions {
372
+ apiKey: string;
373
+ baseUrl?: string;
374
+ enableRealtime?: boolean;
375
+ fetchImplementation?: typeof fetch;
376
+ persistCache?: boolean;
377
+ offlineMode?: boolean;
378
+ enableVisibilityHandling?: boolean;
379
+ enablePollingFallback?: boolean;
380
+ pollingInterval?: number;
381
+ maxSseRetries?: number;
382
+ }
163
383
 
164
- The SSE client automatically reconnects with exponential backoff:
165
- - Initial delay: 1 second
166
- - Maximum delay: 30 seconds
167
- - Backoff multiplier: 2x
384
+ type SseConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
168
385
 
169
- When reconnected, the provider status changes from `STALE` back to `READY`.
386
+ interface FlagChangeEvent {
387
+ flagKey: string | null;
388
+ timestamp: string;
389
+ }
390
+
391
+ interface FlagEvaluation {
392
+ key: string;
393
+ value: unknown;
394
+ valueType: 'boolean' | 'string' | 'number' | 'object' | 'array' | 'null' | 'unknown';
395
+ reason: string | null;
396
+ variant: string | null;
397
+ }
398
+ ```
399
+
400
+ ## Troubleshooting
401
+
402
+ ### SSE Connection Fails
403
+
404
+ - Check that your API key is valid
405
+ - Verify your server URL is correct
406
+ - Check for network/firewall issues blocking SSE
407
+ - The SDK will automatically fall back to polling
408
+
409
+ ### Flags Not Updating in Real-Time
410
+
411
+ - Ensure `enableRealtime` is not set to `false`
412
+ - Check SSE status with `provider.getSseStatus()`
413
+ - Verify the SSE endpoint is accessible
414
+ - Check browser console for error messages
415
+
416
+ ### Offline Mode Not Working
417
+
418
+ - Verify `offlineMode` is enabled
419
+ - Check that localStorage is available
420
+ - Clear browser storage if cache is corrupted
170
421
 
171
422
  ## Demo
172
423
 
173
- A complete working demo is included. To run it:
424
+ Run the included demo:
174
425
 
175
426
  ```bash
176
427
  npm install
177
428
  npm run demo -- <your-api-key>
178
429
  ```
179
430
 
180
- The demo will:
181
- 1. Connect to Flipswitch and validate your API key
182
- 2. Load and display all flags with their types and values
183
- 3. Listen for real-time flag changes and display updates
184
-
185
- See [examples/demo.ts](./examples/demo.ts) for the full source.
431
+ The demo will connect, display all flags, and listen for real-time updates.
186
432
 
187
433
  ## Contributing
188
434