@supashiphq/javascript-sdk 0.7.10 → 0.7.12
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/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +120 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -72,6 +72,11 @@ declare class SupaToolbarPlugin implements SupaPlugin {
|
|
|
72
72
|
private attachEventListeners;
|
|
73
73
|
private updateToolbarUI;
|
|
74
74
|
private escapeHtml;
|
|
75
|
+
/**
|
|
76
|
+
* Escapes special characters in CSS attribute selectors to prevent CSS injection
|
|
77
|
+
* @param value The value to escape for use in CSS attribute selectors
|
|
78
|
+
*/
|
|
79
|
+
private escapeCssSelector;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
interface SupaClientConfig {
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ declare class SupaToolbarPlugin implements SupaPlugin {
|
|
|
72
72
|
private attachEventListeners;
|
|
73
73
|
private updateToolbarUI;
|
|
74
74
|
private escapeHtml;
|
|
75
|
+
/**
|
|
76
|
+
* Escapes special characters in CSS attribute selectors to prevent CSS injection
|
|
77
|
+
* @param value The value to escape for use in CSS attribute selectors
|
|
78
|
+
*/
|
|
79
|
+
private escapeCssSelector;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
interface SupaClientConfig {
|
package/dist/index.js
CHANGED
|
@@ -196,9 +196,11 @@ var SupaToolbarPlugin = class {
|
|
|
196
196
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
197
197
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
198
198
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
199
|
+
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
200
|
+
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
199
201
|
return `
|
|
200
202
|
<div class="supaship-toolbar-container ${positionClass}" style="--offset-x: ${offsetX}; --offset-y: ${offsetY};">
|
|
201
|
-
<button class="supaship-toolbar-toggle" id="${toggleId}" aria-label="
|
|
203
|
+
<button class="supaship-toolbar-toggle" id="${toggleId}" aria-label="Supaship Toolbar" title="Supaship Toolbar">
|
|
202
204
|
<svg
|
|
203
205
|
xmlns="http://www.w3.org/2000/svg"
|
|
204
206
|
viewBox="0 0 256 256"
|
|
@@ -236,6 +238,7 @@ var SupaToolbarPlugin = class {
|
|
|
236
238
|
stroke-linejoin="round"
|
|
237
239
|
stroke-width="16"></line>
|
|
238
240
|
</svg>
|
|
241
|
+
<span class="supaship-toolbar-badge" id="${badgeId}"></span>
|
|
239
242
|
</button>
|
|
240
243
|
<div class="supaship-toolbar-panel" id="${panelId}">
|
|
241
244
|
<div class="supaship-toolbar-header">
|
|
@@ -245,6 +248,7 @@ var SupaToolbarPlugin = class {
|
|
|
245
248
|
id="${searchId}"
|
|
246
249
|
placeholder="Search features"
|
|
247
250
|
/>
|
|
251
|
+
<span class="supaship-toolbar-overrides-label" id="${headerOverrideCountId}">0 overrides</span>
|
|
248
252
|
<button
|
|
249
253
|
class="supaship-header-btn"
|
|
250
254
|
id="${clearId}"
|
|
@@ -321,6 +325,29 @@ var SupaToolbarPlugin = class {
|
|
|
321
325
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
322
326
|
}
|
|
323
327
|
|
|
328
|
+
.supaship-toolbar-badge {
|
|
329
|
+
position: absolute;
|
|
330
|
+
top: -4px;
|
|
331
|
+
right: -4px;
|
|
332
|
+
background: #ef4444;
|
|
333
|
+
color: white;
|
|
334
|
+
font-size: 10px;
|
|
335
|
+
font-weight: 600;
|
|
336
|
+
min-width: 18px;
|
|
337
|
+
height: 18px;
|
|
338
|
+
border-radius: 9px;
|
|
339
|
+
display: none;
|
|
340
|
+
align-items: center;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
padding: 0 5px;
|
|
343
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
344
|
+
pointer-events: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.supaship-toolbar-badge.show {
|
|
348
|
+
display: flex;
|
|
349
|
+
}
|
|
350
|
+
|
|
324
351
|
.supaship-toolbar-panel {
|
|
325
352
|
position: absolute;
|
|
326
353
|
bottom: 48px;
|
|
@@ -359,10 +386,13 @@ var SupaToolbarPlugin = class {
|
|
|
359
386
|
padding: 12px;
|
|
360
387
|
border-bottom: 1px solid #333;
|
|
361
388
|
background: #0f0f0f;
|
|
389
|
+
min-width: 0;
|
|
390
|
+
overflow: hidden;
|
|
362
391
|
}
|
|
363
392
|
|
|
364
393
|
.supaship-search-input {
|
|
365
394
|
flex: 1;
|
|
395
|
+
min-width: 0;
|
|
366
396
|
background: transparent;
|
|
367
397
|
border: none;
|
|
368
398
|
color: #e5e5e5;
|
|
@@ -397,7 +427,8 @@ var SupaToolbarPlugin = class {
|
|
|
397
427
|
flex: 1;
|
|
398
428
|
overflow-y: auto;
|
|
399
429
|
padding: 8px;
|
|
400
|
-
min-height:
|
|
430
|
+
min-height: 300px;
|
|
431
|
+
max-height: 300px;
|
|
401
432
|
}
|
|
402
433
|
|
|
403
434
|
.supaship-toolbar-empty {
|
|
@@ -429,6 +460,39 @@ var SupaToolbarPlugin = class {
|
|
|
429
460
|
font-size: 13px;
|
|
430
461
|
flex: 1;
|
|
431
462
|
min-width: 0;
|
|
463
|
+
display: inline-flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
gap: 6px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.supaship-feature-override-indicator {
|
|
469
|
+
width: 6px;
|
|
470
|
+
height: 6px;
|
|
471
|
+
border-radius: 50%;
|
|
472
|
+
background: #ef4444;
|
|
473
|
+
flex-shrink: 0;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.supaship-toolbar-overrides-label {
|
|
477
|
+
font-size: 12px;
|
|
478
|
+
color: #888;
|
|
479
|
+
white-space: nowrap;
|
|
480
|
+
flex-shrink: 0;
|
|
481
|
+
transition: all 0.2s;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.supaship-toolbar-overrides-label-count.has-overrides {
|
|
485
|
+
background: #ef4444;
|
|
486
|
+
color: white;
|
|
487
|
+
font-size: 10px;
|
|
488
|
+
font-weight: 600;
|
|
489
|
+
min-width: 18px;
|
|
490
|
+
height: 18px;
|
|
491
|
+
border-radius: 9px;
|
|
492
|
+
padding: 2px;
|
|
493
|
+
display: inline-flex;
|
|
494
|
+
align-items: center;
|
|
495
|
+
justify-content: center;
|
|
432
496
|
}
|
|
433
497
|
|
|
434
498
|
.supaship-feature-actions {
|
|
@@ -629,7 +693,7 @@ var SupaToolbarPlugin = class {
|
|
|
629
693
|
this.removeOverride(featureName);
|
|
630
694
|
} else if (action === "set") {
|
|
631
695
|
const textarea = content.querySelector(
|
|
632
|
-
`textarea[data-feature="${featureName}"]`
|
|
696
|
+
`textarea[data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
633
697
|
);
|
|
634
698
|
if (textarea && textarea.value.trim()) {
|
|
635
699
|
try {
|
|
@@ -655,7 +719,7 @@ var SupaToolbarPlugin = class {
|
|
|
655
719
|
const featureName = target.dataset.feature;
|
|
656
720
|
const originalValue = target.dataset.original || "";
|
|
657
721
|
const overrideBtn = content.querySelector(
|
|
658
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
722
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
659
723
|
);
|
|
660
724
|
if (overrideBtn) {
|
|
661
725
|
const hasChanged = target.value !== originalValue;
|
|
@@ -687,7 +751,7 @@ var SupaToolbarPlugin = class {
|
|
|
687
751
|
e.preventDefault();
|
|
688
752
|
const featureName = target.dataset.feature;
|
|
689
753
|
const overrideBtn = content.querySelector(
|
|
690
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
754
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
691
755
|
);
|
|
692
756
|
if (overrideBtn && !overrideBtn.disabled) {
|
|
693
757
|
overrideBtn.click();
|
|
@@ -702,16 +766,50 @@ var SupaToolbarPlugin = class {
|
|
|
702
766
|
}
|
|
703
767
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
704
768
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
769
|
+
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
770
|
+
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
771
|
+
const toggleId = `supaship-toolbar-toggle-${this.clientId}`;
|
|
705
772
|
const content = document.getElementById(contentId);
|
|
706
773
|
const clearAllBtn = document.getElementById(clearId);
|
|
774
|
+
const badge = document.getElementById(badgeId);
|
|
775
|
+
const headerOverrideCount = document.getElementById(headerOverrideCountId);
|
|
776
|
+
const toggleBtn = document.getElementById(toggleId);
|
|
707
777
|
if (!content) {
|
|
708
778
|
console.warn("[Toolbar] Content element not found:", contentId);
|
|
709
779
|
return;
|
|
710
780
|
}
|
|
711
|
-
const
|
|
781
|
+
const overrideCount = Object.keys(this.state.overrides).length;
|
|
782
|
+
const hasOverrides = overrideCount > 0;
|
|
712
783
|
if (clearAllBtn) {
|
|
713
784
|
clearAllBtn.disabled = !hasOverrides;
|
|
714
785
|
}
|
|
786
|
+
if (badge) {
|
|
787
|
+
if (hasOverrides) {
|
|
788
|
+
badge.textContent = overrideCount > 99 ? "99+" : String(overrideCount);
|
|
789
|
+
badge.classList.add("show");
|
|
790
|
+
} else {
|
|
791
|
+
badge.classList.remove("show");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (toggleBtn) {
|
|
795
|
+
if (hasOverrides) {
|
|
796
|
+
toggleBtn.setAttribute(
|
|
797
|
+
"title",
|
|
798
|
+
`Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? "" : "s"}`
|
|
799
|
+
);
|
|
800
|
+
toggleBtn.setAttribute(
|
|
801
|
+
"aria-label",
|
|
802
|
+
`Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? "" : "s"}`
|
|
803
|
+
);
|
|
804
|
+
} else {
|
|
805
|
+
toggleBtn.setAttribute("title", "Supaship Toolbar");
|
|
806
|
+
toggleBtn.setAttribute("aria-label", "Supaship Toolbar");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (headerOverrideCount) {
|
|
810
|
+
const escapedCount = this.escapeHtml(String(overrideCount));
|
|
811
|
+
headerOverrideCount.innerHTML = `<span class="supaship-toolbar-overrides-label-count ${hasOverrides ? "has-overrides" : ""}">${escapedCount}</span> override${overrideCount === 1 ? "" : "s"}`;
|
|
812
|
+
}
|
|
715
813
|
const features = Array.from(this.state.features).sort();
|
|
716
814
|
const filteredFeatures = features.filter(
|
|
717
815
|
(name) => name.toLowerCase().includes(this.state.searchQuery)
|
|
@@ -732,7 +830,10 @@ var SupaToolbarPlugin = class {
|
|
|
732
830
|
return `
|
|
733
831
|
<div class="${itemClass}">
|
|
734
832
|
<div class="supaship-feature-row">
|
|
735
|
-
<span class="supaship-feature-name"
|
|
833
|
+
<span class="supaship-feature-name">
|
|
834
|
+
${this.escapeHtml(featureName)}
|
|
835
|
+
${hasOverride ? '<span class="supaship-feature-override-indicator" title="Serving local override"></span>' : ""}
|
|
836
|
+
</span>
|
|
736
837
|
<div class="supaship-feature-actions">
|
|
737
838
|
${hasOverride ? `
|
|
738
839
|
<button
|
|
@@ -767,7 +868,10 @@ var SupaToolbarPlugin = class {
|
|
|
767
868
|
return `
|
|
768
869
|
<div class="${itemClass}">
|
|
769
870
|
<div class="supaship-feature-row">
|
|
770
|
-
<span class="supaship-feature-name"
|
|
871
|
+
<span class="supaship-feature-name">
|
|
872
|
+
${escapedFeatureName}
|
|
873
|
+
${hasOverride ? '<span class="supaship-feature-override-indicator" title="Serving local override"></span>' : ""}
|
|
874
|
+
</span>
|
|
771
875
|
<div class="supaship-feature-actions">
|
|
772
876
|
${hasOverride ? `
|
|
773
877
|
<button
|
|
@@ -809,7 +913,7 @@ var SupaToolbarPlugin = class {
|
|
|
809
913
|
const featureName = textareaElement.dataset.feature;
|
|
810
914
|
const originalValue = textareaElement.dataset.original || "";
|
|
811
915
|
const overrideBtn = content.querySelector(
|
|
812
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
916
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
813
917
|
);
|
|
814
918
|
if (overrideBtn) {
|
|
815
919
|
const hasChanged = textareaElement.value !== originalValue;
|
|
@@ -836,6 +940,13 @@ var SupaToolbarPlugin = class {
|
|
|
836
940
|
return escapeMap[char];
|
|
837
941
|
});
|
|
838
942
|
}
|
|
943
|
+
/**
|
|
944
|
+
* Escapes special characters in CSS attribute selectors to prevent CSS injection
|
|
945
|
+
* @param value The value to escape for use in CSS attribute selectors
|
|
946
|
+
*/
|
|
947
|
+
escapeCssSelector(value) {
|
|
948
|
+
return value.replace(/["'\\\]]/g, "\\$&");
|
|
949
|
+
}
|
|
839
950
|
};
|
|
840
951
|
|
|
841
952
|
// src/constants.ts
|
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\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=\"Toggle feature flags\">\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=\"16\" fill=\"none\"></rect>\n <line\n x1=\"40\"\n y1=\"128\"\n x2=\"128\"\n y2=\"40\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"40\"\n x2=\"40\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"128\"\n x2=\"128\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n </svg>\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 <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-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 }\n\n .supaship-search-input {\n flex: 1;\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: 200px;\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 }\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=\"${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=\"${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=\"${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\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) 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\n const hasOverrides = Object.keys(this.state.overrides).length > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\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\">${this.escapeHtml(featureName)}</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\">${escapedFeatureName}</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=\"${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","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 apiKey: this.apiKey,\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,MAAM,IAA2B;AAC/C,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAEA,eAAsB,MACpB,IACA,cAAsB,GACtB,UAAkB,KAClB,SACY;AACZ,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,OAAO;AACd,kBAAY;AACZ,YAAM,YAAY,UAAU;AAE5B,UAAI,SAAS;AACX,gBAAQ,SAAS,WAAW,SAAS;AAAA,MACvC;AAEA,UAAI,CAAC,UAAW;AAChB,YAAM,MAAM,UAAU,KAAK,IAAI,GAAG,UAAU,CAAC,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,QAAM;AACR;;;ACIA,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAMrB,IAAM,oBAAN,MAA8C;AAAA,EAWnD,YAAY,SAAkC,CAAC,GAAG;AAVlD,gBAAO;AAQP,SAAQ,aAAqB;AAG3B,SAAK,SAAS;AAAA,MACZ,SAAS,OAAO,WAAW;AAAA,MAC3B,UAAU;AAAA,QACR,WAAW,OAAO,UAAU,aAAa;AAAA,QACzC,QAAQ,OAAO,UAAU,UAAU,EAAE,GAAG,QAAQ,GAAG,OAAO;AAAA,MAC5D;AAAA,MACA,kBAAkB,OAAO;AAAA,IAC3B;AAEA,SAAK,QAAQ;AAAA,MACX,WAAW,CAAC;AAAA,MACZ,UAAU,oBAAI,IAAI;AAAA,MAClB,eAAe,CAAC;AAAA,MAChB,aAAa;AAAA,MACb,mBAAmB;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,oBAA6B;AACnC,QAAI,KAAK,OAAO,YAAY,KAAM,QAAO;AACzC,QAAI,KAAK,OAAO,YAAY,MAAO,QAAO;AAG1C,QAAI,OAAO,WAAW,aAAa;AACjC,aACE,OAAO,SAAS,aAAa,eAC7B,OAAO,SAAS,aAAa,eAC7B,OAAO,SAAS,aAAa,MAC7B,OAAO,SAAS,SAAS,SAAS,QAAQ,KAC1C,OAAO,SAAS,SAAS,SAAS,YAAY;AAAA,IAElD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,QAIE;AACP,UAAM,EAAE,mBAAmB,SAAS,SAAS,IAAI;AAGjD,SAAK,WAAW;AAGhB,SAAK,aAAa;AAGlB,SAAK,MAAM,YAAY,KAAK,cAAc;AAG1C,SAAK,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,iBAAiB,CAAC;AAC5D,SAAK,MAAM,gBAAgB,EAAE,GAAG,kBAAkB;AAClD,SAAK,MAAM,UAAU;AAGrB,QAAI,KAAK,kBAAkB,GAAG;AAC5B,WAAK,cAAc;AAAA,IACrB;AAGA,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,MAAM,kBAAkB,eAAyB,SAAyC;AAExF,SAAK,MAAM,UAAU;AAGrB,SAAK,MAAM,YAAY,KAAK,cAAc;AAG1C,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,MAAM,iBACJ,SACA,SACe;AAEf,WAAO,KAAK,OAAO,EAAE,QAAQ,UAAQ;AACnC,WAAK,MAAM,cAAc,IAAI,IAAI,QAAQ,IAAI;AAAA,IAC/C,CAAC;AAGD,QAAI,KAAK,MAAM,mBAAmB;AAChC,aAAO,KAAK,KAAK,MAAM,SAAS,EAAE,QAAQ,iBAAe;AACvD,YAAI,eAAe,SAAS;AAC1B,kBAAQ,WAAW,IAAI,KAAK,MAAM,UAAU,WAAW;AAAA,QACzD;AAAA,MACF,CAAC;AAAA,IACH;AAGA,WAAO,KAAK,OAAO,EAAE,QAAQ,UAAQ,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;AAClE,SAAK,MAAM,UAAU;AACrB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,gBAA8C;AACpD,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO,cAAc;AACzD,aAAO,CAAC;AAAA,IACV;AAEA,QAAI;AACF,YAAM,SAAS,OAAO,aAAa,QAAQ,KAAK,UAAU;AAC1D,aAAO,SAAS,KAAK,MAAM,MAAM,IAAI,CAAC;AAAA,IACxC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEQ,cACN,SACA,OACA,cACM;AACN,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO,cAAc;AACzD;AAAA,IACF;AAEA,QAAI;AACF,aAAO,aAAa,QAAQ,KAAK,YAAY,KAAK,UAAU,YAAY,CAAC;AACzE,WAAK,OAAO;AAAA,QACV,EAAE,SAAS,WAAW,IAAI,OAAO,SAAS,KAAK;AAAA,QAC/C,gBAAgB,CAAC;AAAA,MACnB;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,+CAA+C,KAAK;AAAA,IACpE;AAAA,EACF;AAAA,EAEO,YAAY,aAAqB,OAA2B;AACjE,SAAK,MAAM,UAAU,WAAW,IAAI;AACpC,SAAK,cAAc,aAAa,OAAO,KAAK,MAAM,SAAS;AAC3D,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEO,eAAe,aAA2B;AAC/C,WAAO,KAAK,MAAM,UAAU,WAAW;AACvC,SAAK,cAAc,aAAa,MAAM,KAAK,MAAM,SAAS;AAC1D,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEO,oBAA0B;AAC/B,SAAK,MAAM,YAAY,CAAC;AACxB,SAAK,cAAc,IAAI,MAAM,KAAK,MAAM,SAAS;AACjD,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEO,eAA6C;AAClD,WAAO,EAAE,GAAG,KAAK,MAAM,UAAU;AAAA,EACnC;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE;AAAA,IACF;AAGA,UAAM,YAAY,oBAAoB,KAAK,QAAQ;AACnD,QAAI,SAAS,eAAe,SAAS,GAAG;AACtC;AAAA,IACF;AAGA,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,KAAK;AACb,YAAQ,aAAa,wBAAwB,KAAK,YAAY,EAAE;AAChE,YAAQ,YAAY,KAAK,eAAe;AAGxC,SAAK,aAAa;AAGlB,aAAS,KAAK,YAAY,OAAO;AAGjC,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AAEA,UAAM,UAAU,SAAS,eAAe,oBAAoB,KAAK,QAAQ,EAAE;AAC3E,QAAI,SAAS;AACX,cAAQ,OAAO;AAAA,IACjB;AAEA,UAAM,SAAS,SAAS,eAAe,yBAAyB;AAChE,QAAI,QAAQ;AACV,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,iBAAyB;AAC/B,UAAM,EAAE,WAAW,OAAO,IAAI,KAAK,OAAO;AAC1C,UAAM,gBAAgB,oBAAoB,SAAS;AACnD,UAAM,UAAU,QAAQ,KAAK;AAC7B,UAAM,UAAU,QAAQ,KAAK;AAC7B,UAAM,WAAW,2BAA2B,KAAK,QAAQ;AACzD,UAAM,UAAU,0BAA0B,KAAK,QAAQ;AACvD,UAAM,WAAW,yBAAyB,KAAK,QAAQ;AACvD,UAAM,UAAU,sBAAsB,KAAK,QAAQ;AACnD,UAAM,YAAY,4BAA4B,KAAK,QAAQ;AAE3D,WAAO;AAAA,+CACoC,aAAa,wBAAwB,OAAO,iBAAiB,OAAO;AAAA,sDAC7D,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kDAuCZ,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKrC,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKR,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sDAS2B,SAAS;AAAA,kDACb,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKnE;AAAA,EAEQ,eAAqB;AAC3B,QAAI,OAAO,aAAa,aAAa;AACnC;AAAA,IACF;AAEA,QAAI,SAAS,eAAe,yBAAyB,GAAG;AACtD;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,cAAc,OAAO;AAC7C,WAAO,KAAK;AACZ,WAAO,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","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 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=\"16\" fill=\"none\"></rect>\n <line\n x1=\"40\"\n y1=\"128\"\n x2=\"128\"\n y2=\"40\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"40\"\n x2=\"40\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"128\"\n x2=\"128\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></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 apiKey: this.apiKey,\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;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","names":["result"]}
|
package/dist/index.mjs
CHANGED
|
@@ -169,9 +169,11 @@ var SupaToolbarPlugin = class {
|
|
|
169
169
|
const searchId = `supaship-search-input-${this.clientId}`;
|
|
170
170
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
171
171
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
172
|
+
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
173
|
+
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
172
174
|
return `
|
|
173
175
|
<div class="supaship-toolbar-container ${positionClass}" style="--offset-x: ${offsetX}; --offset-y: ${offsetY};">
|
|
174
|
-
<button class="supaship-toolbar-toggle" id="${toggleId}" aria-label="
|
|
176
|
+
<button class="supaship-toolbar-toggle" id="${toggleId}" aria-label="Supaship Toolbar" title="Supaship Toolbar">
|
|
175
177
|
<svg
|
|
176
178
|
xmlns="http://www.w3.org/2000/svg"
|
|
177
179
|
viewBox="0 0 256 256"
|
|
@@ -209,6 +211,7 @@ var SupaToolbarPlugin = class {
|
|
|
209
211
|
stroke-linejoin="round"
|
|
210
212
|
stroke-width="16"></line>
|
|
211
213
|
</svg>
|
|
214
|
+
<span class="supaship-toolbar-badge" id="${badgeId}"></span>
|
|
212
215
|
</button>
|
|
213
216
|
<div class="supaship-toolbar-panel" id="${panelId}">
|
|
214
217
|
<div class="supaship-toolbar-header">
|
|
@@ -218,6 +221,7 @@ var SupaToolbarPlugin = class {
|
|
|
218
221
|
id="${searchId}"
|
|
219
222
|
placeholder="Search features"
|
|
220
223
|
/>
|
|
224
|
+
<span class="supaship-toolbar-overrides-label" id="${headerOverrideCountId}">0 overrides</span>
|
|
221
225
|
<button
|
|
222
226
|
class="supaship-header-btn"
|
|
223
227
|
id="${clearId}"
|
|
@@ -294,6 +298,29 @@ var SupaToolbarPlugin = class {
|
|
|
294
298
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
.supaship-toolbar-badge {
|
|
302
|
+
position: absolute;
|
|
303
|
+
top: -4px;
|
|
304
|
+
right: -4px;
|
|
305
|
+
background: #ef4444;
|
|
306
|
+
color: white;
|
|
307
|
+
font-size: 10px;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
min-width: 18px;
|
|
310
|
+
height: 18px;
|
|
311
|
+
border-radius: 9px;
|
|
312
|
+
display: none;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
padding: 0 5px;
|
|
316
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
317
|
+
pointer-events: none;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.supaship-toolbar-badge.show {
|
|
321
|
+
display: flex;
|
|
322
|
+
}
|
|
323
|
+
|
|
297
324
|
.supaship-toolbar-panel {
|
|
298
325
|
position: absolute;
|
|
299
326
|
bottom: 48px;
|
|
@@ -332,10 +359,13 @@ var SupaToolbarPlugin = class {
|
|
|
332
359
|
padding: 12px;
|
|
333
360
|
border-bottom: 1px solid #333;
|
|
334
361
|
background: #0f0f0f;
|
|
362
|
+
min-width: 0;
|
|
363
|
+
overflow: hidden;
|
|
335
364
|
}
|
|
336
365
|
|
|
337
366
|
.supaship-search-input {
|
|
338
367
|
flex: 1;
|
|
368
|
+
min-width: 0;
|
|
339
369
|
background: transparent;
|
|
340
370
|
border: none;
|
|
341
371
|
color: #e5e5e5;
|
|
@@ -370,7 +400,8 @@ var SupaToolbarPlugin = class {
|
|
|
370
400
|
flex: 1;
|
|
371
401
|
overflow-y: auto;
|
|
372
402
|
padding: 8px;
|
|
373
|
-
min-height:
|
|
403
|
+
min-height: 300px;
|
|
404
|
+
max-height: 300px;
|
|
374
405
|
}
|
|
375
406
|
|
|
376
407
|
.supaship-toolbar-empty {
|
|
@@ -402,6 +433,39 @@ var SupaToolbarPlugin = class {
|
|
|
402
433
|
font-size: 13px;
|
|
403
434
|
flex: 1;
|
|
404
435
|
min-width: 0;
|
|
436
|
+
display: inline-flex;
|
|
437
|
+
align-items: center;
|
|
438
|
+
gap: 6px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.supaship-feature-override-indicator {
|
|
442
|
+
width: 6px;
|
|
443
|
+
height: 6px;
|
|
444
|
+
border-radius: 50%;
|
|
445
|
+
background: #ef4444;
|
|
446
|
+
flex-shrink: 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.supaship-toolbar-overrides-label {
|
|
450
|
+
font-size: 12px;
|
|
451
|
+
color: #888;
|
|
452
|
+
white-space: nowrap;
|
|
453
|
+
flex-shrink: 0;
|
|
454
|
+
transition: all 0.2s;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.supaship-toolbar-overrides-label-count.has-overrides {
|
|
458
|
+
background: #ef4444;
|
|
459
|
+
color: white;
|
|
460
|
+
font-size: 10px;
|
|
461
|
+
font-weight: 600;
|
|
462
|
+
min-width: 18px;
|
|
463
|
+
height: 18px;
|
|
464
|
+
border-radius: 9px;
|
|
465
|
+
padding: 2px;
|
|
466
|
+
display: inline-flex;
|
|
467
|
+
align-items: center;
|
|
468
|
+
justify-content: center;
|
|
405
469
|
}
|
|
406
470
|
|
|
407
471
|
.supaship-feature-actions {
|
|
@@ -602,7 +666,7 @@ var SupaToolbarPlugin = class {
|
|
|
602
666
|
this.removeOverride(featureName);
|
|
603
667
|
} else if (action === "set") {
|
|
604
668
|
const textarea = content.querySelector(
|
|
605
|
-
`textarea[data-feature="${featureName}"]`
|
|
669
|
+
`textarea[data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
606
670
|
);
|
|
607
671
|
if (textarea && textarea.value.trim()) {
|
|
608
672
|
try {
|
|
@@ -628,7 +692,7 @@ var SupaToolbarPlugin = class {
|
|
|
628
692
|
const featureName = target.dataset.feature;
|
|
629
693
|
const originalValue = target.dataset.original || "";
|
|
630
694
|
const overrideBtn = content.querySelector(
|
|
631
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
695
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
632
696
|
);
|
|
633
697
|
if (overrideBtn) {
|
|
634
698
|
const hasChanged = target.value !== originalValue;
|
|
@@ -660,7 +724,7 @@ var SupaToolbarPlugin = class {
|
|
|
660
724
|
e.preventDefault();
|
|
661
725
|
const featureName = target.dataset.feature;
|
|
662
726
|
const overrideBtn = content.querySelector(
|
|
663
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
727
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
664
728
|
);
|
|
665
729
|
if (overrideBtn && !overrideBtn.disabled) {
|
|
666
730
|
overrideBtn.click();
|
|
@@ -675,16 +739,50 @@ var SupaToolbarPlugin = class {
|
|
|
675
739
|
}
|
|
676
740
|
const contentId = `supaship-toolbar-content-${this.clientId}`;
|
|
677
741
|
const clearId = `supaship-clear-all-${this.clientId}`;
|
|
742
|
+
const badgeId = `supaship-toolbar-badge-${this.clientId}`;
|
|
743
|
+
const headerOverrideCountId = `supaship-header-override-count-${this.clientId}`;
|
|
744
|
+
const toggleId = `supaship-toolbar-toggle-${this.clientId}`;
|
|
678
745
|
const content = document.getElementById(contentId);
|
|
679
746
|
const clearAllBtn = document.getElementById(clearId);
|
|
747
|
+
const badge = document.getElementById(badgeId);
|
|
748
|
+
const headerOverrideCount = document.getElementById(headerOverrideCountId);
|
|
749
|
+
const toggleBtn = document.getElementById(toggleId);
|
|
680
750
|
if (!content) {
|
|
681
751
|
console.warn("[Toolbar] Content element not found:", contentId);
|
|
682
752
|
return;
|
|
683
753
|
}
|
|
684
|
-
const
|
|
754
|
+
const overrideCount = Object.keys(this.state.overrides).length;
|
|
755
|
+
const hasOverrides = overrideCount > 0;
|
|
685
756
|
if (clearAllBtn) {
|
|
686
757
|
clearAllBtn.disabled = !hasOverrides;
|
|
687
758
|
}
|
|
759
|
+
if (badge) {
|
|
760
|
+
if (hasOverrides) {
|
|
761
|
+
badge.textContent = overrideCount > 99 ? "99+" : String(overrideCount);
|
|
762
|
+
badge.classList.add("show");
|
|
763
|
+
} else {
|
|
764
|
+
badge.classList.remove("show");
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (toggleBtn) {
|
|
768
|
+
if (hasOverrides) {
|
|
769
|
+
toggleBtn.setAttribute(
|
|
770
|
+
"title",
|
|
771
|
+
`Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? "" : "s"}`
|
|
772
|
+
);
|
|
773
|
+
toggleBtn.setAttribute(
|
|
774
|
+
"aria-label",
|
|
775
|
+
`Supaship Toolbar, ${overrideCount} override${overrideCount === 1 ? "" : "s"}`
|
|
776
|
+
);
|
|
777
|
+
} else {
|
|
778
|
+
toggleBtn.setAttribute("title", "Supaship Toolbar");
|
|
779
|
+
toggleBtn.setAttribute("aria-label", "Supaship Toolbar");
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (headerOverrideCount) {
|
|
783
|
+
const escapedCount = this.escapeHtml(String(overrideCount));
|
|
784
|
+
headerOverrideCount.innerHTML = `<span class="supaship-toolbar-overrides-label-count ${hasOverrides ? "has-overrides" : ""}">${escapedCount}</span> override${overrideCount === 1 ? "" : "s"}`;
|
|
785
|
+
}
|
|
688
786
|
const features = Array.from(this.state.features).sort();
|
|
689
787
|
const filteredFeatures = features.filter(
|
|
690
788
|
(name) => name.toLowerCase().includes(this.state.searchQuery)
|
|
@@ -705,7 +803,10 @@ var SupaToolbarPlugin = class {
|
|
|
705
803
|
return `
|
|
706
804
|
<div class="${itemClass}">
|
|
707
805
|
<div class="supaship-feature-row">
|
|
708
|
-
<span class="supaship-feature-name"
|
|
806
|
+
<span class="supaship-feature-name">
|
|
807
|
+
${this.escapeHtml(featureName)}
|
|
808
|
+
${hasOverride ? '<span class="supaship-feature-override-indicator" title="Serving local override"></span>' : ""}
|
|
809
|
+
</span>
|
|
709
810
|
<div class="supaship-feature-actions">
|
|
710
811
|
${hasOverride ? `
|
|
711
812
|
<button
|
|
@@ -740,7 +841,10 @@ var SupaToolbarPlugin = class {
|
|
|
740
841
|
return `
|
|
741
842
|
<div class="${itemClass}">
|
|
742
843
|
<div class="supaship-feature-row">
|
|
743
|
-
<span class="supaship-feature-name"
|
|
844
|
+
<span class="supaship-feature-name">
|
|
845
|
+
${escapedFeatureName}
|
|
846
|
+
${hasOverride ? '<span class="supaship-feature-override-indicator" title="Serving local override"></span>' : ""}
|
|
847
|
+
</span>
|
|
744
848
|
<div class="supaship-feature-actions">
|
|
745
849
|
${hasOverride ? `
|
|
746
850
|
<button
|
|
@@ -782,7 +886,7 @@ var SupaToolbarPlugin = class {
|
|
|
782
886
|
const featureName = textareaElement.dataset.feature;
|
|
783
887
|
const originalValue = textareaElement.dataset.original || "";
|
|
784
888
|
const overrideBtn = content.querySelector(
|
|
785
|
-
`button[data-action="set"][data-feature="${featureName}"]`
|
|
889
|
+
`button[data-action="set"][data-feature="${this.escapeCssSelector(featureName)}"]`
|
|
786
890
|
);
|
|
787
891
|
if (overrideBtn) {
|
|
788
892
|
const hasChanged = textareaElement.value !== originalValue;
|
|
@@ -809,6 +913,13 @@ var SupaToolbarPlugin = class {
|
|
|
809
913
|
return escapeMap[char];
|
|
810
914
|
});
|
|
811
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Escapes special characters in CSS attribute selectors to prevent CSS injection
|
|
918
|
+
* @param value The value to escape for use in CSS attribute selectors
|
|
919
|
+
*/
|
|
920
|
+
escapeCssSelector(value) {
|
|
921
|
+
return value.replace(/["'\\\]]/g, "\\$&");
|
|
922
|
+
}
|
|
812
923
|
};
|
|
813
924
|
|
|
814
925
|
// src/constants.ts
|
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\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=\"Toggle feature flags\">\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=\"16\" fill=\"none\"></rect>\n <line\n x1=\"40\"\n y1=\"128\"\n x2=\"128\"\n y2=\"40\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"40\"\n x2=\"40\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"128\"\n x2=\"128\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n </svg>\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 <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-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 }\n\n .supaship-search-input {\n flex: 1;\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: 200px;\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 }\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=\"${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=\"${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=\"${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\n const content = document.getElementById(contentId)\n const clearAllBtn = document.getElementById(clearId) 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\n const hasOverrides = Object.keys(this.state.overrides).length > 0\n if (clearAllBtn) {\n clearAllBtn.disabled = !hasOverrides\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\">${this.escapeHtml(featureName)}</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\">${escapedFeatureName}</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=\"${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","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 apiKey: this.apiKey,\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;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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 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=\"16\" fill=\"none\"></rect>\n <line\n x1=\"40\"\n y1=\"128\"\n x2=\"128\"\n y2=\"40\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"40\"\n x2=\"40\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></line>\n <line\n x1=\"216\"\n y1=\"128\"\n x2=\"128\"\n y2=\"216\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"16\"></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 apiKey: this.apiKey,\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supashiphq/javascript-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"description": "JavaScript SDK for Supaship",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
45
|
-
"url": "https://github.com/supashiphq/sdk.git",
|
|
45
|
+
"url": "git+https://github.com/supashiphq/sdk.git",
|
|
46
46
|
"directory": "packages/javascript"
|
|
47
47
|
},
|
|
48
48
|
"bugs": {
|