@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 +301 -55
- package/dist/index.d.mts +228 -15
- package/dist/index.d.ts +228 -15
- package/dist/index.js +455 -30
- package/dist/index.mjs +454 -30
- package/package.json +2 -2
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
102
|
+
// Object flag
|
|
103
|
+
const config = await client.getObjectValue('feature-config', { enabled: false });
|
|
104
|
+
```
|
|
76
105
|
|
|
77
|
-
|
|
106
|
+
### Evaluation Context
|
|
78
107
|
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
+
### Bulk Flag Evaluation
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
269
|
+
### Vue 3
|
|
145
270
|
|
|
146
|
-
|
|
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
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
//
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
- Initial delay: 1 second
|
|
166
|
-
- Maximum delay: 30 seconds
|
|
167
|
-
- Backoff multiplier: 2x
|
|
384
|
+
type SseConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
168
385
|
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|