@unleash/toolbar 1.0.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.
package/README.md ADDED
@@ -0,0 +1,599 @@
1
+ # Unleash Toolbar
2
+
3
+ A client-side debugging toolbar for [Unleash](https://www.getunleash.io/) feature flags. Override flag values and context in real-time without server changes.
4
+
5
+ ## Features
6
+
7
+ - **Flag Overrides**: Force boolean flags ON/OFF or override variant values
8
+ - **Context Overrides**: Modify userId, sessionId, environment, and custom properties
9
+ - **Persistence**: Save overrides in memory, sessionStorage, or localStorage
10
+ - **React Integration**: Seamlessly wraps the official `@unleash/proxy-client-react` SDK
11
+ - **Next.js SSR Support**: Server-side rendering with cookie-based state sync using `@unleash/nextjs`
12
+ - **Customizable UI**: Theming support and positioning options
13
+ - **SDK Compatible**: Works with Unleash JavaScript SDK
14
+
15
+ ## Bundle Size
16
+
17
+ The toolbar is optimized for minimal impact on your application:
18
+
19
+ - **Core**: ~8 KB gzipped
20
+ - **React**: ~0.2 KB gzipped (thin wrapper)
21
+ - **Next.js**: ~0.6 KB gzipped (server utilities)
22
+ - **CSS**: ~2.4 KB gzipped
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @unleash/toolbar
28
+
29
+ # For React integration
30
+ npm install @unleash/toolbar @unleash/proxy-client-react unleash-proxy-client
31
+
32
+ # For Next.js SSR integration
33
+ npm install @unleash/toolbar @unleash/nextjs
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ### Vanilla JavaScript
39
+
40
+ ```javascript
41
+ import { initUnleashToolbar } from '@unleash/toolbar';
42
+ import { UnleashClient } from 'unleash-proxy-client';
43
+ import '@unleash/toolbar/toolbar.css';
44
+
45
+ // Initialize toolbar with new Unleash client - returns wrapped client
46
+ const client = initUnleashToolbar(new UnleashClient({
47
+ url: 'https://your-unleash-instance.com/api/frontend',
48
+ clientKey: 'your-client-key',
49
+ appName: 'my-app'
50
+ }), {
51
+ storageMode: 'local',
52
+ position: 'bottom-right',
53
+ initiallyVisible: true
54
+ });
55
+
56
+ // Start the client
57
+ await client.start();
58
+
59
+ // Use the client for all flag evaluations
60
+ const isEnabled = client.isEnabled('my-feature');
61
+ const variant = client.getVariant('my-experiment');
62
+
63
+ // Listen for changes (from toolbar or SDK updates)
64
+ client.on('update', () => {
65
+ // Re-evaluate flags when overrides change
66
+ const newValue = client.isEnabled('my-feature');
67
+ updateUI(newValue);
68
+ });
69
+ ```
70
+
71
+ ### React
72
+
73
+ The toolbar integrates seamlessly with the official **`@unleash/proxy-client-react`** SDK. Just pass your configuration - the toolbar handles everything automatically!
74
+
75
+ ```bash
76
+ npm install @unleash/toolbar @unleash/proxy-client-react unleash-proxy-client
77
+ ```
78
+
79
+ ```tsx
80
+ import { useFlag, useVariant } from '@unleash/proxy-client-react';
81
+ import { UnleashToolbarProvider } from '@unleash/toolbar/react';
82
+ import '@unleash/toolbar/toolbar.css';
83
+
84
+ // Same config format as the official React SDK
85
+ const config = {
86
+ url: 'https://your-unleash-instance.com/api/frontend',
87
+ clientKey: 'your-client-key',
88
+ appName: 'my-app',
89
+ refreshInterval: 15
90
+ };
91
+
92
+ // That's it! No need to import FlagProvider or manage the SDK client
93
+ function App() {
94
+ return (
95
+ <UnleashToolbarProvider
96
+ config={config}
97
+ toolbarOptions={{
98
+ storageMode: 'local',
99
+ position: 'bottom-right'
100
+ }}
101
+ >
102
+ <MyComponent />
103
+ </UnleashToolbarProvider>
104
+ );
105
+ }
106
+
107
+ // Use hooks from the official React SDK - they work with toolbar overrides!
108
+ function MyComponent() {
109
+ const isEnabled = useFlag('my-feature');
110
+ const variant = useVariant('my-experiment');
111
+
112
+ return (
113
+ <div>
114
+ {isEnabled && <NewFeature />}
115
+ {variant.name === 'variant-a' && <VariantA />}
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ **Key Points:**
122
+ - Pass `config` directly to `UnleashToolbarProvider` - no other imports needed!
123
+ - The toolbar automatically uses `FlagProvider` from the React SDK
124
+ - Import hooks from `@unleash/proxy-client-react` (the official SDK)
125
+ - All official React SDK hooks work seamlessly with toolbar overrides
126
+
127
+ **Advanced: Custom FlagProvider or pre-instantiated client**
128
+ ```tsx
129
+ import { FlagProvider } from '@unleash/proxy-client-react';
130
+ import { UnleashClient } from 'unleash-proxy-client';
131
+
132
+ // Option 1: Custom FlagProvider
133
+ <UnleashToolbarProvider
134
+ FlagProvider={FlagProvider} // Optional - use if you need customization
135
+ config={config}
136
+ >
137
+ <MyApp />
138
+ </UnleashToolbarProvider>
139
+
140
+ // Option 2: Pre-instantiated client
141
+ const client = new UnleashClient({ /* config */ });
142
+
143
+ <UnleashToolbarProvider
144
+ client={client} // Pass client instead of config
145
+ toolbarOptions={{ /* ... */ }}
146
+ >
147
+ <MyApp />
148
+ </UnleashToolbarProvider>
149
+ ```
150
+ ```
151
+
152
+ ### Next.js App Router (Client & Server Components)
153
+
154
+ The toolbar provides full Next.js App Router support with server-side rendering and cookie-based state synchronization.
155
+
156
+ #### Client Components
157
+
158
+ ```tsx
159
+ // app/layout.tsx
160
+ import { UnleashToolbarProvider } from '@unleash/toolbar/next';
161
+ import '@unleash/toolbar/toolbar.css';
162
+
163
+ export default function RootLayout({ children }) {
164
+ return (
165
+ <html>
166
+ <body>
167
+ <UnleashToolbarProvider
168
+ config={{
169
+ url: process.env.NEXT_PUBLIC_UNLEASH_URL!,
170
+ clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY!,
171
+ appName: 'my-next-app',
172
+ }}
173
+ >
174
+ {children}
175
+ </UnleashToolbarProvider>
176
+ </body>
177
+ </html>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ```tsx
183
+ // app/page.tsx
184
+ 'use client';
185
+
186
+ import { useFlag, useVariant } from '@unleash/toolbar/next';
187
+
188
+ export default function HomePage() {
189
+ const isEnabled = useFlag('new-checkout');
190
+ const variant = useVariant('payment-provider');
191
+
192
+ return (
193
+ <div>
194
+ {isEnabled && <NewCheckout />}
195
+ <PaymentForm provider={variant.name} />
196
+ </div>
197
+ );
198
+ }
199
+ ```
200
+
201
+ #### Server Components with SSR
202
+
203
+ ```tsx
204
+ // app/server-page/page.tsx
205
+ import { cookies } from 'next/headers';
206
+ import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
207
+ import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
208
+
209
+ export default async function ServerPage() {
210
+ // Fetch definitions from Unleash API (uses env config)
211
+ const definitions = await getDefinitions({
212
+ fetchOptions: { next: { revalidate: 15 } },
213
+ });
214
+
215
+ // Apply toolbar overrides from cookies
216
+ const cookieStore = await cookies();
217
+ const modifiedDefinitions = applyToolbarOverrides(definitions, cookieStore);
218
+
219
+ // Evaluate flags with context
220
+ const { toggles } = evaluateFlags(modifiedDefinitions, {
221
+ sessionId: 'session-id',
222
+ userId: 'user-id',
223
+ });
224
+
225
+ // Create offline client
226
+ const flags = flagsClient(toggles);
227
+
228
+ // Check flags server-side
229
+ const isEnabled = flags.isEnabled('new-feature');
230
+
231
+ return <div>{isEnabled ? 'Feature ON' : 'Feature OFF'}</div>;
232
+ }
233
+ ```
234
+
235
+ **Environment Variables for Next.js:**
236
+ ```env
237
+ # Used by @unleash/nextjs SDK
238
+ UNLEASH_SERVER_API_URL=https://your-unleash-instance.com/api
239
+ UNLEASH_SERVER_API_TOKEN=your-server-token
240
+ UNLEASH_APP_NAME=my-app
241
+
242
+ # Used by client-side toolbar
243
+ NEXT_PUBLIC_UNLEASH_URL=https://your-unleash-instance.com/api/frontend
244
+ NEXT_PUBLIC_UNLEASH_CLIENT_KEY=your-client-key
245
+ NEXT_PUBLIC_UNLEASH_APP_NAME=my-app
246
+ ```
247
+
248
+ **How it works:**
249
+ 1. Client-side toolbar automatically syncs state to cookies
250
+ 2. Server components read toolbar state from cookies
251
+ 3. `applyToolbarOverrides()` modifies flag definitions before evaluation
252
+ 4. Flags evaluate server-side with toolbar overrides applied
253
+ 5. Changes in toolbar immediately affect both client and server rendering
254
+
255
+ ### Vue 3 (Composition API)
256
+
257
+ ```vue
258
+ <script setup lang="ts">
259
+ import { ref, watch } from 'vue'
260
+ import { useUnleash } from './composables/useUnleash'
261
+
262
+ // Initialize Unleash with the composable
263
+ const { unleashClient, isReady, updateTrigger } = useUnleash()
264
+
265
+ // Reactive flag states
266
+ const isEnabled = ref(false)
267
+
268
+ // Evaluate flags
269
+ const evaluateFlags = () => {
270
+ if (!unleashClient.value) return
271
+ isEnabled.value = unleashClient.value.isEnabled('my-feature')
272
+ }
273
+
274
+ // Evaluate when ready
275
+ watch(isReady, (ready) => {
276
+ if (ready) evaluateFlags()
277
+ })
278
+ </script>
279
+ ```
280
+
281
+ ## Configuration Options
282
+
283
+ ### `initUnleashToolbar(client, options)`
284
+
285
+ ```typescript
286
+ interface InitToolbarOptions {
287
+ // Persistence mode (default: 'local')
288
+ // - 'local': Persists across tabs and browser restarts (RECOMMENDED for development)
289
+ // - 'session': Persists only in current tab, cleared when tab closes
290
+ // - 'memory': No persistence, cleared on page reload
291
+ storageMode?: 'memory' | 'session' | 'local';
292
+
293
+ // Storage key for persistence (default: 'unleash-toolbar-state')
294
+ storageKey?: string;
295
+
296
+ // UI position (default: 'bottom-right')
297
+ // Corner positions: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
298
+ // Side positions (vertically centered): 'left' | 'right'
299
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right';
300
+
301
+ // Initial visibility (default: false, but respects persisted state if available)
302
+ initiallyVisible?: boolean;
303
+
304
+ // Sort flags alphabetically instead of by evaluation order (default: false)
305
+ sortAlphabetically?: boolean;
306
+
307
+ // Theme preset (default: 'light')
308
+ themePreset?: 'light' | 'dark';
309
+
310
+ // Theme customization (overrides preset)
311
+ theme?: {
312
+ primaryColor?: string;
313
+ backgroundColor?: string;
314
+ textColor?: string;
315
+ borderColor?: string;
316
+ fontFamily?: string;
317
+ };
318
+
319
+ // Custom container element (default: document.body)
320
+ container?: HTMLElement | null;
321
+
322
+ // Enable cookie sync for SSR (default: false)
323
+ // Set to true for Next.js or other SSR frameworks
324
+ enableCookieSync?: boolean;
325
+ }
326
+ ```
327
+
328
+ ### Next.js Server Utilities
329
+
330
+ ```typescript
331
+ import { applyToolbarOverrides, applyToolbarOverridesToToggles, getToolbarStateFromCookies } from '@unleash/toolbar/next/server';
332
+ import type { ClientFeaturesResponse, IToggle } from '@unleash/nextjs';
333
+
334
+ // Apply overrides before evaluation (recommended)
335
+ const modifiedDefinitions = applyToolbarOverrides(
336
+ definitions: ClientFeaturesResponse,
337
+ cookieStore: CookieStore
338
+ ): ClientFeaturesResponse;
339
+
340
+ // Apply overrides after evaluation (alternative)
341
+ const modifiedToggles = applyToolbarOverridesToToggles(
342
+ toggles: IToggle[],
343
+ cookieStore: CookieStore
344
+ ): IToggle[];
345
+
346
+ // Read toolbar state from cookies
347
+ const state = getToolbarStateFromCookies(
348
+ cookieStore: CookieStore
349
+ ): ToolbarState | null;
350
+ ```
351
+
352
+ ### Storage Modes Explained
353
+
354
+ **`local` (default)**: Best for development workflows
355
+ - ✅ Persists across all browser tabs
356
+ - ✅ Survives page reloads and browser restarts
357
+ - ✅ Set overrides once, test everywhere
358
+ - Use case: Daily feature development and debugging
359
+
360
+ **`session`**: Useful for isolated testing
361
+ - ✅ Persists within current tab only
362
+ - ✅ Survives page reloads in the same tab
363
+ - ❌ Lost when tab is closed
364
+ - Use case: Testing different configurations in multiple tabs simultaneously
365
+
366
+ **`memory`**: Temporary testing only
367
+ - ❌ Lost on every page reload
368
+ - ❌ No persistence whatsoever
369
+ - Use case: Quick one-off tests or strict security requirements
370
+
371
+ ## API Reference
372
+
373
+ ### Toolbar Instance
374
+
375
+ ```typescript
376
+ const toolbar = window.unleashToolbar;
377
+
378
+ // Show/hide the toolbar
379
+ toolbar.show();
380
+ toolbar.hide();
381
+
382
+ // Get current state
383
+ const state = toolbar.getState();
384
+
385
+ // Set flag overrides
386
+ toolbar.setFlagOverride('my-feature', { type: 'flag', value: true });
387
+ toolbar.setFlagOverride('my-variant', { type: 'variant', variantKey: 'variant-b' });
388
+ toolbar.setFlagOverride('my-feature', null); // Clear override
389
+
390
+ // Set context overrides
391
+ toolbar.setContextOverride({
392
+ userId: 'test-user-123',
393
+ properties: { tier: 'premium' }
394
+ });
395
+
396
+ // Reset overrides
397
+ toolbar.resetOverrides();
398
+ toolbar.resetContextOverrides();
399
+
400
+ // Cleanup
401
+ toolbar.destroy();
402
+ ```
403
+
404
+ ### Listening to Changes
405
+
406
+ The toolbar automatically triggers the Unleash SDK's `'update'` event when overrides change. Use the standard SDK pattern to listen for changes:
407
+
408
+ ```typescript
409
+ // Listen to flag/context changes from the toolbar
410
+ client.on('update', () => {
411
+ console.log('Flags updated - re-evaluate your flags');
412
+ // Re-render your UI, re-check flags, etc.
413
+ });
414
+ ```
415
+
416
+ This works for both toolbar changes (flag overrides, context overrides) and SDK changes (new config from server).
417
+
418
+ ### React Hooks
419
+
420
+ Use hooks from the official `@unleash/proxy-client-react` SDK - they automatically work with toolbar overrides!
421
+
422
+ ```typescript
423
+ import {
424
+ useFlag,
425
+ useVariant,
426
+ useUnleashClient,
427
+ useUnleashContext,
428
+ useFlagsStatus
429
+ } from '@unleash/proxy-client-react';
430
+
431
+ // Check flag status - updates automatically when toolbar overrides change
432
+ const isEnabled = useFlag('my-feature');
433
+
434
+ // Get variant - updates automatically when toolbar overrides change
435
+ const variant = useVariant('my-experiment');
436
+
437
+ // Access client (the wrapped client from the toolbar)
438
+ const client = useUnleashClient();
439
+
440
+ // Update context dynamically
441
+ const updateContext = useUnleashContext();
442
+ await updateContext({ userId: 'new-user-id' });
443
+
444
+ // Check loading/ready state
445
+ const { flagsReady, flagsError } = useFlagsStatus();
446
+ ```
447
+
448
+ ### Next.js Hooks
449
+
450
+ For Next.js, import hooks from `@unleash/toolbar/next`, they're re-exported for convenience:
451
+
452
+ ```typescript
453
+ import { useFlag, useVariant, useUnleashClient } from '@unleash/toolbar/next';
454
+
455
+ const isEnabled = useFlag('my-feature');
456
+ const variant = useVariant('my-experiment');
457
+ ```
458
+
459
+ ## UI Features
460
+
461
+ ### Flag List Tab
462
+
463
+ - **Override Controls**: Dropdown to set boolean or variant overrides
464
+ - **Flag Info**: See default vs. effective values for each flag
465
+ - **Quick Actions**: Reset individual flag overrides
466
+
467
+ ### Context Tab
468
+
469
+ - **Standard Fields**: userId, sessionId, remoteAddress, environment, appName
470
+ - **Custom Properties**: Edit or reset property values
471
+ - **Live Updates**: Changes apply immediately to all evaluations
472
+
473
+ ### Header Actions
474
+
475
+ - **Reset Flags**: Clear all flag overrides
476
+ - **Reset Context**: Clear all context overrides
477
+ - **Close**: Hide the toolbar
478
+
479
+ ## Theme Customization
480
+
481
+ Override CSS variables or use the theme option:
482
+
483
+ ```javascript
484
+ const toolbar = initUnleashSessionToolbar({
485
+ theme: {
486
+ primaryColor: '#ff6b6b',
487
+ backgroundColor: '#ffffff',
488
+ textColor: '#2d3436',
489
+ borderColor: '#dfe6e9',
490
+ fontFamily: 'Inter, sans-serif'
491
+ }
492
+ });
493
+ ```
494
+
495
+ Or override CSS variables globally:
496
+
497
+ ```css
498
+ :root {
499
+ --unleash-toolbar-primary: #your-color;
500
+ --unleash-toolbar-bg: #your-bg;
501
+ --unleash-toolbar-text: #your-text;
502
+ --unleash-toolbar-border: #your-border;
503
+ --unleash-toolbar-font: your-font;
504
+ }
505
+ ```
506
+
507
+ ## Development
508
+
509
+ ```bash
510
+ # Install dependencies
511
+ npm install
512
+
513
+ # Build the library
514
+ npm run build
515
+
516
+ # Run tests
517
+ npm test
518
+
519
+ # Run tests in watch mode
520
+ npm run test:watch
521
+
522
+ # Run tests with coverage
523
+ npm run test:coverage
524
+
525
+ # Run type checking
526
+ npm run type-check
527
+
528
+ # Run linter
529
+ npm run lint
530
+
531
+ # Auto-fix linting issues
532
+ npm run lint:fix
533
+ ```
534
+
535
+ ### Example Applications
536
+
537
+ The repository includes example applications demonstrating integration with different frameworks:
538
+
539
+ - **Vanilla JS** - Basic HTML/JavaScript integration
540
+ ```bash
541
+ npm run serve:example:vanilla
542
+ ```
543
+
544
+ - **React** - Hooks and provider pattern with official React SDK
545
+ ```bash
546
+ npm run serve:example:react
547
+ ```
548
+
549
+ - **Next.js App Router** - Server-side rendering with client and server components
550
+ ```bash
551
+ npm run serve:example:nextjs
552
+ ```
553
+
554
+ - **Angular** - Service-based integration
555
+ ```bash
556
+ npm run serve:example:angular
557
+ ```
558
+
559
+ - **Vue 3** - Composition API with composables
560
+ ```bash
561
+ npm run serve:example:vue
562
+ ```
563
+
564
+ All examples include:
565
+ - Environment configuration setup
566
+ - Multiple feature flags for testing
567
+ - Variant flag demonstrations
568
+ - Toolbar integration best practices
569
+ - Server-side rendering examples (Next.js)
570
+
571
+ ## Requirements
572
+
573
+ - **Browser**: Modern browsers with ES2020 support (Chrome 90+, Firefox 88+, Safari 14+)
574
+ - **Unleash SDKs**:
575
+ - `unleash-proxy-client` ^3.0.0 (required)
576
+ - `@unleash/proxy-client-react` ^5.0.0 (optional, for React)
577
+ - `@unleash/nextjs` ^1.0.0 (optional, for Next.js SSR)
578
+
579
+ ## Contributing
580
+
581
+ Contributions are welcome! Please feel free to submit a Pull Request.
582
+
583
+ ## License
584
+
585
+ Apache-2.0
586
+
587
+ ## Links
588
+
589
+ - [Unleash Documentation](https://docs.getunleash.io/)
590
+ - [Unleash JavaScript SDK](https://github.com/Unleash/unleash-proxy-client-js)
591
+ - [Unleash React SDK](https://github.com/Unleash/proxy-client-react)
592
+ - [Unleash Next.js SDK](https://github.com/Unleash/unleash-nextjs-sdk)
593
+ - [npm Package](https://www.npmjs.com/package/@unleash/toolbar)
594
+
595
+ ## Support
596
+
597
+ - [GitHub Issues](https://github.com/Unleash/toolbar/issues)
598
+ - [Unleash Slack Community](https://unleash-community.slack.com/)
599
+ - [Unleash Documentation](https://docs.getunleash.io/)
@@ -0,0 +1,5 @@
1
+ interface GlobalWithLit {
2
+ litIssuedWarnings?: Set<string>;
3
+ }
4
+ declare const g: GlobalWithLit;
5
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACjC;AAED,QAAA,MAAM,CAAC,EAA4B,aAAa,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { UnleashClient } from 'unleash-proxy-client';
2
+ import { ToolbarStateManager } from './state';
3
+ import { FlagOverride, InitToolbarOptions, ToolbarState, UnleashContext, UnleashToolbarInstance, WrappedUnleashClient } from './types';
4
+ /**
5
+ * Main toolbar instance implementation
6
+ */
7
+ export declare class UnleashToolbar implements UnleashToolbarInstance {
8
+ private stateManager;
9
+ private ui;
10
+ readonly client: WrappedUnleashClient;
11
+ constructor(stateManager: ToolbarStateManager, wrappedClient: WrappedUnleashClient, options: InitToolbarOptions);
12
+ private initUI;
13
+ show(): void;
14
+ hide(): void;
15
+ destroy(): void;
16
+ getState(): ToolbarState;
17
+ getFlagNames(): string[];
18
+ setFlagOverride(name: string, override: FlagOverride | null): void;
19
+ setContextOverride(context: Partial<UnleashContext>): void;
20
+ removeContextOverride(fieldName: keyof UnleashContext): void;
21
+ resetOverrides(): void;
22
+ resetContextOverrides(): void;
23
+ }
24
+ /**
25
+ * Initialize the Unleash Toolbar with a client
26
+ * This is the main entry point - handles both toolbar creation and client wrapping
27
+ * Returns the wrapped client directly for immediate use
28
+ *
29
+ * @param client - The Unleash client to wrap
30
+ * @param options - Toolbar configuration options
31
+ */
32
+ export declare function initUnleashToolbar(client: UnleashClient, options?: InitToolbarOptions): WrappedUnleashClient;
33
+ export { ToolbarStateManager } from './state';
34
+ export * from './types';
35
+ export { wrapUnleashClient } from './wrapper';
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAElB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,qBAAa,cAAe,YAAW,sBAAsB;IAC3D,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,EAAE,CAAoB;IAC9B,SAAgB,MAAM,EAAE,oBAAoB,CAAC;gBAG3C,YAAY,EAAE,mBAAmB,EACjC,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,kBAAkB;YAUf,MAAM;IASpB,IAAI,IAAI,IAAI;IAIZ,IAAI,IAAI,IAAI;IAIZ,OAAO,IAAI,IAAI;IAKf,QAAQ,IAAI,YAAY;IAIxB,YAAY,IAAI,MAAM,EAAE;IAIxB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAIlE,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAI1D,qBAAqB,CAAC,SAAS,EAAE,MAAM,cAAc,GAAG,IAAI;IAI5D,cAAc,IAAI,IAAI;IAItB,qBAAqB,IAAI,IAAI;CAG9B;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,kBAAuB,GAC/B,oBAAoB,CAuBtB;AAUD,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ class e{listeners=/* @__PURE__ */new Set;subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){this.listeners.forEach(t=>{try{t(e)}catch(a){console.error("[Unleash Toolbar] Error in event listener:",a)}})}}class t{constructor(e,t){this.mode=e,this.key=t}enableCookieSync=!1;setCookieSyncEnabled(e){this.enableCookieSync=e}getStorage(){if("undefined"==typeof window)return null;switch(this.mode){case"local":return window.localStorage;case"session":return window.sessionStorage;default:return null}}syncToCookies(e){if(this.enableCookieSync&&"undefined"!=typeof document)try{!async function(e,t){if("undefined"==typeof document)return;const a=encodeURIComponent(t);if("cookieStore"in window)try{return void(await window.cookieStore.set({name:e,value:a,path:"/",sameSite:"lax",expires:Date.now()+6048e5}))}catch{}document.cookie=[`${e}=${a}`,"path=/","max-age=604800","SameSite=Lax"].join("; ")}("unleash-toolbar-state",JSON.stringify(e))}catch(t){console.error("[Unleash Toolbar] Failed to sync state to cookies:",t)}}load(){const e=this.getStorage();if(!e)return null;try{const t=e.getItem(this.key);return t?JSON.parse(t):null}catch(t){return console.error("[Unleash Toolbar] Failed to load state from storage:",t),null}}save(e){const t=this.getStorage();if(t)try{t.setItem(this.key,JSON.stringify(e)),this.syncToCookies(e)}catch(a){console.error("[Unleash Toolbar] Failed to save state to storage:",a)}}clear(){const e=this.getStorage();if(e)try{e.removeItem(this.key),this.enableCookieSync&&async function(e){if("undefined"!=typeof document){if("cookieStore"in window)try{return void(await window.cookieStore.delete(e))}catch{}document.cookie=`${e}=; path=/; max-age=0`}}("unleash-toolbar-state")}catch(t){console.error("[Unleash Toolbar] Failed to clear storage:",t)}}}class a{state;eventEmitter;storage;sortAlphabetically;constructor(a="local",s="unleash-toolbar-state",r=!1){this.eventEmitter=new e,this.storage=new t(a,s),this.sortAlphabetically=r;const i=this.storage.load();this.state=i||this.getInitialState()}getInitialState(){return{flags:{},contextOverrides:{}}}persist(){this.storage.save(this.state)}applyOverride(e,t){return t?"flag"===t.type?t.value:"variant"===t.type?"object"==typeof e&&null!==e&&"name"in e?{...e,name:t.variantKey,enabled:!0}:{name:t.variantKey,enabled:!0}:t:e}reEvaluateAllFlags(e){Object.keys(this.state.flags).forEach(t=>{const a=this.state.flags[t];if(a)try{const{defaultValue:s,effectiveValue:r}=e(t);this.state.flags[t]={...a,lastDefaultValue:s,lastEffectiveValue:r}}catch(s){console.error(`[Unleash Toolbar] Failed to re-evaluate flag ${t}:`,s)}}),this.persist(),this.eventEmitter.emit({type:"sdk_updated",timestamp:Date.now()})}getState(){return JSON.parse(JSON.stringify(this.state))}subscribe(e){return this.eventEmitter.subscribe(e)}recordEvaluation(e,t,a,s,r){const i=this.state.flags[e],n=!i;this.state.flags[e]={flagType:t,lastDefaultValue:a,lastEffectiveValue:s,lastContext:r,override:i?.override||null},this.persist(),n&&this.eventEmitter.emit({type:"sdk_updated",timestamp:Date.now()})}setFlagOverride(e,t){this.state.flags[e]?(this.state.flags[e].override=t,this.state.flags[e].lastEffectiveValue=this.applyOverride(this.state.flags[e].lastDefaultValue,t)):this.state.flags[e]={flagType:"variant"===t?.type?"variant":"flag",lastDefaultValue:null,lastEffectiveValue:this.applyOverride(null,t),lastContext:null,override:t},this.persist(),this.eventEmitter.emit({type:"flag_override_changed",name:e,override:t,timestamp:Date.now()})}getFlagOverride(e){return this.state.flags[e]?.override||null}setContextOverride(e){this.state.contextOverrides={...this.state.contextOverrides,...e},this.persist(),this.eventEmitter.emit({type:"context_override_changed",contextOverrides:this.state.contextOverrides})}removeContextOverride(e){const t={...this.state.contextOverrides};delete t[e],this.state.contextOverrides=t,this.persist(),this.eventEmitter.emit({type:"context_override_changed",contextOverrides:this.state.contextOverrides})}getMergedContext(e={}){return{...e,...this.state.contextOverrides,properties:{...e.properties||{},...this.state.contextOverrides.properties||{}}}}resetOverrides(){Object.keys(this.state.flags).forEach(e=>{this.state.flags[e].override=null,this.state.flags[e].lastEffectiveValue=this.state.flags[e].lastDefaultValue,this.eventEmitter.emit({type:"flag_override_changed",name:e,override:null,timestamp:Date.now()})}),this.persist()}resetContextOverrides(){this.state.contextOverrides={},this.persist(),this.eventEmitter.emit({type:"context_override_changed",contextOverrides:{}})}setVisibility(e){this.state.isVisible=e,this.persist()}getVisibility(){return this.state.isVisible}clearPersistence(){this.storage.clear()}getFlagNames(){const e=Object.keys(this.state.flags);return this.sortAlphabetically?e.sort():e}getFlagMetadata(e){return this.state.flags[e]||null}enableCookieSync(){this.storage.setCookieSyncEnabled(!0)}}function s(e,t){if("__original"in e)return e;const a=e.getContext(),s=/* @__PURE__ */new Set;let i;const n={__original:e,isEnabled:function(a){const s=e.getContext(),i=t.getMergedContext(s),n=e.isEnabled(a),o=r(n,t.getFlagOverride(a));return t.recordEvaluation(a,"flag",n,o,i),o},getVariant:function(a){const s=e.getContext(),i=t.getMergedContext(s),n=e.getVariant(a),o=r(n,t.getFlagOverride(a));return t.recordEvaluation(a,"variant",n,o,i),o},getContext:function(){const a=e.getContext();return{appName:"",...t.getMergedContext(a)}},on:function(t,a){return"update"===t&&s.add(a),e.on(t,a),i}};function o(){s.forEach(e=>{try{e()}catch(t){console.error("[Unleash Toolbar] Error in update listener:",t)}})}return e.on("update",()=>{t.reEvaluateAllFlags(a=>{const s=t.getFlagMetadata(a),i="variant"===s?.flagType?e.getVariant(a):e.isEnabled(a);return{defaultValue:i,effectiveValue:r(i,t.getFlagOverride(a))}})}),t.subscribe(s=>{if("context_override_changed"===s.type){const s=t.getMergedContext(a),{appName:i,environment:n,...l}=s;e.updateContext(l).then(()=>{t.reEvaluateAllFlags(a=>{const s=t.getFlagMetadata(a),i="variant"===s?.flagType?e.getVariant(a):e.isEnabled(a);return{defaultValue:i,effectiveValue:r(i,t.getFlagOverride(a))}}),o()}).catch(e=>{console.error("[Unleash Toolbar] Failed to update context:",e)})}"flag_override_changed"===s.type&&o()}),i=new Proxy(n,{get(t,a){if(a in t)return Reflect.get(t,a);const s=Reflect.get(e,a);return"function"==typeof s?s.bind(e):s},set:(t,a,s)=>a in t?(Reflect.set(t,a,s),!0):(Reflect.set(e,a,s),!0)}),i}function r(e,t){return t?"flag"===t.type?t.value:"variant"===t.type?"object"==typeof e&&null!==e&&"name"in e?{...e,name:t.variantKey,enabled:!0}:{name:t.variantKey,enabled:!0}:e:e}class i{stateManager;ui;client;constructor(e,t,a){this.stateManager=e,this.client=t,this.ui=null,this.initUI(e,t,a)}async initUI(e,t,a){const{ToolbarUI:s}=await import("./ui-LKEosjLO.js");this.ui=new s(e,t,a)}show(){this.ui&&this.ui.show()}hide(){this.ui&&this.ui.hide()}destroy(){this.ui&&this.ui.destroy(),this.stateManager.clearPersistence()}getState(){return this.stateManager.getState()}getFlagNames(){return this.stateManager.getFlagNames()}setFlagOverride(e,t){this.stateManager.setFlagOverride(e,t)}setContextOverride(e){this.stateManager.setContextOverride(e)}removeContextOverride(e){this.stateManager.removeContextOverride(e)}resetOverrides(){this.stateManager.resetOverrides()}resetContextOverrides(){this.stateManager.resetContextOverrides()}}function n(e,t={}){const r=t.enableCookieSync||!1,n=new a(t.storageMode||"local",t.storageKey||"unleash-toolbar-state",t.sortAlphabetically||!1);r&&n.enableCookieSync();const o=s(e,n),l=new i(n,o,t);return"undefined"!=typeof window&&(window.unleashToolbar=l),o}"undefined"!=typeof window&&(window.UnleashToolbar={init:n});export{a as ToolbarStateManager,i as UnleashToolbar,n as initUnleashToolbar,s as wrapUnleashClient};
2
+ //# sourceMappingURL=index.es.js.map