@traffical/svelte 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,365 @@
1
+ # @traffical/svelte
2
+
3
+ Traffical SDK for Svelte 5 applications. Provides reactive hooks and components for parameter resolution, A/B testing, and feature flags with full SSR/hydration support.
4
+
5
+ ## Features
6
+
7
+ - **Svelte 5 Runes** - Uses `$state`, `$derived`, and `$effect` for reactive, fine-grained updates
8
+ - **Full SSR Support** - Pre-fetch config bundles in SvelteKit load functions
9
+ - **Hydration-Safe** - No FOOC (Flash of Original Content) with proper initialization
10
+ - **Feature Parity** - Same capabilities as the React SDK
11
+ - **Type-Safe** - Full TypeScript support with generic parameter types
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ bun add @traffical/svelte
17
+ # or
18
+ npm install @traffical/svelte
19
+ # or
20
+ pnpm add @traffical/svelte
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Set up the Provider
26
+
27
+ In your root layout, initialize Traffical:
28
+
29
+ ```svelte
30
+ <!-- src/routes/+layout.svelte -->
31
+ <script lang="ts">
32
+ import { TrafficalProvider } from '@traffical/svelte';
33
+ import { PUBLIC_TRAFFICAL_API_KEY } from '$env/static/public';
34
+
35
+ let { data, children } = $props();
36
+ </script>
37
+
38
+ <TrafficalProvider
39
+ config={{
40
+ orgId: 'org_123',
41
+ projectId: 'proj_456',
42
+ env: 'production',
43
+ apiKey: PUBLIC_TRAFFICAL_API_KEY,
44
+ initialBundle: data.traffical?.bundle,
45
+ }}
46
+ >
47
+ {@render children()}
48
+ </TrafficalProvider>
49
+ ```
50
+
51
+ ### 2. Use Parameters in Components
52
+
53
+ ```svelte
54
+ <!-- src/routes/checkout/+page.svelte -->
55
+ <script lang="ts">
56
+ import { useTraffical } from '@traffical/svelte';
57
+
58
+ const { params, ready, track } = useTraffical({
59
+ defaults: {
60
+ 'checkout.ctaText': 'Buy Now',
61
+ 'checkout.ctaColor': '#000000',
62
+ },
63
+ });
64
+
65
+ function handlePurchase(amount: number) {
66
+ // track has the decisionId already bound!
67
+ track('purchase', { value: amount, orderId: 'ord_123' });
68
+ }
69
+ </script>
70
+
71
+ {#if ready}
72
+ <button
73
+ style="background: {params['checkout.ctaColor']}"
74
+ onclick={() => handlePurchase(99.99)}
75
+ >
76
+ {params['checkout.ctaText']}
77
+ </button>
78
+ {:else}
79
+ <button disabled>Loading...</button>
80
+ {/if}
81
+ ```
82
+
83
+ ## SSR with SvelteKit
84
+
85
+ For optimal performance and to prevent content flashing, fetch the config bundle on the server:
86
+
87
+ ### Server Load Function
88
+
89
+ ```typescript
90
+ // src/routes/+layout.server.ts
91
+ import { loadTrafficalBundle } from '@traffical/svelte/sveltekit';
92
+ import { TRAFFICAL_API_KEY } from '$env/static/private';
93
+
94
+ export async function load({ fetch }) {
95
+ const { bundle, error } = await loadTrafficalBundle({
96
+ orgId: 'org_123',
97
+ projectId: 'proj_456',
98
+ env: 'production',
99
+ apiKey: TRAFFICAL_API_KEY,
100
+ fetch,
101
+ });
102
+
103
+ if (error) {
104
+ console.warn('[Traffical] Failed to load config:', error);
105
+ }
106
+
107
+ return {
108
+ traffical: { bundle },
109
+ };
110
+ }
111
+ ```
112
+
113
+ ### Pre-resolve Parameters (Optional)
114
+
115
+ For pages where you need resolved values during SSR:
116
+
117
+ ```typescript
118
+ // src/routes/checkout/+page.server.ts
119
+ import { loadTrafficalBundle, resolveParamsSSR } from '@traffical/svelte/sveltekit';
120
+ import { TRAFFICAL_API_KEY } from '$env/static/private';
121
+
122
+ export async function load({ fetch, cookies }) {
123
+ const { bundle } = await loadTrafficalBundle({
124
+ orgId: 'org_123',
125
+ projectId: 'proj_456',
126
+ env: 'production',
127
+ apiKey: TRAFFICAL_API_KEY,
128
+ fetch,
129
+ });
130
+
131
+ // Get user context from cookies/session
132
+ const userId = cookies.get('userId') || 'anonymous';
133
+
134
+ // Pre-resolve params for this page
135
+ const checkoutParams = resolveParamsSSR(
136
+ bundle,
137
+ { userId },
138
+ {
139
+ 'checkout.ctaText': 'Buy Now',
140
+ 'checkout.ctaColor': '#000000',
141
+ }
142
+ );
143
+
144
+ return {
145
+ traffical: { bundle },
146
+ checkoutParams,
147
+ };
148
+ }
149
+ ```
150
+
151
+ ## API Reference
152
+
153
+ ### Provider
154
+
155
+ #### `<TrafficalProvider>`
156
+
157
+ Wrapper component that initializes Traffical context.
158
+
159
+ ```svelte
160
+ <TrafficalProvider config={...}>
161
+ <slot />
162
+ </TrafficalProvider>
163
+ ```
164
+
165
+ #### `initTraffical(config)`
166
+
167
+ Function-based alternative to the Provider component.
168
+
169
+ ```svelte
170
+ <script>
171
+ import { initTraffical } from '@traffical/svelte';
172
+
173
+ initTraffical({
174
+ orgId: 'org_123',
175
+ projectId: 'proj_456',
176
+ env: 'production',
177
+ apiKey: 'pk_...',
178
+ });
179
+ </script>
180
+ ```
181
+
182
+ ### Hooks
183
+
184
+ #### `useTraffical(options)`
185
+
186
+ Primary hook for parameter resolution and decision tracking.
187
+
188
+ ```typescript
189
+ const { params, ready, decision, error, trackExposure, track } = useTraffical({
190
+ defaults: { 'feature.name': 'default-value' },
191
+ context: { customField: 'value' }, // Optional
192
+ tracking: 'full', // 'full' | 'decision' | 'none'
193
+ });
194
+ ```
195
+
196
+ **Tracking Modes:**
197
+ - `'full'` (default) - Track decision + automatic exposure
198
+ - `'decision'` - Track decision only, manual exposure control
199
+ - `'none'` - No tracking (for SSR, tests, or internal logic)
200
+
201
+ **Return Value:**
202
+ - `params` - Resolved parameter values (reactive)
203
+ - `decision` - Decision metadata (null when `tracking="none"`)
204
+ - `ready` - Whether the client is ready
205
+ - `error` - Any initialization error
206
+ - `trackExposure` - Manually track exposure (no-op when `tracking="none"`)
207
+ - `track` - Track event with bound decisionId (no-op when `tracking="none"`)
208
+
209
+ #### `useTrafficalTrack()`
210
+
211
+ Returns a function to track user events.
212
+
213
+ > **Tip:** For most use cases, use the bound `track` from `useTraffical()` instead. It automatically includes the `decisionId`. Use this standalone hook for advanced scenarios like cross-component event tracking.
214
+
215
+ ```typescript
216
+ // Recommended: use bound track from useTraffical
217
+ const { params, track } = useTraffical({
218
+ defaults: { 'checkout.ctaText': 'Buy Now' },
219
+ });
220
+ track('purchase', { value: 99.99, orderId: 'ord_123' });
221
+
222
+ // Advanced: standalone hook when you need to attribute to a specific decision
223
+ const standaloneTrack = useTrafficalTrack();
224
+ standaloneTrack({
225
+ event: 'purchase',
226
+ properties: { value: 99.99 },
227
+ decisionId: someOtherDecision.decisionId,
228
+ });
229
+ ```
230
+
231
+ #### `useTrafficalReward()` (deprecated)
232
+
233
+ > **Deprecated:** Use `useTrafficalTrack()` instead.
234
+
235
+ #### `useTrafficalClient()`
236
+
237
+ Access the underlying Traffical client directly.
238
+
239
+ ```typescript
240
+ const { client, ready, error } = useTrafficalClient();
241
+
242
+ if (client) {
243
+ const version = client.getConfigVersion();
244
+ const stableId = client.getStableId();
245
+ }
246
+ ```
247
+
248
+ #### `useTrafficalPlugin(name)`
249
+
250
+ Access a registered plugin by name.
251
+
252
+ ```typescript
253
+ import type { DOMBindingPlugin } from '@traffical/js-client';
254
+
255
+ const domPlugin = useTrafficalPlugin<DOMBindingPlugin>('dom-binding');
256
+ domPlugin?.applyBindings();
257
+ ```
258
+
259
+ ### SvelteKit Helpers
260
+
261
+ #### `loadTrafficalBundle(options)`
262
+
263
+ Fetch the config bundle in a SvelteKit load function.
264
+
265
+ ```typescript
266
+ import { loadTrafficalBundle } from '@traffical/svelte/sveltekit';
267
+
268
+ const { bundle, error } = await loadTrafficalBundle({
269
+ orgId: 'org_123',
270
+ projectId: 'proj_456',
271
+ env: 'production',
272
+ apiKey: 'pk_...',
273
+ fetch, // SvelteKit's fetch
274
+ });
275
+ ```
276
+
277
+ #### `resolveParamsSSR(bundle, context, defaults)`
278
+
279
+ Resolve parameters on the server for SSR.
280
+
281
+ ```typescript
282
+ import { resolveParamsSSR } from '@traffical/svelte/sveltekit';
283
+
284
+ const params = resolveParamsSSR(bundle, { userId: 'user_123' }, {
285
+ 'feature.name': 'default',
286
+ });
287
+ ```
288
+
289
+ ## Configuration Options
290
+
291
+ ```typescript
292
+ interface TrafficalProviderConfig {
293
+ // Required
294
+ orgId: string;
295
+ projectId: string;
296
+ env: string;
297
+ apiKey: string;
298
+
299
+ // Optional - Connection
300
+ baseUrl?: string;
301
+ localConfig?: ConfigBundle;
302
+ refreshIntervalMs?: number; // Default: 60000
303
+
304
+ // Optional - Identity
305
+ unitKeyFn?: () => string;
306
+ contextFn?: () => Context;
307
+
308
+ // Optional - Tracking
309
+ trackDecisions?: boolean; // Default: true
310
+ decisionDeduplicationTtlMs?: number; // Default: 1 hour
311
+ exposureSessionTtlMs?: number; // Default: 30 minutes
312
+
313
+ // Optional - Plugins
314
+ plugins?: TrafficalPlugin[];
315
+
316
+ // Optional - Event Batching
317
+ eventBatchSize?: number; // Default: 10
318
+ eventFlushIntervalMs?: number; // Default: 30000
319
+
320
+ // Optional - SSR
321
+ initialBundle?: ConfigBundle | null;
322
+ initialParams?: Record<string, unknown>;
323
+ }
324
+ ```
325
+
326
+ ## TypeScript
327
+
328
+ The SDK is fully typed. Use generics for type-safe parameter access:
329
+
330
+ ```typescript
331
+ interface CheckoutParams {
332
+ 'checkout.ctaText': string;
333
+ 'checkout.ctaColor': string;
334
+ 'checkout.showDiscount': boolean;
335
+ }
336
+
337
+ const { params } = useTraffical<CheckoutParams>({
338
+ defaults: {
339
+ 'checkout.ctaText': 'Buy Now',
340
+ 'checkout.ctaColor': '#000000',
341
+ 'checkout.showDiscount': false,
342
+ },
343
+ });
344
+
345
+ // params['checkout.ctaText'] is typed as string
346
+ // params['checkout.showDiscount'] is typed as boolean
347
+ ```
348
+
349
+ ## Comparison with React SDK
350
+
351
+ | Feature | React | Svelte |
352
+ |---------|-------|--------|
353
+ | Provider | `<TrafficalProvider>` | `<TrafficalProvider>` |
354
+ | Main hook | `useTraffical()` | `useTraffical()` |
355
+ | Bound event tracking | `track` from `useTraffical()` | `track` from `useTraffical()` |
356
+ | Standalone track hook | `useTrafficalTrack()` | `useTrafficalTrack()` |
357
+ | Client access | `useTrafficalClient()` | `useTrafficalClient()` |
358
+ | Plugin access | `useTrafficalPlugin()` | `useTrafficalPlugin()` |
359
+ | Reactivity | `useState`/`useEffect` | `$state`/`$derived` |
360
+ | SSR | `initialParams` prop | `loadTrafficalBundle()` helper |
361
+
362
+ ## License
363
+
364
+ MIT
365
+
@@ -0,0 +1,34 @@
1
+ <!--
2
+ @traffical/svelte - TrafficalProvider Component
3
+
4
+ Wrapper component that initializes the Traffical context.
5
+ Alternative to calling initTraffical() directly in your layout.
6
+ -->
7
+ <script lang="ts">
8
+ import type { Snippet } from "svelte";
9
+ import { initTraffical } from "./context.svelte.js";
10
+ import type { TrafficalProviderConfig } from "./types.js";
11
+
12
+ interface Props {
13
+ /** Configuration for the Traffical client */
14
+ config: TrafficalProviderConfig;
15
+ /** Child content */
16
+ children: Snippet;
17
+ }
18
+
19
+ let { config, children }: Props = $props();
20
+
21
+ // Initialize context - this sets up the client and makes it available to children
22
+ const context = initTraffical(config);
23
+
24
+ // Cleanup on component destroy
25
+ $effect(() => {
26
+ return () => {
27
+ // Destroy client when provider unmounts
28
+ context.client?.destroy();
29
+ };
30
+ });
31
+ </script>
32
+
33
+ {@render children()}
34
+
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { TrafficalProviderConfig } from "./types.js";
3
+ interface Props {
4
+ /** Configuration for the Traffical client */
5
+ config: TrafficalProviderConfig;
6
+ /** Child content */
7
+ children: Snippet;
8
+ }
9
+ declare const TrafficalProvider: import("svelte").Component<Props, {}, "">;
10
+ type TrafficalProvider = ReturnType<typeof TrafficalProvider>;
11
+ export default TrafficalProvider;
12
+ //# sourceMappingURL=TrafficalProvider.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrafficalProvider.svelte.d.ts","sourceRoot":"","sources":["../src/TrafficalProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAGxD,UAAU,KAAK;IACb,6CAA6C;IAC7C,MAAM,EAAE,uBAAuB,CAAC;IAChC,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA2BH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @traffical/svelte - Context Layer
3
+ *
4
+ * SSR-safe context management using Svelte 5 runes and Svelte's context API.
5
+ * Initializes the TrafficalClient with environment-appropriate providers.
6
+ */
7
+ import type { TrafficalProviderConfig, TrafficalContextValue } from "./types.js";
8
+ /**
9
+ * Initializes the Traffical context.
10
+ * Must be called at the root of your application (e.g., in +layout.svelte).
11
+ *
12
+ * @example
13
+ * ```svelte
14
+ * <script>
15
+ * import { initTraffical } from '@traffical/svelte';
16
+ *
17
+ * let { data, children } = $props();
18
+ *
19
+ * initTraffical({
20
+ * orgId: 'org_123',
21
+ * projectId: 'proj_456',
22
+ * env: 'production',
23
+ * apiKey: 'pk_...',
24
+ * initialBundle: data.traffical?.bundle,
25
+ * });
26
+ * </script>
27
+ *
28
+ * {@render children()}
29
+ * ```
30
+ */
31
+ export declare function initTraffical(config: TrafficalProviderConfig): TrafficalContextValue;
32
+ /**
33
+ * Gets the Traffical context value.
34
+ * Must be called within a component tree where initTraffical() has been called.
35
+ *
36
+ * @throws Error if called outside of Traffical context
37
+ */
38
+ export declare function getTrafficalContext(): TrafficalContextValue;
39
+ /**
40
+ * Checks if Traffical context is available.
41
+ * Useful for conditional rendering or optional Traffical integration.
42
+ */
43
+ export declare function hasTrafficalContext(): boolean;
44
+ //# sourceMappingURL=context.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.svelte.d.ts","sourceRoot":"","sources":["../src/context.svelte.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAmJpB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,uBAAuB,GAC9B,qBAAqB,CAIvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,qBAAqB,CAa3D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAS7C"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * @traffical/svelte - Context Layer
3
+ *
4
+ * SSR-safe context management using Svelte 5 runes and Svelte's context API.
5
+ * Initializes the TrafficalClient with environment-appropriate providers.
6
+ */
7
+ import { getContext, setContext } from "svelte";
8
+ import { TrafficalClient, createTrafficalClientSync, MemoryStorageProvider, LocalStorageProvider, } from "@traffical/js-client";
9
+ // =============================================================================
10
+ // Constants
11
+ // =============================================================================
12
+ const TRAFFICAL_CONTEXT_KEY = Symbol("traffical");
13
+ // =============================================================================
14
+ // Browser Detection
15
+ // =============================================================================
16
+ /**
17
+ * Check if we're running in a browser environment.
18
+ * SSR-safe - returns false during server-side rendering.
19
+ */
20
+ function isBrowser() {
21
+ return typeof window !== "undefined" && typeof document !== "undefined";
22
+ }
23
+ // =============================================================================
24
+ // Context State Factory
25
+ // =============================================================================
26
+ /**
27
+ * Creates the reactive Traffical context state.
28
+ * Uses $state for reactive properties that work with SSR.
29
+ */
30
+ function createTrafficalContextState(config) {
31
+ // Reactive state using Svelte 5 runes
32
+ let client = $state(null);
33
+ let ready = $state(!!config.initialBundle); // Ready immediately if we have initial bundle
34
+ let error = $state(null);
35
+ let bundle = $state(config.initialBundle ?? null);
36
+ // Initialize client only in browser
37
+ if (isBrowser()) {
38
+ // Use localStorage in browser, memory storage would lose data
39
+ const storage = new LocalStorageProvider();
40
+ const clientInstance = createTrafficalClientSync({
41
+ orgId: config.orgId,
42
+ projectId: config.projectId,
43
+ env: config.env,
44
+ apiKey: config.apiKey,
45
+ baseUrl: config.baseUrl,
46
+ localConfig: config.initialBundle ?? config.localConfig,
47
+ refreshIntervalMs: config.refreshIntervalMs,
48
+ storage,
49
+ // Decision tracking options
50
+ trackDecisions: config.trackDecisions,
51
+ decisionDeduplicationTtlMs: config.decisionDeduplicationTtlMs,
52
+ // Exposure options
53
+ exposureSessionTtlMs: config.exposureSessionTtlMs,
54
+ // Event batching options
55
+ eventBatchSize: config.eventBatchSize,
56
+ eventFlushIntervalMs: config.eventFlushIntervalMs,
57
+ // Plugins
58
+ plugins: config.plugins,
59
+ });
60
+ client = clientInstance;
61
+ // Initialize asynchronously (fetches fresh config if needed)
62
+ clientInstance
63
+ .initialize()
64
+ .then(() => {
65
+ ready = true;
66
+ // Update bundle if client fetched a newer one
67
+ const configVersion = clientInstance.getConfigVersion();
68
+ if (configVersion) {
69
+ // Bundle is internal, but we track ready state
70
+ }
71
+ })
72
+ .catch((err) => {
73
+ error = err instanceof Error ? err : new Error(String(err));
74
+ // Still mark as ready - we'll use defaults/initial bundle
75
+ ready = true;
76
+ });
77
+ }
78
+ else {
79
+ // On server, use memory storage and mark as ready if we have initial data
80
+ // The client won't actually be used for tracking on server
81
+ if (config.initialBundle) {
82
+ const storage = new MemoryStorageProvider();
83
+ const clientInstance = createTrafficalClientSync({
84
+ orgId: config.orgId,
85
+ projectId: config.projectId,
86
+ env: config.env,
87
+ apiKey: config.apiKey,
88
+ localConfig: config.initialBundle,
89
+ storage,
90
+ // Disable background operations on server
91
+ refreshIntervalMs: 0,
92
+ });
93
+ client = clientInstance;
94
+ ready = true;
95
+ }
96
+ }
97
+ // Unit key getter - uses config function or client's stable ID
98
+ function getUnitKey() {
99
+ if (config.unitKeyFn) {
100
+ return config.unitKeyFn();
101
+ }
102
+ // Fall back to client's auto-generated stable ID
103
+ return client?.getStableId() ?? "";
104
+ }
105
+ // Context getter - merges unit key with additional context
106
+ function getContext() {
107
+ const unitKey = getUnitKey();
108
+ const additionalContext = config.contextFn?.() ?? {};
109
+ return {
110
+ ...additionalContext,
111
+ // Include common unit key field names for compatibility
112
+ userId: unitKey,
113
+ deviceId: unitKey,
114
+ anonymousId: unitKey,
115
+ };
116
+ }
117
+ return {
118
+ get client() {
119
+ return client;
120
+ },
121
+ get ready() {
122
+ return ready;
123
+ },
124
+ get error() {
125
+ return error;
126
+ },
127
+ get bundle() {
128
+ return bundle;
129
+ },
130
+ getUnitKey,
131
+ getContext,
132
+ initialParams: config.initialParams,
133
+ };
134
+ }
135
+ // =============================================================================
136
+ // Public API
137
+ // =============================================================================
138
+ /**
139
+ * Initializes the Traffical context.
140
+ * Must be called at the root of your application (e.g., in +layout.svelte).
141
+ *
142
+ * @example
143
+ * ```svelte
144
+ * <script>
145
+ * import { initTraffical } from '@traffical/svelte';
146
+ *
147
+ * let { data, children } = $props();
148
+ *
149
+ * initTraffical({
150
+ * orgId: 'org_123',
151
+ * projectId: 'proj_456',
152
+ * env: 'production',
153
+ * apiKey: 'pk_...',
154
+ * initialBundle: data.traffical?.bundle,
155
+ * });
156
+ * </script>
157
+ *
158
+ * {@render children()}
159
+ * ```
160
+ */
161
+ export function initTraffical(config) {
162
+ const contextValue = createTrafficalContextState(config);
163
+ setContext(TRAFFICAL_CONTEXT_KEY, contextValue);
164
+ return contextValue;
165
+ }
166
+ /**
167
+ * Gets the Traffical context value.
168
+ * Must be called within a component tree where initTraffical() has been called.
169
+ *
170
+ * @throws Error if called outside of Traffical context
171
+ */
172
+ export function getTrafficalContext() {
173
+ const context = getContext(TRAFFICAL_CONTEXT_KEY);
174
+ if (!context) {
175
+ throw new Error("getTrafficalContext() must be called within a component tree where initTraffical() has been called. " +
176
+ "Make sure to call initTraffical() in your root layout or wrap your app with <TrafficalProvider>.");
177
+ }
178
+ return context;
179
+ }
180
+ /**
181
+ * Checks if Traffical context is available.
182
+ * Useful for conditional rendering or optional Traffical integration.
183
+ */
184
+ export function hasTrafficalContext() {
185
+ try {
186
+ const context = getContext(TRAFFICAL_CONTEXT_KEY);
187
+ return context !== undefined;
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }