@ops-ai/react-router-toggly 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,787 @@
1
+ import { registerContext, normalizeEntityContext, serializeJsonForInlineScript, resolveEvaluatedDefinition, appendEvaluationContext } from '@ops-ai/toggly-hooks-types';
2
+ export { normalizeEntityContext, registerContext } from '@ops-ai/toggly-hooks-types';
3
+ import '@ops-ai/toggly-eval';
4
+ import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
5
+ import { buildFlagGateIndex, applyLocalGate } from '@ops-ai/toggly-local-gates';
6
+ import { jsx, Fragment } from 'react/jsx-runtime';
7
+ import { useRouteLoaderData, useLoaderData } from 'react-router';
8
+
9
+ // src/core/index.ts
10
+
11
+ // src/core/types.ts
12
+ var TogglyError = class extends Error {
13
+ constructor(message, code, cause) {
14
+ super(message);
15
+ this.code = code;
16
+ this.cause = cause;
17
+ this.name = "TogglyError";
18
+ }
19
+ };
20
+ var TogglyNetworkError = class extends TogglyError {
21
+ constructor(message, cause) {
22
+ super(message, "NETWORK_ERROR", cause);
23
+ this.name = "TogglyNetworkError";
24
+ }
25
+ };
26
+ var TogglyConfigError = class extends TogglyError {
27
+ constructor(message) {
28
+ super(message, "CONFIG_ERROR");
29
+ this.name = "TogglyConfigError";
30
+ }
31
+ };
32
+ var TogglyTimeoutError = class extends TogglyError {
33
+ constructor(message) {
34
+ super(message, "TIMEOUT_ERROR");
35
+ this.name = "TogglyTimeoutError";
36
+ }
37
+ };
38
+ var DEFAULT_CONFIG = {
39
+ baseUrl: "https://definitions.toggly.io",
40
+ environment: "Production",
41
+ timeout: 1e4,
42
+ debug: false,
43
+ evaluationMode: "remote"
44
+ };
45
+ function mergeConfig(config) {
46
+ return {
47
+ ...DEFAULT_CONFIG,
48
+ ...config
49
+ };
50
+ }
51
+ function buildDefinitionsUrl(config, context) {
52
+ const { baseUrl, appKey, environment, groups, claims, evaluationMode } = mergeConfig(config);
53
+ if (!appKey) {
54
+ throw new Error("appKey is required");
55
+ }
56
+ const mode = evaluationMode ?? "remote";
57
+ const pathSegment = mode === "local" ? "definitions-signed" : "evaluated-signed";
58
+ const url = new URL(`${baseUrl}/${pathSegment}/${appKey}/${environment}`);
59
+ if (mode === "local") {
60
+ return url.toString();
61
+ }
62
+ const fromParam = typeof context === "string" ? { identity: context } : context;
63
+ appendEvaluationContext(
64
+ url,
65
+ {
66
+ identity: fromParam?.identity,
67
+ groups: fromParam?.groups ?? groups,
68
+ claims: fromParam?.claims ?? claims
69
+ },
70
+ "evaluated"
71
+ );
72
+ return url.toString();
73
+ }
74
+ function isFeatureEnabled(flags, featureKey, defaultValue = false, entityContext) {
75
+ if (!flags || Object.keys(flags).length === 0) {
76
+ return defaultValue;
77
+ }
78
+ const value = flags[featureKey];
79
+ if (value === void 0) {
80
+ return defaultValue;
81
+ }
82
+ return resolveEvaluatedDefinition(value, entityContext);
83
+ }
84
+ function createLogger(debug) {
85
+ return {
86
+ debug: (...args) => {
87
+ if (debug) {
88
+ console.debug("[Toggly]", ...args);
89
+ }
90
+ },
91
+ info: (...args) => {
92
+ if (debug) {
93
+ console.info("[Toggly]", ...args);
94
+ }
95
+ },
96
+ warn: (...args) => {
97
+ console.warn("[Toggly]", ...args);
98
+ },
99
+ error: (...args) => {
100
+ console.error("[Toggly]", ...args);
101
+ }
102
+ };
103
+ }
104
+ async function fetchWithTimeout(url, options = {}, timeout = 1e4) {
105
+ const controller = new AbortController();
106
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
107
+ try {
108
+ const response = await fetch(url, {
109
+ ...options,
110
+ signal: controller.signal
111
+ });
112
+ return response;
113
+ } finally {
114
+ clearTimeout(timeoutId);
115
+ }
116
+ }
117
+
118
+ // src/core/constants.ts
119
+ var STORAGE_KEYS = {
120
+ /** Identity cookie/storage key */
121
+ IDENTITY: "toggly_identity",
122
+ /** Feature flags cache key */
123
+ FLAGS: "toggly_flags",
124
+ /** Config cache key */
125
+ CONFIG: "toggly_config",
126
+ /** Last fetch timestamp key */
127
+ LAST_FETCH: "toggly_last_fetch"
128
+ };
129
+ var HEADERS = {
130
+ /** Identity header */
131
+ IDENTITY: "x-toggly-identity",
132
+ /** Feature flags header */
133
+ FLAGS: "x-toggly-flags",
134
+ /** Cache control */
135
+ CACHE_CONTROL: "cache-control"
136
+ };
137
+ var TOGGLY_LOADER_KEY = "__toggly";
138
+
139
+ // src/client/sdk-identity.ts
140
+ var SDK_ID = "react-router";
141
+ var SDK_VERSION = "1.0.0";
142
+ function appendSdkQueryParams(params) {
143
+ params.set("sdk", SDK_ID);
144
+ params.set("sdkVersion", SDK_VERSION);
145
+ }
146
+ var TogglyContext = createContext(void 0);
147
+ function TogglyProvider({
148
+ children,
149
+ serverContext,
150
+ config,
151
+ enableRefresh = false,
152
+ refreshInterval = 6e4,
153
+ onFlagsChange
154
+ }) {
155
+ const mergedConfig = useMemo(
156
+ () => config ? mergeConfig(config) : void 0,
157
+ [config]
158
+ );
159
+ const logger = useMemo(
160
+ () => createLogger(mergedConfig?.debug ?? false),
161
+ [mergedConfig?.debug]
162
+ );
163
+ const [flags, setFlags] = useState(
164
+ serverContext?.flags ?? mergedConfig?.featureDefaults ?? {}
165
+ );
166
+ const [identity, setIdentity] = useState(
167
+ serverContext ? serverContext.identity : mergedConfig?.identity
168
+ );
169
+ const [isReady, setIsReady] = useState(!!serverContext);
170
+ const [hooks, setHooks] = useState([]);
171
+ const [localGatesRevision, setLocalGatesRevision] = useState(0);
172
+ const localGatesRef = useRef(mergedConfig?.localGates ?? []);
173
+ const localGateIndexRef = useRef(
174
+ buildFlagGateIndex(localGatesRef.current)
175
+ );
176
+ const localGatesListenersRef = useRef(/* @__PURE__ */ new Set());
177
+ const getEffectiveFlag = useCallback(
178
+ (featureKey, defaultValue = false, entityContext) => {
179
+ const remote = isFeatureEnabled(
180
+ flags,
181
+ featureKey,
182
+ defaultValue,
183
+ entityContext
184
+ );
185
+ return applyLocalGate(
186
+ remote,
187
+ featureKey,
188
+ localGatesRef.current,
189
+ localGateIndexRef.current
190
+ );
191
+ },
192
+ [flags, localGatesRevision]
193
+ );
194
+ const registerContext2 = useCallback(
195
+ (kind, mapper) => {
196
+ registerContext(kind, mapper);
197
+ },
198
+ []
199
+ );
200
+ const setLocalGates = useCallback((gates) => {
201
+ localGatesRef.current = [...gates];
202
+ localGateIndexRef.current = buildFlagGateIndex(localGatesRef.current);
203
+ }, []);
204
+ const notifyLocalGatesChanged = useCallback(() => {
205
+ setLocalGatesRevision((revision) => revision + 1);
206
+ localGatesListenersRef.current.forEach((listener) => {
207
+ try {
208
+ listener();
209
+ } catch (error) {
210
+ logger.error("Local gate listener error:", error);
211
+ }
212
+ });
213
+ }, [logger]);
214
+ const subscribeLocalGatesChanged = useCallback(
215
+ (listener) => {
216
+ localGatesListenersRef.current.add(listener);
217
+ return () => {
218
+ localGatesListenersRef.current.delete(listener);
219
+ };
220
+ },
221
+ []
222
+ );
223
+ useEffect(() => {
224
+ if (mergedConfig?.localGates) {
225
+ setLocalGates(mergedConfig.localGates);
226
+ }
227
+ }, [mergedConfig?.localGates, setLocalGates]);
228
+ const executeAfterRefresh = useCallback(
229
+ async (newFlags) => {
230
+ for (const hook of hooks) {
231
+ if (hook.afterRefresh) {
232
+ try {
233
+ await hook.afterRefresh(newFlags);
234
+ } catch (error) {
235
+ logger.error(
236
+ `Error in hook "${hook.getMetadata().name}.afterRefresh":`,
237
+ error
238
+ );
239
+ }
240
+ }
241
+ }
242
+ },
243
+ [hooks, logger]
244
+ );
245
+ const fetchFlags = useCallback(
246
+ async (userIdentity) => {
247
+ if (!mergedConfig?.appKey) {
248
+ logger.debug("No appKey, using current flags.");
249
+ return flags;
250
+ }
251
+ try {
252
+ const url = buildDefinitionsUrl(mergedConfig, userIdentity);
253
+ logger.debug(`Fetching flags from: ${url}`);
254
+ const response = await fetchWithTimeout(url, {}, mergedConfig.timeout);
255
+ if (!response.ok) {
256
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
257
+ }
258
+ const payload = await response.json();
259
+ const newFlags = payload && typeof payload === "object" ? payload : {};
260
+ logger.debug(`Fetched ${Object.keys(newFlags).length} flags.`);
261
+ return newFlags;
262
+ } catch (error) {
263
+ logger.warn("Failed to fetch flags:", error);
264
+ return flags;
265
+ }
266
+ },
267
+ [mergedConfig, flags, logger]
268
+ );
269
+ const updateFlags = useCallback(
270
+ async (newFlags) => {
271
+ setFlags(newFlags);
272
+ await executeAfterRefresh(newFlags);
273
+ onFlagsChange?.(newFlags);
274
+ },
275
+ [executeAfterRefresh, onFlagsChange]
276
+ );
277
+ const isEnabled = useCallback(
278
+ (featureKey, defaultValue = false, entity, kind) => {
279
+ return getEffectiveFlag(
280
+ featureKey,
281
+ defaultValue,
282
+ normalizeEntityContext(entity, kind)
283
+ );
284
+ },
285
+ [getEffectiveFlag]
286
+ );
287
+ const isDisabled = useCallback(
288
+ (featureKey, defaultValue = true, entity, kind) => {
289
+ return !isEnabled(featureKey, !defaultValue, entity, kind);
290
+ },
291
+ [isEnabled]
292
+ );
293
+ const evaluateGate = useCallback(
294
+ (featureKeys, requirement = "all", negate = false, entity, kind) => {
295
+ if (featureKeys.length === 0) {
296
+ return !negate;
297
+ }
298
+ const entityContext = normalizeEntityContext(entity, kind);
299
+ let result;
300
+ if (requirement === "any") {
301
+ result = featureKeys.some(
302
+ (key) => getEffectiveFlag(key, false, entityContext)
303
+ );
304
+ } else {
305
+ result = featureKeys.every(
306
+ (key) => getEffectiveFlag(key, false, entityContext)
307
+ );
308
+ }
309
+ return negate ? !result : result;
310
+ },
311
+ [getEffectiveFlag]
312
+ );
313
+ const identify = useCallback(
314
+ async (newIdentity, _context) => {
315
+ logger.debug(`Identifying user: ${newIdentity}`);
316
+ for (const hook of hooks) {
317
+ if (hook.beforeIdentify) {
318
+ try {
319
+ await hook.beforeIdentify(newIdentity);
320
+ } catch (error) {
321
+ logger.error(
322
+ `Error in hook "${hook.getMetadata().name}.beforeIdentify":`,
323
+ error
324
+ );
325
+ }
326
+ }
327
+ }
328
+ setIdentity(newIdentity);
329
+ const newFlags = await fetchFlags(newIdentity);
330
+ await updateFlags(newFlags);
331
+ for (let i = hooks.length - 1; i >= 0; i--) {
332
+ const hook = hooks[i];
333
+ if (hook.afterIdentify) {
334
+ try {
335
+ await hook.afterIdentify(newIdentity, void 0);
336
+ } catch (error) {
337
+ logger.error(
338
+ `Error in hook "${hook.getMetadata().name}.afterIdentify":`,
339
+ error
340
+ );
341
+ }
342
+ }
343
+ }
344
+ setIsReady(true);
345
+ },
346
+ [hooks, fetchFlags, updateFlags, logger]
347
+ );
348
+ const reset = useCallback(async () => {
349
+ logger.debug("Resetting identity");
350
+ setIdentity(void 0);
351
+ const newFlags = await fetchFlags();
352
+ await updateFlags(newFlags);
353
+ }, [fetchFlags, updateFlags, logger]);
354
+ const refresh = useCallback(async () => {
355
+ logger.debug("Refreshing flags");
356
+ const newFlags = await fetchFlags(identity);
357
+ await updateFlags(newFlags);
358
+ }, [identity, fetchFlags, updateFlags, logger]);
359
+ const addHook = useCallback(
360
+ (hook) => {
361
+ setHooks((currentHooks) => {
362
+ const metadata = hook.getMetadata();
363
+ const exists = currentHooks.find(
364
+ (h) => h.getMetadata().name === metadata.name
365
+ );
366
+ if (exists) {
367
+ logger.warn(`Hook "${metadata.name}" already registered. Skipping.`);
368
+ return currentHooks;
369
+ }
370
+ logger.debug(`Hook "${metadata.name}" registered.`);
371
+ return [...currentHooks, hook];
372
+ });
373
+ },
374
+ [logger]
375
+ );
376
+ const removeHook = useCallback(
377
+ (name) => {
378
+ let removed = false;
379
+ setHooks((currentHooks) => {
380
+ const index = currentHooks.findIndex(
381
+ (h) => h.getMetadata().name === name
382
+ );
383
+ if (index > -1) {
384
+ logger.debug(`Hook "${name}" removed.`);
385
+ removed = true;
386
+ return [
387
+ ...currentHooks.slice(0, index),
388
+ ...currentHooks.slice(index + 1)
389
+ ];
390
+ }
391
+ return currentHooks;
392
+ });
393
+ return removed;
394
+ },
395
+ [logger]
396
+ );
397
+ const wsRef = useRef(null);
398
+ const wsReconnectTimerRef = useRef(null);
399
+ const wsConnectedRef = useRef(false);
400
+ const lastFallbackRefreshRef = useRef(0);
401
+ const FALLBACK_REFRESH_INTERVAL = 20 * 60 * 1e3;
402
+ const WS_RECONNECT_DELAY = 5e3;
403
+ const refreshRef = useRef(refresh);
404
+ useEffect(() => {
405
+ refreshRef.current = refresh;
406
+ }, [refresh]);
407
+ useEffect(() => {
408
+ if (!enableRefresh || !mergedConfig?.appKey) {
409
+ return;
410
+ }
411
+ logger.debug(`Setting up refresh interval: ${refreshInterval}ms`);
412
+ const intervalId = setInterval(() => {
413
+ if (wsConnectedRef.current) {
414
+ const now = Date.now();
415
+ if (now - lastFallbackRefreshRef.current < FALLBACK_REFRESH_INTERVAL) {
416
+ return;
417
+ }
418
+ lastFallbackRefreshRef.current = now;
419
+ }
420
+ refresh();
421
+ }, refreshInterval);
422
+ return () => {
423
+ clearInterval(intervalId);
424
+ };
425
+ }, [enableRefresh, refreshInterval, refresh, mergedConfig?.appKey, logger]);
426
+ useEffect(() => {
427
+ if (typeof window === "undefined") {
428
+ return;
429
+ }
430
+ if (!mergedConfig?.appKey || !mergedConfig?.baseUrl) {
431
+ return;
432
+ }
433
+ if (typeof WebSocket === "undefined") {
434
+ return;
435
+ }
436
+ function buildWebSocketUrl() {
437
+ const baseUrl = mergedConfig.baseUrl;
438
+ const wsUrl = baseUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
439
+ const params = new URLSearchParams();
440
+ appendSdkQueryParams(params);
441
+ const query = params.toString();
442
+ return `${wsUrl.replace(/\/$/, "")}/${mergedConfig.appKey}/ws${query ? `?${query}` : ""}`;
443
+ }
444
+ function connect() {
445
+ if (wsRef.current) {
446
+ return;
447
+ }
448
+ const wsUrl = buildWebSocketUrl();
449
+ logger.debug(`WebSocket connecting to: ${wsUrl}`);
450
+ try {
451
+ const socket = new WebSocket(wsUrl);
452
+ wsRef.current = socket;
453
+ socket.onopen = () => {
454
+ wsConnectedRef.current = true;
455
+ lastFallbackRefreshRef.current = Date.now();
456
+ logger.debug("WebSocket connected");
457
+ };
458
+ socket.onmessage = (event) => {
459
+ const text = typeof event.data === "string" ? event.data : "";
460
+ try {
461
+ const msg = JSON.parse(text);
462
+ if (msg.type === "ping") {
463
+ return;
464
+ }
465
+ if (msg.type === "flags-updated" || msg.type === "update") {
466
+ logger.debug("WebSocket: definitions updated, refreshing");
467
+ refreshRef.current();
468
+ }
469
+ } catch {
470
+ if (text === "update" || text === "flags-updated") {
471
+ refreshRef.current();
472
+ }
473
+ }
474
+ };
475
+ socket.onclose = () => {
476
+ wsConnectedRef.current = false;
477
+ wsRef.current = null;
478
+ logger.debug("WebSocket disconnected, reconnecting in 5s");
479
+ scheduleReconnect();
480
+ };
481
+ socket.onerror = () => {
482
+ logger.warn("WebSocket error");
483
+ };
484
+ } catch (error) {
485
+ logger.warn("Failed to create WebSocket:", error);
486
+ wsRef.current = null;
487
+ scheduleReconnect();
488
+ }
489
+ }
490
+ function scheduleReconnect() {
491
+ if (wsReconnectTimerRef.current) {
492
+ return;
493
+ }
494
+ wsReconnectTimerRef.current = setTimeout(() => {
495
+ wsReconnectTimerRef.current = null;
496
+ connect();
497
+ }, WS_RECONNECT_DELAY);
498
+ }
499
+ connect();
500
+ return () => {
501
+ if (wsReconnectTimerRef.current) {
502
+ clearTimeout(wsReconnectTimerRef.current);
503
+ wsReconnectTimerRef.current = null;
504
+ }
505
+ if (wsRef.current) {
506
+ wsRef.current.onclose = null;
507
+ wsRef.current.onerror = null;
508
+ wsRef.current.onmessage = null;
509
+ wsRef.current.onopen = null;
510
+ wsRef.current.close();
511
+ wsRef.current = null;
512
+ wsConnectedRef.current = false;
513
+ }
514
+ };
515
+ }, [mergedConfig?.appKey, mergedConfig?.baseUrl, logger]);
516
+ useEffect(() => {
517
+ if (!serverContext && mergedConfig?.appKey) {
518
+ logger.debug("No server context, initializing client-side");
519
+ fetchFlags(identity).then((newFlags) => {
520
+ setFlags(newFlags);
521
+ setIsReady(true);
522
+ });
523
+ }
524
+ }, []);
525
+ const contextValue = useMemo(
526
+ () => ({
527
+ flags,
528
+ isReady,
529
+ identity,
530
+ isEnabled,
531
+ isDisabled,
532
+ evaluateGate,
533
+ registerContext: registerContext2,
534
+ identify,
535
+ reset,
536
+ refresh,
537
+ addHook,
538
+ removeHook,
539
+ setLocalGates,
540
+ notifyLocalGatesChanged,
541
+ subscribeLocalGatesChanged
542
+ }),
543
+ [
544
+ flags,
545
+ isReady,
546
+ identity,
547
+ isEnabled,
548
+ isDisabled,
549
+ evaluateGate,
550
+ registerContext2,
551
+ identify,
552
+ reset,
553
+ refresh,
554
+ addHook,
555
+ removeHook,
556
+ setLocalGates,
557
+ notifyLocalGatesChanged,
558
+ subscribeLocalGatesChanged
559
+ ]
560
+ );
561
+ return /* @__PURE__ */ jsx(TogglyContext.Provider, { value: contextValue, children });
562
+ }
563
+ function useTogglyContext() {
564
+ const context = useContext(TogglyContext);
565
+ if (!context) {
566
+ throw new Error("useTogglyContext must be used within a TogglyProvider");
567
+ }
568
+ return context;
569
+ }
570
+ function useToggly() {
571
+ return useTogglyContext();
572
+ }
573
+ function useFeature(featureKey, defaultValue = false, entity, kind) {
574
+ const { isEnabled } = useTogglyContext();
575
+ return isEnabled(featureKey, defaultValue, entity, kind);
576
+ }
577
+ function useFeatureDisabled(featureKey, defaultValue = true, entity, kind) {
578
+ const { isDisabled } = useTogglyContext();
579
+ return isDisabled(featureKey, defaultValue, entity, kind);
580
+ }
581
+ function useFeatureGate(featureKeys, requirement = "all", negate = false, entity, kind) {
582
+ const { evaluateGate } = useTogglyContext();
583
+ return evaluateGate(featureKeys, requirement, negate, entity, kind);
584
+ }
585
+ function useFeatureFlags() {
586
+ const { flags } = useTogglyContext();
587
+ return flags;
588
+ }
589
+ function useFeatures(featureKeys, defaultValue = false) {
590
+ const { isEnabled } = useTogglyContext();
591
+ return useMemo(() => {
592
+ const result = {};
593
+ for (const key of featureKeys) {
594
+ result[key] = isEnabled(key, defaultValue);
595
+ }
596
+ return result;
597
+ }, [featureKeys, isEnabled, defaultValue]);
598
+ }
599
+ function useFeatureCallback(featureKey, enabledCallback, disabledCallback, defaultValue = false) {
600
+ const enabled = useFeature(featureKey, defaultValue);
601
+ return enabled ? enabledCallback() : disabledCallback();
602
+ }
603
+ function useFeatureValue(featureKey, enabledValue, disabledValue, defaultValue = false) {
604
+ const enabled = useFeature(featureKey, defaultValue);
605
+ return enabled ? enabledValue : disabledValue;
606
+ }
607
+ function useFeatureChange(featureKey, _onChange) {
608
+ const enabled = useFeature(featureKey);
609
+ return enabled;
610
+ }
611
+ function useIdentity() {
612
+ const { identity, identify, reset } = useTogglyContext();
613
+ return {
614
+ identity,
615
+ identify,
616
+ reset
617
+ };
618
+ }
619
+ function useTogglyReady() {
620
+ const { isReady } = useTogglyContext();
621
+ return isReady;
622
+ }
623
+ function useRefreshFlags() {
624
+ const { refresh } = useTogglyContext();
625
+ return refresh;
626
+ }
627
+ function useFeatureRender(featureKey, enabled, disabled, defaultValue = false) {
628
+ const isEnabled = useFeature(featureKey, defaultValue);
629
+ return isEnabled ? enabled : disabled;
630
+ }
631
+ function useABTest(featureKey, variantA, variantB, defaultVariant = "A") {
632
+ const enabled = useFeature(featureKey, defaultVariant === "B");
633
+ return enabled ? variantB : variantA;
634
+ }
635
+ function useFeatureWithLoading(featureKey, defaultValue = false) {
636
+ const { isReady, isEnabled } = useTogglyContext();
637
+ return {
638
+ enabled: isEnabled(featureKey, defaultValue),
639
+ isLoading: !isReady
640
+ };
641
+ }
642
+ function Feature({
643
+ featureKey,
644
+ featureKeys,
645
+ requirement = "all",
646
+ negate = false,
647
+ defaultValue = false,
648
+ children,
649
+ render,
650
+ context,
651
+ contextKind
652
+ }) {
653
+ const keys = featureKeys ?? (featureKey ? [featureKey] : []);
654
+ const singleEnabled = useFeature(keys[0] ?? "", defaultValue, context, contextKind);
655
+ const gateEnabled = useFeatureGate(keys, requirement, false, context, contextKind);
656
+ let enabled;
657
+ if (keys.length === 0) {
658
+ enabled = defaultValue;
659
+ } else if (keys.length === 1) {
660
+ enabled = singleEnabled;
661
+ } else {
662
+ enabled = gateEnabled;
663
+ }
664
+ if (negate) {
665
+ enabled = !enabled;
666
+ }
667
+ if (render) {
668
+ return /* @__PURE__ */ jsx(Fragment, { children: render(enabled) });
669
+ }
670
+ return enabled ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
671
+ }
672
+ function FeatureEnabled({
673
+ featureKey,
674
+ defaultValue = false,
675
+ children
676
+ }) {
677
+ return /* @__PURE__ */ jsx(Feature, { featureKey, defaultValue, children });
678
+ }
679
+ function FeatureDisabled({
680
+ featureKey,
681
+ defaultValue = true,
682
+ children
683
+ }) {
684
+ return /* @__PURE__ */ jsx(Feature, { featureKey, defaultValue: !defaultValue, negate: true, children });
685
+ }
686
+ function FeatureSwitch({
687
+ featureKey,
688
+ defaultValue = false,
689
+ enabled,
690
+ disabled
691
+ }) {
692
+ const isEnabled = useFeature(featureKey, defaultValue);
693
+ return /* @__PURE__ */ jsx(Fragment, { children: isEnabled ? enabled : disabled });
694
+ }
695
+ function FeatureGate({
696
+ featureKeys,
697
+ requirement = "all",
698
+ negate = false,
699
+ children,
700
+ context,
701
+ contextKind
702
+ }) {
703
+ const enabled = useFeatureGate(featureKeys, requirement, negate, context, contextKind);
704
+ return enabled ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
705
+ }
706
+ function RouterTogglyProvider({
707
+ children,
708
+ routeId,
709
+ fallbackContext,
710
+ config,
711
+ ...props
712
+ }) {
713
+ let serverContext;
714
+ try {
715
+ if (routeId) {
716
+ const routeData = useRouteLoaderData(routeId);
717
+ serverContext = routeData?.[TOGGLY_LOADER_KEY];
718
+ } else {
719
+ const loaderData = useLoaderData();
720
+ serverContext = loaderData?.[TOGGLY_LOADER_KEY];
721
+ }
722
+ } catch {
723
+ serverContext = fallbackContext;
724
+ }
725
+ serverContext = serverContext ?? fallbackContext;
726
+ const mergedConfig = config ?? (serverContext ? {
727
+ appKey: serverContext.appKey,
728
+ environment: serverContext.environment
729
+ } : void 0);
730
+ return /* @__PURE__ */ jsx(
731
+ TogglyProvider,
732
+ {
733
+ serverContext,
734
+ config: mergedConfig,
735
+ ...props,
736
+ children
737
+ }
738
+ );
739
+ }
740
+ function useTogglyLoaderData() {
741
+ const loaderData = useLoaderData();
742
+ return {
743
+ data: loaderData,
744
+ toggly: loaderData[TOGGLY_LOADER_KEY]
745
+ };
746
+ }
747
+ function useTogglyRouteLoaderData(routeId) {
748
+ const routeData = useRouteLoaderData(routeId);
749
+ return {
750
+ data: routeData,
751
+ toggly: routeData?.[TOGGLY_LOADER_KEY]
752
+ };
753
+ }
754
+ function extractServerContext(loaderData) {
755
+ return loaderData[TOGGLY_LOADER_KEY];
756
+ }
757
+ function hasTogglyContext(loaderData) {
758
+ const data = loaderData;
759
+ return TOGGLY_LOADER_KEY in data && data[TOGGLY_LOADER_KEY] !== void 0;
760
+ }
761
+ function TogglyScript({
762
+ serverContext,
763
+ nonce
764
+ }) {
765
+ if (!serverContext) {
766
+ return null;
767
+ }
768
+ const script = `window.__TOGGLY_DATA__=${serializeJsonForInlineScript(serverContext)};`;
769
+ return /* @__PURE__ */ jsx(
770
+ "script",
771
+ {
772
+ nonce,
773
+ dangerouslySetInnerHTML: { __html: script },
774
+ suppressHydrationWarning: true
775
+ }
776
+ );
777
+ }
778
+ function getWindowTogglyData() {
779
+ if (typeof window === "undefined") {
780
+ return void 0;
781
+ }
782
+ return window.__TOGGLY_DATA__;
783
+ }
784
+
785
+ export { Feature, FeatureDisabled, FeatureEnabled, FeatureGate, FeatureSwitch, HEADERS, RouterTogglyProvider, STORAGE_KEYS, TOGGLY_LOADER_KEY, TogglyConfigError, TogglyContext, TogglyError, TogglyNetworkError, TogglyProvider, TogglyScript, TogglyTimeoutError, extractServerContext, getWindowTogglyData, hasTogglyContext, useABTest, useFeature, useFeatureCallback, useFeatureChange, useFeatureDisabled, useFeatureFlags, useFeatureGate, useFeatureRender, useFeatureValue, useFeatureWithLoading, useFeatures, useIdentity, useRefreshFlags, useToggly, useTogglyContext, useTogglyLoaderData, useTogglyReady, useTogglyRouteLoaderData };
786
+ //# sourceMappingURL=client.js.map
787
+ //# sourceMappingURL=client.js.map