@traffical/react 0.1.1

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/hooks.js ADDED
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Traffical React Hooks
3
+ *
4
+ * React hooks for parameter resolution and decision tracking.
5
+ * Uses the browser-optimized JS Client for full feature support.
6
+ */
7
+ import { useState, useEffect, useCallback, useRef } from "react";
8
+ import { resolveParameters } from "@traffical/core";
9
+ import { useTrafficalContext } from "./context.js";
10
+ // =============================================================================
11
+ // Internal Utilities
12
+ // =============================================================================
13
+ /**
14
+ * Creates a stable string key from an object for use in dependency arrays.
15
+ * This prevents infinite re-renders when users pass inline objects to hooks.
16
+ *
17
+ * Uses JSON.stringify with sorted keys to ensure consistent ordering.
18
+ */
19
+ function createStableKey(obj) {
20
+ if (obj === null || obj === undefined) {
21
+ return String(obj);
22
+ }
23
+ if (typeof obj !== "object") {
24
+ return String(obj);
25
+ }
26
+ // Sort keys for consistent ordering
27
+ return JSON.stringify(obj, Object.keys(obj).sort());
28
+ }
29
+ /**
30
+ * Custom hook that returns a stable reference to an object.
31
+ * Only updates the reference when the object's serialized value changes.
32
+ *
33
+ * This allows users to pass inline objects without causing infinite re-renders:
34
+ * ```tsx
35
+ * // This now works without memoization!
36
+ * const { params } = useTraffical({
37
+ * defaults: { 'ui.color': '#000' }, // inline object is fine
38
+ * });
39
+ * ```
40
+ */
41
+ function useStableObject(obj) {
42
+ const stableKey = createStableKey(obj);
43
+ const ref = useRef(obj);
44
+ // Only update the ref when the serialized value actually changes
45
+ // This is safe because we're comparing by value, not reference
46
+ if (createStableKey(ref.current) !== stableKey) {
47
+ ref.current = obj;
48
+ }
49
+ return ref.current;
50
+ }
51
+ /**
52
+ * Primary hook for Traffical parameter resolution and decision tracking.
53
+ *
54
+ * On first render, returns defaults immediately (no blocking).
55
+ * When the config bundle loads, recomputes and returns resolved values.
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * // Full tracking (default) - decision + exposure events
60
+ * const { params, decision, ready } = useTraffical({
61
+ * defaults: { "checkout.ctaText": "Buy Now" },
62
+ * });
63
+ *
64
+ * // Decision tracking only - manual exposure control
65
+ * const { params, decision, trackExposure } = useTraffical({
66
+ * defaults: { "checkout.ctaText": "Buy Now" },
67
+ * tracking: "decision",
68
+ * });
69
+ *
70
+ * // No tracking - for SSR, tests, or internal logic
71
+ * const { params, ready } = useTraffical({
72
+ * defaults: { "ui.hero.title": "Welcome" },
73
+ * tracking: "none",
74
+ * });
75
+ * ```
76
+ */
77
+ export function useTraffical(options) {
78
+ const { client, ready, error, getContext, getUnitKey, initialParams, localConfig } = useTrafficalContext();
79
+ const trackingMode = options.tracking ?? "full";
80
+ const shouldTrackDecision = trackingMode !== "none";
81
+ const shouldAutoTrackExposure = trackingMode === "full";
82
+ // Create stable references for objects to prevent infinite re-renders
83
+ // when users pass inline objects like: useTraffical({ defaults: { ... } })
84
+ const stableDefaults = useStableObject(options.defaults);
85
+ const stableContext = useStableObject(options.context);
86
+ // Track if we resolved synchronously (to avoid duplicate resolution in useEffect)
87
+ const resolvedSyncRef = useRef(false);
88
+ const syncDecisionRef = useRef(null);
89
+ // State - resolve synchronously if possible to prevent flicker
90
+ const [params, setParams] = useState(() => {
91
+ // If client is already ready (e.g., subsequent page navigation), resolve synchronously
92
+ // This prevents the default -> resolved flicker (classic A/B testing problem)
93
+ if (client && ready) {
94
+ resolvedSyncRef.current = true;
95
+ const context = {
96
+ ...getContext(),
97
+ ...(options.context ?? {}),
98
+ };
99
+ if (shouldTrackDecision) {
100
+ // Use decide() for tracked decisions
101
+ const result = client.decide({
102
+ context,
103
+ defaults: options.defaults,
104
+ });
105
+ syncDecisionRef.current = result;
106
+ return result.assignments;
107
+ }
108
+ else {
109
+ // Use getParams() for untracked
110
+ return client.getParams({
111
+ context,
112
+ defaults: options.defaults,
113
+ });
114
+ }
115
+ }
116
+ // NEW: If we have localConfig bundle, resolve synchronously even before client is ready
117
+ // This is the key to flicker-free SSR: server and client both resolve from the same bundle
118
+ if (localConfig) {
119
+ try {
120
+ const context = getContext();
121
+ // Only resolve if we have a userId (set by server via cookie/header)
122
+ if (context.userId) {
123
+ resolvedSyncRef.current = true;
124
+ const fullContext = {
125
+ ...context,
126
+ ...(options.context ?? {}),
127
+ };
128
+ // Use pure resolution function from core - no tracking on initial render
129
+ const resolved = resolveParameters(localConfig, fullContext, options.defaults);
130
+ return resolved;
131
+ }
132
+ }
133
+ catch {
134
+ // Context function not ready, fall through to defaults
135
+ }
136
+ }
137
+ // Fallback to defaults (no localConfig or no userId)
138
+ if (initialParams) {
139
+ return { ...stableDefaults, ...initialParams };
140
+ }
141
+ return stableDefaults;
142
+ });
143
+ const [decision, setDecision] = useState(() => syncDecisionRef.current);
144
+ const [hasTrackedExposure, setHasTrackedExposure] = useState(false);
145
+ // Manual exposure tracking (synchronous - batched internally)
146
+ const trackExposure = useCallback(() => {
147
+ // No-op when tracking is "none"
148
+ if (trackingMode === "none") {
149
+ return;
150
+ }
151
+ if (!client || !decision || hasTrackedExposure) {
152
+ return;
153
+ }
154
+ client.trackExposure(decision);
155
+ setHasTrackedExposure(true);
156
+ }, [client, decision, hasTrackedExposure, trackingMode]);
157
+ // Resolve params or make decision when client is ready
158
+ useEffect(() => {
159
+ if (!client || !ready) {
160
+ return;
161
+ }
162
+ // Build context using stable references
163
+ const context = {
164
+ ...getContext(),
165
+ ...stableContext,
166
+ };
167
+ if (shouldTrackDecision) {
168
+ // Use decide() - tracks decision event via DecisionTrackingPlugin
169
+ const result = client.decide({
170
+ context,
171
+ defaults: stableDefaults,
172
+ });
173
+ // Only update state if we didn't already resolve synchronously
174
+ // This prevents the params from flickering (default -> resolved)
175
+ // But we ALWAYS call decide() to ensure tracking happens
176
+ if (!resolvedSyncRef.current) {
177
+ setParams(result.assignments);
178
+ }
179
+ setDecision(result);
180
+ setHasTrackedExposure(false);
181
+ }
182
+ else {
183
+ // Use getParams() - no tracking
184
+ if (!resolvedSyncRef.current) {
185
+ const resolved = client.getParams({
186
+ context,
187
+ defaults: stableDefaults,
188
+ });
189
+ setParams(resolved);
190
+ }
191
+ setDecision(null);
192
+ }
193
+ // Clear the sync flag after first effect run
194
+ resolvedSyncRef.current = false;
195
+ }, [client, ready, getContext, stableContext, stableDefaults, shouldTrackDecision]);
196
+ // Auto-track exposure when tracking is "full"
197
+ useEffect(() => {
198
+ if (shouldAutoTrackExposure && decision && !hasTrackedExposure) {
199
+ trackExposure();
200
+ }
201
+ }, [shouldAutoTrackExposure, decision, hasTrackedExposure, trackExposure]);
202
+ // Ref to store current decision for stable track function reference
203
+ const decisionRef = useRef(null);
204
+ decisionRef.current = decision;
205
+ // Buffer for track events that arrive before decision is ready
206
+ // This prevents race conditions where track() is called in a useEffect
207
+ // that runs before the decision has been set
208
+ const pendingTracksRef = useRef([]);
209
+ // Flush pending track events when decision becomes available
210
+ useEffect(() => {
211
+ if (decision && client && pendingTracksRef.current.length > 0) {
212
+ const pending = pendingTracksRef.current;
213
+ pendingTracksRef.current = [];
214
+ for (const { event, properties } of pending) {
215
+ client.track(event, properties, {
216
+ decisionId: decision.decisionId,
217
+ unitKey: getUnitKey(),
218
+ });
219
+ }
220
+ }
221
+ }, [decision, client, getUnitKey]);
222
+ // Track user events - decisionId is automatically included
223
+ // If decision isn't ready yet, events are queued and flushed when it becomes available
224
+ const track = useCallback((event, properties) => {
225
+ if (!client) {
226
+ console.warn("[Traffical] Client not initialized, cannot track event");
227
+ return;
228
+ }
229
+ const currentDecision = decisionRef.current;
230
+ if (!currentDecision) {
231
+ // Queue the event instead of dropping it - will be flushed when decision is ready
232
+ // This handles the race condition where track() is called before decision is set
233
+ if (trackingMode === "none") {
234
+ console.warn("[Traffical] Cannot track event with tracking: 'none'. Use tracking: 'full' or 'decision'.");
235
+ return;
236
+ }
237
+ pendingTracksRef.current.push({ event, properties });
238
+ return;
239
+ }
240
+ client.track(event, properties, {
241
+ decisionId: currentDecision.decisionId,
242
+ unitKey: getUnitKey(),
243
+ });
244
+ }, [client, getUnitKey, trackingMode]);
245
+ // Deprecated: Bound reward tracking - decisionId is automatically included
246
+ const trackReward = useCallback((options) => {
247
+ if (!client) {
248
+ console.warn("[Traffical] Client not initialized, cannot track reward");
249
+ return;
250
+ }
251
+ const currentDecision = decisionRef.current;
252
+ if (!currentDecision) {
253
+ console.warn("[Traffical] No decision available, cannot track reward. Did you use tracking: 'none'?");
254
+ return;
255
+ }
256
+ // Map old API to new track() API
257
+ track(options.rewardType || "reward", {
258
+ value: options.reward,
259
+ ...(options.rewards ? { rewards: options.rewards } : {}),
260
+ });
261
+ }, [client, track]);
262
+ return { params, decision, ready, error, trackExposure, track, trackReward };
263
+ }
264
+ /**
265
+ * Hook to get resolved parameter values.
266
+ *
267
+ * @deprecated Use `useTraffical({ tracking: "none" })` instead.
268
+ *
269
+ * @example
270
+ * ```tsx
271
+ * // Old way (deprecated)
272
+ * const { params, ready } = useTrafficalParams({ defaults: { ... } });
273
+ *
274
+ * // New way
275
+ * const { params, ready } = useTraffical({ defaults: { ... }, tracking: "none" });
276
+ * ```
277
+ */
278
+ export function useTrafficalParams(options) {
279
+ // Show deprecation warning in development only
280
+ useEffect(() => {
281
+ if (process.env.NODE_ENV === "development") {
282
+ console.warn('[Traffical] useTrafficalParams is deprecated. Use useTraffical({ tracking: "none" }) instead.');
283
+ }
284
+ }, []);
285
+ const result = useTraffical({ ...options, tracking: "none" });
286
+ return { params: result.params, ready: result.ready, error: result.error };
287
+ }
288
+ /**
289
+ * Hook to get a decision with full metadata for tracking.
290
+ *
291
+ * @deprecated Use `useTraffical()` instead.
292
+ *
293
+ * @example
294
+ * ```tsx
295
+ * // Old way (deprecated)
296
+ * const { params, decision } = useTrafficalDecision({ defaults: { ... } });
297
+ *
298
+ * // New way
299
+ * const { params, decision } = useTraffical({ defaults: { ... } });
300
+ *
301
+ * // Old way with manual exposure (deprecated)
302
+ * const { params, trackExposure } = useTrafficalDecision({ defaults: { ... }, trackExposure: false });
303
+ *
304
+ * // New way with manual exposure
305
+ * const { params, trackExposure } = useTraffical({ defaults: { ... }, tracking: "decision" });
306
+ * ```
307
+ */
308
+ export function useTrafficalDecision(options) {
309
+ // Show deprecation warning in development only
310
+ useEffect(() => {
311
+ if (process.env.NODE_ENV === "development") {
312
+ console.warn("[Traffical] useTrafficalDecision is deprecated. Use useTraffical() instead.");
313
+ }
314
+ }, []);
315
+ // Map old trackExposure option to new tracking mode
316
+ const tracking = options.trackExposure === false ? "decision" : "full";
317
+ return useTraffical({ defaults: options.defaults, context: options.context, tracking });
318
+ }
319
+ /**
320
+ * Hook to track user events.
321
+ *
322
+ * @example
323
+ * ```tsx
324
+ * const track = useTrafficalTrack();
325
+ *
326
+ * const handlePurchase = (amount: number) => {
327
+ * track('purchase', { value: amount, orderId: 'ord_123' });
328
+ * };
329
+ * ```
330
+ */
331
+ export function useTrafficalTrack() {
332
+ const { client, getUnitKey } = useTrafficalContext();
333
+ const track = useCallback((event, properties, options) => {
334
+ if (!client) {
335
+ console.warn("[Traffical] Client not initialized, cannot track event");
336
+ return;
337
+ }
338
+ client.track(event, properties, {
339
+ decisionId: options?.decisionId,
340
+ unitKey: getUnitKey(),
341
+ });
342
+ }, [client, getUnitKey]);
343
+ return track;
344
+ }
345
+ /**
346
+ * @deprecated Use useTrafficalTrack() instead.
347
+ *
348
+ * Hook to track a reward.
349
+ *
350
+ * @example
351
+ * ```tsx
352
+ * const trackReward = useTrafficalReward();
353
+ *
354
+ * const handlePurchase = (amount: number) => {
355
+ * trackReward({
356
+ * decisionId: decision.decisionId,
357
+ * reward: amount,
358
+ * rewardType: "revenue",
359
+ * });
360
+ * };
361
+ * ```
362
+ */
363
+ export function useTrafficalReward() {
364
+ const { client, getUnitKey, getContext } = useTrafficalContext();
365
+ const trackReward = useCallback((options) => {
366
+ if (!client) {
367
+ console.warn("[Traffical] Client not initialized, cannot track reward");
368
+ return;
369
+ }
370
+ // Map old API to new track() API
371
+ client.track(options.rewardType || "reward", {
372
+ value: options.reward,
373
+ ...(options.rewards ? { rewards: options.rewards } : {}),
374
+ }, {
375
+ decisionId: options.decisionId,
376
+ unitKey: getUnitKey(),
377
+ });
378
+ }, [client, getUnitKey, getContext]);
379
+ return trackReward;
380
+ }
381
+ /**
382
+ * Hook to access a registered plugin by name.
383
+ *
384
+ * @example
385
+ * ```tsx
386
+ * import { createDOMBindingPlugin, DOMBindingPlugin } from '@traffical/js-client';
387
+ *
388
+ * // In your provider config:
389
+ * plugins: [createDOMBindingPlugin()]
390
+ *
391
+ * // In a component:
392
+ * const domPlugin = useTrafficalPlugin<DOMBindingPlugin>('dom-binding');
393
+ *
394
+ * // Re-apply bindings after dynamic content changes
395
+ * useEffect(() => {
396
+ * domPlugin?.applyBindings();
397
+ * }, [contentLoaded, domPlugin]);
398
+ * ```
399
+ */
400
+ export function useTrafficalPlugin(name) {
401
+ const { client, ready } = useTrafficalContext();
402
+ if (!client || !ready) {
403
+ return undefined;
404
+ }
405
+ return client.getPlugin(name);
406
+ }
407
+ /**
408
+ * Hook to access the Traffical client directly.
409
+ *
410
+ * @example
411
+ * ```tsx
412
+ * const { client, ready } = useTrafficalClient();
413
+ *
414
+ * if (ready && client) {
415
+ * const version = client.getConfigVersion();
416
+ * const stableId = client.getStableId();
417
+ * }
418
+ * ```
419
+ */
420
+ export function useTrafficalClient() {
421
+ const { client, ready, error } = useTrafficalContext();
422
+ return { client, ready, error };
423
+ }
424
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @traffical/react
3
+ *
4
+ * Traffical SDK for React applications.
5
+ * Provides Provider and hooks for parameter resolution and decision tracking.
6
+ *
7
+ * Features:
8
+ * - Browser-optimized with sendBeacon, localStorage persistence
9
+ * - Automatic stable ID for anonymous users
10
+ * - Plugin system support (DecisionTrackingPlugin enabled by default)
11
+ * - Decision and exposure deduplication
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { TrafficalProvider, useTraffical } from '@traffical/react';
16
+ *
17
+ * function App() {
18
+ * return (
19
+ * <TrafficalProvider
20
+ * config={{
21
+ * orgId: 'org_123',
22
+ * projectId: 'proj_456',
23
+ * env: 'production',
24
+ * apiKey: 'pk_...',
25
+ * }}
26
+ * >
27
+ * <MyComponent />
28
+ * </TrafficalProvider>
29
+ * );
30
+ * }
31
+ *
32
+ * function MyComponent() {
33
+ * const { params, ready } = useTraffical({
34
+ * defaults: { 'ui.hero.title': 'Welcome' },
35
+ * });
36
+ *
37
+ * if (!ready) return <div>Loading...</div>;
38
+ * return <h1>{params['ui.hero.title']}</h1>;
39
+ * }
40
+ * ```
41
+ */
42
+ export * from "@traffical/core";
43
+ export { TrafficalClient, createTrafficalClient, createTrafficalClientSync, type TrafficalClientOptions, } from "@traffical/js-client";
44
+ export { type TrafficalPlugin, type PluginOptions, createDOMBindingPlugin, type DOMBindingPlugin, type DOMBindingPluginOptions, } from "@traffical/js-client";
45
+ export { TrafficalProvider, type TrafficalProviderProps } from "./provider.js";
46
+ export { TrafficalContext, useTrafficalContext, type TrafficalProviderConfig, type TrafficalContextValue, } from "./context.js";
47
+ export { useTraffical, type UseTrafficalOptions, type UseTrafficalResult, type BoundTrackOptions, useTrafficalTrack, useTrafficalPlugin, useTrafficalClient, type BoundTrackRewardOptions, useTrafficalReward, useTrafficalParams, useTrafficalDecision, type UseTrafficalParamsOptions, type UseTrafficalParamsResult, type UseTrafficalDecisionOptions, type UseTrafficalDecisionResult, } from "./hooks.js";
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAGH,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,sBAAsB,GAC5B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EAEtB,iBAAiB,EAEjB,kBAAkB,EAClB,kBAAkB,EAElB,KAAK,uBAAuB,EAC5B,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @traffical/react
3
+ *
4
+ * Traffical SDK for React applications.
5
+ * Provides Provider and hooks for parameter resolution and decision tracking.
6
+ *
7
+ * Features:
8
+ * - Browser-optimized with sendBeacon, localStorage persistence
9
+ * - Automatic stable ID for anonymous users
10
+ * - Plugin system support (DecisionTrackingPlugin enabled by default)
11
+ * - Decision and exposure deduplication
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { TrafficalProvider, useTraffical } from '@traffical/react';
16
+ *
17
+ * function App() {
18
+ * return (
19
+ * <TrafficalProvider
20
+ * config={{
21
+ * orgId: 'org_123',
22
+ * projectId: 'proj_456',
23
+ * env: 'production',
24
+ * apiKey: 'pk_...',
25
+ * }}
26
+ * >
27
+ * <MyComponent />
28
+ * </TrafficalProvider>
29
+ * );
30
+ * }
31
+ *
32
+ * function MyComponent() {
33
+ * const { params, ready } = useTraffical({
34
+ * defaults: { 'ui.hero.title': 'Welcome' },
35
+ * });
36
+ *
37
+ * if (!ready) return <div>Loading...</div>;
38
+ * return <h1>{params['ui.hero.title']}</h1>;
39
+ * }
40
+ * ```
41
+ */
42
+ // Re-export everything from core
43
+ export * from "@traffical/core";
44
+ // Re-export client and utilities from JS Client
45
+ export { TrafficalClient, createTrafficalClient, createTrafficalClientSync, } from "@traffical/js-client";
46
+ // Re-export plugin utilities from JS Client
47
+ export { createDOMBindingPlugin, } from "@traffical/js-client";
48
+ // Export React-specific components and hooks
49
+ export { TrafficalProvider } from "./provider.js";
50
+ export { TrafficalContext, useTrafficalContext, } from "./context.js";
51
+ export {
52
+ // Primary hook
53
+ useTraffical,
54
+ // Track hook
55
+ useTrafficalTrack,
56
+ // Other hooks
57
+ useTrafficalPlugin, useTrafficalClient, useTrafficalReward, useTrafficalParams, useTrafficalDecision, } from "./hooks.js";
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,iCAAiC;AACjC,cAAc,iBAAiB,CAAC;AAEhC,gDAAgD;AAChD,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,yBAAyB,GAE1B,MAAM,sBAAsB,CAAC;AAE9B,4CAA4C;AAC5C,OAAO,EAGL,sBAAsB,GAGvB,MAAM,sBAAsB,CAAC;AAE9B,6CAA6C;AAC7C,OAAO,EAAE,iBAAiB,EAA+B,MAAM,eAAe,CAAC;AAE/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAGpB,MAAM,cAAc,CAAC;AAEtB,OAAO;AACL,eAAe;AACf,YAAY;AAIZ,aAAa;AACb,iBAAiB;AACjB,cAAc;AACd,kBAAkB,EAClB,kBAAkB,EAGlB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GAKrB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Traffical React Provider
3
+ *
4
+ * Initializes the Traffical client (browser-optimized) and provides it to child components.
5
+ * Supports plugins, automatic stable ID, and decision tracking out of the box.
6
+ */
7
+ import React, { type ReactNode } from "react";
8
+ import { type TrafficalProviderConfig } from "./context.js";
9
+ /**
10
+ * Props for the TrafficalProvider component.
11
+ */
12
+ export interface TrafficalProviderProps {
13
+ /** Configuration for the Traffical client */
14
+ config: TrafficalProviderConfig;
15
+ /** Child components */
16
+ children: ReactNode;
17
+ }
18
+ /**
19
+ * TrafficalProvider - initializes and provides the Traffical client to React components.
20
+ *
21
+ * Features:
22
+ * - Browser-optimized with sendBeacon, localStorage persistence
23
+ * - Automatic stable ID for anonymous users (unless unitKeyFn provided)
24
+ * - Plugin system support (DecisionTrackingPlugin enabled by default)
25
+ * - Decision and exposure deduplication
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * <TrafficalProvider
30
+ * config={{
31
+ * orgId: "org_123",
32
+ * projectId: "proj_456",
33
+ * env: "production",
34
+ * apiKey: "pk_...",
35
+ * // Optional: provide unitKeyFn for logged-in users
36
+ * unitKeyFn: () => getUserId(),
37
+ * // Optional: add context
38
+ * contextFn: () => ({ locale: "en-US" }),
39
+ * // Optional: add custom plugins
40
+ * plugins: [createDOMBindingPlugin()],
41
+ * }}
42
+ * >
43
+ * <App />
44
+ * </TrafficalProvider>
45
+ * ```
46
+ */
47
+ export declare function TrafficalProvider({ config, children, }: TrafficalProviderProps): React.ReactElement;
48
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf,OAAO,EAEL,KAAK,uBAAuB,EAE7B,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,MAAM,EAAE,uBAAuB,CAAC;IAChC,uBAAuB;IACvB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,GACT,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,CA6H7C"}