@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/src/index.ts ADDED
@@ -0,0 +1,93 @@
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
+
43
+ // Re-export everything from core
44
+ export * from "@traffical/core";
45
+
46
+ // Re-export client and utilities from JS Client
47
+ export {
48
+ TrafficalClient,
49
+ createTrafficalClient,
50
+ createTrafficalClientSync,
51
+ type TrafficalClientOptions,
52
+ } from "@traffical/js-client";
53
+
54
+ // Re-export plugin utilities from JS Client
55
+ export {
56
+ type TrafficalPlugin,
57
+ type PluginOptions,
58
+ createDOMBindingPlugin,
59
+ type DOMBindingPlugin,
60
+ type DOMBindingPluginOptions,
61
+ } from "@traffical/js-client";
62
+
63
+ // Export React-specific components and hooks
64
+ export { TrafficalProvider, type TrafficalProviderProps } from "./provider.js";
65
+
66
+ export {
67
+ TrafficalContext,
68
+ useTrafficalContext,
69
+ type TrafficalProviderConfig,
70
+ type TrafficalContextValue,
71
+ } from "./context.js";
72
+
73
+ export {
74
+ // Primary hook
75
+ useTraffical,
76
+ type UseTrafficalOptions,
77
+ type UseTrafficalResult,
78
+ type BoundTrackOptions,
79
+ // Track hook
80
+ useTrafficalTrack,
81
+ // Other hooks
82
+ useTrafficalPlugin,
83
+ useTrafficalClient,
84
+ // Deprecated (kept for backward compatibility)
85
+ type BoundTrackRewardOptions,
86
+ useTrafficalReward,
87
+ useTrafficalParams,
88
+ useTrafficalDecision,
89
+ type UseTrafficalParamsOptions,
90
+ type UseTrafficalParamsResult,
91
+ type UseTrafficalDecisionOptions,
92
+ type UseTrafficalDecisionResult,
93
+ } from "./hooks.js";
@@ -0,0 +1,194 @@
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
+
8
+ import React, {
9
+ useEffect,
10
+ useState,
11
+ useCallback,
12
+ useMemo,
13
+ useRef,
14
+ type ReactNode,
15
+ } from "react";
16
+ import {
17
+ TrafficalClient,
18
+ createTrafficalClientSync,
19
+ } from "@traffical/js-client";
20
+ import type { Context } from "@traffical/core";
21
+ import {
22
+ TrafficalContext,
23
+ type TrafficalProviderConfig,
24
+ type TrafficalContextValue,
25
+ } from "./context.js";
26
+
27
+ /**
28
+ * Props for the TrafficalProvider component.
29
+ */
30
+ export interface TrafficalProviderProps {
31
+ /** Configuration for the Traffical client */
32
+ config: TrafficalProviderConfig;
33
+ /** Child components */
34
+ children: ReactNode;
35
+ }
36
+
37
+ /**
38
+ * TrafficalProvider - initializes and provides the Traffical client to React components.
39
+ *
40
+ * Features:
41
+ * - Browser-optimized with sendBeacon, localStorage persistence
42
+ * - Automatic stable ID for anonymous users (unless unitKeyFn provided)
43
+ * - Plugin system support (DecisionTrackingPlugin enabled by default)
44
+ * - Decision and exposure deduplication
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * <TrafficalProvider
49
+ * config={{
50
+ * orgId: "org_123",
51
+ * projectId: "proj_456",
52
+ * env: "production",
53
+ * apiKey: "pk_...",
54
+ * // Optional: provide unitKeyFn for logged-in users
55
+ * unitKeyFn: () => getUserId(),
56
+ * // Optional: add context
57
+ * contextFn: () => ({ locale: "en-US" }),
58
+ * // Optional: add custom plugins
59
+ * plugins: [createDOMBindingPlugin()],
60
+ * }}
61
+ * >
62
+ * <App />
63
+ * </TrafficalProvider>
64
+ * ```
65
+ */
66
+ export function TrafficalProvider({
67
+ config,
68
+ children,
69
+ }: TrafficalProviderProps): React.ReactElement {
70
+ const [client, setClient] = useState<TrafficalClient | null>(null);
71
+ const [ready, setReady] = useState(false);
72
+ const [error, setError] = useState<Error | null>(null);
73
+
74
+ // Keep a ref to the client for cleanup
75
+ const clientRef = useRef<TrafficalClient | null>(null);
76
+
77
+ // Memoize the unit key function
78
+ // If no unitKeyFn is provided, use the client's stable ID
79
+ const getUnitKey = useCallback(() => {
80
+ if (config.unitKeyFn) {
81
+ return config.unitKeyFn();
82
+ }
83
+ // Fall back to the client's auto-generated stable ID
84
+ return clientRef.current?.getStableId() ?? "";
85
+ }, [config.unitKeyFn]);
86
+
87
+ const getContext = useCallback((): Context => {
88
+ const unitKey = getUnitKey();
89
+ const additionalContext = config.contextFn?.() ?? {};
90
+
91
+ // The unit key field name comes from the bundle's hashing config
92
+ // For now, we use common conventions and let the SDK handle it
93
+ return {
94
+ ...additionalContext,
95
+ // Include common unit key field names
96
+ userId: unitKey,
97
+ deviceId: unitKey,
98
+ anonymousId: unitKey,
99
+ };
100
+ }, [getUnitKey, config.contextFn]);
101
+
102
+ // Initialize client on mount
103
+ useEffect(() => {
104
+ let mounted = true;
105
+
106
+ const initClient = async () => {
107
+ try {
108
+ // Create client synchronously so it's available immediately
109
+ const newClient = createTrafficalClientSync({
110
+ orgId: config.orgId,
111
+ projectId: config.projectId,
112
+ env: config.env,
113
+ apiKey: config.apiKey,
114
+ baseUrl: config.baseUrl,
115
+ localConfig: config.localConfig,
116
+ refreshIntervalMs: config.refreshIntervalMs,
117
+ trackDecisions: config.trackDecisions,
118
+ decisionDeduplicationTtlMs: config.decisionDeduplicationTtlMs,
119
+ exposureSessionTtlMs: config.exposureSessionTtlMs,
120
+ eventBatchSize: config.eventBatchSize,
121
+ eventFlushIntervalMs: config.eventFlushIntervalMs,
122
+ plugins: config.plugins,
123
+ });
124
+
125
+ clientRef.current = newClient;
126
+
127
+ if (mounted) {
128
+ setClient(newClient);
129
+ // If localConfig was provided, mark as ready immediately
130
+ if (config.localConfig) {
131
+ setReady(true);
132
+ }
133
+ }
134
+
135
+ // Initialize asynchronously (fetches/refreshes config bundle)
136
+ await newClient.initialize();
137
+
138
+ if (mounted) {
139
+ setReady(true);
140
+ }
141
+ } catch (err) {
142
+ if (mounted) {
143
+ setError(
144
+ err instanceof Error ? err : new Error(String(err))
145
+ );
146
+ // Still mark as ready - we'll use defaults
147
+ setReady(true);
148
+ }
149
+ }
150
+ };
151
+
152
+ initClient();
153
+
154
+ return () => {
155
+ mounted = false;
156
+ clientRef.current?.destroy();
157
+ clientRef.current = null;
158
+ };
159
+ }, [
160
+ config.orgId,
161
+ config.projectId,
162
+ config.env,
163
+ config.apiKey,
164
+ config.baseUrl,
165
+ config.localConfig,
166
+ config.refreshIntervalMs,
167
+ config.trackDecisions,
168
+ config.decisionDeduplicationTtlMs,
169
+ config.exposureSessionTtlMs,
170
+ config.eventBatchSize,
171
+ config.eventFlushIntervalMs,
172
+ config.plugins,
173
+ ]);
174
+
175
+ // Memoize context value
176
+ const contextValue = useMemo<TrafficalContextValue>(
177
+ () => ({
178
+ client,
179
+ ready,
180
+ error,
181
+ getUnitKey,
182
+ getContext,
183
+ initialParams: config.initialParams,
184
+ localConfig: config.localConfig,
185
+ }),
186
+ [client, ready, error, getUnitKey, getContext, config.initialParams, config.localConfig]
187
+ );
188
+
189
+ return (
190
+ <TrafficalContext.Provider value={contextValue}>
191
+ {children}
192
+ </TrafficalContext.Provider>
193
+ );
194
+ }