@supashiphq/javascript-sdk 0.7.15 → 0.7.17
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 +11 -11
- package/dist/index.js +33 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ const client = new SupaClient({
|
|
|
33
33
|
environment: 'production',
|
|
34
34
|
features,
|
|
35
35
|
context: {
|
|
36
|
-
|
|
36
|
+
userId: '123',
|
|
37
37
|
email: 'user@example.com',
|
|
38
38
|
plan: 'premium',
|
|
39
39
|
},
|
|
@@ -197,7 +197,7 @@ const theme = await client.getFeature('theme-config')
|
|
|
197
197
|
|
|
198
198
|
// With context override
|
|
199
199
|
const darkMode = await client.getFeature('dark-mode', {
|
|
200
|
-
context: {
|
|
200
|
+
context: { userId: '123', plan: 'premium' },
|
|
201
201
|
})
|
|
202
202
|
```
|
|
203
203
|
|
|
@@ -239,7 +239,7 @@ const results = await client.getFeatures(['new-ui', 'premium-content'])
|
|
|
239
239
|
|
|
240
240
|
// With context override
|
|
241
241
|
const results = await client.getFeatures(['new-ui', 'beta-mode'], {
|
|
242
|
-
context: {
|
|
242
|
+
context: { userId: '123', plan: 'premium' },
|
|
243
243
|
})
|
|
244
244
|
```
|
|
245
245
|
|
|
@@ -260,10 +260,10 @@ updateContext(context: FeatureContext, mergeWithExisting?: boolean): void
|
|
|
260
260
|
|
|
261
261
|
```typescript
|
|
262
262
|
// Merge with existing context
|
|
263
|
-
client.updateContext({
|
|
263
|
+
client.updateContext({ userId: '456' })
|
|
264
264
|
|
|
265
265
|
// Replace entire context
|
|
266
|
-
client.updateContext({
|
|
266
|
+
client.updateContext({ userId: '456', newField: 'value' }, false)
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
##### getContext()
|
|
@@ -303,7 +303,7 @@ interface FeatureContext {
|
|
|
303
303
|
|
|
304
304
|
Common context properties:
|
|
305
305
|
|
|
306
|
-
- `
|
|
306
|
+
- `userId`: User identifier
|
|
307
307
|
- `email`: User email
|
|
308
308
|
- `plan`: Membership plan (e.g., 'premium', 'free')
|
|
309
309
|
- `version`: Application version (e.g., 1.0.0)
|
|
@@ -386,7 +386,7 @@ const client = new SupaClient({
|
|
|
386
386
|
environment: 'production',
|
|
387
387
|
features,
|
|
388
388
|
context: {
|
|
389
|
-
|
|
389
|
+
userId: user.id,
|
|
390
390
|
email: user.email,
|
|
391
391
|
plan: user.subscriptionPlan,
|
|
392
392
|
version: process.env.APP_VERSION!,
|
|
@@ -396,7 +396,7 @@ const client = new SupaClient({
|
|
|
396
396
|
// Update context when user state changes
|
|
397
397
|
function onUserLogin(user) {
|
|
398
398
|
client.updateContext({
|
|
399
|
-
|
|
399
|
+
userId: user.id,
|
|
400
400
|
email: user.email,
|
|
401
401
|
plan: user.plan,
|
|
402
402
|
})
|
|
@@ -519,7 +519,7 @@ app.get('/api/config', async (req, res) => {
|
|
|
519
519
|
app.get('/api/user-features/:userId', async (req, res) => {
|
|
520
520
|
const results = await featureClient.getFeatures(['new-api', 'cache-enabled'], {
|
|
521
521
|
context: {
|
|
522
|
-
|
|
522
|
+
userId: req.params.userId,
|
|
523
523
|
plan: req.user.plan,
|
|
524
524
|
region: req.headers['cloudfront-viewer-country'],
|
|
525
525
|
},
|
|
@@ -604,7 +604,7 @@ export class FeatureFlagService {
|
|
|
604
604
|
environment: 'production',
|
|
605
605
|
features,
|
|
606
606
|
context: {
|
|
607
|
-
|
|
607
|
+
userId: this.getCurrentUserId(),
|
|
608
608
|
version: environment.version,
|
|
609
609
|
},
|
|
610
610
|
})
|
|
@@ -623,7 +623,7 @@ export class FeatureFlagService {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
private getCurrentUserId(): string {
|
|
626
|
-
return localStorage.getItem('
|
|
626
|
+
return localStorage.getItem('userId') || 'anonymous'
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
629
|
|
package/dist/index.js
CHANGED
|
@@ -195,6 +195,7 @@ var SupaToolbarPlugin = class {
|
|
|
195
195
|
const panelId = `supaship-toolbar-panel-${this.clientId}`;
|
|
196
196
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
197
197
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
198
|
+
const closeId = `supaship-close-toolbar-${this.clientId}`;
|
|
198
199
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
199
200
|
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
200
201
|
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
@@ -204,7 +205,7 @@ var SupaToolbarPlugin = class {
|
|
|
204
205
|
<svg
|
|
205
206
|
xmlns="http://www.w3.org/2000/svg"
|
|
206
207
|
viewBox="0 0 256 256"
|
|
207
|
-
width="
|
|
208
|
+
width="32"
|
|
208
209
|
style="vertical-align: middle;">
|
|
209
210
|
<rect width='256' height='256' rx='128' fill='#e4f222'></rect>
|
|
210
211
|
<line
|
|
@@ -255,8 +256,22 @@ var SupaToolbarPlugin = class {
|
|
|
255
256
|
aria-label="Reset all overrides"
|
|
256
257
|
title="Reset all overrides to default"
|
|
257
258
|
>
|
|
258
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
259
|
-
<path d="
|
|
259
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
260
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
261
|
+
<path d="M3 3v5h5"/>
|
|
262
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
263
|
+
<path d="M16 16h5v5"/>
|
|
264
|
+
</svg>
|
|
265
|
+
</button>
|
|
266
|
+
<button
|
|
267
|
+
class="supaship-header-btn"
|
|
268
|
+
id="${closeId}"
|
|
269
|
+
aria-label="Close toolbar"
|
|
270
|
+
title="Close toolbar"
|
|
271
|
+
>
|
|
272
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
273
|
+
<path d="M18 6 6 18"/>
|
|
274
|
+
<path d="m6 6 12 12"/>
|
|
260
275
|
</svg>
|
|
261
276
|
</button>
|
|
262
277
|
</div>
|
|
@@ -309,7 +324,6 @@ var SupaToolbarPlugin = class {
|
|
|
309
324
|
width: 36px;
|
|
310
325
|
height: 36px;
|
|
311
326
|
border-radius: 100%;
|
|
312
|
-
background: #000;
|
|
313
327
|
border: none;
|
|
314
328
|
color: white;
|
|
315
329
|
cursor: pointer;
|
|
@@ -663,16 +677,21 @@ var SupaToolbarPlugin = class {
|
|
|
663
677
|
const toggleId = `supaship-toolbar-toggle-${this.clientId}`;
|
|
664
678
|
const panelId = `supaship-toolbar-panel-${this.clientId}`;
|
|
665
679
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
680
|
+
const closeId = `supaship-close-toolbar-${this.clientId}`;
|
|
666
681
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
667
682
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
668
683
|
const toggle = document.getElementById(toggleId);
|
|
669
684
|
const panel = document.getElementById(panelId);
|
|
670
685
|
const clearAll = document.getElementById(clearId);
|
|
686
|
+
const close = document.getElementById(closeId);
|
|
671
687
|
const searchInput = document.getElementById(searchId);
|
|
672
688
|
const content = document.getElementById(contentId);
|
|
673
689
|
toggle?.addEventListener("click", () => {
|
|
674
690
|
panel?.classList.toggle("open");
|
|
675
691
|
});
|
|
692
|
+
close?.addEventListener("click", () => {
|
|
693
|
+
panel?.classList.remove("open");
|
|
694
|
+
});
|
|
676
695
|
clearAll?.addEventListener("click", () => {
|
|
677
696
|
this.clearAllOverrides();
|
|
678
697
|
});
|
|
@@ -842,8 +861,11 @@ var SupaToolbarPlugin = class {
|
|
|
842
861
|
data-action="remove"
|
|
843
862
|
title="Reset to default"
|
|
844
863
|
>
|
|
845
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
846
|
-
<path d="
|
|
864
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
865
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
866
|
+
<path d="M3 3v5h5"/>
|
|
867
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
868
|
+
<path d="M16 16h5v5"/>
|
|
847
869
|
</svg>
|
|
848
870
|
</button>
|
|
849
871
|
` : ""}
|
|
@@ -880,8 +902,11 @@ var SupaToolbarPlugin = class {
|
|
|
880
902
|
data-action="remove"
|
|
881
903
|
title="Reset to default"
|
|
882
904
|
>
|
|
883
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
884
|
-
<path d="
|
|
905
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
906
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
907
|
+
<path d="M3 3v5h5"/>
|
|
908
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
909
|
+
<path d="M16 16h5v5"/>
|
|
885
910
|
</svg>
|
|
886
911
|
</button>
|
|
887
912
|
` : ""}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/plugins/toolbar-plugin.ts","../src/constants.ts","../src/client.ts"],"sourcesContent":["export type * from './types'\nexport { SupaClient } from './client'\nexport type { SupaPlugin, SupaPluginConfig } from './plugins/types'\nexport { SupaToolbarPlugin as ToolbarPlugin } from './plugins/toolbar-plugin'\nexport type {\n SupaToolbarPluginConfig,\n SupaToolbarPosition,\n SupaToolbarOverrideChange,\n SupaToolbarOverrideChangeCallback,\n} from './plugins/toolbar-plugin'\n// export { LoggingPlugin } from \"./plugins/logging-plugin\";\n// export { CachingPlugin } from \"./plugins/caching-plugin\";\n// export { AnalyticsPlugin } from \"./plugins/analytics-plugin\";\n// export { LocalDevPlugin } from \"./plugins/local-dev-plugin\";\n","export function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n maxAttempts: number = 3,\n backoff: number = 1000,\n onRetry?: (attempt: number, error: Error, willRetry: boolean) => void\n): Promise<T> {\n let lastError: Error\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn()\n } catch (error) {\n lastError = error as Error\n const willRetry = attempt < maxAttempts\n\n if (onRetry) {\n onRetry(attempt, lastError, willRetry)\n }\n\n if (!willRetry) break\n await sleep(backoff * Math.pow(2, attempt - 1))\n }\n }\n\n throw lastError!\n}\n","import { SupaPlugin, SupaPluginConfig } from './types'\nimport { FeatureContext, FeatureValue } from '../types'\n\nexport interface SupaToolbarPosition {\n placement?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n offset?: { x: string; y: string }\n}\n\nexport type SupaToolbarOverrideChange = {\n feature: string\n value: FeatureValue\n}\n\nexport type SupaToolbarOverrideChangeCallback = (\n featureOverride: SupaToolbarOverrideChange,\n allOverrides: Record<string, FeatureValue>\n) => void\n\nexport interface SupaToolbarPluginConfig extends Omit<SupaPluginConfig, 'enabled'> {\n enabled?: boolean | 'auto' // auto means show only on localhost\n position?: SupaToolbarPosition\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n}\n\ninterface SupaToolbarState {\n overrides: Record<string, FeatureValue>\n features: Set<string>\n featureValues: Record<string, FeatureValue>\n context?: FeatureContext\n searchQuery: string\n useLocalOverrides: boolean\n}\n\nconst DEFAULT_STORAGE_KEY = 'supaship-feature-overrides'\n\nconst NO_FEATURES_MESSAGE = `No feature flags configured in the client.`\n\n/**\n * Toolbar plugin for local feature flag testing\n * Provides a visual interface to override feature flags during development\n */\nexport class SupaToolbarPlugin implements SupaPlugin {\n name = 'toolbar-plugin'\n private config: {\n enabled: boolean | 'auto'\n position: Required<SupaToolbarPosition>\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n }\n private state: SupaToolbarState\n private clientId?: string\n private storageKey: string = DEFAULT_STORAGE_KEY\n\n constructor(config: SupaToolbarPluginConfig = {}) {\n this.config = {\n enabled: config.enabled ?? 'auto',\n position: {\n placement: config.position?.placement ?? 'bottom-right',\n offset: config.position?.offset ?? { x: '1rem', y: '1rem' },\n },\n onOverrideChange: config.onOverrideChange,\n }\n\n this.state = {\n overrides: {},\n features: new Set(),\n featureValues: {},\n searchQuery: '',\n useLocalOverrides: true,\n }\n }\n\n cleanup(): void {\n this.removeToolbar()\n }\n\n private shouldShowToolbar(): boolean {\n if (this.config.enabled === true) return true\n if (this.config.enabled === false) return false\n\n // Auto mode: show only on localhost\n if (typeof window !== 'undefined') {\n return (\n window.location.hostname === 'localhost' ||\n window.location.hostname === '127.0.0.1' ||\n window.location.hostname === '' ||\n window.location.hostname.endsWith('.local') ||\n window.location.hostname.endsWith('.localhost')\n )\n }\n return false\n }\n\n onInit(params: {\n availableFeatures: Record<string, FeatureValue>\n context?: FeatureContext\n clientId: string\n }): void {\n const { availableFeatures, context, clientId } = params\n\n // Set client ID for DOM element IDs\n this.clientId = clientId\n\n // Use shared storage key (not client-specific) to persist across refreshes\n this.storageKey = DEFAULT_STORAGE_KEY\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Initialize with all available features and their fallback values from config\n this.state.features = new Set(Object.keys(availableFeatures))\n this.state.featureValues = { ...availableFeatures }\n this.state.context = context\n\n // Inject toolbar if conditions are met\n if (this.shouldShowToolbar()) {\n this.injectToolbar()\n }\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async beforeGetFeatures(_featureNames: string[], context?: FeatureContext): Promise<void> {\n // Update context if it changed\n this.state.context = context\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async afterGetFeatures(\n results: Record<string, FeatureValue>,\n context?: FeatureContext\n ): Promise<void> {\n // Update feature values with fetched results (this replaces config fallback values)\n Object.keys(results).forEach(name => {\n this.state.featureValues[name] = results[name]\n })\n\n // Apply overrides to results only if local overrides are enabled\n if (this.state.useLocalOverrides) {\n Object.keys(this.state.overrides).forEach(featureName => {\n if (featureName in results) {\n results[featureName] = this.state.overrides[featureName]\n }\n })\n }\n\n // Track features and update UI\n Object.keys(results).forEach(name => this.state.features.add(name))\n this.state.context = context\n this.updateToolbarUI()\n }\n\n private loadOverrides(): Record<string, FeatureValue> {\n if (typeof window === 'undefined' || !window.localStorage) {\n return {}\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey)\n return stored ? JSON.parse(stored) : {}\n } catch {\n return {}\n }\n }\n\n private saveOverrides(\n feature?: string,\n value?: FeatureValue,\n allOverrides?: Record<string, FeatureValue>\n ): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return\n }\n\n try {\n window.localStorage.setItem(this.storageKey, JSON.stringify(allOverrides))\n this.config.onOverrideChange?.(\n { feature: feature ?? '', value: value ?? null },\n allOverrides ?? {}\n )\n } catch (error) {\n console.error('Supaship: Failed to save feature overrides:', error)\n }\n }\n\n public setOverride(featureName: string, value: FeatureValue): void {\n this.state.overrides[featureName] = value\n this.saveOverrides(featureName, value, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public removeOverride(featureName: string): void {\n delete this.state.overrides[featureName]\n this.saveOverrides(featureName, null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public clearAllOverrides(): void {\n this.state.overrides = {}\n this.saveOverrides('', null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public getOverrides(): Record<string, FeatureValue> {\n return { ...this.state.overrides }\n }\n\n private injectToolbar(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return\n }\n\n // Check if toolbar with this client ID already exists\n const toolbarId = `supaship-toolbar-${this.clientId}`\n if (document.getElementById(toolbarId)) {\n return\n }\n\n // Create toolbar container\n const toolbar = document.createElement('div')\n toolbar.id = toolbarId\n toolbar.setAttribute('data-supaship-client', this.clientId || '')\n toolbar.innerHTML = this.getToolbarHTML()\n\n // Add styles\n this.injectStyles()\n\n // Add to DOM\n document.body.appendChild(toolbar)\n\n // Add event listeners\n this.attachEventListeners()\n }\n\n private removeToolbar(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toolbar = document.getElementById(`supaship-toolbar-${this.clientId}`)\n if (toolbar) {\n toolbar.remove()\n }\n\n const styles = document.getElementById('supaship-toolbar-styles')\n if (styles) {\n styles.remove()\n }\n }\n\n private getToolbarHTML(): string {\n const { placement, offset } = this.config.position\n const positionClass = `supaship-toolbar-${placement}`\n const offsetX = offset?.x ?? '1rem'\n const offsetY = offset?.y ?? '1rem'\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n\n return `\n <div class=\"supaship-toolbar-container ${positionClass}\" style=\"--offset-x: ${offsetX}; --offset-y: ${offsetY};\">\n <button class=\"supaship-toolbar-toggle\" id=\"${toggleId}\" aria-label=\"Supaship Toolbar\" title=\"Supaship Toolbar\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 256 256\"\n width=\"24\"\n style=\"vertical-align: middle;\">\n <rect width='256' height='256' rx='128' fill='#e4f222'></rect>\n <line\n x1='66'\n y1='118'\n x2='118'\n y2='66'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='186'\n y1='70'\n x2='70'\n y2='186'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='190'\n y1='138'\n x2='138'\n y2='190'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n </svg>\n <span class=\"supaship-toolbar-badge\" id=\"${badgeId}\"></span>\n </button>\n <div class=\"supaship-toolbar-panel\" id=\"${panelId}\">\n <div class=\"supaship-toolbar-header\">\n <input\n type=\"text\"\n class=\"supaship-search-input\"\n id=\"${searchId}\"\n placeholder=\"Search features\"\n />\n <span class=\"supaship-toolbar-overrides-label\" id=\"${headerOverrideCountId}\">0 overrides</span>\n <button\n class=\"supaship-header-btn\"\n id=\"${clearId}\"\n aria-label=\"Reset all overrides\"\n title=\"Reset all overrides to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"18\" height=\"18\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n </div>\n <div class=\"supaship-toolbar-content\" id=\"${contentId}\">\n <div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>\n </div>\n </div>\n </div>\n `\n }\n\n private injectStyles(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n if (document.getElementById('supaship-toolbar-styles')) {\n return\n }\n\n const styles = document.createElement('style')\n styles.id = 'supaship-toolbar-styles'\n styles.textContent = `\n .supaship-toolbar-container {\n position: fixed;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n }\n\n .supaship-toolbar-bottom-right {\n bottom: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-bottom-left {\n bottom: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-top-right {\n top: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-top-left {\n top: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-toggle {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 100%;\n background: #000;\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .supaship-toolbar-toggle:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .supaship-toolbar-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n }\n\n .supaship-toolbar-badge.show {\n display: flex;\n }\n\n .supaship-toolbar-panel {\n position: absolute;\n bottom: 48px;\n right: 0;\n width: 300px;\n max-height: 600px;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n display: none;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid #333;\n }\n\n .supaship-toolbar-bottom-left .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n right: auto;\n left: 0;\n }\n\n .supaship-toolbar-top-right .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n bottom: auto;\n top: 60px;\n }\n\n .supaship-toolbar-panel.open {\n display: flex;\n }\n\n .supaship-toolbar-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border-bottom: 1px solid #333;\n background: #0f0f0f;\n min-width: 0;\n overflow: hidden;\n }\n\n .supaship-search-input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n color: #e5e5e5;\n padding: 0;\n font-size: 13px;\n outline: none;\n }\n\n .supaship-search-input::placeholder {\n color: #888;\n }\n\n .supaship-header-btn {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 24px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n padding: 0;\n }\n\n .supaship-header-btn:hover {\n color: #ef4444;\n }\n\n .supaship-toolbar-content {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n min-height: 300px;\n max-height: 300px;\n }\n\n .supaship-toolbar-empty {\n padding: 32px 16px;\n text-align: center;\n color: #888;\n }\n\n .supaship-feature-item {\n padding: 0 6px;\n }\n\n .supaship-feature-item.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .supaship-feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n }\n\n .supaship-feature-name {\n font-weight: 500;\n color: #e5e5e5;\n font-size: 13px;\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .supaship-feature-override-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #ef4444;\n flex-shrink: 0;\n }\n\n .supaship-toolbar-overrides-label {\n font-size: 12px;\n color: #888;\n white-space: nowrap;\n flex-shrink: 0;\n transition: all 0.2s;\n }\n\n .supaship-toolbar-overrides-label-count.has-overrides {\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .supaship-feature-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n min-height: 20px;\n }\n\n .supaship-feature-content {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .supaship-feature-input {\n flex: 1;\n padding: 6px 8px;\n background: #1a1a1a;\n border: 1px solid #555;\n color: #e5e5e5;\n border-radius: 4px;\n font-size: 13px;\n font-family: 'Monaco', 'Courier New', monospace;\n outline: none;\n resize: vertical;\n min-height: 60px;\n margin-bottom: 8px;\n }\n\n .supaship-feature-input:focus {\n border-color: #667eea;\n }\n\n .supaship-btn {\n padding: 4px 12px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .supaship-btn-primary {\n background: #444;\n color: white;\n }\n\n .supaship-btn-primary:hover {\n background: #555;\n }\n\n .supaship-btn-secondary {\n background: #444;\n color: #e5e5e5;\n }\n\n .supaship-btn-secondary:hover {\n background: #555;\n }\n\n .supaship-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-btn:disabled:hover {\n background: #444;\n }\n\n .supaship-header-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n\n .supaship-header-btn:disabled:hover {\n color: #e5e5e5;\n }\n\n .supaship-toggle {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n }\n\n .supaship-toggle input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .supaship-toggle-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #333;\n border: 1px solid #555;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 20px;\n }\n\n .supaship-toggle-slider:before {\n position: absolute;\n content: \"\";\n height: 14px;\n width: 14px;\n left: 2px;\n bottom: 1px;\n background-color: #666;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider {\n background-color: #fff;\n border-color: #fff;\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider:before {\n transform: translateX(13px);\n background-color: #000;\n }\n\n .supaship-toggle input:disabled + .supaship-toggle-slider {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-toggle:hover .supaship-toggle-slider:before {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .supaship-btn-icon {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n cursor: pointer;\n padding: 0;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .supaship-btn-icon:hover {\n background: #444;\n color: #ef4444;\n }\n `\n document.head.appendChild(styles)\n }\n\n private attachEventListeners(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n\n const toggle = document.getElementById(toggleId)\n const panel = document.getElementById(panelId)\n const clearAll = document.getElementById(clearId)\n const searchInput = document.getElementById(searchId) as HTMLInputElement\n const content = document.getElementById(contentId)\n\n toggle?.addEventListener('click', () => {\n panel?.classList.toggle('open')\n })\n\n clearAll?.addEventListener('click', () => {\n this.clearAllOverrides()\n })\n\n searchInput?.addEventListener('input', e => {\n this.state.searchQuery = (e.target as HTMLInputElement).value.toLowerCase()\n this.updateToolbarUI()\n })\n\n // Use event delegation on content element - survives innerHTML updates\n if (content) {\n // Handle button clicks (remove and set actions)\n content.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement\n const buttonElement = target.closest('button[data-action]') as HTMLButtonElement\n if (!buttonElement) return\n\n e.preventDefault()\n e.stopPropagation()\n\n const featureName = buttonElement.dataset.feature!\n const action = buttonElement.dataset.action\n\n if (action === 'remove') {\n this.removeOverride(featureName)\n } else if (action === 'set') {\n const textarea = content.querySelector(\n `textarea[data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLTextAreaElement\n if (textarea && textarea.value.trim()) {\n try {\n const value = JSON.parse(textarea.value)\n this.setOverride(featureName, value)\n } catch {\n // If not valid JSON, wrap string in object\n this.setOverride(featureName, { value: textarea.value })\n }\n }\n }\n })\n\n // Handle checkbox changes for boolean toggles\n content.addEventListener('change', (e: Event) => {\n const target = e.target as HTMLInputElement\n if (target.type === 'checkbox' && target.dataset.type === 'boolean') {\n const featureName = target.dataset.feature!\n const newValue = target.checked\n this.setOverride(featureName, newValue)\n }\n })\n\n // Handle textarea input to update button states\n content.addEventListener('input', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }\n })\n\n // Handle textarea paste events\n content.addEventListener('paste', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n setTimeout(() => {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${featureName}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }, 0)\n }\n })\n\n // Handle Ctrl/Cmd+Enter to set override\n content.addEventListener('keydown', (e: KeyboardEvent) => {\n const target = e.target as HTMLTextAreaElement\n if (\n target.tagName === 'TEXTAREA' &&\n target.dataset.feature &&\n (e.ctrlKey || e.metaKey) &&\n e.key === 'Enter'\n ) {\n e.preventDefault()\n const featureName = target.dataset.feature!\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn && !overrideBtn.disabled) {\n overrideBtn.click()\n }\n }\n })\n }\n }\n\n private updateToolbarUI(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) as HTMLButtonElement\n const badge = document.getElementById(badgeId)\n const headerOverrideCount = document.getElementById(headerOverrideCountId)\n const toggleBtn = document.getElementById(toggleId) as HTMLButtonElement\n\n if (!content) {\n console.warn('[Toolbar] Content element not found:', contentId)\n return\n }\n\n // Update clear all button state and badge\n const overrideCount = Object.keys(this.state.overrides).length\n const hasOverrides = overrideCount > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\n }\n\n // Update badge on toggle button\n if (badge) {\n if (hasOverrides) {\n badge.textContent = overrideCount > 99 ? '99+' : String(overrideCount)\n badge.classList.add('show')\n } else {\n badge.classList.remove('show')\n }\n }\n\n // Update tooltip on toggle button\n if (toggleBtn) {\n if (hasOverrides) {\n toggleBtn.setAttribute(\n 'title',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n toggleBtn.setAttribute(\n 'aria-label',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n } else {\n toggleBtn.setAttribute('title', 'Supaship Toolbar')\n toggleBtn.setAttribute('aria-label', 'Supaship Toolbar')\n }\n }\n\n // Update override count in header\n if (headerOverrideCount) {\n // Escape overrideCount to prevent any potential XSS (defense in depth)\n const escapedCount = this.escapeHtml(String(overrideCount))\n headerOverrideCount.innerHTML = `<span class=\"supaship-toolbar-overrides-label-count ${hasOverrides ? 'has-overrides' : ''}\">${escapedCount}</span> override${overrideCount === 1 ? '' : 's'}`\n }\n\n const features = Array.from(this.state.features).sort()\n\n // Filter features based on search query\n const filteredFeatures = features.filter(name =>\n name.toLowerCase().includes(this.state.searchQuery)\n )\n\n if (filteredFeatures.length === 0) {\n content.innerHTML = this.state.searchQuery\n ? '<div class=\"supaship-toolbar-empty\">No matching features found</div>'\n : `<div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>`\n return\n }\n\n const htmlContent = filteredFeatures\n .map(featureName => {\n const hasOverride = featureName in this.state.overrides\n const currentValue = this.state.featureValues[featureName]\n const overrideValue = hasOverride ? this.state.overrides[featureName] : currentValue\n const isDisabled = !this.state.useLocalOverrides\n const itemClass = `supaship-feature-item ${isDisabled ? 'disabled' : ''}`\n\n // Check if the feature is boolean\n const isBoolean =\n typeof currentValue === 'boolean' || (hasOverride && typeof overrideValue === 'boolean')\n\n if (isBoolean) {\n // Render toggle switch for boolean values (single row layout)\n const isChecked = hasOverride ? overrideValue === true : currentValue === true\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${this.escapeHtml(featureName)}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"14\" height=\"14\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n `\n : ''\n }\n <label class=\"supaship-toggle\">\n <input\n type=\"checkbox\"\n ${isChecked ? 'checked' : ''}\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-type=\"boolean\"\n />\n <span class=\"supaship-toggle-slider\"></span>\n </label>\n </div>\n </div>\n </div>\n `\n } else {\n // Render textarea for non-boolean values\n const currentDisplayValue = hasOverride\n ? JSON.stringify(overrideValue)\n : currentValue !== undefined\n ? JSON.stringify(currentValue)\n : ''\n const escapedFeatureName = this.escapeHtml(featureName)\n const escapedCurrentDisplayValue = this.escapeHtml(currentDisplayValue)\n const escapedTextareaContent = hasOverride\n ? this.escapeHtml(JSON.stringify(overrideValue))\n : escapedCurrentDisplayValue\n\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${escapedFeatureName}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"14\" height=\"14\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n `\n : ''\n }\n <button\n class=\"supaship-btn supaship-btn-primary\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"set\"\n disabled>\n Override\n </button>\n </div>\n </div>\n <div class=\"supaship-feature-content\">\n <textarea\n class=\"supaship-feature-input\"\n placeholder=\"Override JSON value\"\n data-feature=\"${escapedFeatureName}\"\n data-original=\"${escapedCurrentDisplayValue}\"\n >${escapedTextareaContent}</textarea>\n </div>\n </div>\n `\n }\n })\n .join('')\n\n requestAnimationFrame(() => {\n // Set innerHTML - event listeners are handled via delegation in attachEventListeners()\n content.innerHTML = htmlContent\n\n // Update button states for textareas that already have values\n content.querySelectorAll('textarea[data-feature]').forEach(textarea => {\n const textareaElement = textarea as HTMLTextAreaElement\n const featureName = textareaElement.dataset.feature!\n const originalValue = textareaElement.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = textareaElement.value !== originalValue\n const hasContent = textareaElement.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n })\n })\n }\n\n private escapeHtml(text: string): string {\n const div = typeof document !== 'undefined' ? document.createElement('div') : null\n if (div) {\n div.textContent = text\n return div.innerHTML\n }\n return text.replace(/[&<>\"']/g, char => {\n const escapeMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return escapeMap[char]\n })\n }\n\n /**\n * Escapes special characters in CSS attribute selectors to prevent CSS injection\n * @param value The value to escape for use in CSS attribute selectors\n */\n private escapeCssSelector(value: string): string {\n // Escape special CSS selector characters: \", ', ], \\\n return value.replace(/[\"'\\\\\\]]/g, '\\\\$&')\n }\n}\n","export const DEFAULT_FEATURES_URL = 'https://edge.supaship.com/v1/features'\nexport const DEFAULT_EVENTS_URL = 'https://edge.supaship.com/v1/events'\n","import {\n SupaClientConfig,\n FeatureContext,\n FeatureValue,\n NetworkConfig,\n Features,\n FeaturesWithFallbacks,\n} from './types'\nimport { retry } from './utils'\nimport { SupaPlugin } from './plugins/types'\nimport { SupaToolbarPlugin } from './plugins/toolbar-plugin'\nimport { DEFAULT_FEATURES_URL, DEFAULT_EVENTS_URL } from './constants'\n\ntype RequiredRetryConfig = Required<NonNullable<NetworkConfig['retry']>>\ntype ResolvedNetworkConfig = {\n featuresAPIUrl: string\n eventsAPIUrl: string\n retry: RequiredRetryConfig\n requestTimeoutMs: number\n}\n\nexport class SupaClient<TFeatures extends FeaturesWithFallbacks> {\n private apiKey: string\n private environment: string\n private defaultContext?: FeatureContext\n private plugins: SupaPlugin[]\n private featureDefinitions: Features<TFeatures>\n private clientId: string\n\n private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n private networkConfig: ResolvedNetworkConfig\n\n constructor(config: SupaClientConfig & { features: TFeatures }) {\n this.apiKey = config.apiKey\n this.environment = config.environment\n this.defaultContext = config.context\n this.featureDefinitions = config.features as Features<TFeatures>\n\n // Generate unique client ID\n this.clientId = this.generateClientId()\n\n this.networkConfig = {\n featuresAPIUrl: config.networkConfig?.featuresAPIUrl || DEFAULT_FEATURES_URL,\n eventsAPIUrl: config.networkConfig?.eventsAPIUrl || DEFAULT_EVENTS_URL,\n retry: {\n enabled: config.networkConfig?.retry?.enabled ?? true,\n maxAttempts: config.networkConfig?.retry?.maxAttempts ?? 3,\n backoff: config.networkConfig?.retry?.backoff ?? 1000,\n },\n requestTimeoutMs: config.networkConfig?.requestTimeoutMs ?? 10000,\n }\n\n // Prefer injected fetch, then global fetch if available\n const globalFetch: typeof fetch | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { fetch?: typeof fetch }).fetch\n : undefined\n if (config.networkConfig?.fetchFn) {\n this.fetchImpl = config.networkConfig.fetchFn\n } else if (typeof globalFetch === 'function') {\n this.fetchImpl = globalFetch.bind(globalThis)\n } else {\n throw new Error(\n 'No fetch implementation available. Provide fetchFn in config or use a runtime with global fetch (e.g., Node 18+, browsers).'\n )\n }\n\n // Initialize plugins with automatic toolbar plugin in browser\n this.plugins = this.initializePlugins(config)\n\n // Initialize plugins with available features and their fallback values\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onInit?.({\n clientId: this.clientId,\n availableFeatures: this.featureDefinitions,\n context: this.defaultContext,\n })\n )\n ).catch(console.error)\n }\n\n /**\n * Generate a unique client ID\n */\n private generateClientId(): string {\n return `supaship-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * Initialize plugins with automatic toolbar plugin in browser environments\n */\n private initializePlugins(config: SupaClientConfig & { features: TFeatures }): SupaPlugin[] {\n const plugins = config.plugins || []\n\n // Check if we're in a browser environment\n const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'\n\n // If toolbar is explicitly disabled, don't add it\n if (config.toolbar === false) {\n return plugins\n }\n\n // If in browser and toolbar not disabled, add it automatically\n if (isBrowser) {\n // Check if user already added toolbar plugin manually\n const hasToolbarPlugin = plugins.some(p => p.name === 'toolbar-plugin')\n\n if (!hasToolbarPlugin) {\n // Add toolbar with user config or defaults\n const toolbarConfig = config.toolbar || { enabled: 'auto' }\n const toolbarPlugin = new SupaToolbarPlugin(toolbarConfig)\n return [toolbarPlugin, ...plugins]\n }\n }\n\n return plugins\n }\n\n /**\n * Updates the default context for the client\n * @param context - New context to merge with or replace the existing context\n * @param mergeWithExisting - Whether to merge with existing context (default: true)\n */\n updateContext(context: FeatureContext, mergeWithExisting: boolean = true): void {\n const oldContext = this.defaultContext\n\n if (mergeWithExisting && this.defaultContext) {\n this.defaultContext = { ...this.defaultContext, ...context }\n } else {\n this.defaultContext = context\n }\n\n // Notify plugins of context change\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(oldContext, this.defaultContext!, 'updateContext')\n )\n ).catch(console.error)\n }\n\n /**\n * Gets the current default context\n */\n getContext(): FeatureContext | undefined {\n return this.defaultContext\n }\n\n /**\n * Gets the fallback value for a feature from its definition\n */\n getFeatureFallback<TKey extends keyof TFeatures>(featureName: TKey): Features<TFeatures>[TKey] {\n return this.featureDefinitions[featureName]\n }\n\n private getVariationValue(variation: FeatureValue, fallback: FeatureValue): FeatureValue {\n if (variation !== undefined && variation !== null) {\n return variation\n }\n\n return fallback ?? null\n }\n\n async getFeature<TKey extends keyof TFeatures>(\n featureName: TKey,\n options?: { context?: FeatureContext }\n ): Promise<Features<TFeatures>[TKey]> {\n const { context } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof context === 'object' && context !== null\n ? { ...(this.defaultContext ?? {}), ...context }\n : this.defaultContext\n\n try {\n const response = await this.getFeatures([featureName as string], {\n context: mergedContext,\n })\n\n // Get the specific feature value\n const value = response[featureName as string]\n return value as Features<TFeatures>[TKey]\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Use fallback feature value when API fails\n const fallbackValue = this.featureDefinitions[featureName]\n\n // Notify plugins that fallback was used\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName as string,\n fallbackValue as FeatureValue,\n error as Error\n )\n )\n )\n return fallbackValue as Features<TFeatures>[TKey]\n }\n }\n\n async getFeatures<TKeys extends readonly (keyof TFeatures)[]>(\n featureNames: TKeys,\n options?: { context?: FeatureContext }\n ): Promise<{ [K in TKeys[number]]: Features<TFeatures>[K] }> {\n const { context: contextOverride } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof contextOverride === 'object' && contextOverride !== null\n ? { ...(this.defaultContext ?? {}), ...contextOverride }\n : this.defaultContext\n\n // Notify plugins of context update for this request\n if (contextOverride) {\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(this.defaultContext, mergedContext!, 'request')\n )\n )\n }\n\n // Convert feature names to strings for API call\n const featureNamesArray = featureNames.map(name => name as string)\n\n try {\n // Run beforeGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.beforeGetFeatures?.(featureNamesArray, mergedContext))\n )\n\n type FeaturesResponse = { features: Record<string, { variation: FeatureValue }> }\n const fetchFeatures = async (): Promise<Record<string, FeatureValue>> => {\n const url = this.networkConfig.featuresAPIUrl\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n }\n const body = JSON.stringify({\n environment: this.environment,\n features: featureNamesArray,\n context: mergedContext,\n })\n\n // Notify plugins before request\n await Promise.all(this.plugins.map(plugin => plugin.beforeRequest?.(url, body, headers)))\n\n const startTime = Date.now()\n // Support timeout via AbortController when available\n const AbortCtrl: typeof AbortController | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { AbortController?: typeof AbortController })\n .AbortController\n : undefined\n let controller: AbortController | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n if (this.networkConfig.requestTimeoutMs && typeof AbortCtrl === 'function') {\n controller = new AbortCtrl()\n timeoutId = setTimeout(() => controller?.abort(), this.networkConfig.requestTimeoutMs)\n }\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n signal: controller?.signal,\n })\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n const duration = Date.now() - startTime\n\n // Notify plugins after response\n await Promise.all(\n this.plugins.map(plugin => plugin.afterResponse?.(response, { duration }))\n )\n\n if (!response.ok) {\n throw new Error(`Failed to fetch features: ${response.statusText}`)\n }\n\n const data = (await response.json()) as FeaturesResponse\n const result: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(name => {\n const variation = data.features[name]?.variation\n result[name] = this.getVariationValue(\n variation,\n this.featureDefinitions[name as keyof TFeatures]\n )\n })\n\n return result\n }\n\n const result = this.networkConfig.retry.enabled\n ? await retry(\n fetchFeatures,\n this.networkConfig.retry.maxAttempts,\n this.networkConfig.retry.backoff,\n (attempt, error, willRetry) => {\n // Notify plugins of retry attempts\n Promise.all(\n this.plugins.map(plugin => plugin.onRetryAttempt?.(attempt, error, willRetry))\n ).catch(console.error)\n }\n )\n : await fetchFeatures()\n\n // Run afterGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.afterGetFeatures?.(result, mergedContext))\n )\n\n // Return the fetched features\n return result as { [K in TKeys[number]]: Features<TFeatures>[K] }\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Create fallback result with requested feature names\n const fallbackResult: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(featureName => {\n fallbackResult[featureName] = this.featureDefinitions[featureName as keyof TFeatures]\n\n // Notify plugins that fallback was used for each feature\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName,\n this.featureDefinitions[featureName as keyof TFeatures],\n error as Error\n )\n )\n ).catch(console.error)\n })\n\n return fallbackResult as { [K in TKeys[number]]: Features<TFeatures>[K] }\n }\n }\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0XrB,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC;AAAA,EAEQ,uBAA6B;AACnC,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AAEA,UAAM,WAAW,2BAA2B,KAAK,QAAQ;AACzD,UAAM,UAAU,0BAA0B,KAAK,QAAQ;AACvD,UAAM,UAAU,sBAAsB,KAAK,QAAQ;AACnD,UAAM,WAAW,yBAAyB,KAAK,QAAQ;AACvD,UAAM,YAAY,4BAA4B,KAAK,QAAQ;AAE3D,UAAM,SAAS,SAAS,eAAe,QAAQ;AAC/C,UAAM,QAAQ,SAAS,eAAe,OAAO;AAC7C,UAAM,WAAW,SAAS,eAAe,OAAO;AAChD,UAAM,cAAc,SAAS,eAAe,QAAQ;AACpD,UAAM,UAAU,SAAS,eAAe,SAAS;AAEjD,YAAQ,iBAAiB,SAAS,MAAM;AACtC,aAAO,UAAU,OAAO,MAAM;AAAA,IAChC,CAAC;AAED,cAAU,iBAAiB,SAAS,MAAM;AACxC,WAAK,kBAAkB;AAAA,IACzB,CAAC;AAED,iBAAa,iBAAiB,SAAS,OAAK;AAC1C,WAAK,MAAM,cAAe,EAAE,OAA4B,MAAM,YAAY;AAC1E,WAAK,gBAAgB;AAAA,IACvB,CAAC;AAGD,QAAI,SAAS;AAEX,cAAQ,iBAAiB,SAAS,CAAC,MAAa;AAC9C,cAAM,SAAS,EAAE;AACjB,cAAM,gBAAgB,OAAO,QAAQ,qBAAqB;AAC1D,YAAI,CAAC,cAAe;AAEpB,UAAE,eAAe;AACjB,UAAE,gBAAgB;AAElB,cAAM,cAAc,cAAc,QAAQ;AAC1C,cAAM,SAAS,cAAc,QAAQ;AAErC,YAAI,WAAW,UAAU;AACvB,eAAK,eAAe,WAAW;AAAA,QACjC,WAAW,WAAW,OAAO;AAC3B,gBAAM,WAAW,QAAQ;AAAA,YACvB,0BAA0B,KAAK,kBAAkB,WAAW,CAAC;AAAA,UAC/D;AACA,cAAI,YAAY,SAAS,MAAM,KAAK,GAAG;AACrC,gBAAI;AACF,oBAAM,QAAQ,KAAK,MAAM,SAAS,KAAK;AACvC,mBAAK,YAAY,aAAa,KAAK;AAAA,YACrC,QAAQ;AAEN,mBAAK,YAAY,aAAa,EAAE,OAAO,SAAS,MAAM,CAAC;AAAA,YACzD;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAGD,cAAQ,iBAAiB,UAAU,CAAC,MAAa;AAC/C,cAAM,SAAS,EAAE;AACjB,YAAI,OAAO,SAAS,cAAc,OAAO,QAAQ,SAAS,WAAW;AACnE,gBAAM,cAAc,OAAO,QAAQ;AACnC,gBAAM,WAAW,OAAO;AACxB,eAAK,YAAY,aAAa,QAAQ;AAAA,QACxC;AAAA,MACF,CAAC;AAGD,cAAQ,iBAAiB,SAAS,CAAC,MAAa;AAC9C,cAAM,SAAS,EAAE;AACjB,YAAI,OAAO,YAAY,cAAc,OAAO,QAAQ,SAAS;AAC3D,gBAAM,cAAc,OAAO,QAAQ;AACnC,gBAAM,gBAAgB,OAAO,QAAQ,YAAY;AACjD,gBAAM,cAAc,QAAQ;AAAA,YAC1B,2CAA2C,KAAK,kBAAkB,WAAW,CAAC;AAAA,UAChF;AAEA,cAAI,aAAa;AACf,kBAAM,aAAa,OAAO,UAAU;AACpC,kBAAM,aAAa,OAAO,MAAM,KAAK,EAAE,SAAS;AAChD,wBAAY,WAAW,CAAC,cAAc,CAAC;AAAA,UACzC;AAAA,QACF;AAAA,MACF,CAAC;AAGD,cAAQ,iBAAiB,SAAS,CAAC,MAAa;AAC9C,cAAM,SAAS,EAAE;AACjB,YAAI,OAAO,YAAY,cAAc,OAAO,QAAQ,SAAS;AAC3D,qBAAW,MAAM;AACf,kBAAM,cAAc,OAAO,QAAQ;AACnC,kBAAM,gBAAgB,OAAO,QAAQ,YAAY;AACjD,kBAAM,cAAc,QAAQ;AAAA,cAC1B,2CAA2C,WAAW;AAAA,YACxD;AAEA,gBAAI,aAAa;AACf,oBAAM,aAAa,OAAO,UAAU;AACpC,oBAAM,aAAa,OAAO,MAAM,KAAK,EAAE,SAAS;AAChD,0BAAY,WAAW,CAAC,cAAc,CAAC;AAAA,YACzC;AAAA,UACF,GAAG,CAAC;AAAA,QACN;AAAA,MACF,CAAC;AAGD,cAAQ,iBAAiB,WAAW,CAAC,MAAqB;AACxD,cAAM,SAAS,EAAE;AACjB,YACE,OAAO,YAAY,cACnB,OAAO,QAAQ,YACd,EAAE,WAAW,EAAE,YAChB,EAAE,QAAQ,SACV;AACA,YAAE,eAAe;AACjB,gBAAM,cAAc,OAAO,QAAQ;AACnC,gBAAM,cAAc,QAAQ;AAAA,YAC1B,2CAA2C,KAAK,kBAAkB,WAAW,CAAC;AAAA,UAChF;AAEA,cAAI,eAAe,CAAC,YAAY,UAAU;AACxC,wBAAY,MAAM;AAAA,UACpB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,kBAAwB;AAC9B,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AAEA,UAAM,YAAY,4BAA4B,KAAK,QAAQ;AAC3D,UAAM,UAAU,sBAAsB,KAAK,QAAQ;AACnD,UAAM,UAAU,0BAA0B,KAAK,QAAQ;AACvD,UAAM,wBAAwB,kCAAkC,KAAK,QAAQ;AAC7E,UAAM,WAAW,2BAA2B,KAAK,QAAQ;AAEzD,UAAM,UAAU,SAAS,eAAe,SAAS;AACjD,UAAM,cAAc,SAAS,eAAe,OAAO;AACnD,UAAM,QAAQ,SAAS,eAAe,OAAO;AAC7C,UAAM,sBAAsB,SAAS,eAAe,qBAAqB;AACzE,UAAM,YAAY,SAAS,eAAe,QAAQ;AAElD,QAAI,CAAC,SAAS;AACZ,cAAQ,KAAK,wCAAwC,SAAS;AAC9D;AAAA,IACF;AAGA,UAAM,gBAAgB,OAAO,KAAK,KAAK,MAAM,SAAS,EAAE;AACxD,UAAM,eAAe,gBAAgB;AACrC,QAAI,aAAa;AACf,kBAAY,WAAW,CAAC;AAAA,IAC1B;AAGA,QAAI,OAAO;AACT,UAAI,cAAc;AAChB,cAAM,cAAc,gBAAgB,KAAK,QAAQ,OAAO,aAAa;AACrE,cAAM,UAAU,IAAI,MAAM;AAAA,MAC5B,OAAO;AACL,cAAM,UAAU,OAAO,MAAM;AAAA,MAC/B;AAAA,IACF;AAGA,QAAI,WAAW;AACb,UAAI,cAAc;AAChB,kBAAU;AAAA,UACR;AAAA,UACA,qBAAqB,aAAa,YAAY,kBAAkB,IAAI,KAAK,GAAG;AAAA,QAC9E;AACA,kBAAU;AAAA,UACR;AAAA,UACA,qBAAqB,aAAa,YAAY,kBAAkB,IAAI,KAAK,GAAG;AAAA,QAC9E;AAAA,MACF,OAAO;AACL,kBAAU,aAAa,SAAS,kBAAkB;AAClD,kBAAU,aAAa,cAAc,kBAAkB;AAAA,MACzD;AAAA,IACF;AAGA,QAAI,qBAAqB;AAEvB,YAAM,eAAe,KAAK,WAAW,OAAO,aAAa,CAAC;AAC1D,0BAAoB,YAAY,uDAAuD,eAAe,kBAAkB,EAAE,KAAK,YAAY,mBAAmB,kBAAkB,IAAI,KAAK,GAAG;AAAA,IAC9L;AAEA,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,QAAQ,EAAE,KAAK;AAGtD,UAAM,mBAAmB,SAAS;AAAA,MAAO,UACvC,KAAK,YAAY,EAAE,SAAS,KAAK,MAAM,WAAW;AAAA,IACpD;AAEA,QAAI,iBAAiB,WAAW,GAAG;AACjC,cAAQ,YAAY,KAAK,MAAM,cAC3B,yEACA,uCAAuC,mBAAmB;AAC9D;AAAA,IACF;AAEA,UAAM,cAAc,iBACjB,IAAI,iBAAe;AAClB,YAAM,cAAc,eAAe,KAAK,MAAM;AAC9C,YAAM,eAAe,KAAK,MAAM,cAAc,WAAW;AACzD,YAAM,gBAAgB,cAAc,KAAK,MAAM,UAAU,WAAW,IAAI;AACxE,YAAM,aAAa,CAAC,KAAK,MAAM;AAC/B,YAAM,YAAY,yBAAyB,aAAa,aAAa,EAAE;AAGvE,YAAM,YACJ,OAAO,iBAAiB,aAAc,eAAe,OAAO,kBAAkB;AAEhF,UAAI,WAAW;AAEb,cAAM,YAAY,cAAc,kBAAkB,OAAO,iBAAiB;AAC1E,eAAO;AAAA,wBACO,SAAS;AAAA;AAAA;AAAA,kBAGf,KAAK,WAAW,WAAW,CAAC;AAAA,kBAC5B,cAAc,6FAA6F,EAAE;AAAA;AAAA;AAAA,kBAI7G,cACI;AAAA;AAAA;AAAA,oCAGc,KAAK,WAAW,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAS1C,EACN;AAAA;AAAA;AAAA;AAAA,sBAIM,YAAY,YAAY,EAAE;AAAA,oCACZ,KAAK,WAAW,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxD,OAAO;AAEL,cAAM,sBAAsB,cACxB,KAAK,UAAU,aAAa,IAC5B,iBAAiB,SACf,KAAK,UAAU,YAAY,IAC3B;AACN,cAAM,qBAAqB,KAAK,WAAW,WAAW;AACtD,cAAM,6BAA6B,KAAK,WAAW,mBAAmB;AACtE,cAAM,yBAAyB,cAC3B,KAAK,WAAW,KAAK,UAAU,aAAa,CAAC,IAC7C;AAEJ,eAAO;AAAA,wBACO,SAAS;AAAA;AAAA;AAAA,kBAGf,kBAAkB;AAAA,kBAClB,cAAc,6FAA6F,EAAE;AAAA;AAAA;AAAA,kBAI7G,cACI;AAAA;AAAA;AAAA,sCAGgB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBASlC,EACN;AAAA;AAAA;AAAA,kCAGkB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAWpB,kBAAkB;AAAA,iCACjB,0BAA0B;AAAA,iBAC1C,sBAAsB;AAAA;AAAA;AAAA;AAAA,MAI/B;AAAA,IACF,CAAC,EACA,KAAK,EAAE;AAEV,0BAAsB,MAAM;AAE1B,cAAQ,YAAY;AAGpB,cAAQ,iBAAiB,wBAAwB,EAAE,QAAQ,cAAY;AACrE,cAAM,kBAAkB;AACxB,cAAM,cAAc,gBAAgB,QAAQ;AAC5C,cAAM,gBAAgB,gBAAgB,QAAQ,YAAY;AAC1D,cAAM,cAAc,QAAQ;AAAA,UAC1B,2CAA2C,KAAK,kBAAkB,WAAW,CAAC;AAAA,QAChF;AAEA,YAAI,aAAa;AACf,gBAAM,aAAa,gBAAgB,UAAU;AAC7C,gBAAM,aAAa,gBAAgB,MAAM,KAAK,EAAE,SAAS;AACzD,sBAAY,WAAW,CAAC,cAAc,CAAC;AAAA,QACzC;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,WAAW,MAAsB;AACvC,UAAM,MAAM,OAAO,aAAa,cAAc,SAAS,cAAc,KAAK,IAAI;AAC9E,QAAI,KAAK;AACP,UAAI,cAAc;AAClB,aAAO,IAAI;AAAA,IACb;AACA,WAAO,KAAK,QAAQ,YAAY,UAAQ;AACtC,YAAM,YAAoC;AAAA,QACxC,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AACA,aAAO,UAAU,IAAI;AAAA,IACvB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,OAAuB;AAE/C,WAAO,MAAM,QAAQ,aAAa,MAAM;AAAA,EAC1C;AACF;;;AC5kCO,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;;;ACoB3B,IAAM,aAAN,MAA0D;AAAA,EAW/D,YAAY,QAAoD;AAC9D,SAAK,SAAS,OAAO;AACrB,SAAK,cAAc,OAAO;AAC1B,SAAK,iBAAiB,OAAO;AAC7B,SAAK,qBAAqB,OAAO;AAGjC,SAAK,WAAW,KAAK,iBAAiB;AAEtC,SAAK,gBAAgB;AAAA,MACnB,gBAAgB,OAAO,eAAe,kBAAkB;AAAA,MACxD,cAAc,OAAO,eAAe,gBAAgB;AAAA,MACpD,OAAO;AAAA,QACL,SAAS,OAAO,eAAe,OAAO,WAAW;AAAA,QACjD,aAAa,OAAO,eAAe,OAAO,eAAe;AAAA,QACzD,SAAS,OAAO,eAAe,OAAO,WAAW;AAAA,MACnD;AAAA,MACA,kBAAkB,OAAO,eAAe,oBAAoB;AAAA,IAC9D;AAGA,UAAM,cACJ,OAAO,eAAe,cACjB,WAAmD,QACpD;AACN,QAAI,OAAO,eAAe,SAAS;AACjC,WAAK,YAAY,OAAO,cAAc;AAAA,IACxC,WAAW,OAAO,gBAAgB,YAAY;AAC5C,WAAK,YAAY,YAAY,KAAK,UAAU;AAAA,IAC9C,OAAO;AACL,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,SAAK,UAAU,KAAK,kBAAkB,MAAM;AAG5C,YAAQ;AAAA,MACN,KAAK,QAAQ;AAAA,QAAI,YACf,OAAO,SAAS;AAAA,UACd,UAAU,KAAK;AAAA,UACf,mBAAmB,KAAK;AAAA,UACxB,SAAS,KAAK;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF,EAAE,MAAM,QAAQ,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAA2B;AACjC,WAAO,YAAY,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC,CAAC;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,QAAkE;AAC1F,UAAM,UAAU,OAAO,WAAW,CAAC;AAGnC,UAAM,YAAY,OAAO,WAAW,eAAe,OAAO,aAAa;AAGvE,QAAI,OAAO,YAAY,OAAO;AAC5B,aAAO;AAAA,IACT;AAGA,QAAI,WAAW;AAEb,YAAM,mBAAmB,QAAQ,KAAK,OAAK,EAAE,SAAS,gBAAgB;AAEtE,UAAI,CAAC,kBAAkB;AAErB,cAAM,gBAAgB,OAAO,WAAW,EAAE,SAAS,OAAO;AAC1D,cAAM,gBAAgB,IAAI,kBAAkB,aAAa;AACzD,eAAO,CAAC,eAAe,GAAG,OAAO;AAAA,MACnC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,SAAyB,oBAA6B,MAAY;AAC9E,UAAM,aAAa,KAAK;AAExB,QAAI,qBAAqB,KAAK,gBAAgB;AAC5C,WAAK,iBAAiB,EAAE,GAAG,KAAK,gBAAgB,GAAG,QAAQ;AAAA,IAC7D,OAAO;AACL,WAAK,iBAAiB;AAAA,IACxB;AAGA,YAAQ;AAAA,MACN,KAAK,QAAQ;AAAA,QAAI,YACf,OAAO,kBAAkB,YAAY,KAAK,gBAAiB,eAAe;AAAA,MAC5E;AAAA,IACF,EAAE,MAAM,QAAQ,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAyC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAiD,aAA8C;AAC7F,WAAO,KAAK,mBAAmB,WAAW;AAAA,EAC5C;AAAA,EAEQ,kBAAkB,WAAyB,UAAsC;AACvF,QAAI,cAAc,UAAa,cAAc,MAAM;AACjD,aAAO;AAAA,IACT;AAEA,WAAO,YAAY;AAAA,EACrB;AAAA,EAEA,MAAM,WACJ,aACA,SACoC;AACpC,UAAM,EAAE,QAAQ,IAAI,WAAW,CAAC;AAGhC,UAAM,gBACJ,OAAO,YAAY,YAAY,YAAY,OACvC,EAAE,GAAI,KAAK,kBAAkB,CAAC,GAAI,GAAG,QAAQ,IAC7C,KAAK;AAEX,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,YAAY,CAAC,WAAqB,GAAG;AAAA,QAC/D,SAAS;AAAA,MACX,CAAC;AAGD,YAAM,QAAQ,SAAS,WAAqB;AAC5C,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,YAAM,QAAQ,IAAI,KAAK,QAAQ,IAAI,YAAU,OAAO,UAAU,OAAgB,aAAa,CAAC,CAAC;AAG7F,YAAM,gBAAgB,KAAK,mBAAmB,WAAW;AAGzD,YAAM,QAAQ;AAAA,QACZ,KAAK,QAAQ;AAAA,UAAI,YACf,OAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,cACA,SAC2D;AAC3D,UAAM,EAAE,SAAS,gBAAgB,IAAI,WAAW,CAAC;AAGjD,UAAM,gBACJ,OAAO,oBAAoB,YAAY,oBAAoB,OACvD,EAAE,GAAI,KAAK,kBAAkB,CAAC,GAAI,GAAG,gBAAgB,IACrD,KAAK;AAGX,QAAI,iBAAiB;AACnB,YAAM,QAAQ;AAAA,QACZ,KAAK,QAAQ;AAAA,UAAI,YACf,OAAO,kBAAkB,KAAK,gBAAgB,eAAgB,SAAS;AAAA,QACzE;AAAA,MACF;AAAA,IACF;AAGA,UAAM,oBAAoB,aAAa,IAAI,UAAQ,IAAc;AAEjE,QAAI;AAEF,YAAM,QAAQ;AAAA,QACZ,KAAK,QAAQ,IAAI,YAAU,OAAO,oBAAoB,mBAAmB,aAAa,CAAC;AAAA,MACzF;AAGA,YAAM,gBAAgB,YAAmD;AACvE,cAAM,MAAM,KAAK,cAAc;AAC/B,cAAM,UAAkC;AAAA,UACtC,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK,MAAM;AAAA,QACtC;AACA,cAAM,OAAO,KAAK,UAAU;AAAA,UAC1B,aAAa,KAAK;AAAA,UAClB,UAAU;AAAA,UACV,SAAS;AAAA,QACX,CAAC;AAGD,cAAM,QAAQ,IAAI,KAAK,QAAQ,IAAI,YAAU,OAAO,gBAAgB,KAAK,MAAM,OAAO,CAAC,CAAC;AAExF,cAAM,YAAY,KAAK,IAAI;AAE3B,cAAM,YACJ,OAAO,eAAe,cACjB,WACE,kBACH;AACN,YAAI;AACJ,YAAI;AACJ,YAAI,KAAK,cAAc,oBAAoB,OAAO,cAAc,YAAY;AAC1E,uBAAa,IAAI,UAAU;AAC3B,sBAAY,WAAW,MAAM,YAAY,MAAM,GAAG,KAAK,cAAc,gBAAgB;AAAA,QACvF;AACA,YAAI;AACJ,YAAI;AACF,qBAAW,MAAM,KAAK,UAAU,KAAK;AAAA,YACnC,QAAQ;AAAA,YACR;AAAA,YACA;AAAA,YACA,QAAQ,YAAY;AAAA,UACtB,CAAC;AAAA,QACH,UAAE;AACA,cAAI,UAAW,cAAa,SAAS;AAAA,QACvC;AACA,cAAM,WAAW,KAAK,IAAI,IAAI;AAG9B,cAAM,QAAQ;AAAA,UACZ,KAAK,QAAQ,IAAI,YAAU,OAAO,gBAAgB,UAAU,EAAE,SAAS,CAAC,CAAC;AAAA,QAC3E;AAEA,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI,MAAM,6BAA6B,SAAS,UAAU,EAAE;AAAA,QACpE;AAEA,cAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,cAAMA,UAAuC,CAAC;AAE9C,0BAAkB,QAAQ,UAAQ;AAChC,gBAAM,YAAY,KAAK,SAAS,IAAI,GAAG;AACvC,UAAAA,QAAO,IAAI,IAAI,KAAK;AAAA,YAClB;AAAA,YACA,KAAK,mBAAmB,IAAuB;AAAA,UACjD;AAAA,QACF,CAAC;AAED,eAAOA;AAAA,MACT;AAEA,YAAM,SAAS,KAAK,cAAc,MAAM,UACpC,MAAM;AAAA,QACJ;AAAA,QACA,KAAK,cAAc,MAAM;AAAA,QACzB,KAAK,cAAc,MAAM;AAAA,QACzB,CAAC,SAAS,OAAO,cAAc;AAE7B,kBAAQ;AAAA,YACN,KAAK,QAAQ,IAAI,YAAU,OAAO,iBAAiB,SAAS,OAAO,SAAS,CAAC;AAAA,UAC/E,EAAE,MAAM,QAAQ,KAAK;AAAA,QACvB;AAAA,MACF,IACA,MAAM,cAAc;AAGxB,YAAM,QAAQ;AAAA,QACZ,KAAK,QAAQ,IAAI,YAAU,OAAO,mBAAmB,QAAQ,aAAa,CAAC;AAAA,MAC7E;AAGA,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,YAAM,QAAQ,IAAI,KAAK,QAAQ,IAAI,YAAU,OAAO,UAAU,OAAgB,aAAa,CAAC,CAAC;AAG7F,YAAM,iBAA+C,CAAC;AAEtD,wBAAkB,QAAQ,iBAAe;AACvC,uBAAe,WAAW,IAAI,KAAK,mBAAmB,WAA8B;AAGpF,gBAAQ;AAAA,UACN,KAAK,QAAQ;AAAA,YAAI,YACf,OAAO;AAAA,cACL;AAAA,cACA,KAAK,mBAAmB,WAA8B;AAAA,cACtD;AAAA,YACF;AAAA,UACF;AAAA,QACF,EAAE,MAAM,QAAQ,KAAK;AAAA,MACvB,CAAC;AAED,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":["result"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/plugins/toolbar-plugin.ts","../src/constants.ts","../src/client.ts"],"sourcesContent":["export type * from './types'\nexport { SupaClient } from './client'\nexport type { SupaPlugin, SupaPluginConfig } from './plugins/types'\nexport { SupaToolbarPlugin as ToolbarPlugin } from './plugins/toolbar-plugin'\nexport type {\n SupaToolbarPluginConfig,\n SupaToolbarPosition,\n SupaToolbarOverrideChange,\n SupaToolbarOverrideChangeCallback,\n} from './plugins/toolbar-plugin'\n// export { LoggingPlugin } from \"./plugins/logging-plugin\";\n// export { CachingPlugin } from \"./plugins/caching-plugin\";\n// export { AnalyticsPlugin } from \"./plugins/analytics-plugin\";\n// export { LocalDevPlugin } from \"./plugins/local-dev-plugin\";\n","export function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n maxAttempts: number = 3,\n backoff: number = 1000,\n onRetry?: (attempt: number, error: Error, willRetry: boolean) => void\n): Promise<T> {\n let lastError: Error\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn()\n } catch (error) {\n lastError = error as Error\n const willRetry = attempt < maxAttempts\n\n if (onRetry) {\n onRetry(attempt, lastError, willRetry)\n }\n\n if (!willRetry) break\n await sleep(backoff * Math.pow(2, attempt - 1))\n }\n }\n\n throw lastError!\n}\n","import { SupaPlugin, SupaPluginConfig } from './types'\nimport { FeatureContext, FeatureValue } from '../types'\n\nexport interface SupaToolbarPosition {\n placement?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n offset?: { x: string; y: string }\n}\n\nexport type SupaToolbarOverrideChange = {\n feature: string\n value: FeatureValue\n}\n\nexport type SupaToolbarOverrideChangeCallback = (\n featureOverride: SupaToolbarOverrideChange,\n allOverrides: Record<string, FeatureValue>\n) => void\n\nexport interface SupaToolbarPluginConfig extends Omit<SupaPluginConfig, 'enabled'> {\n enabled?: boolean | 'auto' // auto means show only on localhost\n position?: SupaToolbarPosition\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n}\n\ninterface SupaToolbarState {\n overrides: Record<string, FeatureValue>\n features: Set<string>\n featureValues: Record<string, FeatureValue>\n context?: FeatureContext\n searchQuery: string\n useLocalOverrides: boolean\n}\n\nconst DEFAULT_STORAGE_KEY = 'supaship-feature-overrides'\n\nconst NO_FEATURES_MESSAGE = `No feature flags configured in the client.`\n\n/**\n * Toolbar plugin for local feature flag testing\n * Provides a visual interface to override feature flags during development\n */\nexport class SupaToolbarPlugin implements SupaPlugin {\n name = 'toolbar-plugin'\n private config: {\n enabled: boolean | 'auto'\n position: Required<SupaToolbarPosition>\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n }\n private state: SupaToolbarState\n private clientId?: string\n private storageKey: string = DEFAULT_STORAGE_KEY\n\n constructor(config: SupaToolbarPluginConfig = {}) {\n this.config = {\n enabled: config.enabled ?? 'auto',\n position: {\n placement: config.position?.placement ?? 'bottom-right',\n offset: config.position?.offset ?? { x: '1rem', y: '1rem' },\n },\n onOverrideChange: config.onOverrideChange,\n }\n\n this.state = {\n overrides: {},\n features: new Set(),\n featureValues: {},\n searchQuery: '',\n useLocalOverrides: true,\n }\n }\n\n cleanup(): void {\n this.removeToolbar()\n }\n\n private shouldShowToolbar(): boolean {\n if (this.config.enabled === true) return true\n if (this.config.enabled === false) return false\n\n // Auto mode: show only on localhost\n if (typeof window !== 'undefined') {\n return (\n window.location.hostname === 'localhost' ||\n window.location.hostname === '127.0.0.1' ||\n window.location.hostname === '' ||\n window.location.hostname.endsWith('.local') ||\n window.location.hostname.endsWith('.localhost')\n )\n }\n return false\n }\n\n onInit(params: {\n availableFeatures: Record<string, FeatureValue>\n context?: FeatureContext\n clientId: string\n }): void {\n const { availableFeatures, context, clientId } = params\n\n // Set client ID for DOM element IDs\n this.clientId = clientId\n\n // Use shared storage key (not client-specific) to persist across refreshes\n this.storageKey = DEFAULT_STORAGE_KEY\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Initialize with all available features and their fallback values from config\n this.state.features = new Set(Object.keys(availableFeatures))\n this.state.featureValues = { ...availableFeatures }\n this.state.context = context\n\n // Inject toolbar if conditions are met\n if (this.shouldShowToolbar()) {\n this.injectToolbar()\n }\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async beforeGetFeatures(_featureNames: string[], context?: FeatureContext): Promise<void> {\n // Update context if it changed\n this.state.context = context\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async afterGetFeatures(\n results: Record<string, FeatureValue>,\n context?: FeatureContext\n ): Promise<void> {\n // Update feature values with fetched results (this replaces config fallback values)\n Object.keys(results).forEach(name => {\n this.state.featureValues[name] = results[name]\n })\n\n // Apply overrides to results only if local overrides are enabled\n if (this.state.useLocalOverrides) {\n Object.keys(this.state.overrides).forEach(featureName => {\n if (featureName in results) {\n results[featureName] = this.state.overrides[featureName]\n }\n })\n }\n\n // Track features and update UI\n Object.keys(results).forEach(name => this.state.features.add(name))\n this.state.context = context\n this.updateToolbarUI()\n }\n\n private loadOverrides(): Record<string, FeatureValue> {\n if (typeof window === 'undefined' || !window.localStorage) {\n return {}\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey)\n return stored ? JSON.parse(stored) : {}\n } catch {\n return {}\n }\n }\n\n private saveOverrides(\n feature?: string,\n value?: FeatureValue,\n allOverrides?: Record<string, FeatureValue>\n ): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return\n }\n\n try {\n window.localStorage.setItem(this.storageKey, JSON.stringify(allOverrides))\n this.config.onOverrideChange?.(\n { feature: feature ?? '', value: value ?? null },\n allOverrides ?? {}\n )\n } catch (error) {\n console.error('Supaship: Failed to save feature overrides:', error)\n }\n }\n\n public setOverride(featureName: string, value: FeatureValue): void {\n this.state.overrides[featureName] = value\n this.saveOverrides(featureName, value, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public removeOverride(featureName: string): void {\n delete this.state.overrides[featureName]\n this.saveOverrides(featureName, null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public clearAllOverrides(): void {\n this.state.overrides = {}\n this.saveOverrides('', null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public getOverrides(): Record<string, FeatureValue> {\n return { ...this.state.overrides }\n }\n\n private injectToolbar(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return\n }\n\n // Check if toolbar with this client ID already exists\n const toolbarId = `supaship-toolbar-${this.clientId}`\n if (document.getElementById(toolbarId)) {\n return\n }\n\n // Create toolbar container\n const toolbar = document.createElement('div')\n toolbar.id = toolbarId\n toolbar.setAttribute('data-supaship-client', this.clientId || '')\n toolbar.innerHTML = this.getToolbarHTML()\n\n // Add styles\n this.injectStyles()\n\n // Add to DOM\n document.body.appendChild(toolbar)\n\n // Add event listeners\n this.attachEventListeners()\n }\n\n private removeToolbar(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toolbar = document.getElementById(`supaship-toolbar-${this.clientId}`)\n if (toolbar) {\n toolbar.remove()\n }\n\n const styles = document.getElementById('supaship-toolbar-styles')\n if (styles) {\n styles.remove()\n }\n }\n\n private getToolbarHTML(): string {\n const { placement, offset } = this.config.position\n const positionClass = `supaship-toolbar-${placement}`\n const offsetX = offset?.x ?? '1rem'\n const offsetY = offset?.y ?? '1rem'\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const closeId = `supaship-close-toolbar-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n\n return `\n <div class=\"supaship-toolbar-container ${positionClass}\" style=\"--offset-x: ${offsetX}; --offset-y: ${offsetY};\">\n <button class=\"supaship-toolbar-toggle\" id=\"${toggleId}\" aria-label=\"Supaship Toolbar\" title=\"Supaship Toolbar\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 256 256\"\n width=\"32\"\n style=\"vertical-align: middle;\">\n <rect width='256' height='256' rx='128' fill='#e4f222'></rect>\n <line\n x1='66'\n y1='118'\n x2='118'\n y2='66'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='186'\n y1='70'\n x2='70'\n y2='186'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='190'\n y1='138'\n x2='138'\n y2='190'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n </svg>\n <span class=\"supaship-toolbar-badge\" id=\"${badgeId}\"></span>\n </button>\n <div class=\"supaship-toolbar-panel\" id=\"${panelId}\">\n <div class=\"supaship-toolbar-header\">\n <input\n type=\"text\"\n class=\"supaship-search-input\"\n id=\"${searchId}\"\n placeholder=\"Search features\"\n />\n <span class=\"supaship-toolbar-overrides-label\" id=\"${headerOverrideCountId}\">0 overrides</span>\n <button\n class=\"supaship-header-btn\"\n id=\"${clearId}\"\n aria-label=\"Reset all overrides\"\n title=\"Reset all overrides to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n <button\n class=\"supaship-header-btn\"\n id=\"${closeId}\"\n aria-label=\"Close toolbar\"\n title=\"Close toolbar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\"/>\n <path d=\"m6 6 12 12\"/>\n </svg>\n </button>\n </div>\n <div class=\"supaship-toolbar-content\" id=\"${contentId}\">\n <div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>\n </div>\n </div>\n </div>\n `\n }\n\n private injectStyles(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n if (document.getElementById('supaship-toolbar-styles')) {\n return\n }\n\n const styles = document.createElement('style')\n styles.id = 'supaship-toolbar-styles'\n styles.textContent = `\n .supaship-toolbar-container {\n position: fixed;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n }\n\n .supaship-toolbar-bottom-right {\n bottom: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-bottom-left {\n bottom: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-top-right {\n top: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-top-left {\n top: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-toggle {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 100%;\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .supaship-toolbar-toggle:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .supaship-toolbar-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n }\n\n .supaship-toolbar-badge.show {\n display: flex;\n }\n\n .supaship-toolbar-panel {\n position: absolute;\n bottom: 48px;\n right: 0;\n width: 300px;\n max-height: 600px;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n display: none;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid #333;\n }\n\n .supaship-toolbar-bottom-left .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n right: auto;\n left: 0;\n }\n\n .supaship-toolbar-top-right .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n bottom: auto;\n top: 60px;\n }\n\n .supaship-toolbar-panel.open {\n display: flex;\n }\n\n .supaship-toolbar-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border-bottom: 1px solid #333;\n background: #0f0f0f;\n min-width: 0;\n overflow: hidden;\n }\n\n .supaship-search-input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n color: #e5e5e5;\n padding: 0;\n font-size: 13px;\n outline: none;\n }\n\n .supaship-search-input::placeholder {\n color: #888;\n }\n\n .supaship-header-btn {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 24px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n padding: 0;\n }\n\n .supaship-header-btn:hover {\n color: #ef4444;\n }\n\n .supaship-toolbar-content {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n min-height: 300px;\n max-height: 300px;\n }\n\n .supaship-toolbar-empty {\n padding: 32px 16px;\n text-align: center;\n color: #888;\n }\n\n .supaship-feature-item {\n padding: 0 6px;\n }\n\n .supaship-feature-item.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .supaship-feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n }\n\n .supaship-feature-name {\n font-weight: 500;\n color: #e5e5e5;\n font-size: 13px;\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .supaship-feature-override-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #ef4444;\n flex-shrink: 0;\n }\n\n .supaship-toolbar-overrides-label {\n font-size: 12px;\n color: #888;\n white-space: nowrap;\n flex-shrink: 0;\n transition: all 0.2s;\n }\n\n .supaship-toolbar-overrides-label-count.has-overrides {\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .supaship-feature-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n min-height: 20px;\n }\n\n .supaship-feature-content {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .supaship-feature-input {\n flex: 1;\n padding: 6px 8px;\n background: #1a1a1a;\n border: 1px solid #555;\n color: #e5e5e5;\n border-radius: 4px;\n font-size: 13px;\n font-family: 'Monaco', 'Courier New', monospace;\n outline: none;\n resize: vertical;\n min-height: 60px;\n margin-bottom: 8px;\n }\n\n .supaship-feature-input:focus {\n border-color: #667eea;\n }\n\n .supaship-btn {\n padding: 4px 12px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .supaship-btn-primary {\n background: #444;\n color: white;\n }\n\n .supaship-btn-primary:hover {\n background: #555;\n }\n\n .supaship-btn-secondary {\n background: #444;\n color: #e5e5e5;\n }\n\n .supaship-btn-secondary:hover {\n background: #555;\n }\n\n .supaship-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-btn:disabled:hover {\n background: #444;\n }\n\n .supaship-header-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n\n .supaship-header-btn:disabled:hover {\n color: #e5e5e5;\n }\n\n .supaship-toggle {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n }\n\n .supaship-toggle input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .supaship-toggle-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #333;\n border: 1px solid #555;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 20px;\n }\n\n .supaship-toggle-slider:before {\n position: absolute;\n content: \"\";\n height: 14px;\n width: 14px;\n left: 2px;\n bottom: 1px;\n background-color: #666;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider {\n background-color: #fff;\n border-color: #fff;\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider:before {\n transform: translateX(13px);\n background-color: #000;\n }\n\n .supaship-toggle input:disabled + .supaship-toggle-slider {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-toggle:hover .supaship-toggle-slider:before {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .supaship-btn-icon {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n cursor: pointer;\n padding: 0;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .supaship-btn-icon:hover {\n background: #444;\n color: #ef4444;\n }\n `\n document.head.appendChild(styles)\n }\n\n private attachEventListeners(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const closeId = `supaship-close-toolbar-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n\n const toggle = document.getElementById(toggleId)\n const panel = document.getElementById(panelId)\n const clearAll = document.getElementById(clearId)\n const close = document.getElementById(closeId)\n const searchInput = document.getElementById(searchId) as HTMLInputElement\n const content = document.getElementById(contentId)\n\n toggle?.addEventListener('click', () => {\n panel?.classList.toggle('open')\n })\n\n close?.addEventListener('click', () => {\n panel?.classList.remove('open')\n })\n\n clearAll?.addEventListener('click', () => {\n this.clearAllOverrides()\n })\n\n searchInput?.addEventListener('input', e => {\n this.state.searchQuery = (e.target as HTMLInputElement).value.toLowerCase()\n this.updateToolbarUI()\n })\n\n // Use event delegation on content element - survives innerHTML updates\n if (content) {\n // Handle button clicks (remove and set actions)\n content.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement\n const buttonElement = target.closest('button[data-action]') as HTMLButtonElement\n if (!buttonElement) return\n\n e.preventDefault()\n e.stopPropagation()\n\n const featureName = buttonElement.dataset.feature!\n const action = buttonElement.dataset.action\n\n if (action === 'remove') {\n this.removeOverride(featureName)\n } else if (action === 'set') {\n const textarea = content.querySelector(\n `textarea[data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLTextAreaElement\n if (textarea && textarea.value.trim()) {\n try {\n const value = JSON.parse(textarea.value)\n this.setOverride(featureName, value)\n } catch {\n // If not valid JSON, wrap string in object\n this.setOverride(featureName, { value: textarea.value })\n }\n }\n }\n })\n\n // Handle checkbox changes for boolean toggles\n content.addEventListener('change', (e: Event) => {\n const target = e.target as HTMLInputElement\n if (target.type === 'checkbox' && target.dataset.type === 'boolean') {\n const featureName = target.dataset.feature!\n const newValue = target.checked\n this.setOverride(featureName, newValue)\n }\n })\n\n // Handle textarea input to update button states\n content.addEventListener('input', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }\n })\n\n // Handle textarea paste events\n content.addEventListener('paste', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n setTimeout(() => {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${featureName}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }, 0)\n }\n })\n\n // Handle Ctrl/Cmd+Enter to set override\n content.addEventListener('keydown', (e: KeyboardEvent) => {\n const target = e.target as HTMLTextAreaElement\n if (\n target.tagName === 'TEXTAREA' &&\n target.dataset.feature &&\n (e.ctrlKey || e.metaKey) &&\n e.key === 'Enter'\n ) {\n e.preventDefault()\n const featureName = target.dataset.feature!\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn && !overrideBtn.disabled) {\n overrideBtn.click()\n }\n }\n })\n }\n }\n\n private updateToolbarUI(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) as HTMLButtonElement\n const badge = document.getElementById(badgeId)\n const headerOverrideCount = document.getElementById(headerOverrideCountId)\n const toggleBtn = document.getElementById(toggleId) as HTMLButtonElement\n\n if (!content) {\n console.warn('[Toolbar] Content element not found:', contentId)\n return\n }\n\n // Update clear all button state and badge\n const overrideCount = Object.keys(this.state.overrides).length\n const hasOverrides = overrideCount > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\n }\n\n // Update badge on toggle button\n if (badge) {\n if (hasOverrides) {\n badge.textContent = overrideCount > 99 ? '99+' : String(overrideCount)\n badge.classList.add('show')\n } else {\n badge.classList.remove('show')\n }\n }\n\n // Update tooltip on toggle button\n if (toggleBtn) {\n if (hasOverrides) {\n toggleBtn.setAttribute(\n 'title',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n toggleBtn.setAttribute(\n 'aria-label',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n } else {\n toggleBtn.setAttribute('title', 'Supaship Toolbar')\n toggleBtn.setAttribute('aria-label', 'Supaship Toolbar')\n }\n }\n\n // Update override count in header\n if (headerOverrideCount) {\n // Escape overrideCount to prevent any potential XSS (defense in depth)\n const escapedCount = this.escapeHtml(String(overrideCount))\n headerOverrideCount.innerHTML = `<span class=\"supaship-toolbar-overrides-label-count ${hasOverrides ? 'has-overrides' : ''}\">${escapedCount}</span> override${overrideCount === 1 ? '' : 's'}`\n }\n\n const features = Array.from(this.state.features).sort()\n\n // Filter features based on search query\n const filteredFeatures = features.filter(name =>\n name.toLowerCase().includes(this.state.searchQuery)\n )\n\n if (filteredFeatures.length === 0) {\n content.innerHTML = this.state.searchQuery\n ? '<div class=\"supaship-toolbar-empty\">No matching features found</div>'\n : `<div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>`\n return\n }\n\n const htmlContent = filteredFeatures\n .map(featureName => {\n const hasOverride = featureName in this.state.overrides\n const currentValue = this.state.featureValues[featureName]\n const overrideValue = hasOverride ? this.state.overrides[featureName] : currentValue\n const isDisabled = !this.state.useLocalOverrides\n const itemClass = `supaship-feature-item ${isDisabled ? 'disabled' : ''}`\n\n // Check if the feature is boolean\n const isBoolean =\n typeof currentValue === 'boolean' || (hasOverride && typeof overrideValue === 'boolean')\n\n if (isBoolean) {\n // Render toggle switch for boolean values (single row layout)\n const isChecked = hasOverride ? overrideValue === true : currentValue === true\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${this.escapeHtml(featureName)}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n `\n : ''\n }\n <label class=\"supaship-toggle\">\n <input\n type=\"checkbox\"\n ${isChecked ? 'checked' : ''}\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-type=\"boolean\"\n />\n <span class=\"supaship-toggle-slider\"></span>\n </label>\n </div>\n </div>\n </div>\n `\n } else {\n // Render textarea for non-boolean values\n const currentDisplayValue = hasOverride\n ? JSON.stringify(overrideValue)\n : currentValue !== undefined\n ? JSON.stringify(currentValue)\n : ''\n const escapedFeatureName = this.escapeHtml(featureName)\n const escapedCurrentDisplayValue = this.escapeHtml(currentDisplayValue)\n const escapedTextareaContent = hasOverride\n ? this.escapeHtml(JSON.stringify(overrideValue))\n : escapedCurrentDisplayValue\n\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${escapedFeatureName}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n `\n : ''\n }\n <button\n class=\"supaship-btn supaship-btn-primary\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"set\"\n disabled>\n Override\n </button>\n </div>\n </div>\n <div class=\"supaship-feature-content\">\n <textarea\n class=\"supaship-feature-input\"\n placeholder=\"Override JSON value\"\n data-feature=\"${escapedFeatureName}\"\n data-original=\"${escapedCurrentDisplayValue}\"\n >${escapedTextareaContent}</textarea>\n </div>\n </div>\n `\n }\n })\n .join('')\n\n requestAnimationFrame(() => {\n // Set innerHTML - event listeners are handled via delegation in attachEventListeners()\n content.innerHTML = htmlContent\n\n // Update button states for textareas that already have values\n content.querySelectorAll('textarea[data-feature]').forEach(textarea => {\n const textareaElement = textarea as HTMLTextAreaElement\n const featureName = textareaElement.dataset.feature!\n const originalValue = textareaElement.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = textareaElement.value !== originalValue\n const hasContent = textareaElement.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n })\n })\n }\n\n private escapeHtml(text: string): string {\n const div = typeof document !== 'undefined' ? document.createElement('div') : null\n if (div) {\n div.textContent = text\n return div.innerHTML\n }\n return text.replace(/[&<>\"']/g, char => {\n const escapeMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return escapeMap[char]\n })\n }\n\n /**\n * Escapes special characters in CSS attribute selectors to prevent CSS injection\n * @param value The value to escape for use in CSS attribute selectors\n */\n private escapeCssSelector(value: string): string {\n // Escape special CSS selector characters: \", ', ], \\\n return value.replace(/[\"'\\\\\\]]/g, '\\\\$&')\n }\n}\n","export const DEFAULT_FEATURES_URL = 'https://edge.supaship.com/v1/features'\nexport const DEFAULT_EVENTS_URL = 'https://edge.supaship.com/v1/events'\n","import {\n SupaClientConfig,\n FeatureContext,\n FeatureValue,\n NetworkConfig,\n Features,\n FeaturesWithFallbacks,\n} from './types'\nimport { retry } from './utils'\nimport { SupaPlugin } from './plugins/types'\nimport { SupaToolbarPlugin } from './plugins/toolbar-plugin'\nimport { DEFAULT_FEATURES_URL, DEFAULT_EVENTS_URL } from './constants'\n\ntype RequiredRetryConfig = Required<NonNullable<NetworkConfig['retry']>>\ntype ResolvedNetworkConfig = {\n featuresAPIUrl: string\n eventsAPIUrl: string\n retry: RequiredRetryConfig\n requestTimeoutMs: number\n}\n\nexport class SupaClient<TFeatures extends FeaturesWithFallbacks> {\n private apiKey: string\n private environment: string\n private defaultContext?: FeatureContext\n private plugins: SupaPlugin[]\n private featureDefinitions: Features<TFeatures>\n private clientId: string\n\n private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n private networkConfig: ResolvedNetworkConfig\n\n constructor(config: SupaClientConfig & { features: TFeatures }) {\n this.apiKey = config.apiKey\n this.environment = config.environment\n this.defaultContext = config.context\n this.featureDefinitions = config.features as Features<TFeatures>\n\n // Generate unique client ID\n this.clientId = this.generateClientId()\n\n this.networkConfig = {\n featuresAPIUrl: config.networkConfig?.featuresAPIUrl || DEFAULT_FEATURES_URL,\n eventsAPIUrl: config.networkConfig?.eventsAPIUrl || DEFAULT_EVENTS_URL,\n retry: {\n enabled: config.networkConfig?.retry?.enabled ?? true,\n maxAttempts: config.networkConfig?.retry?.maxAttempts ?? 3,\n backoff: config.networkConfig?.retry?.backoff ?? 1000,\n },\n requestTimeoutMs: config.networkConfig?.requestTimeoutMs ?? 10000,\n }\n\n // Prefer injected fetch, then global fetch if available\n const globalFetch: typeof fetch | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { fetch?: typeof fetch }).fetch\n : undefined\n if (config.networkConfig?.fetchFn) {\n this.fetchImpl = config.networkConfig.fetchFn\n } else if (typeof globalFetch === 'function') {\n this.fetchImpl = globalFetch.bind(globalThis)\n } else {\n throw new Error(\n 'No fetch implementation available. Provide fetchFn in config or use a runtime with global fetch (e.g., Node 18+, browsers).'\n )\n }\n\n // Initialize plugins with automatic toolbar plugin in browser\n this.plugins = this.initializePlugins(config)\n\n // Initialize plugins with available features and their fallback values\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onInit?.({\n clientId: this.clientId,\n availableFeatures: this.featureDefinitions,\n context: this.defaultContext,\n })\n )\n ).catch(console.error)\n }\n\n /**\n * Generate a unique client ID\n */\n private generateClientId(): string {\n return `supaship-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * Initialize plugins with automatic toolbar plugin in browser environments\n */\n private initializePlugins(config: SupaClientConfig & { features: TFeatures }): SupaPlugin[] {\n const plugins = config.plugins || []\n\n // Check if we're in a browser environment\n const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'\n\n // If toolbar is explicitly disabled, don't add it\n if (config.toolbar === false) {\n return plugins\n }\n\n // If in browser and toolbar not disabled, add it automatically\n if (isBrowser) {\n // Check if user already added toolbar plugin manually\n const hasToolbarPlugin = plugins.some(p => p.name === 'toolbar-plugin')\n\n if (!hasToolbarPlugin) {\n // Add toolbar with user config or defaults\n const toolbarConfig = config.toolbar || { enabled: 'auto' }\n const toolbarPlugin = new SupaToolbarPlugin(toolbarConfig)\n return [toolbarPlugin, ...plugins]\n }\n }\n\n return plugins\n }\n\n /**\n * Updates the default context for the client\n * @param context - New context to merge with or replace the existing context\n * @param mergeWithExisting - Whether to merge with existing context (default: true)\n */\n updateContext(context: FeatureContext, mergeWithExisting: boolean = true): void {\n const oldContext = this.defaultContext\n\n if (mergeWithExisting && this.defaultContext) {\n this.defaultContext = { ...this.defaultContext, ...context }\n } else {\n this.defaultContext = context\n }\n\n // Notify plugins of context change\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(oldContext, this.defaultContext!, 'updateContext')\n )\n ).catch(console.error)\n }\n\n /**\n * Gets the current default context\n */\n getContext(): FeatureContext | undefined {\n return this.defaultContext\n }\n\n /**\n * Gets the fallback value for a feature from its definition\n */\n getFeatureFallback<TKey extends keyof TFeatures>(featureName: TKey): Features<TFeatures>[TKey] {\n return this.featureDefinitions[featureName]\n }\n\n private getVariationValue(variation: FeatureValue, fallback: FeatureValue): FeatureValue {\n if (variation !== undefined && variation !== null) {\n return variation\n }\n\n return fallback ?? null\n }\n\n async getFeature<TKey extends keyof TFeatures>(\n featureName: TKey,\n options?: { context?: FeatureContext }\n ): Promise<Features<TFeatures>[TKey]> {\n const { context } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof context === 'object' && context !== null\n ? { ...(this.defaultContext ?? {}), ...context }\n : this.defaultContext\n\n try {\n const response = await this.getFeatures([featureName as string], {\n context: mergedContext,\n })\n\n // Get the specific feature value\n const value = response[featureName as string]\n return value as Features<TFeatures>[TKey]\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Use fallback feature value when API fails\n const fallbackValue = this.featureDefinitions[featureName]\n\n // Notify plugins that fallback was used\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName as string,\n fallbackValue as FeatureValue,\n error as Error\n )\n )\n )\n return fallbackValue as Features<TFeatures>[TKey]\n }\n }\n\n async getFeatures<TKeys extends readonly (keyof TFeatures)[]>(\n featureNames: TKeys,\n options?: { context?: FeatureContext }\n ): Promise<{ [K in TKeys[number]]: Features<TFeatures>[K] }> {\n const { context: contextOverride } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof contextOverride === 'object' && contextOverride !== null\n ? { ...(this.defaultContext ?? {}), ...contextOverride }\n : this.defaultContext\n\n // Notify plugins of context update for this request\n if (contextOverride) {\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(this.defaultContext, mergedContext!, 'request')\n )\n )\n }\n\n // Convert feature names to strings for API call\n const featureNamesArray = featureNames.map(name => name as string)\n\n try {\n // Run beforeGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.beforeGetFeatures?.(featureNamesArray, mergedContext))\n )\n\n type FeaturesResponse = { features: Record<string, { variation: FeatureValue }> }\n const fetchFeatures = async (): Promise<Record<string, FeatureValue>> => {\n const url = this.networkConfig.featuresAPIUrl\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n }\n const body = JSON.stringify({\n environment: this.environment,\n features: featureNamesArray,\n context: mergedContext,\n })\n\n // Notify plugins before request\n await Promise.all(this.plugins.map(plugin => plugin.beforeRequest?.(url, body, headers)))\n\n const startTime = Date.now()\n // Support timeout via AbortController when available\n const AbortCtrl: typeof AbortController | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { AbortController?: typeof AbortController })\n .AbortController\n : undefined\n let controller: AbortController | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n if (this.networkConfig.requestTimeoutMs && typeof AbortCtrl === 'function') {\n controller = new AbortCtrl()\n timeoutId = setTimeout(() => controller?.abort(), this.networkConfig.requestTimeoutMs)\n }\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n signal: controller?.signal,\n })\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n const duration = Date.now() - startTime\n\n // Notify plugins after response\n await Promise.all(\n this.plugins.map(plugin => plugin.afterResponse?.(response, { duration }))\n )\n\n if (!response.ok) {\n throw new Error(`Failed to fetch features: ${response.statusText}`)\n }\n\n const data = (await response.json()) as FeaturesResponse\n const result: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(name => {\n const variation = data.features[name]?.variation\n result[name] = this.getVariationValue(\n variation,\n this.featureDefinitions[name as keyof TFeatures]\n )\n })\n\n return result\n }\n\n const result = this.networkConfig.retry.enabled\n ? await retry(\n fetchFeatures,\n this.networkConfig.retry.maxAttempts,\n this.networkConfig.retry.backoff,\n (attempt, error, willRetry) => {\n // Notify plugins of retry attempts\n Promise.all(\n this.plugins.map(plugin => plugin.onRetryAttempt?.(attempt, error, willRetry))\n ).catch(console.error)\n }\n )\n : await fetchFeatures()\n\n // Run afterGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.afterGetFeatures?.(result, mergedContext))\n )\n\n // Return the fetched features\n return result as { [K in TKeys[number]]: Features<TFeatures>[K] }\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Create fallback result with requested feature names\n const fallbackResult: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(featureName => {\n fallbackResult[featureName] = this.featureDefinitions[featureName as keyof TFeatures]\n\n // Notify plugins that fallback was used for each feature\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName,\n this.featureDefinitions[featureName as keyof TFeatures],\n error as Error\n )\n )\n ).catch(console.error)\n })\n\n return fallbackResult as { [K in TKeys[number]]: Features<TFeatures>[K] }\n }\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["result"]}
|
package/dist/index.mjs
CHANGED
|
@@ -168,6 +168,7 @@ var SupaToolbarPlugin = class {
|
|
|
168
168
|
const panelId = `supaship-toolbar-panel-${this.clientId}`;
|
|
169
169
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
170
170
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
171
|
+
const closeId = `supaship-close-toolbar-${this.clientId}`;
|
|
171
172
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
172
173
|
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
173
174
|
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
@@ -177,7 +178,7 @@ var SupaToolbarPlugin = class {
|
|
|
177
178
|
<svg
|
|
178
179
|
xmlns="http://www.w3.org/2000/svg"
|
|
179
180
|
viewBox="0 0 256 256"
|
|
180
|
-
width="
|
|
181
|
+
width="32"
|
|
181
182
|
style="vertical-align: middle;">
|
|
182
183
|
<rect width='256' height='256' rx='128' fill='#e4f222'></rect>
|
|
183
184
|
<line
|
|
@@ -228,8 +229,22 @@ var SupaToolbarPlugin = class {
|
|
|
228
229
|
aria-label="Reset all overrides"
|
|
229
230
|
title="Reset all overrides to default"
|
|
230
231
|
>
|
|
231
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
232
|
-
<path d="
|
|
232
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
233
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
234
|
+
<path d="M3 3v5h5"/>
|
|
235
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
236
|
+
<path d="M16 16h5v5"/>
|
|
237
|
+
</svg>
|
|
238
|
+
</button>
|
|
239
|
+
<button
|
|
240
|
+
class="supaship-header-btn"
|
|
241
|
+
id="${closeId}"
|
|
242
|
+
aria-label="Close toolbar"
|
|
243
|
+
title="Close toolbar"
|
|
244
|
+
>
|
|
245
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
246
|
+
<path d="M18 6 6 18"/>
|
|
247
|
+
<path d="m6 6 12 12"/>
|
|
233
248
|
</svg>
|
|
234
249
|
</button>
|
|
235
250
|
</div>
|
|
@@ -282,7 +297,6 @@ var SupaToolbarPlugin = class {
|
|
|
282
297
|
width: 36px;
|
|
283
298
|
height: 36px;
|
|
284
299
|
border-radius: 100%;
|
|
285
|
-
background: #000;
|
|
286
300
|
border: none;
|
|
287
301
|
color: white;
|
|
288
302
|
cursor: pointer;
|
|
@@ -636,16 +650,21 @@ var SupaToolbarPlugin = class {
|
|
|
636
650
|
const toggleId = `supaship-toolbar-toggle-${this.clientId}`;
|
|
637
651
|
const panelId = `supaship-toolbar-panel-${this.clientId}`;
|
|
638
652
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
653
|
+
const closeId = `supaship-close-toolbar-${this.clientId}`;
|
|
639
654
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
640
655
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
641
656
|
const toggle = document.getElementById(toggleId);
|
|
642
657
|
const panel = document.getElementById(panelId);
|
|
643
658
|
const clearAll = document.getElementById(clearId);
|
|
659
|
+
const close = document.getElementById(closeId);
|
|
644
660
|
const searchInput = document.getElementById(searchId);
|
|
645
661
|
const content = document.getElementById(contentId);
|
|
646
662
|
toggle?.addEventListener("click", () => {
|
|
647
663
|
panel?.classList.toggle("open");
|
|
648
664
|
});
|
|
665
|
+
close?.addEventListener("click", () => {
|
|
666
|
+
panel?.classList.remove("open");
|
|
667
|
+
});
|
|
649
668
|
clearAll?.addEventListener("click", () => {
|
|
650
669
|
this.clearAllOverrides();
|
|
651
670
|
});
|
|
@@ -815,8 +834,11 @@ var SupaToolbarPlugin = class {
|
|
|
815
834
|
data-action="remove"
|
|
816
835
|
title="Reset to default"
|
|
817
836
|
>
|
|
818
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
819
|
-
<path d="
|
|
837
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
838
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
839
|
+
<path d="M3 3v5h5"/>
|
|
840
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
841
|
+
<path d="M16 16h5v5"/>
|
|
820
842
|
</svg>
|
|
821
843
|
</button>
|
|
822
844
|
` : ""}
|
|
@@ -853,8 +875,11 @@ var SupaToolbarPlugin = class {
|
|
|
853
875
|
data-action="remove"
|
|
854
876
|
title="Reset to default"
|
|
855
877
|
>
|
|
856
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
857
|
-
<path d="
|
|
878
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
879
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
|
880
|
+
<path d="M3 3v5h5"/>
|
|
881
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
|
882
|
+
<path d="M16 16h5v5"/>
|
|
858
883
|
</svg>
|
|
859
884
|
</button>
|
|
860
885
|
` : ""}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts","../src/plugins/toolbar-plugin.ts","../src/constants.ts","../src/client.ts"],"sourcesContent":["export function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n maxAttempts: number = 3,\n backoff: number = 1000,\n onRetry?: (attempt: number, error: Error, willRetry: boolean) => void\n): Promise<T> {\n let lastError: Error\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn()\n } catch (error) {\n lastError = error as Error\n const willRetry = attempt < maxAttempts\n\n if (onRetry) {\n onRetry(attempt, lastError, willRetry)\n }\n\n if (!willRetry) break\n await sleep(backoff * Math.pow(2, attempt - 1))\n }\n }\n\n throw lastError!\n}\n","import { SupaPlugin, SupaPluginConfig } from './types'\nimport { FeatureContext, FeatureValue } from '../types'\n\nexport interface SupaToolbarPosition {\n placement?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n offset?: { x: string; y: string }\n}\n\nexport type SupaToolbarOverrideChange = {\n feature: string\n value: FeatureValue\n}\n\nexport type SupaToolbarOverrideChangeCallback = (\n featureOverride: SupaToolbarOverrideChange,\n allOverrides: Record<string, FeatureValue>\n) => void\n\nexport interface SupaToolbarPluginConfig extends Omit<SupaPluginConfig, 'enabled'> {\n enabled?: boolean | 'auto' // auto means show only on localhost\n position?: SupaToolbarPosition\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n}\n\ninterface SupaToolbarState {\n overrides: Record<string, FeatureValue>\n features: Set<string>\n featureValues: Record<string, FeatureValue>\n context?: FeatureContext\n searchQuery: string\n useLocalOverrides: boolean\n}\n\nconst DEFAULT_STORAGE_KEY = 'supaship-feature-overrides'\n\nconst NO_FEATURES_MESSAGE = `No feature flags configured in the client.`\n\n/**\n * Toolbar plugin for local feature flag testing\n * Provides a visual interface to override feature flags during development\n */\nexport class SupaToolbarPlugin implements SupaPlugin {\n name = 'toolbar-plugin'\n private config: {\n enabled: boolean | 'auto'\n position: Required<SupaToolbarPosition>\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n }\n private state: SupaToolbarState\n private clientId?: string\n private storageKey: string = DEFAULT_STORAGE_KEY\n\n constructor(config: SupaToolbarPluginConfig = {}) {\n this.config = {\n enabled: config.enabled ?? 'auto',\n position: {\n placement: config.position?.placement ?? 'bottom-right',\n offset: config.position?.offset ?? { x: '1rem', y: '1rem' },\n },\n onOverrideChange: config.onOverrideChange,\n }\n\n this.state = {\n overrides: {},\n features: new Set(),\n featureValues: {},\n searchQuery: '',\n useLocalOverrides: true,\n }\n }\n\n cleanup(): void {\n this.removeToolbar()\n }\n\n private shouldShowToolbar(): boolean {\n if (this.config.enabled === true) return true\n if (this.config.enabled === false) return false\n\n // Auto mode: show only on localhost\n if (typeof window !== 'undefined') {\n return (\n window.location.hostname === 'localhost' ||\n window.location.hostname === '127.0.0.1' ||\n window.location.hostname === '' ||\n window.location.hostname.endsWith('.local') ||\n window.location.hostname.endsWith('.localhost')\n )\n }\n return false\n }\n\n onInit(params: {\n availableFeatures: Record<string, FeatureValue>\n context?: FeatureContext\n clientId: string\n }): void {\n const { availableFeatures, context, clientId } = params\n\n // Set client ID for DOM element IDs\n this.clientId = clientId\n\n // Use shared storage key (not client-specific) to persist across refreshes\n this.storageKey = DEFAULT_STORAGE_KEY\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Initialize with all available features and their fallback values from config\n this.state.features = new Set(Object.keys(availableFeatures))\n this.state.featureValues = { ...availableFeatures }\n this.state.context = context\n\n // Inject toolbar if conditions are met\n if (this.shouldShowToolbar()) {\n this.injectToolbar()\n }\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async beforeGetFeatures(_featureNames: string[], context?: FeatureContext): Promise<void> {\n // Update context if it changed\n this.state.context = context\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async afterGetFeatures(\n results: Record<string, FeatureValue>,\n context?: FeatureContext\n ): Promise<void> {\n // Update feature values with fetched results (this replaces config fallback values)\n Object.keys(results).forEach(name => {\n this.state.featureValues[name] = results[name]\n })\n\n // Apply overrides to results only if local overrides are enabled\n if (this.state.useLocalOverrides) {\n Object.keys(this.state.overrides).forEach(featureName => {\n if (featureName in results) {\n results[featureName] = this.state.overrides[featureName]\n }\n })\n }\n\n // Track features and update UI\n Object.keys(results).forEach(name => this.state.features.add(name))\n this.state.context = context\n this.updateToolbarUI()\n }\n\n private loadOverrides(): Record<string, FeatureValue> {\n if (typeof window === 'undefined' || !window.localStorage) {\n return {}\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey)\n return stored ? JSON.parse(stored) : {}\n } catch {\n return {}\n }\n }\n\n private saveOverrides(\n feature?: string,\n value?: FeatureValue,\n allOverrides?: Record<string, FeatureValue>\n ): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return\n }\n\n try {\n window.localStorage.setItem(this.storageKey, JSON.stringify(allOverrides))\n this.config.onOverrideChange?.(\n { feature: feature ?? '', value: value ?? null },\n allOverrides ?? {}\n )\n } catch (error) {\n console.error('Supaship: Failed to save feature overrides:', error)\n }\n }\n\n public setOverride(featureName: string, value: FeatureValue): void {\n this.state.overrides[featureName] = value\n this.saveOverrides(featureName, value, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public removeOverride(featureName: string): void {\n delete this.state.overrides[featureName]\n this.saveOverrides(featureName, null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public clearAllOverrides(): void {\n this.state.overrides = {}\n this.saveOverrides('', null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public getOverrides(): Record<string, FeatureValue> {\n return { ...this.state.overrides }\n }\n\n private injectToolbar(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return\n }\n\n // Check if toolbar with this client ID already exists\n const toolbarId = `supaship-toolbar-${this.clientId}`\n if (document.getElementById(toolbarId)) {\n return\n }\n\n // Create toolbar container\n const toolbar = document.createElement('div')\n toolbar.id = toolbarId\n toolbar.setAttribute('data-supaship-client', this.clientId || '')\n toolbar.innerHTML = this.getToolbarHTML()\n\n // Add styles\n this.injectStyles()\n\n // Add to DOM\n document.body.appendChild(toolbar)\n\n // Add event listeners\n this.attachEventListeners()\n }\n\n private removeToolbar(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toolbar = document.getElementById(`supaship-toolbar-${this.clientId}`)\n if (toolbar) {\n toolbar.remove()\n }\n\n const styles = document.getElementById('supaship-toolbar-styles')\n if (styles) {\n styles.remove()\n }\n }\n\n private getToolbarHTML(): string {\n const { placement, offset } = this.config.position\n const positionClass = `supaship-toolbar-${placement}`\n const offsetX = offset?.x ?? '1rem'\n const offsetY = offset?.y ?? '1rem'\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n\n return `\n <div class=\"supaship-toolbar-container ${positionClass}\" style=\"--offset-x: ${offsetX}; --offset-y: ${offsetY};\">\n <button class=\"supaship-toolbar-toggle\" id=\"${toggleId}\" aria-label=\"Supaship Toolbar\" title=\"Supaship Toolbar\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 256 256\"\n width=\"24\"\n style=\"vertical-align: middle;\">\n <rect width='256' height='256' rx='128' fill='#e4f222'></rect>\n <line\n x1='66'\n y1='118'\n x2='118'\n y2='66'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='186'\n y1='70'\n x2='70'\n y2='186'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='190'\n y1='138'\n x2='138'\n y2='190'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n </svg>\n <span class=\"supaship-toolbar-badge\" id=\"${badgeId}\"></span>\n </button>\n <div class=\"supaship-toolbar-panel\" id=\"${panelId}\">\n <div class=\"supaship-toolbar-header\">\n <input\n type=\"text\"\n class=\"supaship-search-input\"\n id=\"${searchId}\"\n placeholder=\"Search features\"\n />\n <span class=\"supaship-toolbar-overrides-label\" id=\"${headerOverrideCountId}\">0 overrides</span>\n <button\n class=\"supaship-header-btn\"\n id=\"${clearId}\"\n aria-label=\"Reset all overrides\"\n title=\"Reset all overrides to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"18\" height=\"18\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n </div>\n <div class=\"supaship-toolbar-content\" id=\"${contentId}\">\n <div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>\n </div>\n </div>\n </div>\n `\n }\n\n private injectStyles(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n if (document.getElementById('supaship-toolbar-styles')) {\n return\n }\n\n const styles = document.createElement('style')\n styles.id = 'supaship-toolbar-styles'\n styles.textContent = `\n .supaship-toolbar-container {\n position: fixed;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n }\n\n .supaship-toolbar-bottom-right {\n bottom: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-bottom-left {\n bottom: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-top-right {\n top: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-top-left {\n top: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-toggle {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 100%;\n background: #000;\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .supaship-toolbar-toggle:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .supaship-toolbar-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n }\n\n .supaship-toolbar-badge.show {\n display: flex;\n }\n\n .supaship-toolbar-panel {\n position: absolute;\n bottom: 48px;\n right: 0;\n width: 300px;\n max-height: 600px;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n display: none;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid #333;\n }\n\n .supaship-toolbar-bottom-left .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n right: auto;\n left: 0;\n }\n\n .supaship-toolbar-top-right .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n bottom: auto;\n top: 60px;\n }\n\n .supaship-toolbar-panel.open {\n display: flex;\n }\n\n .supaship-toolbar-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border-bottom: 1px solid #333;\n background: #0f0f0f;\n min-width: 0;\n overflow: hidden;\n }\n\n .supaship-search-input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n color: #e5e5e5;\n padding: 0;\n font-size: 13px;\n outline: none;\n }\n\n .supaship-search-input::placeholder {\n color: #888;\n }\n\n .supaship-header-btn {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 24px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n padding: 0;\n }\n\n .supaship-header-btn:hover {\n color: #ef4444;\n }\n\n .supaship-toolbar-content {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n min-height: 300px;\n max-height: 300px;\n }\n\n .supaship-toolbar-empty {\n padding: 32px 16px;\n text-align: center;\n color: #888;\n }\n\n .supaship-feature-item {\n padding: 0 6px;\n }\n\n .supaship-feature-item.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .supaship-feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n }\n\n .supaship-feature-name {\n font-weight: 500;\n color: #e5e5e5;\n font-size: 13px;\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .supaship-feature-override-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #ef4444;\n flex-shrink: 0;\n }\n\n .supaship-toolbar-overrides-label {\n font-size: 12px;\n color: #888;\n white-space: nowrap;\n flex-shrink: 0;\n transition: all 0.2s;\n }\n\n .supaship-toolbar-overrides-label-count.has-overrides {\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .supaship-feature-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n min-height: 20px;\n }\n\n .supaship-feature-content {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .supaship-feature-input {\n flex: 1;\n padding: 6px 8px;\n background: #1a1a1a;\n border: 1px solid #555;\n color: #e5e5e5;\n border-radius: 4px;\n font-size: 13px;\n font-family: 'Monaco', 'Courier New', monospace;\n outline: none;\n resize: vertical;\n min-height: 60px;\n margin-bottom: 8px;\n }\n\n .supaship-feature-input:focus {\n border-color: #667eea;\n }\n\n .supaship-btn {\n padding: 4px 12px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .supaship-btn-primary {\n background: #444;\n color: white;\n }\n\n .supaship-btn-primary:hover {\n background: #555;\n }\n\n .supaship-btn-secondary {\n background: #444;\n color: #e5e5e5;\n }\n\n .supaship-btn-secondary:hover {\n background: #555;\n }\n\n .supaship-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-btn:disabled:hover {\n background: #444;\n }\n\n .supaship-header-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n\n .supaship-header-btn:disabled:hover {\n color: #e5e5e5;\n }\n\n .supaship-toggle {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n }\n\n .supaship-toggle input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .supaship-toggle-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #333;\n border: 1px solid #555;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 20px;\n }\n\n .supaship-toggle-slider:before {\n position: absolute;\n content: \"\";\n height: 14px;\n width: 14px;\n left: 2px;\n bottom: 1px;\n background-color: #666;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider {\n background-color: #fff;\n border-color: #fff;\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider:before {\n transform: translateX(13px);\n background-color: #000;\n }\n\n .supaship-toggle input:disabled + .supaship-toggle-slider {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-toggle:hover .supaship-toggle-slider:before {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .supaship-btn-icon {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n cursor: pointer;\n padding: 0;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .supaship-btn-icon:hover {\n background: #444;\n color: #ef4444;\n }\n `\n document.head.appendChild(styles)\n }\n\n private attachEventListeners(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n\n const toggle = document.getElementById(toggleId)\n const panel = document.getElementById(panelId)\n const clearAll = document.getElementById(clearId)\n const searchInput = document.getElementById(searchId) as HTMLInputElement\n const content = document.getElementById(contentId)\n\n toggle?.addEventListener('click', () => {\n panel?.classList.toggle('open')\n })\n\n clearAll?.addEventListener('click', () => {\n this.clearAllOverrides()\n })\n\n searchInput?.addEventListener('input', e => {\n this.state.searchQuery = (e.target as HTMLInputElement).value.toLowerCase()\n this.updateToolbarUI()\n })\n\n // Use event delegation on content element - survives innerHTML updates\n if (content) {\n // Handle button clicks (remove and set actions)\n content.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement\n const buttonElement = target.closest('button[data-action]') as HTMLButtonElement\n if (!buttonElement) return\n\n e.preventDefault()\n e.stopPropagation()\n\n const featureName = buttonElement.dataset.feature!\n const action = buttonElement.dataset.action\n\n if (action === 'remove') {\n this.removeOverride(featureName)\n } else if (action === 'set') {\n const textarea = content.querySelector(\n `textarea[data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLTextAreaElement\n if (textarea && textarea.value.trim()) {\n try {\n const value = JSON.parse(textarea.value)\n this.setOverride(featureName, value)\n } catch {\n // If not valid JSON, wrap string in object\n this.setOverride(featureName, { value: textarea.value })\n }\n }\n }\n })\n\n // Handle checkbox changes for boolean toggles\n content.addEventListener('change', (e: Event) => {\n const target = e.target as HTMLInputElement\n if (target.type === 'checkbox' && target.dataset.type === 'boolean') {\n const featureName = target.dataset.feature!\n const newValue = target.checked\n this.setOverride(featureName, newValue)\n }\n })\n\n // Handle textarea input to update button states\n content.addEventListener('input', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }\n })\n\n // Handle textarea paste events\n content.addEventListener('paste', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n setTimeout(() => {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${featureName}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }, 0)\n }\n })\n\n // Handle Ctrl/Cmd+Enter to set override\n content.addEventListener('keydown', (e: KeyboardEvent) => {\n const target = e.target as HTMLTextAreaElement\n if (\n target.tagName === 'TEXTAREA' &&\n target.dataset.feature &&\n (e.ctrlKey || e.metaKey) &&\n e.key === 'Enter'\n ) {\n e.preventDefault()\n const featureName = target.dataset.feature!\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn && !overrideBtn.disabled) {\n overrideBtn.click()\n }\n }\n })\n }\n }\n\n private updateToolbarUI(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) as HTMLButtonElement\n const badge = document.getElementById(badgeId)\n const headerOverrideCount = document.getElementById(headerOverrideCountId)\n const toggleBtn = document.getElementById(toggleId) as HTMLButtonElement\n\n if (!content) {\n console.warn('[Toolbar] Content element not found:', contentId)\n return\n }\n\n // Update clear all button state and badge\n const overrideCount = Object.keys(this.state.overrides).length\n const hasOverrides = overrideCount > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\n }\n\n // Update badge on toggle button\n if (badge) {\n if (hasOverrides) {\n badge.textContent = overrideCount > 99 ? '99+' : String(overrideCount)\n badge.classList.add('show')\n } else {\n badge.classList.remove('show')\n }\n }\n\n // Update tooltip on toggle button\n if (toggleBtn) {\n if (hasOverrides) {\n toggleBtn.setAttribute(\n 'title',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n toggleBtn.setAttribute(\n 'aria-label',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n } else {\n toggleBtn.setAttribute('title', 'Supaship Toolbar')\n toggleBtn.setAttribute('aria-label', 'Supaship Toolbar')\n }\n }\n\n // Update override count in header\n if (headerOverrideCount) {\n // Escape overrideCount to prevent any potential XSS (defense in depth)\n const escapedCount = this.escapeHtml(String(overrideCount))\n headerOverrideCount.innerHTML = `<span class=\"supaship-toolbar-overrides-label-count ${hasOverrides ? 'has-overrides' : ''}\">${escapedCount}</span> override${overrideCount === 1 ? '' : 's'}`\n }\n\n const features = Array.from(this.state.features).sort()\n\n // Filter features based on search query\n const filteredFeatures = features.filter(name =>\n name.toLowerCase().includes(this.state.searchQuery)\n )\n\n if (filteredFeatures.length === 0) {\n content.innerHTML = this.state.searchQuery\n ? '<div class=\"supaship-toolbar-empty\">No matching features found</div>'\n : `<div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>`\n return\n }\n\n const htmlContent = filteredFeatures\n .map(featureName => {\n const hasOverride = featureName in this.state.overrides\n const currentValue = this.state.featureValues[featureName]\n const overrideValue = hasOverride ? this.state.overrides[featureName] : currentValue\n const isDisabled = !this.state.useLocalOverrides\n const itemClass = `supaship-feature-item ${isDisabled ? 'disabled' : ''}`\n\n // Check if the feature is boolean\n const isBoolean =\n typeof currentValue === 'boolean' || (hasOverride && typeof overrideValue === 'boolean')\n\n if (isBoolean) {\n // Render toggle switch for boolean values (single row layout)\n const isChecked = hasOverride ? overrideValue === true : currentValue === true\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${this.escapeHtml(featureName)}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"14\" height=\"14\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n `\n : ''\n }\n <label class=\"supaship-toggle\">\n <input\n type=\"checkbox\"\n ${isChecked ? 'checked' : ''}\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-type=\"boolean\"\n />\n <span class=\"supaship-toggle-slider\"></span>\n </label>\n </div>\n </div>\n </div>\n `\n } else {\n // Render textarea for non-boolean values\n const currentDisplayValue = hasOverride\n ? JSON.stringify(overrideValue)\n : currentValue !== undefined\n ? JSON.stringify(currentValue)\n : ''\n const escapedFeatureName = this.escapeHtml(featureName)\n const escapedCurrentDisplayValue = this.escapeHtml(currentDisplayValue)\n const escapedTextareaContent = hasOverride\n ? this.escapeHtml(JSON.stringify(overrideValue))\n : escapedCurrentDisplayValue\n\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${escapedFeatureName}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"14\" height=\"14\">\n <path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\" fill=\"currentColor\"/>\n </svg>\n </button>\n `\n : ''\n }\n <button\n class=\"supaship-btn supaship-btn-primary\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"set\"\n disabled>\n Override\n </button>\n </div>\n </div>\n <div class=\"supaship-feature-content\">\n <textarea\n class=\"supaship-feature-input\"\n placeholder=\"Override JSON value\"\n data-feature=\"${escapedFeatureName}\"\n data-original=\"${escapedCurrentDisplayValue}\"\n >${escapedTextareaContent}</textarea>\n </div>\n </div>\n `\n }\n })\n .join('')\n\n requestAnimationFrame(() => {\n // Set innerHTML - event listeners are handled via delegation in attachEventListeners()\n content.innerHTML = htmlContent\n\n // Update button states for textareas that already have values\n content.querySelectorAll('textarea[data-feature]').forEach(textarea => {\n const textareaElement = textarea as HTMLTextAreaElement\n const featureName = textareaElement.dataset.feature!\n const originalValue = textareaElement.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = textareaElement.value !== originalValue\n const hasContent = textareaElement.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n })\n })\n }\n\n private escapeHtml(text: string): string {\n const div = typeof document !== 'undefined' ? document.createElement('div') : null\n if (div) {\n div.textContent = text\n return div.innerHTML\n }\n return text.replace(/[&<>\"']/g, char => {\n const escapeMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return escapeMap[char]\n })\n }\n\n /**\n * Escapes special characters in CSS attribute selectors to prevent CSS injection\n * @param value The value to escape for use in CSS attribute selectors\n */\n private escapeCssSelector(value: string): string {\n // Escape special CSS selector characters: \", ', ], \\\n return value.replace(/[\"'\\\\\\]]/g, '\\\\$&')\n }\n}\n","export const DEFAULT_FEATURES_URL = 'https://edge.supaship.com/v1/features'\nexport const DEFAULT_EVENTS_URL = 'https://edge.supaship.com/v1/events'\n","import {\n SupaClientConfig,\n FeatureContext,\n FeatureValue,\n NetworkConfig,\n Features,\n FeaturesWithFallbacks,\n} from './types'\nimport { retry } from './utils'\nimport { SupaPlugin } from './plugins/types'\nimport { SupaToolbarPlugin } from './plugins/toolbar-plugin'\nimport { DEFAULT_FEATURES_URL, DEFAULT_EVENTS_URL } from './constants'\n\ntype RequiredRetryConfig = Required<NonNullable<NetworkConfig['retry']>>\ntype ResolvedNetworkConfig = {\n featuresAPIUrl: string\n eventsAPIUrl: string\n retry: RequiredRetryConfig\n requestTimeoutMs: number\n}\n\nexport class SupaClient<TFeatures extends FeaturesWithFallbacks> {\n private apiKey: string\n private environment: string\n private defaultContext?: FeatureContext\n private plugins: SupaPlugin[]\n private featureDefinitions: Features<TFeatures>\n private clientId: string\n\n private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n private networkConfig: ResolvedNetworkConfig\n\n constructor(config: SupaClientConfig & { features: TFeatures }) {\n this.apiKey = config.apiKey\n this.environment = config.environment\n this.defaultContext = config.context\n this.featureDefinitions = config.features as Features<TFeatures>\n\n // Generate unique client ID\n this.clientId = this.generateClientId()\n\n this.networkConfig = {\n featuresAPIUrl: config.networkConfig?.featuresAPIUrl || DEFAULT_FEATURES_URL,\n eventsAPIUrl: config.networkConfig?.eventsAPIUrl || DEFAULT_EVENTS_URL,\n retry: {\n enabled: config.networkConfig?.retry?.enabled ?? true,\n maxAttempts: config.networkConfig?.retry?.maxAttempts ?? 3,\n backoff: config.networkConfig?.retry?.backoff ?? 1000,\n },\n requestTimeoutMs: config.networkConfig?.requestTimeoutMs ?? 10000,\n }\n\n // Prefer injected fetch, then global fetch if available\n const globalFetch: typeof fetch | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { fetch?: typeof fetch }).fetch\n : undefined\n if (config.networkConfig?.fetchFn) {\n this.fetchImpl = config.networkConfig.fetchFn\n } else if (typeof globalFetch === 'function') {\n this.fetchImpl = globalFetch.bind(globalThis)\n } else {\n throw new Error(\n 'No fetch implementation available. Provide fetchFn in config or use a runtime with global fetch (e.g., Node 18+, browsers).'\n )\n }\n\n // Initialize plugins with automatic toolbar plugin in browser\n this.plugins = this.initializePlugins(config)\n\n // Initialize plugins with available features and their fallback values\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onInit?.({\n clientId: this.clientId,\n availableFeatures: this.featureDefinitions,\n context: this.defaultContext,\n })\n )\n ).catch(console.error)\n }\n\n /**\n * Generate a unique client ID\n */\n private generateClientId(): string {\n return `supaship-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * Initialize plugins with automatic toolbar plugin in browser environments\n */\n private initializePlugins(config: SupaClientConfig & { features: TFeatures }): SupaPlugin[] {\n const plugins = config.plugins || []\n\n // Check if we're in a browser environment\n const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'\n\n // If toolbar is explicitly disabled, don't add it\n if (config.toolbar === false) {\n return plugins\n }\n\n // If in browser and toolbar not disabled, add it automatically\n if (isBrowser) {\n // Check if user already added toolbar plugin manually\n const hasToolbarPlugin = plugins.some(p => p.name === 'toolbar-plugin')\n\n if (!hasToolbarPlugin) {\n // Add toolbar with user config or defaults\n const toolbarConfig = config.toolbar || { enabled: 'auto' }\n const toolbarPlugin = new SupaToolbarPlugin(toolbarConfig)\n return [toolbarPlugin, ...plugins]\n }\n }\n\n return plugins\n }\n\n /**\n * Updates the default context for the client\n * @param context - New context to merge with or replace the existing context\n * @param mergeWithExisting - Whether to merge with existing context (default: true)\n */\n updateContext(context: FeatureContext, mergeWithExisting: boolean = true): void {\n const oldContext = this.defaultContext\n\n if (mergeWithExisting && this.defaultContext) {\n this.defaultContext = { ...this.defaultContext, ...context }\n } else {\n this.defaultContext = context\n }\n\n // Notify plugins of context change\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(oldContext, this.defaultContext!, 'updateContext')\n )\n ).catch(console.error)\n }\n\n /**\n * Gets the current default context\n */\n getContext(): FeatureContext | undefined {\n return this.defaultContext\n }\n\n /**\n * Gets the fallback value for a feature from its definition\n */\n getFeatureFallback<TKey extends keyof TFeatures>(featureName: TKey): Features<TFeatures>[TKey] {\n return this.featureDefinitions[featureName]\n }\n\n private getVariationValue(variation: FeatureValue, fallback: FeatureValue): FeatureValue {\n if (variation !== undefined && variation !== null) {\n return variation\n }\n\n return fallback ?? null\n }\n\n async getFeature<TKey extends keyof TFeatures>(\n featureName: TKey,\n options?: { context?: FeatureContext }\n ): Promise<Features<TFeatures>[TKey]> {\n const { context } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof context === 'object' && context !== null\n ? { ...(this.defaultContext ?? {}), ...context }\n : this.defaultContext\n\n try {\n const response = await this.getFeatures([featureName as string], {\n context: mergedContext,\n })\n\n // Get the specific feature value\n const value = response[featureName as string]\n return value as Features<TFeatures>[TKey]\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Use fallback feature value when API fails\n const fallbackValue = this.featureDefinitions[featureName]\n\n // Notify plugins that fallback was used\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName as string,\n fallbackValue as FeatureValue,\n error as Error\n )\n )\n )\n return fallbackValue as Features<TFeatures>[TKey]\n }\n }\n\n async getFeatures<TKeys extends readonly (keyof TFeatures)[]>(\n featureNames: TKeys,\n options?: { context?: FeatureContext }\n ): Promise<{ [K in TKeys[number]]: Features<TFeatures>[K] }> {\n const { context: contextOverride } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof contextOverride === 'object' && contextOverride !== null\n ? { ...(this.defaultContext ?? {}), ...contextOverride }\n : this.defaultContext\n\n // Notify plugins of context update for this request\n if (contextOverride) {\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(this.defaultContext, mergedContext!, 'request')\n )\n )\n }\n\n // Convert feature names to strings for API call\n const featureNamesArray = featureNames.map(name => name as string)\n\n try {\n // Run beforeGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.beforeGetFeatures?.(featureNamesArray, mergedContext))\n )\n\n type FeaturesResponse = { features: Record<string, { variation: FeatureValue }> }\n const fetchFeatures = async (): Promise<Record<string, FeatureValue>> => {\n const url = this.networkConfig.featuresAPIUrl\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n }\n const body = JSON.stringify({\n environment: this.environment,\n features: featureNamesArray,\n context: mergedContext,\n })\n\n // Notify plugins before request\n await Promise.all(this.plugins.map(plugin => plugin.beforeRequest?.(url, body, headers)))\n\n const startTime = Date.now()\n // Support timeout via AbortController when available\n const AbortCtrl: typeof AbortController | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { AbortController?: typeof AbortController })\n .AbortController\n : undefined\n let controller: AbortController | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n if (this.networkConfig.requestTimeoutMs && typeof AbortCtrl === 'function') {\n controller = new AbortCtrl()\n timeoutId = setTimeout(() => controller?.abort(), this.networkConfig.requestTimeoutMs)\n }\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n signal: controller?.signal,\n })\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n const duration = Date.now() - startTime\n\n // Notify plugins after response\n await Promise.all(\n this.plugins.map(plugin => plugin.afterResponse?.(response, { duration }))\n )\n\n if (!response.ok) {\n throw new Error(`Failed to fetch features: ${response.statusText}`)\n }\n\n const data = (await response.json()) as FeaturesResponse\n const result: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(name => {\n const variation = data.features[name]?.variation\n result[name] = this.getVariationValue(\n variation,\n this.featureDefinitions[name as keyof TFeatures]\n )\n })\n\n return result\n }\n\n const result = this.networkConfig.retry.enabled\n ? await retry(\n fetchFeatures,\n this.networkConfig.retry.maxAttempts,\n this.networkConfig.retry.backoff,\n (attempt, error, willRetry) => {\n // Notify plugins of retry attempts\n Promise.all(\n this.plugins.map(plugin => plugin.onRetryAttempt?.(attempt, error, willRetry))\n ).catch(console.error)\n }\n )\n : await fetchFeatures()\n\n // Run afterGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.afterGetFeatures?.(result, mergedContext))\n )\n\n // Return the fetched features\n return result as { [K in TKeys[number]]: Features<TFeatures>[K] }\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Create fallback result with requested feature names\n const fallbackResult: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(featureName => {\n fallbackResult[featureName] = this.featureDefinitions[featureName as keyof TFeatures]\n\n // Notify plugins that fallback was used for each feature\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName,\n this.featureDefinitions[featureName as keyof TFeatures],\n error as Error\n )\n )\n ).catch(console.error)\n })\n\n return fallbackResult as { [K in TKeys[number]]: Features<TFeatures>[K] }\n }\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["result"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/plugins/toolbar-plugin.ts","../src/constants.ts","../src/client.ts"],"sourcesContent":["export function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n maxAttempts: number = 3,\n backoff: number = 1000,\n onRetry?: (attempt: number, error: Error, willRetry: boolean) => void\n): Promise<T> {\n let lastError: Error\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn()\n } catch (error) {\n lastError = error as Error\n const willRetry = attempt < maxAttempts\n\n if (onRetry) {\n onRetry(attempt, lastError, willRetry)\n }\n\n if (!willRetry) break\n await sleep(backoff * Math.pow(2, attempt - 1))\n }\n }\n\n throw lastError!\n}\n","import { SupaPlugin, SupaPluginConfig } from './types'\nimport { FeatureContext, FeatureValue } from '../types'\n\nexport interface SupaToolbarPosition {\n placement?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n offset?: { x: string; y: string }\n}\n\nexport type SupaToolbarOverrideChange = {\n feature: string\n value: FeatureValue\n}\n\nexport type SupaToolbarOverrideChangeCallback = (\n featureOverride: SupaToolbarOverrideChange,\n allOverrides: Record<string, FeatureValue>\n) => void\n\nexport interface SupaToolbarPluginConfig extends Omit<SupaPluginConfig, 'enabled'> {\n enabled?: boolean | 'auto' // auto means show only on localhost\n position?: SupaToolbarPosition\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n}\n\ninterface SupaToolbarState {\n overrides: Record<string, FeatureValue>\n features: Set<string>\n featureValues: Record<string, FeatureValue>\n context?: FeatureContext\n searchQuery: string\n useLocalOverrides: boolean\n}\n\nconst DEFAULT_STORAGE_KEY = 'supaship-feature-overrides'\n\nconst NO_FEATURES_MESSAGE = `No feature flags configured in the client.`\n\n/**\n * Toolbar plugin for local feature flag testing\n * Provides a visual interface to override feature flags during development\n */\nexport class SupaToolbarPlugin implements SupaPlugin {\n name = 'toolbar-plugin'\n private config: {\n enabled: boolean | 'auto'\n position: Required<SupaToolbarPosition>\n onOverrideChange?: SupaToolbarOverrideChangeCallback\n }\n private state: SupaToolbarState\n private clientId?: string\n private storageKey: string = DEFAULT_STORAGE_KEY\n\n constructor(config: SupaToolbarPluginConfig = {}) {\n this.config = {\n enabled: config.enabled ?? 'auto',\n position: {\n placement: config.position?.placement ?? 'bottom-right',\n offset: config.position?.offset ?? { x: '1rem', y: '1rem' },\n },\n onOverrideChange: config.onOverrideChange,\n }\n\n this.state = {\n overrides: {},\n features: new Set(),\n featureValues: {},\n searchQuery: '',\n useLocalOverrides: true,\n }\n }\n\n cleanup(): void {\n this.removeToolbar()\n }\n\n private shouldShowToolbar(): boolean {\n if (this.config.enabled === true) return true\n if (this.config.enabled === false) return false\n\n // Auto mode: show only on localhost\n if (typeof window !== 'undefined') {\n return (\n window.location.hostname === 'localhost' ||\n window.location.hostname === '127.0.0.1' ||\n window.location.hostname === '' ||\n window.location.hostname.endsWith('.local') ||\n window.location.hostname.endsWith('.localhost')\n )\n }\n return false\n }\n\n onInit(params: {\n availableFeatures: Record<string, FeatureValue>\n context?: FeatureContext\n clientId: string\n }): void {\n const { availableFeatures, context, clientId } = params\n\n // Set client ID for DOM element IDs\n this.clientId = clientId\n\n // Use shared storage key (not client-specific) to persist across refreshes\n this.storageKey = DEFAULT_STORAGE_KEY\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Initialize with all available features and their fallback values from config\n this.state.features = new Set(Object.keys(availableFeatures))\n this.state.featureValues = { ...availableFeatures }\n this.state.context = context\n\n // Inject toolbar if conditions are met\n if (this.shouldShowToolbar()) {\n this.injectToolbar()\n }\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async beforeGetFeatures(_featureNames: string[], context?: FeatureContext): Promise<void> {\n // Update context if it changed\n this.state.context = context\n\n // Load overrides from shared storage\n this.state.overrides = this.loadOverrides()\n\n // Update toolbar UI if it exists\n this.updateToolbarUI()\n }\n\n async afterGetFeatures(\n results: Record<string, FeatureValue>,\n context?: FeatureContext\n ): Promise<void> {\n // Update feature values with fetched results (this replaces config fallback values)\n Object.keys(results).forEach(name => {\n this.state.featureValues[name] = results[name]\n })\n\n // Apply overrides to results only if local overrides are enabled\n if (this.state.useLocalOverrides) {\n Object.keys(this.state.overrides).forEach(featureName => {\n if (featureName in results) {\n results[featureName] = this.state.overrides[featureName]\n }\n })\n }\n\n // Track features and update UI\n Object.keys(results).forEach(name => this.state.features.add(name))\n this.state.context = context\n this.updateToolbarUI()\n }\n\n private loadOverrides(): Record<string, FeatureValue> {\n if (typeof window === 'undefined' || !window.localStorage) {\n return {}\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey)\n return stored ? JSON.parse(stored) : {}\n } catch {\n return {}\n }\n }\n\n private saveOverrides(\n feature?: string,\n value?: FeatureValue,\n allOverrides?: Record<string, FeatureValue>\n ): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return\n }\n\n try {\n window.localStorage.setItem(this.storageKey, JSON.stringify(allOverrides))\n this.config.onOverrideChange?.(\n { feature: feature ?? '', value: value ?? null },\n allOverrides ?? {}\n )\n } catch (error) {\n console.error('Supaship: Failed to save feature overrides:', error)\n }\n }\n\n public setOverride(featureName: string, value: FeatureValue): void {\n this.state.overrides[featureName] = value\n this.saveOverrides(featureName, value, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public removeOverride(featureName: string): void {\n delete this.state.overrides[featureName]\n this.saveOverrides(featureName, null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public clearAllOverrides(): void {\n this.state.overrides = {}\n this.saveOverrides('', null, this.state.overrides)\n this.updateToolbarUI()\n }\n\n public getOverrides(): Record<string, FeatureValue> {\n return { ...this.state.overrides }\n }\n\n private injectToolbar(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return\n }\n\n // Check if toolbar with this client ID already exists\n const toolbarId = `supaship-toolbar-${this.clientId}`\n if (document.getElementById(toolbarId)) {\n return\n }\n\n // Create toolbar container\n const toolbar = document.createElement('div')\n toolbar.id = toolbarId\n toolbar.setAttribute('data-supaship-client', this.clientId || '')\n toolbar.innerHTML = this.getToolbarHTML()\n\n // Add styles\n this.injectStyles()\n\n // Add to DOM\n document.body.appendChild(toolbar)\n\n // Add event listeners\n this.attachEventListeners()\n }\n\n private removeToolbar(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toolbar = document.getElementById(`supaship-toolbar-${this.clientId}`)\n if (toolbar) {\n toolbar.remove()\n }\n\n const styles = document.getElementById('supaship-toolbar-styles')\n if (styles) {\n styles.remove()\n }\n }\n\n private getToolbarHTML(): string {\n const { placement, offset } = this.config.position\n const positionClass = `supaship-toolbar-${placement}`\n const offsetX = offset?.x ?? '1rem'\n const offsetY = offset?.y ?? '1rem'\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const closeId = `supaship-close-toolbar-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n\n return `\n <div class=\"supaship-toolbar-container ${positionClass}\" style=\"--offset-x: ${offsetX}; --offset-y: ${offsetY};\">\n <button class=\"supaship-toolbar-toggle\" id=\"${toggleId}\" aria-label=\"Supaship Toolbar\" title=\"Supaship Toolbar\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 256 256\"\n width=\"32\"\n style=\"vertical-align: middle;\">\n <rect width='256' height='256' rx='128' fill='#e4f222'></rect>\n <line\n x1='66'\n y1='118'\n x2='118'\n y2='66'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='186'\n y1='70'\n x2='70'\n y2='186'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n <line\n x1='190'\n y1='138'\n x2='138'\n y2='190'\n fill='none'\n stroke='#000'\n stroke-linecap='round'\n stroke-linejoin='round'\n stroke-width='24'></line>\n </svg>\n <span class=\"supaship-toolbar-badge\" id=\"${badgeId}\"></span>\n </button>\n <div class=\"supaship-toolbar-panel\" id=\"${panelId}\">\n <div class=\"supaship-toolbar-header\">\n <input\n type=\"text\"\n class=\"supaship-search-input\"\n id=\"${searchId}\"\n placeholder=\"Search features\"\n />\n <span class=\"supaship-toolbar-overrides-label\" id=\"${headerOverrideCountId}\">0 overrides</span>\n <button\n class=\"supaship-header-btn\"\n id=\"${clearId}\"\n aria-label=\"Reset all overrides\"\n title=\"Reset all overrides to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n <button\n class=\"supaship-header-btn\"\n id=\"${closeId}\"\n aria-label=\"Close toolbar\"\n title=\"Close toolbar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\"/>\n <path d=\"m6 6 12 12\"/>\n </svg>\n </button>\n </div>\n <div class=\"supaship-toolbar-content\" id=\"${contentId}\">\n <div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>\n </div>\n </div>\n </div>\n `\n }\n\n private injectStyles(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n if (document.getElementById('supaship-toolbar-styles')) {\n return\n }\n\n const styles = document.createElement('style')\n styles.id = 'supaship-toolbar-styles'\n styles.textContent = `\n .supaship-toolbar-container {\n position: fixed;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 14px;\n }\n\n .supaship-toolbar-bottom-right {\n bottom: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-bottom-left {\n bottom: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-top-right {\n top: var(--offset-y);\n right: var(--offset-x);\n }\n\n .supaship-toolbar-top-left {\n top: var(--offset-y);\n left: var(--offset-x);\n }\n\n .supaship-toolbar-toggle {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 100%;\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .supaship-toolbar-toggle:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .supaship-toolbar-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n }\n\n .supaship-toolbar-badge.show {\n display: flex;\n }\n\n .supaship-toolbar-panel {\n position: absolute;\n bottom: 48px;\n right: 0;\n width: 300px;\n max-height: 600px;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n display: none;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid #333;\n }\n\n .supaship-toolbar-bottom-left .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n right: auto;\n left: 0;\n }\n\n .supaship-toolbar-top-right .supaship-toolbar-panel,\n .supaship-toolbar-top-left .supaship-toolbar-panel {\n bottom: auto;\n top: 60px;\n }\n\n .supaship-toolbar-panel.open {\n display: flex;\n }\n\n .supaship-toolbar-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border-bottom: 1px solid #333;\n background: #0f0f0f;\n min-width: 0;\n overflow: hidden;\n }\n\n .supaship-search-input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n color: #e5e5e5;\n padding: 0;\n font-size: 13px;\n outline: none;\n }\n\n .supaship-search-input::placeholder {\n color: #888;\n }\n\n .supaship-header-btn {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 24px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n padding: 0;\n }\n\n .supaship-header-btn:hover {\n color: #ef4444;\n }\n\n .supaship-toolbar-content {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n min-height: 300px;\n max-height: 300px;\n }\n\n .supaship-toolbar-empty {\n padding: 32px 16px;\n text-align: center;\n color: #888;\n }\n\n .supaship-feature-item {\n padding: 0 6px;\n }\n\n .supaship-feature-item.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .supaship-feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n }\n\n .supaship-feature-name {\n font-weight: 500;\n color: #e5e5e5;\n font-size: 13px;\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .supaship-feature-override-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #ef4444;\n flex-shrink: 0;\n }\n\n .supaship-toolbar-overrides-label {\n font-size: 12px;\n color: #888;\n white-space: nowrap;\n flex-shrink: 0;\n transition: all 0.2s;\n }\n\n .supaship-toolbar-overrides-label-count.has-overrides {\n background: #ef4444;\n color: white;\n font-size: 10px;\n font-weight: 600;\n min-width: 18px;\n height: 18px;\n border-radius: 9px;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .supaship-feature-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n min-height: 20px;\n }\n\n .supaship-feature-content {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .supaship-feature-input {\n flex: 1;\n padding: 6px 8px;\n background: #1a1a1a;\n border: 1px solid #555;\n color: #e5e5e5;\n border-radius: 4px;\n font-size: 13px;\n font-family: 'Monaco', 'Courier New', monospace;\n outline: none;\n resize: vertical;\n min-height: 60px;\n margin-bottom: 8px;\n }\n\n .supaship-feature-input:focus {\n border-color: #667eea;\n }\n\n .supaship-btn {\n padding: 4px 12px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .supaship-btn-primary {\n background: #444;\n color: white;\n }\n\n .supaship-btn-primary:hover {\n background: #555;\n }\n\n .supaship-btn-secondary {\n background: #444;\n color: #e5e5e5;\n }\n\n .supaship-btn-secondary:hover {\n background: #555;\n }\n\n .supaship-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-btn:disabled:hover {\n background: #444;\n }\n\n .supaship-header-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n\n .supaship-header-btn:disabled:hover {\n color: #e5e5e5;\n }\n\n .supaship-toggle {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n }\n\n .supaship-toggle input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .supaship-toggle-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #333;\n border: 1px solid #555;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 20px;\n }\n\n .supaship-toggle-slider:before {\n position: absolute;\n content: \"\";\n height: 14px;\n width: 14px;\n left: 2px;\n bottom: 1px;\n background-color: #666;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider {\n background-color: #fff;\n border-color: #fff;\n }\n\n .supaship-toggle input:checked + .supaship-toggle-slider:before {\n transform: translateX(13px);\n background-color: #000;\n }\n\n .supaship-toggle input:disabled + .supaship-toggle-slider {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .supaship-toggle:hover .supaship-toggle-slider:before {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .supaship-btn-icon {\n background: transparent;\n border: none;\n color: #e5e5e5;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n cursor: pointer;\n padding: 0;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .supaship-btn-icon:hover {\n background: #444;\n color: #ef4444;\n }\n `\n document.head.appendChild(styles)\n }\n\n private attachEventListeners(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n const panelId = `supaship-toolbar-panel-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const closeId = `supaship-close-toolbar-${this.clientId}`\n const searchId = `supaship-search-input-${this.clientId}`\n const contentId = `supaship-toolbar-content-${this.clientId}`\n\n const toggle = document.getElementById(toggleId)\n const panel = document.getElementById(panelId)\n const clearAll = document.getElementById(clearId)\n const close = document.getElementById(closeId)\n const searchInput = document.getElementById(searchId) as HTMLInputElement\n const content = document.getElementById(contentId)\n\n toggle?.addEventListener('click', () => {\n panel?.classList.toggle('open')\n })\n\n close?.addEventListener('click', () => {\n panel?.classList.remove('open')\n })\n\n clearAll?.addEventListener('click', () => {\n this.clearAllOverrides()\n })\n\n searchInput?.addEventListener('input', e => {\n this.state.searchQuery = (e.target as HTMLInputElement).value.toLowerCase()\n this.updateToolbarUI()\n })\n\n // Use event delegation on content element - survives innerHTML updates\n if (content) {\n // Handle button clicks (remove and set actions)\n content.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement\n const buttonElement = target.closest('button[data-action]') as HTMLButtonElement\n if (!buttonElement) return\n\n e.preventDefault()\n e.stopPropagation()\n\n const featureName = buttonElement.dataset.feature!\n const action = buttonElement.dataset.action\n\n if (action === 'remove') {\n this.removeOverride(featureName)\n } else if (action === 'set') {\n const textarea = content.querySelector(\n `textarea[data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLTextAreaElement\n if (textarea && textarea.value.trim()) {\n try {\n const value = JSON.parse(textarea.value)\n this.setOverride(featureName, value)\n } catch {\n // If not valid JSON, wrap string in object\n this.setOverride(featureName, { value: textarea.value })\n }\n }\n }\n })\n\n // Handle checkbox changes for boolean toggles\n content.addEventListener('change', (e: Event) => {\n const target = e.target as HTMLInputElement\n if (target.type === 'checkbox' && target.dataset.type === 'boolean') {\n const featureName = target.dataset.feature!\n const newValue = target.checked\n this.setOverride(featureName, newValue)\n }\n })\n\n // Handle textarea input to update button states\n content.addEventListener('input', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }\n })\n\n // Handle textarea paste events\n content.addEventListener('paste', (e: Event) => {\n const target = e.target as HTMLTextAreaElement\n if (target.tagName === 'TEXTAREA' && target.dataset.feature) {\n setTimeout(() => {\n const featureName = target.dataset.feature!\n const originalValue = target.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${featureName}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = target.value !== originalValue\n const hasContent = target.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n }, 0)\n }\n })\n\n // Handle Ctrl/Cmd+Enter to set override\n content.addEventListener('keydown', (e: KeyboardEvent) => {\n const target = e.target as HTMLTextAreaElement\n if (\n target.tagName === 'TEXTAREA' &&\n target.dataset.feature &&\n (e.ctrlKey || e.metaKey) &&\n e.key === 'Enter'\n ) {\n e.preventDefault()\n const featureName = target.dataset.feature!\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn && !overrideBtn.disabled) {\n overrideBtn.click()\n }\n }\n })\n }\n }\n\n private updateToolbarUI(): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const contentId = `supaship-toolbar-content-${this.clientId}`\n const clearId = `supaship-clear-all-${this.clientId}`\n const badgeId = `supaship-toolbar-badge-${this.clientId}`\n const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`\n const toggleId = `supaship-toolbar-toggle-${this.clientId}`\n\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) as HTMLButtonElement\n const badge = document.getElementById(badgeId)\n const headerOverrideCount = document.getElementById(headerOverrideCountId)\n const toggleBtn = document.getElementById(toggleId) as HTMLButtonElement\n\n if (!content) {\n console.warn('[Toolbar] Content element not found:', contentId)\n return\n }\n\n // Update clear all button state and badge\n const overrideCount = Object.keys(this.state.overrides).length\n const hasOverrides = overrideCount > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\n }\n\n // Update badge on toggle button\n if (badge) {\n if (hasOverrides) {\n badge.textContent = overrideCount > 99 ? '99+' : String(overrideCount)\n badge.classList.add('show')\n } else {\n badge.classList.remove('show')\n }\n }\n\n // Update tooltip on toggle button\n if (toggleBtn) {\n if (hasOverrides) {\n toggleBtn.setAttribute(\n 'title',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n toggleBtn.setAttribute(\n 'aria-label',\n `Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? '' : 's'}`\n )\n } else {\n toggleBtn.setAttribute('title', 'Supaship Toolbar')\n toggleBtn.setAttribute('aria-label', 'Supaship Toolbar')\n }\n }\n\n // Update override count in header\n if (headerOverrideCount) {\n // Escape overrideCount to prevent any potential XSS (defense in depth)\n const escapedCount = this.escapeHtml(String(overrideCount))\n headerOverrideCount.innerHTML = `<span class=\"supaship-toolbar-overrides-label-count ${hasOverrides ? 'has-overrides' : ''}\">${escapedCount}</span> override${overrideCount === 1 ? '' : 's'}`\n }\n\n const features = Array.from(this.state.features).sort()\n\n // Filter features based on search query\n const filteredFeatures = features.filter(name =>\n name.toLowerCase().includes(this.state.searchQuery)\n )\n\n if (filteredFeatures.length === 0) {\n content.innerHTML = this.state.searchQuery\n ? '<div class=\"supaship-toolbar-empty\">No matching features found</div>'\n : `<div class=\"supaship-toolbar-empty\">${NO_FEATURES_MESSAGE}</div>`\n return\n }\n\n const htmlContent = filteredFeatures\n .map(featureName => {\n const hasOverride = featureName in this.state.overrides\n const currentValue = this.state.featureValues[featureName]\n const overrideValue = hasOverride ? this.state.overrides[featureName] : currentValue\n const isDisabled = !this.state.useLocalOverrides\n const itemClass = `supaship-feature-item ${isDisabled ? 'disabled' : ''}`\n\n // Check if the feature is boolean\n const isBoolean =\n typeof currentValue === 'boolean' || (hasOverride && typeof overrideValue === 'boolean')\n\n if (isBoolean) {\n // Render toggle switch for boolean values (single row layout)\n const isChecked = hasOverride ? overrideValue === true : currentValue === true\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${this.escapeHtml(featureName)}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n `\n : ''\n }\n <label class=\"supaship-toggle\">\n <input\n type=\"checkbox\"\n ${isChecked ? 'checked' : ''}\n data-feature=\"${this.escapeHtml(featureName)}\"\n data-type=\"boolean\"\n />\n <span class=\"supaship-toggle-slider\"></span>\n </label>\n </div>\n </div>\n </div>\n `\n } else {\n // Render textarea for non-boolean values\n const currentDisplayValue = hasOverride\n ? JSON.stringify(overrideValue)\n : currentValue !== undefined\n ? JSON.stringify(currentValue)\n : ''\n const escapedFeatureName = this.escapeHtml(featureName)\n const escapedCurrentDisplayValue = this.escapeHtml(currentDisplayValue)\n const escapedTextareaContent = hasOverride\n ? this.escapeHtml(JSON.stringify(overrideValue))\n : escapedCurrentDisplayValue\n\n return `\n <div class=\"${itemClass}\">\n <div class=\"supaship-feature-row\">\n <span class=\"supaship-feature-name\">\n ${escapedFeatureName}\n ${hasOverride ? '<span class=\"supaship-feature-override-indicator\" title=\"Serving local override\"></span>' : ''}\n </span>\n <div class=\"supaship-feature-actions\">\n ${\n hasOverride\n ? `\n <button\n class=\"supaship-btn-icon\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"remove\"\n title=\"Reset to default\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/>\n <path d=\"M3 3v5h5\"/>\n <path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/>\n <path d=\"M16 16h5v5\"/>\n </svg>\n </button>\n `\n : ''\n }\n <button\n class=\"supaship-btn supaship-btn-primary\"\n data-feature=\"${escapedFeatureName}\"\n data-action=\"set\"\n disabled>\n Override\n </button>\n </div>\n </div>\n <div class=\"supaship-feature-content\">\n <textarea\n class=\"supaship-feature-input\"\n placeholder=\"Override JSON value\"\n data-feature=\"${escapedFeatureName}\"\n data-original=\"${escapedCurrentDisplayValue}\"\n >${escapedTextareaContent}</textarea>\n </div>\n </div>\n `\n }\n })\n .join('')\n\n requestAnimationFrame(() => {\n // Set innerHTML - event listeners are handled via delegation in attachEventListeners()\n content.innerHTML = htmlContent\n\n // Update button states for textareas that already have values\n content.querySelectorAll('textarea[data-feature]').forEach(textarea => {\n const textareaElement = textarea as HTMLTextAreaElement\n const featureName = textareaElement.dataset.feature!\n const originalValue = textareaElement.dataset.original || ''\n const overrideBtn = content.querySelector(\n `button[data-action=\"set\"][data-feature=\"${this.escapeCssSelector(featureName)}\"]`\n ) as HTMLButtonElement\n\n if (overrideBtn) {\n const hasChanged = textareaElement.value !== originalValue\n const hasContent = textareaElement.value.trim().length > 0\n overrideBtn.disabled = !hasChanged || !hasContent\n }\n })\n })\n }\n\n private escapeHtml(text: string): string {\n const div = typeof document !== 'undefined' ? document.createElement('div') : null\n if (div) {\n div.textContent = text\n return div.innerHTML\n }\n return text.replace(/[&<>\"']/g, char => {\n const escapeMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return escapeMap[char]\n })\n }\n\n /**\n * Escapes special characters in CSS attribute selectors to prevent CSS injection\n * @param value The value to escape for use in CSS attribute selectors\n */\n private escapeCssSelector(value: string): string {\n // Escape special CSS selector characters: \", ', ], \\\n return value.replace(/[\"'\\\\\\]]/g, '\\\\$&')\n }\n}\n","export const DEFAULT_FEATURES_URL = 'https://edge.supaship.com/v1/features'\nexport const DEFAULT_EVENTS_URL = 'https://edge.supaship.com/v1/events'\n","import {\n SupaClientConfig,\n FeatureContext,\n FeatureValue,\n NetworkConfig,\n Features,\n FeaturesWithFallbacks,\n} from './types'\nimport { retry } from './utils'\nimport { SupaPlugin } from './plugins/types'\nimport { SupaToolbarPlugin } from './plugins/toolbar-plugin'\nimport { DEFAULT_FEATURES_URL, DEFAULT_EVENTS_URL } from './constants'\n\ntype RequiredRetryConfig = Required<NonNullable<NetworkConfig['retry']>>\ntype ResolvedNetworkConfig = {\n featuresAPIUrl: string\n eventsAPIUrl: string\n retry: RequiredRetryConfig\n requestTimeoutMs: number\n}\n\nexport class SupaClient<TFeatures extends FeaturesWithFallbacks> {\n private apiKey: string\n private environment: string\n private defaultContext?: FeatureContext\n private plugins: SupaPlugin[]\n private featureDefinitions: Features<TFeatures>\n private clientId: string\n\n private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n private networkConfig: ResolvedNetworkConfig\n\n constructor(config: SupaClientConfig & { features: TFeatures }) {\n this.apiKey = config.apiKey\n this.environment = config.environment\n this.defaultContext = config.context\n this.featureDefinitions = config.features as Features<TFeatures>\n\n // Generate unique client ID\n this.clientId = this.generateClientId()\n\n this.networkConfig = {\n featuresAPIUrl: config.networkConfig?.featuresAPIUrl || DEFAULT_FEATURES_URL,\n eventsAPIUrl: config.networkConfig?.eventsAPIUrl || DEFAULT_EVENTS_URL,\n retry: {\n enabled: config.networkConfig?.retry?.enabled ?? true,\n maxAttempts: config.networkConfig?.retry?.maxAttempts ?? 3,\n backoff: config.networkConfig?.retry?.backoff ?? 1000,\n },\n requestTimeoutMs: config.networkConfig?.requestTimeoutMs ?? 10000,\n }\n\n // Prefer injected fetch, then global fetch if available\n const globalFetch: typeof fetch | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { fetch?: typeof fetch }).fetch\n : undefined\n if (config.networkConfig?.fetchFn) {\n this.fetchImpl = config.networkConfig.fetchFn\n } else if (typeof globalFetch === 'function') {\n this.fetchImpl = globalFetch.bind(globalThis)\n } else {\n throw new Error(\n 'No fetch implementation available. Provide fetchFn in config or use a runtime with global fetch (e.g., Node 18+, browsers).'\n )\n }\n\n // Initialize plugins with automatic toolbar plugin in browser\n this.plugins = this.initializePlugins(config)\n\n // Initialize plugins with available features and their fallback values\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onInit?.({\n clientId: this.clientId,\n availableFeatures: this.featureDefinitions,\n context: this.defaultContext,\n })\n )\n ).catch(console.error)\n }\n\n /**\n * Generate a unique client ID\n */\n private generateClientId(): string {\n return `supaship-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * Initialize plugins with automatic toolbar plugin in browser environments\n */\n private initializePlugins(config: SupaClientConfig & { features: TFeatures }): SupaPlugin[] {\n const plugins = config.plugins || []\n\n // Check if we're in a browser environment\n const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'\n\n // If toolbar is explicitly disabled, don't add it\n if (config.toolbar === false) {\n return plugins\n }\n\n // If in browser and toolbar not disabled, add it automatically\n if (isBrowser) {\n // Check if user already added toolbar plugin manually\n const hasToolbarPlugin = plugins.some(p => p.name === 'toolbar-plugin')\n\n if (!hasToolbarPlugin) {\n // Add toolbar with user config or defaults\n const toolbarConfig = config.toolbar || { enabled: 'auto' }\n const toolbarPlugin = new SupaToolbarPlugin(toolbarConfig)\n return [toolbarPlugin, ...plugins]\n }\n }\n\n return plugins\n }\n\n /**\n * Updates the default context for the client\n * @param context - New context to merge with or replace the existing context\n * @param mergeWithExisting - Whether to merge with existing context (default: true)\n */\n updateContext(context: FeatureContext, mergeWithExisting: boolean = true): void {\n const oldContext = this.defaultContext\n\n if (mergeWithExisting && this.defaultContext) {\n this.defaultContext = { ...this.defaultContext, ...context }\n } else {\n this.defaultContext = context\n }\n\n // Notify plugins of context change\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(oldContext, this.defaultContext!, 'updateContext')\n )\n ).catch(console.error)\n }\n\n /**\n * Gets the current default context\n */\n getContext(): FeatureContext | undefined {\n return this.defaultContext\n }\n\n /**\n * Gets the fallback value for a feature from its definition\n */\n getFeatureFallback<TKey extends keyof TFeatures>(featureName: TKey): Features<TFeatures>[TKey] {\n return this.featureDefinitions[featureName]\n }\n\n private getVariationValue(variation: FeatureValue, fallback: FeatureValue): FeatureValue {\n if (variation !== undefined && variation !== null) {\n return variation\n }\n\n return fallback ?? null\n }\n\n async getFeature<TKey extends keyof TFeatures>(\n featureName: TKey,\n options?: { context?: FeatureContext }\n ): Promise<Features<TFeatures>[TKey]> {\n const { context } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof context === 'object' && context !== null\n ? { ...(this.defaultContext ?? {}), ...context }\n : this.defaultContext\n\n try {\n const response = await this.getFeatures([featureName as string], {\n context: mergedContext,\n })\n\n // Get the specific feature value\n const value = response[featureName as string]\n return value as Features<TFeatures>[TKey]\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Use fallback feature value when API fails\n const fallbackValue = this.featureDefinitions[featureName]\n\n // Notify plugins that fallback was used\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName as string,\n fallbackValue as FeatureValue,\n error as Error\n )\n )\n )\n return fallbackValue as Features<TFeatures>[TKey]\n }\n }\n\n async getFeatures<TKeys extends readonly (keyof TFeatures)[]>(\n featureNames: TKeys,\n options?: { context?: FeatureContext }\n ): Promise<{ [K in TKeys[number]]: Features<TFeatures>[K] }> {\n const { context: contextOverride } = options ?? {}\n\n // Only merge context if it's defined and not null\n const mergedContext: FeatureContext | undefined =\n typeof contextOverride === 'object' && contextOverride !== null\n ? { ...(this.defaultContext ?? {}), ...contextOverride }\n : this.defaultContext\n\n // Notify plugins of context update for this request\n if (contextOverride) {\n await Promise.all(\n this.plugins.map(plugin =>\n plugin.onContextUpdate?.(this.defaultContext, mergedContext!, 'request')\n )\n )\n }\n\n // Convert feature names to strings for API call\n const featureNamesArray = featureNames.map(name => name as string)\n\n try {\n // Run beforeGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.beforeGetFeatures?.(featureNamesArray, mergedContext))\n )\n\n type FeaturesResponse = { features: Record<string, { variation: FeatureValue }> }\n const fetchFeatures = async (): Promise<Record<string, FeatureValue>> => {\n const url = this.networkConfig.featuresAPIUrl\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n }\n const body = JSON.stringify({\n environment: this.environment,\n features: featureNamesArray,\n context: mergedContext,\n })\n\n // Notify plugins before request\n await Promise.all(this.plugins.map(plugin => plugin.beforeRequest?.(url, body, headers)))\n\n const startTime = Date.now()\n // Support timeout via AbortController when available\n const AbortCtrl: typeof AbortController | undefined =\n typeof globalThis !== 'undefined'\n ? (globalThis as unknown as { AbortController?: typeof AbortController })\n .AbortController\n : undefined\n let controller: AbortController | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n if (this.networkConfig.requestTimeoutMs && typeof AbortCtrl === 'function') {\n controller = new AbortCtrl()\n timeoutId = setTimeout(() => controller?.abort(), this.networkConfig.requestTimeoutMs)\n }\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n signal: controller?.signal,\n })\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n const duration = Date.now() - startTime\n\n // Notify plugins after response\n await Promise.all(\n this.plugins.map(plugin => plugin.afterResponse?.(response, { duration }))\n )\n\n if (!response.ok) {\n throw new Error(`Failed to fetch features: ${response.statusText}`)\n }\n\n const data = (await response.json()) as FeaturesResponse\n const result: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(name => {\n const variation = data.features[name]?.variation\n result[name] = this.getVariationValue(\n variation,\n this.featureDefinitions[name as keyof TFeatures]\n )\n })\n\n return result\n }\n\n const result = this.networkConfig.retry.enabled\n ? await retry(\n fetchFeatures,\n this.networkConfig.retry.maxAttempts,\n this.networkConfig.retry.backoff,\n (attempt, error, willRetry) => {\n // Notify plugins of retry attempts\n Promise.all(\n this.plugins.map(plugin => plugin.onRetryAttempt?.(attempt, error, willRetry))\n ).catch(console.error)\n }\n )\n : await fetchFeatures()\n\n // Run afterGetFeatures hooks\n await Promise.all(\n this.plugins.map(plugin => plugin.afterGetFeatures?.(result, mergedContext))\n )\n\n // Return the fetched features\n return result as { [K in TKeys[number]]: Features<TFeatures>[K] }\n } catch (error) {\n // Run onError hooks\n await Promise.all(this.plugins.map(plugin => plugin.onError?.(error as Error, mergedContext)))\n\n // Create fallback result with requested feature names\n const fallbackResult: Record<string, FeatureValue> = {}\n\n featureNamesArray.forEach(featureName => {\n fallbackResult[featureName] = this.featureDefinitions[featureName as keyof TFeatures]\n\n // Notify plugins that fallback was used for each feature\n Promise.all(\n this.plugins.map(plugin =>\n plugin.onFallbackUsed?.(\n featureName,\n this.featureDefinitions[featureName as keyof TFeatures],\n error as Error\n )\n )\n ).catch(console.error)\n })\n\n return fallbackResult as { [K in TKeys[number]]: Features<TFeatures>[K] }\n }\n }\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["result"]}
|