@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.
@@ -0,0 +1,144 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Traffical React Provider
4
+ *
5
+ * Initializes the Traffical client (browser-optimized) and provides it to child components.
6
+ * Supports plugins, automatic stable ID, and decision tracking out of the box.
7
+ */
8
+ import { useEffect, useState, useCallback, useMemo, useRef, } from "react";
9
+ import { createTrafficalClientSync, } from "@traffical/js-client";
10
+ import { TrafficalContext, } from "./context.js";
11
+ /**
12
+ * TrafficalProvider - initializes and provides the Traffical client to React components.
13
+ *
14
+ * Features:
15
+ * - Browser-optimized with sendBeacon, localStorage persistence
16
+ * - Automatic stable ID for anonymous users (unless unitKeyFn provided)
17
+ * - Plugin system support (DecisionTrackingPlugin enabled by default)
18
+ * - Decision and exposure deduplication
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <TrafficalProvider
23
+ * config={{
24
+ * orgId: "org_123",
25
+ * projectId: "proj_456",
26
+ * env: "production",
27
+ * apiKey: "pk_...",
28
+ * // Optional: provide unitKeyFn for logged-in users
29
+ * unitKeyFn: () => getUserId(),
30
+ * // Optional: add context
31
+ * contextFn: () => ({ locale: "en-US" }),
32
+ * // Optional: add custom plugins
33
+ * plugins: [createDOMBindingPlugin()],
34
+ * }}
35
+ * >
36
+ * <App />
37
+ * </TrafficalProvider>
38
+ * ```
39
+ */
40
+ export function TrafficalProvider({ config, children, }) {
41
+ const [client, setClient] = useState(null);
42
+ const [ready, setReady] = useState(false);
43
+ const [error, setError] = useState(null);
44
+ // Keep a ref to the client for cleanup
45
+ const clientRef = useRef(null);
46
+ // Memoize the unit key function
47
+ // If no unitKeyFn is provided, use the client's stable ID
48
+ const getUnitKey = useCallback(() => {
49
+ if (config.unitKeyFn) {
50
+ return config.unitKeyFn();
51
+ }
52
+ // Fall back to the client's auto-generated stable ID
53
+ return clientRef.current?.getStableId() ?? "";
54
+ }, [config.unitKeyFn]);
55
+ const getContext = useCallback(() => {
56
+ const unitKey = getUnitKey();
57
+ const additionalContext = config.contextFn?.() ?? {};
58
+ // The unit key field name comes from the bundle's hashing config
59
+ // For now, we use common conventions and let the SDK handle it
60
+ return {
61
+ ...additionalContext,
62
+ // Include common unit key field names
63
+ userId: unitKey,
64
+ deviceId: unitKey,
65
+ anonymousId: unitKey,
66
+ };
67
+ }, [getUnitKey, config.contextFn]);
68
+ // Initialize client on mount
69
+ useEffect(() => {
70
+ let mounted = true;
71
+ const initClient = async () => {
72
+ try {
73
+ // Create client synchronously so it's available immediately
74
+ const newClient = createTrafficalClientSync({
75
+ orgId: config.orgId,
76
+ projectId: config.projectId,
77
+ env: config.env,
78
+ apiKey: config.apiKey,
79
+ baseUrl: config.baseUrl,
80
+ localConfig: config.localConfig,
81
+ refreshIntervalMs: config.refreshIntervalMs,
82
+ trackDecisions: config.trackDecisions,
83
+ decisionDeduplicationTtlMs: config.decisionDeduplicationTtlMs,
84
+ exposureSessionTtlMs: config.exposureSessionTtlMs,
85
+ eventBatchSize: config.eventBatchSize,
86
+ eventFlushIntervalMs: config.eventFlushIntervalMs,
87
+ plugins: config.plugins,
88
+ });
89
+ clientRef.current = newClient;
90
+ if (mounted) {
91
+ setClient(newClient);
92
+ // If localConfig was provided, mark as ready immediately
93
+ if (config.localConfig) {
94
+ setReady(true);
95
+ }
96
+ }
97
+ // Initialize asynchronously (fetches/refreshes config bundle)
98
+ await newClient.initialize();
99
+ if (mounted) {
100
+ setReady(true);
101
+ }
102
+ }
103
+ catch (err) {
104
+ if (mounted) {
105
+ setError(err instanceof Error ? err : new Error(String(err)));
106
+ // Still mark as ready - we'll use defaults
107
+ setReady(true);
108
+ }
109
+ }
110
+ };
111
+ initClient();
112
+ return () => {
113
+ mounted = false;
114
+ clientRef.current?.destroy();
115
+ clientRef.current = null;
116
+ };
117
+ }, [
118
+ config.orgId,
119
+ config.projectId,
120
+ config.env,
121
+ config.apiKey,
122
+ config.baseUrl,
123
+ config.localConfig,
124
+ config.refreshIntervalMs,
125
+ config.trackDecisions,
126
+ config.decisionDeduplicationTtlMs,
127
+ config.exposureSessionTtlMs,
128
+ config.eventBatchSize,
129
+ config.eventFlushIntervalMs,
130
+ config.plugins,
131
+ ]);
132
+ // Memoize context value
133
+ const contextValue = useMemo(() => ({
134
+ client,
135
+ ready,
136
+ error,
137
+ getUnitKey,
138
+ getContext,
139
+ initialParams: config.initialParams,
140
+ localConfig: config.localConfig,
141
+ }), [client, ready, error, getUnitKey, getContext, config.initialParams, config.localConfig]);
142
+ return (_jsx(TrafficalContext.Provider, { value: contextValue, children: children }));
143
+ }
144
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@traffical/react",
3
+ "version": "0.1.1",
4
+ "description": "Traffical SDK for React - Provider and hooks for parameter resolution",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "module": "./src/index.ts",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "bun": "./src/index.ts",
13
+ "import": "./src/index.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsc --watch",
23
+ "test": "bun test",
24
+ "typecheck": "tsc --noEmit",
25
+ "release": "bun scripts/release.ts"
26
+ },
27
+ "dependencies": {
28
+ "@traffical/core": "workspace:^",
29
+ "@traffical/js-client": "workspace:^"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "latest",
33
+ "@types/react": "^18.2.0",
34
+ "typescript": "^5.3.0"
35
+ },
36
+ "peerDependencies": {
37
+ "react": "^18.0.0 || ^19.0.0"
38
+ },
39
+ "keywords": [
40
+ "traffical",
41
+ "feature-flags",
42
+ "experimentation",
43
+ "a/b-testing",
44
+ "react",
45
+ "hooks"
46
+ ],
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/traffical/js-sdk"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
package/src/context.ts ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Traffical React Context
3
+ *
4
+ * Provides the Traffical client instance to React components.
5
+ * Uses the browser-optimized JS Client for full feature support.
6
+ */
7
+
8
+ import { createContext, useContext } from "react";
9
+ import type { TrafficalClient, TrafficalPlugin } from "@traffical/js-client";
10
+ import type { ConfigBundle, Context } from "@traffical/core";
11
+
12
+ /**
13
+ * Configuration for the Traffical provider.
14
+ */
15
+ export interface TrafficalProviderConfig {
16
+ // ==========================================================================
17
+ // Required
18
+ // ==========================================================================
19
+
20
+ /** Organization ID */
21
+ orgId: string;
22
+ /** Project ID */
23
+ projectId: string;
24
+ /** Environment (e.g., "production", "staging") */
25
+ env: string;
26
+ /** API key for authentication */
27
+ apiKey: string;
28
+
29
+ // ==========================================================================
30
+ // Optional - Connection
31
+ // ==========================================================================
32
+
33
+ /** Base URL for the control plane API (optional) */
34
+ baseUrl?: string;
35
+ /** Local config bundle for offline fallback */
36
+ localConfig?: ConfigBundle;
37
+ /** Refresh interval in milliseconds (default: 60000) */
38
+ refreshIntervalMs?: number;
39
+
40
+ // ==========================================================================
41
+ // Optional - Identity
42
+ // ==========================================================================
43
+
44
+ /**
45
+ * Function to get the unit key value.
46
+ * If not provided, the SDK will use automatic stable ID generation.
47
+ */
48
+ unitKeyFn?: () => string;
49
+ /** Function to get additional context (optional) */
50
+ contextFn?: () => Context;
51
+
52
+ // ==========================================================================
53
+ // Optional - Decision Tracking
54
+ // ==========================================================================
55
+
56
+ /**
57
+ * Whether to automatically track decision events (default: true).
58
+ * When enabled, every call to decide() automatically sends a DecisionEvent
59
+ * to the control plane, enabling intent-to-treat analysis.
60
+ */
61
+ trackDecisions?: boolean;
62
+ /**
63
+ * Decision deduplication TTL in milliseconds (default: 1 hour).
64
+ * Same user+assignment combination won't be tracked again within this window.
65
+ */
66
+ decisionDeduplicationTtlMs?: number;
67
+
68
+ // ==========================================================================
69
+ // Optional - Exposure Tracking
70
+ // ==========================================================================
71
+
72
+ /**
73
+ * Exposure deduplication session TTL in milliseconds (default: 30 minutes).
74
+ * Same user seeing same variant won't trigger multiple exposure events.
75
+ */
76
+ exposureSessionTtlMs?: number;
77
+
78
+ // ==========================================================================
79
+ // Optional - Plugins
80
+ // ==========================================================================
81
+
82
+ /**
83
+ * Plugins to register with the client.
84
+ * The DecisionTrackingPlugin is included by default unless trackDecisions is false.
85
+ */
86
+ plugins?: TrafficalPlugin[];
87
+
88
+ // ==========================================================================
89
+ // Optional - Event Batching
90
+ // ==========================================================================
91
+
92
+ /** Max events before auto-flush (default: 10) */
93
+ eventBatchSize?: number;
94
+ /** Auto-flush interval in ms (default: 30000) */
95
+ eventFlushIntervalMs?: number;
96
+
97
+ // ==========================================================================
98
+ // Optional - SSR
99
+ // ==========================================================================
100
+
101
+ /** Initial params from SSR (optional) */
102
+ initialParams?: Record<string, unknown>;
103
+ }
104
+
105
+ /**
106
+ * Internal context value.
107
+ */
108
+ export interface TrafficalContextValue {
109
+ /** The Traffical client instance */
110
+ client: TrafficalClient | null;
111
+ /** Whether the client is ready (config loaded) */
112
+ ready: boolean;
113
+ /** Any initialization error */
114
+ error: Error | null;
115
+ /** Function to get the unit key */
116
+ getUnitKey: () => string;
117
+ /** Function to get the full context */
118
+ getContext: () => Context;
119
+ /** Initial params from SSR */
120
+ initialParams?: Record<string, unknown>;
121
+ /** Local config bundle for synchronous resolution during initial render */
122
+ localConfig?: ConfigBundle;
123
+ }
124
+
125
+ /**
126
+ * React context for Traffical.
127
+ */
128
+ export const TrafficalContext = createContext<TrafficalContextValue | null>(
129
+ null
130
+ );
131
+
132
+ /**
133
+ * Hook to access the Traffical context.
134
+ * Throws if used outside of TrafficalProvider.
135
+ */
136
+ export function useTrafficalContext(): TrafficalContextValue {
137
+ const context = useContext(TrafficalContext);
138
+ if (!context) {
139
+ throw new Error(
140
+ "useTrafficalContext must be used within a TrafficalProvider"
141
+ );
142
+ }
143
+ return context;
144
+ }