@traffical/react-native 0.2.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,404 @@
1
+ # @traffical/react-native
2
+
3
+ React Native SDK for Traffical - a unified parameter decisioning platform for feature flags, A/B testing, and contextual bandits.
4
+
5
+ Server-evaluated by default. Parameters are resolved on Traffical's edge, cached to AsyncStorage, and refreshed automatically when the app returns to the foreground.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @traffical/react-native @react-native-async-storage/async-storage
11
+ # or
12
+ npm install @traffical/react-native @react-native-async-storage/async-storage
13
+ ```
14
+
15
+ `@react-native-async-storage/async-storage` is a required peer dependency for persistent caching.
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Wrap your app with TrafficalRNProvider
20
+
21
+ ```tsx
22
+ import { TrafficalRNProvider } from '@traffical/react-native';
23
+ import { ActivityIndicator } from 'react-native';
24
+
25
+ function App() {
26
+ return (
27
+ <TrafficalRNProvider
28
+ config={{
29
+ orgId: 'org_123',
30
+ projectId: 'proj_456',
31
+ env: 'production',
32
+ apiKey: 'pk_...',
33
+ }}
34
+ loadingComponent={<ActivityIndicator size="large" />}
35
+ >
36
+ <Navigation />
37
+ </TrafficalRNProvider>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ### 2. Use the `useTraffical` hook in your screens
43
+
44
+ ```tsx
45
+ import { useTraffical } from '@traffical/react-native';
46
+
47
+ function OnboardingScreen() {
48
+ const { params, ready, track } = useTraffical({
49
+ defaults: {
50
+ 'onboarding.ctaText': 'Get Started',
51
+ 'onboarding.showSkip': true,
52
+ 'onboarding.accentColor': '#3b82f6',
53
+ },
54
+ });
55
+
56
+ const handleCTATap = () => {
57
+ track('onboarding_cta_tap');
58
+ };
59
+
60
+ return (
61
+ <View>
62
+ <Button
63
+ title={params['onboarding.ctaText']}
64
+ color={params['onboarding.accentColor']}
65
+ onPress={handleCTATap}
66
+ />
67
+ {params['onboarding.showSkip'] && (
68
+ <TouchableOpacity onPress={skipOnboarding}>
69
+ <Text>Skip</Text>
70
+ </TouchableOpacity>
71
+ )}
72
+ </View>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### TrafficalRNProvider
80
+
81
+ Initializes the Traffical client with React Native defaults and provides it to child components.
82
+
83
+ ```tsx
84
+ <TrafficalRNProvider
85
+ config={config}
86
+ loadingComponent={<ActivityIndicator />}
87
+ >
88
+ {children}
89
+ </TrafficalRNProvider>
90
+ ```
91
+
92
+ #### Props
93
+
94
+ | Prop | Type | Required | Description |
95
+ |------|------|----------|-------------|
96
+ | `config.orgId` | `string` | Yes | Organization ID |
97
+ | `config.projectId` | `string` | Yes | Project ID |
98
+ | `config.env` | `string` | Yes | Environment (e.g., "production", "staging") |
99
+ | `config.apiKey` | `string` | Yes | API key for authentication |
100
+ | `config.baseUrl` | `string` | No | Base URL for the control plane API |
101
+ | `config.evaluationMode` | `"server" \| "bundle"` | No | Resolution mode (default: `"server"`) |
102
+ | `config.refreshIntervalMs` | `number` | No | Background refresh interval (default: 60000) |
103
+ | `config.unitKeyFn` | `() => string` | No | Function to get the unit key. If not provided, uses automatic stable ID |
104
+ | `config.contextFn` | `() => Context` | No | Function to get additional context |
105
+ | `config.deviceInfoProvider` | `DeviceInfoProvider` | No | Provider for device metadata (OS, model, etc.) |
106
+ | `config.cacheMaxAgeMs` | `number` | No | Cache TTL for persisted responses (default: 24 hours) |
107
+ | `config.trackDecisions` | `boolean` | No | Whether to track decision events (default: true) |
108
+ | `config.decisionDeduplicationTtlMs` | `number` | No | Decision dedup TTL (default: 1 hour) |
109
+ | `config.exposureSessionTtlMs` | `number` | No | Exposure dedup session TTL (default: 30 min) |
110
+ | `config.plugins` | `TrafficalPlugin[]` | No | Additional plugins to register |
111
+ | `config.eventBatchSize` | `number` | No | Max events before auto-flush (default: 10) |
112
+ | `config.eventFlushIntervalMs` | `number` | No | Auto-flush interval (default: 30000) |
113
+ | `config.initialParams` | `Record<string, unknown>` | No | Initial params for fallback |
114
+ | `config.localConfig` | `ConfigBundle` | No | Local config bundle for offline fallback |
115
+ | `loadingComponent` | `ReactNode` | No | Shown while the SDK is initializing |
116
+
117
+ ---
118
+
119
+ ### useTraffical
120
+
121
+ Primary hook for parameter resolution and decision tracking. Identical API to `@traffical/react`.
122
+
123
+ ```tsx
124
+ const { params, decision, ready, error, trackExposure, track, flushEvents } = useTraffical(options);
125
+ ```
126
+
127
+ #### Options
128
+
129
+ | Option | Type | Default | Description |
130
+ |--------|------|---------|-------------|
131
+ | `defaults` | `T` | Required | Default parameter values |
132
+ | `context` | `Context` | `undefined` | Additional context to merge |
133
+ | `tracking` | `"full" \| "decision" \| "none"` | `"full"` | Tracking mode |
134
+
135
+ #### Tracking Modes
136
+
137
+ | Mode | Decision Event | Exposure Event | Use Case |
138
+ |------|----------------|----------------|----------|
139
+ | `"full"` | Yes | Auto | Default. UI shown to users |
140
+ | `"decision"` | Yes | Manual | Manual exposure control (e.g., screen visibility) |
141
+ | `"none"` | No | No | Internal logic, tests |
142
+
143
+ #### Return Value
144
+
145
+ | Property | Type | Description |
146
+ |----------|------|-------------|
147
+ | `params` | `T` | Resolved parameter values |
148
+ | `decision` | `DecisionResult \| null` | Decision metadata (null when `tracking="none"`) |
149
+ | `ready` | `boolean` | Whether the client is ready |
150
+ | `error` | `Error \| null` | Any initialization error |
151
+ | `trackExposure` | `() => void` | Manually track exposure |
152
+ | `track` | `(event: string, properties?: object) => void` | Track event with bound decisionId |
153
+ | `flushEvents` | `() => Promise<void>` | Flush all pending events immediately |
154
+
155
+ #### Examples
156
+
157
+ ```tsx
158
+ // Full tracking (default) - decision + exposure events
159
+ const { params, track } = useTraffical({
160
+ defaults: { 'checkout.ctaText': 'Buy Now' },
161
+ });
162
+
163
+ // Decision tracking only - manual exposure control
164
+ const { params, trackExposure } = useTraffical({
165
+ defaults: { 'checkout.ctaText': 'Buy Now' },
166
+ tracking: 'decision',
167
+ });
168
+
169
+ // No tracking - for tests or internal logic
170
+ const { params, ready } = useTraffical({
171
+ defaults: { 'ui.theme': 'light' },
172
+ tracking: 'none',
173
+ });
174
+ ```
175
+
176
+ ---
177
+
178
+ ### useTrafficalTrack
179
+
180
+ Standalone hook for tracking events outside of a `useTraffical` decision.
181
+
182
+ ```tsx
183
+ const track = useTrafficalTrack();
184
+
185
+ const handlePurchase = (amount: number) => {
186
+ track('purchase', { value: amount, orderId: 'ord_123' });
187
+ };
188
+ ```
189
+
190
+ > **Tip:** For most use cases, use the bound `track` from `useTraffical()` instead — it automatically includes the `decisionId`.
191
+
192
+ ---
193
+
194
+ ### useTrafficalClient
195
+
196
+ Hook to access the Traffical client directly.
197
+
198
+ ```tsx
199
+ const { client, ready, error } = useTrafficalClient();
200
+
201
+ if (ready && client) {
202
+ const version = client.getConfigVersion();
203
+ const stableId = client.getStableId();
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ### useTrafficalPlugin
210
+
211
+ Hook to access a registered plugin by name.
212
+
213
+ ```tsx
214
+ const myPlugin = useTrafficalPlugin<MyPlugin>('my-plugin');
215
+ ```
216
+
217
+ ---
218
+
219
+ ## How It Works
220
+
221
+ ### Initialization Flow
222
+
223
+ ```
224
+ App Launch
225
+
226
+ ├─ 1. AsyncStorage preload (all traffical:* keys → memory)
227
+ ├─ 2. Load cached server response (if within cache TTL)
228
+ ├─ 3. Mark ready with cached data (or show loadingComponent)
229
+ ├─ 4. Fetch fresh response from edge (/v1/resolve)
230
+ └─ 5. Update params + persist to cache
231
+ ```
232
+
233
+ ### Foreground Resume
234
+
235
+ When the app returns from the background, the SDK checks whether enough time has elapsed since the last resolve (based on `suggestedRefreshMs` from the server, default 60s). If stale, it triggers a background refresh.
236
+
237
+ ```
238
+ Background → Foreground
239
+
240
+ ├─ Check: now - lastResolve >= suggestedRefreshMs?
241
+ │ ├─ Yes → refreshConfig() in background
242
+ │ └─ No → do nothing
243
+ ```
244
+
245
+ ### Cache Priority
246
+
247
+ On cold start, the SDK uses this fallback chain:
248
+
249
+ | Priority | Source | Behavior |
250
+ |----------|--------|----------|
251
+ | 1 | Cached `ServerResolveResponse` (within TTL) | Ready immediately, background refresh |
252
+ | 2 | Cached response (expired) | Ready immediately, background refresh |
253
+ | 3 | No cache | Wait for server response, show `loadingComponent` |
254
+ | 4 | `localConfig` from options | Offline fallback |
255
+ | 5 | `initialParams` from options | Last-resort override |
256
+ | 6 | `defaults` from `useTraffical` | Absolute fallback |
257
+
258
+ ---
259
+
260
+ ## Use Cases
261
+
262
+ ### Feature Flag
263
+
264
+ ```tsx
265
+ function HomeScreen() {
266
+ const { params } = useTraffical({
267
+ defaults: { 'feature.newFeed': false },
268
+ });
269
+
270
+ return params['feature.newFeed'] ? <NewFeed /> : <ClassicFeed />;
271
+ }
272
+ ```
273
+
274
+ ### A/B Test with Conversion Tracking
275
+
276
+ ```tsx
277
+ function PaywallScreen() {
278
+ const { params, track } = useTraffical({
279
+ defaults: {
280
+ 'paywall.headline': 'Go Premium',
281
+ 'paywall.showTrial': true,
282
+ 'paywall.accentColor': '#6366f1',
283
+ },
284
+ });
285
+
286
+ const handleSubscribe = (plan: string, price: number) => {
287
+ track('subscribe', { value: price, plan });
288
+ };
289
+
290
+ return (
291
+ <View>
292
+ <Text style={{ color: params['paywall.accentColor'] }}>
293
+ {params['paywall.headline']}
294
+ </Text>
295
+ {params['paywall.showTrial'] && <TrialBanner />}
296
+ <PlanPicker onSelect={handleSubscribe} />
297
+ </View>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ### Flushing Events Before Navigation
303
+
304
+ ```tsx
305
+ function CheckoutScreen({ navigation }) {
306
+ const { params, track, flushEvents } = useTraffical({
307
+ defaults: { 'checkout.ctaText': 'Complete Purchase' },
308
+ });
309
+
310
+ const handlePurchase = async (total: number) => {
311
+ track('purchase', { value: total });
312
+ await flushEvents();
313
+ navigation.replace('Success');
314
+ };
315
+
316
+ return (
317
+ <Button
318
+ title={params['checkout.ctaText']}
319
+ onPress={() => handlePurchase(99.99)}
320
+ />
321
+ );
322
+ }
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Differences from @traffical/react
328
+
329
+ | | `@traffical/react` | `@traffical/react-native` |
330
+ |---|---|---|
331
+ | **Default evaluation** | Bundle (client-side) | Server (edge-evaluated) |
332
+ | **Storage** | localStorage | AsyncStorage (preloaded to memory) |
333
+ | **Lifecycle** | `visibilitychange` / `pagehide` | `AppState` change |
334
+ | **Loading state** | Manual (`if (!ready)`) | Built-in `loadingComponent` prop |
335
+ | **Unload flush** | sendBeacon on page unload | Regular flush (no unload concept) |
336
+ | **DOM plugin** | Available | Not available (no DOM) |
337
+ | **Hook API** | `useTraffical`, `useTrafficalTrack`, etc. | Identical |
338
+
339
+ ---
340
+
341
+ ## Best Practices
342
+
343
+ ### 1. Always Provide Sensible Defaults
344
+
345
+ Defaults are used when no experiment is running, the user doesn't match targeting, or the SDK is still loading.
346
+
347
+ ```tsx
348
+ // ✅ Good: works without any experiment
349
+ const { params } = useTraffical({
350
+ defaults: {
351
+ 'pricing.discount': 0,
352
+ 'ui.accentColor': '#3b82f6',
353
+ },
354
+ });
355
+ ```
356
+
357
+ ### 2. Use the loadingComponent Prop
358
+
359
+ Unlike web apps where a blank flash is acceptable, mobile users expect immediate content. Use `loadingComponent` to show a spinner or skeleton until the first resolve completes.
360
+
361
+ ```tsx
362
+ <TrafficalRNProvider
363
+ config={config}
364
+ loadingComponent={<SplashScreen />}
365
+ >
366
+ <App />
367
+ </TrafficalRNProvider>
368
+ ```
369
+
370
+ ### 3. Track Events at Conversion Points
371
+
372
+ ```tsx
373
+ const { params, track } = useTraffical({
374
+ defaults: { 'checkout.showUpsells': false },
375
+ });
376
+
377
+ // Track meaningful conversions
378
+ const handlePurchase = (amount: number) => {
379
+ track('purchase', { value: amount });
380
+ };
381
+
382
+ // Track micro-conversions too
383
+ const handleAddToCart = () => {
384
+ track('add_to_cart', { itemId: 'sku_456' });
385
+ };
386
+ ```
387
+
388
+ ### 4. Use Consistent Parameter Naming
389
+
390
+ ```
391
+ category.subcategory.name
392
+
393
+ feature.* → Feature flags (boolean)
394
+ ui.* → Visual variations (string, number)
395
+ pricing.* → Pricing experiments (number)
396
+ copy.* → Copywriting tests (string)
397
+ onboarding.* → Onboarding flow (mixed)
398
+ ```
399
+
400
+ ---
401
+
402
+ ## License
403
+
404
+ MIT
@@ -0,0 +1,32 @@
1
+ import { TrafficalClient, type TrafficalClientOptions } from "@traffical/js-client";
2
+ import type { DeviceInfoProvider } from "./device-info.js";
3
+ export interface TrafficalRNClientOptions extends TrafficalClientOptions {
4
+ deviceInfoProvider?: DeviceInfoProvider;
5
+ /** Cache max age in ms for persisted server responses (default: 86400000 = 24 hours) */
6
+ cacheMaxAgeMs?: number;
7
+ }
8
+ export declare class TrafficalRNClient extends TrafficalClient {
9
+ private readonly _rnStorage;
10
+ private readonly _rnLifecycle;
11
+ readonly deviceInfoProvider?: DeviceInfoProvider;
12
+ private readonly _cacheMaxAgeMs;
13
+ private _lastResolveTimestamp;
14
+ private _suggestedRefreshMs;
15
+ private _visibilityCallback;
16
+ constructor(options: TrafficalRNClientOptions);
17
+ /**
18
+ * RN-specific initialization:
19
+ * 1. Wait for AsyncStorage preload
20
+ * 2. Load cached server response if available
21
+ * 3. Call parent initialize (fetches from server)
22
+ * 4. Persist response to cache
23
+ */
24
+ initialize(): Promise<void>;
25
+ destroy(): void;
26
+ refreshConfig(): Promise<void>;
27
+ private _setupForegroundRefresh;
28
+ private _loadCachedResponse;
29
+ private _loadCachedTimestamp;
30
+ private _persistCurrentResponse;
31
+ }
32
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAE5B,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;IACtE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAOD,qBAAa,iBAAkB,SAAQ,eAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,mBAAmB,CAAgC;IAC3D,OAAO,CAAC,mBAAmB,CACpB;gBAEK,OAAO,EAAE,wBAAwB;IAqB7C;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBxB,OAAO,IAAI,IAAI;IAQT,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7C,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,uBAAuB;CAQhC"}
package/dist/client.js ADDED
@@ -0,0 +1,89 @@
1
+ import { TrafficalClient, } from "@traffical/js-client";
2
+ import { createPreloadedAsyncStorage, } from "./storage.js";
3
+ import { createRNLifecycleProvider } from "./lifecycle.js";
4
+ const CACHED_RESPONSE_KEY = "server_resolve_cache";
5
+ const CACHED_RESPONSE_TIMESTAMP_KEY = "server_resolve_cache_ts";
6
+ const DEFAULT_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
7
+ const DEFAULT_SUGGESTED_REFRESH_MS = 60_000; // 60 seconds
8
+ export class TrafficalRNClient extends TrafficalClient {
9
+ _rnStorage;
10
+ _rnLifecycle;
11
+ deviceInfoProvider;
12
+ _cacheMaxAgeMs;
13
+ _lastResolveTimestamp = 0;
14
+ _suggestedRefreshMs = DEFAULT_SUGGESTED_REFRESH_MS;
15
+ _visibilityCallback = null;
16
+ constructor(options) {
17
+ const rnStorage = options.storage ??
18
+ createPreloadedAsyncStorage();
19
+ const lifecycle = options.lifecycleProvider ?? createRNLifecycleProvider();
20
+ super({
21
+ ...options,
22
+ evaluationMode: options.evaluationMode ?? "server",
23
+ storage: rnStorage,
24
+ lifecycleProvider: lifecycle,
25
+ });
26
+ this._rnStorage = rnStorage;
27
+ this._rnLifecycle = lifecycle;
28
+ this.deviceInfoProvider = options.deviceInfoProvider;
29
+ this._cacheMaxAgeMs = options.cacheMaxAgeMs ?? DEFAULT_CACHE_MAX_AGE_MS;
30
+ this._setupForegroundRefresh();
31
+ }
32
+ /**
33
+ * RN-specific initialization:
34
+ * 1. Wait for AsyncStorage preload
35
+ * 2. Load cached server response if available
36
+ * 3. Call parent initialize (fetches from server)
37
+ * 4. Persist response to cache
38
+ */
39
+ async initialize() {
40
+ await this._rnStorage.waitUntilReady();
41
+ const cached = this._loadCachedResponse();
42
+ if (cached) {
43
+ this._lastResolveTimestamp = this._loadCachedTimestamp();
44
+ if (cached.suggestedRefreshMs) {
45
+ this._suggestedRefreshMs = cached.suggestedRefreshMs;
46
+ }
47
+ }
48
+ await super.initialize();
49
+ this._persistCurrentResponse();
50
+ }
51
+ destroy() {
52
+ if (this._visibilityCallback) {
53
+ this._rnLifecycle.removeVisibilityListener(this._visibilityCallback);
54
+ this._visibilityCallback = null;
55
+ }
56
+ super.destroy();
57
+ }
58
+ async refreshConfig() {
59
+ await super.refreshConfig();
60
+ this._lastResolveTimestamp = Date.now();
61
+ this._persistCurrentResponse();
62
+ }
63
+ _setupForegroundRefresh() {
64
+ this._visibilityCallback = (state) => {
65
+ if (state === "foreground") {
66
+ const elapsed = Date.now() - this._lastResolveTimestamp;
67
+ if (elapsed >= this._suggestedRefreshMs) {
68
+ this.refreshConfig().catch(() => { });
69
+ }
70
+ }
71
+ };
72
+ this._rnLifecycle.onVisibilityChange(this._visibilityCallback);
73
+ }
74
+ _loadCachedResponse() {
75
+ return this._rnStorage.get(CACHED_RESPONSE_KEY);
76
+ }
77
+ _loadCachedTimestamp() {
78
+ return this._rnStorage.get(CACHED_RESPONSE_TIMESTAMP_KEY) ?? 0;
79
+ }
80
+ _persistCurrentResponse() {
81
+ const version = this.getConfigVersion();
82
+ if (!version)
83
+ return;
84
+ const now = Date.now();
85
+ this._lastResolveTimestamp = now;
86
+ this._rnStorage.set(CACHED_RESPONSE_TIMESTAMP_KEY, now, this._cacheMaxAgeMs);
87
+ }
88
+ }
89
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAGhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,2BAA2B,GAE5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAS3D,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AACnD,MAAM,6BAA6B,GAAG,yBAAyB,CAAC;AAChE,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;AACjE,MAAM,4BAA4B,GAAG,MAAM,CAAC,CAAC,aAAa;AAE1D,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IACnC,UAAU,CAAgC;IAC1C,YAAY,CAAoB;IACxC,kBAAkB,CAAsB;IAChC,cAAc,CAAS;IAChC,qBAAqB,GAAG,CAAC,CAAC;IAC1B,mBAAmB,GAAG,4BAA4B,CAAC;IACnD,mBAAmB,GACzB,IAAI,CAAC;IAEP,YAAY,OAAiC;QAC3C,MAAM,SAAS,GACZ,OAAO,CAAC,OAAqD;YAC9D,2BAA2B,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,OAAO,CAAC,iBAAiB,IAAI,yBAAyB,EAAE,CAAC;QAE3E,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,QAAQ;YAClD,OAAO,EAAE,SAAS;YAClB,iBAAiB,EAAE,SAAS;SAC7B,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC9B,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;QACrD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,aAAa,IAAI,wBAAwB,CAAC;QAExE,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAEvC,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC1C,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACzD,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAC9B,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,kBAAkB,CAAC;YACvD,CAAC;QACH,CAAC;QAED,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;QAEzB,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;IAEQ,OAAO;QACd,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,IAAI,CAAC,YAAY,CAAC,wBAAwB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACrE,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;IAEQ,KAAK,CAAC,aAAa;QAC1B,MAAM,KAAK,CAAC,aAAa,EAAE,CAAC;QAC5B,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;IAEO,uBAAuB;QAC7B,IAAI,CAAC,mBAAmB,GAAG,CAAC,KAAK,EAAE,EAAE;YACnC,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;gBAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC;gBACxD,IAAI,OAAO,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBACxC,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACjE,CAAC;IAEO,mBAAmB;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAwB,mBAAmB,CAAC,CAAC;IACzE,CAAC;IAEO,oBAAoB;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAS,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAEO,uBAAuB;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,qBAAqB,GAAG,GAAG,CAAC;QACjC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,6BAA6B,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/E,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ import type { TrafficalClient, TrafficalPlugin } from "@traffical/js-client";
2
+ import type { ConfigBundle, Context } from "@traffical/core";
3
+ export interface TrafficalRNProviderConfig {
4
+ orgId: string;
5
+ projectId: string;
6
+ env: string;
7
+ apiKey: string;
8
+ baseUrl?: string;
9
+ localConfig?: ConfigBundle;
10
+ refreshIntervalMs?: number;
11
+ unitKeyFn?: () => string;
12
+ contextFn?: () => Context;
13
+ trackDecisions?: boolean;
14
+ decisionDeduplicationTtlMs?: number;
15
+ exposureSessionTtlMs?: number;
16
+ plugins?: TrafficalPlugin[];
17
+ eventBatchSize?: number;
18
+ eventFlushIntervalMs?: number;
19
+ initialParams?: Record<string, unknown>;
20
+ /** Evaluation mode (default: "server" for RN) */
21
+ evaluationMode?: "bundle" | "server";
22
+ /** Device info provider for enriching context */
23
+ deviceInfoProvider?: import("./device-info.js").DeviceInfoProvider;
24
+ /** Cache max age in ms for persisted server responses (default: 86400000 = 24 hours) */
25
+ cacheMaxAgeMs?: number;
26
+ }
27
+ export interface TrafficalContextValue {
28
+ client: TrafficalClient | null;
29
+ ready: boolean;
30
+ error: Error | null;
31
+ getUnitKey: () => string;
32
+ getContext: () => Context;
33
+ initialParams?: Record<string, unknown>;
34
+ localConfig?: ConfigBundle;
35
+ }
36
+ export declare const TrafficalContext: import("react").Context<TrafficalContextValue | null>;
37
+ export declare function useTrafficalContext(): TrafficalContextValue;
38
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IAEf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;IAE1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAE5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAExC,iDAAiD;IACjD,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACrC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,EAAE,kBAAkB,CAAC;IACnE,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED,eAAO,MAAM,gBAAgB,uDAE5B,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,qBAAqB,CAQ3D"}
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from "react";
2
+ export const TrafficalContext = createContext(null);
3
+ export function useTrafficalContext() {
4
+ const context = useContext(TrafficalContext);
5
+ if (!context) {
6
+ throw new Error("useTrafficalContext must be used within a TrafficalRNProvider");
7
+ }
8
+ return context;
9
+ }
10
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA8ClD,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAC3C,IAAI,CACL,CAAC;AAEF,MAAM,UAAU,mBAAmB;IACjC,MAAM,OAAO,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,17 @@
1
+ export interface DeviceInfo {
2
+ appVersion?: string;
3
+ appBuildNumber?: string;
4
+ deviceModel?: string;
5
+ deviceModelName?: string;
6
+ osName?: string;
7
+ osVersion?: string;
8
+ locale?: string;
9
+ timezone?: string;
10
+ screenWidth?: number;
11
+ screenHeight?: number;
12
+ pixelRatio?: number;
13
+ }
14
+ export interface DeviceInfoProvider {
15
+ getDeviceInfo(): DeviceInfo;
16
+ }
17
+ //# sourceMappingURL=device-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-info.d.ts","sourceRoot":"","sources":["../src/device-info.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,IAAI,UAAU,CAAC;CAC7B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=device-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-info.js","sourceRoot":"","sources":["../src/device-info.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import type { ParameterValue, DecisionResult, Context } from "@traffical/core";
2
+ import type { TrafficalPlugin } from "@traffical/js-client";
3
+ export interface UseTrafficalOptions<T> {
4
+ defaults: T;
5
+ context?: Context;
6
+ /**
7
+ * Tracking mode (default: "full")
8
+ * - "full": Track decision + exposure (default, recommended for UI)
9
+ * - "decision": Track decision only, manual exposure control
10
+ * - "none": No tracking (internal logic, tests)
11
+ */
12
+ tracking?: "full" | "decision" | "none";
13
+ }
14
+ export interface BoundTrackOptions {
15
+ properties?: Record<string, unknown>;
16
+ }
17
+ export interface UseTrafficalResult<T> {
18
+ params: T;
19
+ decision: DecisionResult | null;
20
+ ready: boolean;
21
+ error: Error | null;
22
+ trackExposure: () => void;
23
+ track: (event: string, properties?: Record<string, unknown>) => void;
24
+ flushEvents: () => Promise<void>;
25
+ }
26
+ export declare function useTraffical<T extends Record<string, ParameterValue>>(options: UseTrafficalOptions<T>): UseTrafficalResult<T>;
27
+ export declare function useTrafficalTrack(): (event: string, properties?: Record<string, unknown>, options?: {
28
+ decisionId?: string;
29
+ }) => void;
30
+ export declare function useTrafficalPlugin<T extends TrafficalPlugin = TrafficalPlugin>(name: string): T | undefined;
31
+ export declare function useTrafficalClient(): {
32
+ client: import("@traffical/js-client").TrafficalClient | null;
33
+ ready: boolean;
34
+ error: Error | null;
35
+ };
36
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,OAAO,EACR,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA8B5D,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,QAAQ,EAAE,CAAC,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACrE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,EACnE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,kBAAkB,CAAC,CAAC,CAAC,CAwLvB;AAMD,wBAAgB,iBAAiB,YAKpB,MAAM,eACA,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAC1B;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,UAkBtC;AAMD,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,eAAe,GAAG,eAAe,EAC3C,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAQ7B;AAMD,wBAAgB,kBAAkB;;;;EAGjC"}
package/dist/hooks.js ADDED
@@ -0,0 +1,203 @@
1
+ import { useState, useEffect, useCallback, useRef } from "react";
2
+ import { resolveParameters } from "@traffical/core";
3
+ import { useTrafficalContext } from "./context.js";
4
+ // =============================================================================
5
+ // Internal Utilities
6
+ // =============================================================================
7
+ function createStableKey(obj) {
8
+ if (obj === null || obj === undefined) {
9
+ return String(obj);
10
+ }
11
+ if (typeof obj !== "object") {
12
+ return String(obj);
13
+ }
14
+ return JSON.stringify(obj, Object.keys(obj).sort());
15
+ }
16
+ function useStableObject(obj) {
17
+ const stableKey = createStableKey(obj);
18
+ const ref = useRef(obj);
19
+ if (createStableKey(ref.current) !== stableKey) {
20
+ ref.current = obj;
21
+ }
22
+ return ref.current;
23
+ }
24
+ export function useTraffical(options) {
25
+ const { client, ready, error, getContext, getUnitKey, initialParams, localConfig, } = useTrafficalContext();
26
+ const trackingMode = options.tracking ?? "full";
27
+ const shouldTrackDecision = trackingMode !== "none";
28
+ const shouldAutoTrackExposure = trackingMode === "full";
29
+ const stableDefaults = useStableObject(options.defaults);
30
+ const stableContext = useStableObject(options.context);
31
+ const resolvedSyncRef = useRef(false);
32
+ const syncDecisionRef = useRef(null);
33
+ const [params, setParams] = useState(() => {
34
+ if (client && ready) {
35
+ resolvedSyncRef.current = true;
36
+ const context = {
37
+ ...getContext(),
38
+ ...(options.context ?? {}),
39
+ };
40
+ if (shouldTrackDecision) {
41
+ const result = client.decide({ context, defaults: options.defaults });
42
+ syncDecisionRef.current = result;
43
+ return result.assignments;
44
+ }
45
+ else {
46
+ return client.getParams({ context, defaults: options.defaults });
47
+ }
48
+ }
49
+ if (localConfig) {
50
+ try {
51
+ const context = getContext();
52
+ if (context.userId) {
53
+ resolvedSyncRef.current = true;
54
+ const fullContext = {
55
+ ...context,
56
+ ...(options.context ?? {}),
57
+ };
58
+ const resolved = resolveParameters(localConfig, fullContext, options.defaults);
59
+ return resolved;
60
+ }
61
+ }
62
+ catch {
63
+ // Fall through to defaults
64
+ }
65
+ }
66
+ if (initialParams) {
67
+ return { ...stableDefaults, ...initialParams };
68
+ }
69
+ return stableDefaults;
70
+ });
71
+ const [decision, setDecision] = useState(() => syncDecisionRef.current);
72
+ const [hasTrackedExposure, setHasTrackedExposure] = useState(false);
73
+ const trackExposure = useCallback(() => {
74
+ if (trackingMode === "none")
75
+ return;
76
+ if (!client || !decision || hasTrackedExposure)
77
+ return;
78
+ client.trackExposure(decision);
79
+ setHasTrackedExposure(true);
80
+ }, [client, decision, hasTrackedExposure, trackingMode]);
81
+ useEffect(() => {
82
+ if (!client || !ready)
83
+ return;
84
+ if (resolvedSyncRef.current && syncDecisionRef.current) {
85
+ resolvedSyncRef.current = false;
86
+ return;
87
+ }
88
+ resolvedSyncRef.current = false;
89
+ const context = {
90
+ ...getContext(),
91
+ ...stableContext,
92
+ };
93
+ if (shouldTrackDecision) {
94
+ const result = client.decide({ context, defaults: stableDefaults });
95
+ setParams(result.assignments);
96
+ setDecision(result);
97
+ setHasTrackedExposure(false);
98
+ }
99
+ else {
100
+ const resolved = client.getParams({
101
+ context,
102
+ defaults: stableDefaults,
103
+ });
104
+ setParams(resolved);
105
+ setDecision(null);
106
+ }
107
+ }, [
108
+ client,
109
+ ready,
110
+ getContext,
111
+ stableContext,
112
+ stableDefaults,
113
+ shouldTrackDecision,
114
+ ]);
115
+ useEffect(() => {
116
+ if (shouldAutoTrackExposure && decision && !hasTrackedExposure) {
117
+ trackExposure();
118
+ }
119
+ }, [shouldAutoTrackExposure, decision, hasTrackedExposure, trackExposure]);
120
+ const decisionRef = useRef(null);
121
+ decisionRef.current = decision;
122
+ const pendingTracksRef = useRef([]);
123
+ useEffect(() => {
124
+ if (decision && client && pendingTracksRef.current.length > 0) {
125
+ const pending = pendingTracksRef.current;
126
+ pendingTracksRef.current = [];
127
+ for (const { event, properties } of pending) {
128
+ client.track(event, properties, {
129
+ decisionId: decision.decisionId,
130
+ unitKey: getUnitKey(),
131
+ });
132
+ }
133
+ }
134
+ }, [decision, client, getUnitKey]);
135
+ const track = useCallback((event, properties) => {
136
+ if (!client) {
137
+ console.warn("[Traffical] Client not initialized, cannot track event");
138
+ return;
139
+ }
140
+ const currentDecision = decisionRef.current;
141
+ if (!currentDecision) {
142
+ if (trackingMode === "none") {
143
+ console.warn("[Traffical] Cannot track event with tracking: 'none'. Use tracking: 'full' or 'decision'.");
144
+ return;
145
+ }
146
+ pendingTracksRef.current.push({ event, properties });
147
+ return;
148
+ }
149
+ client.track(event, properties, {
150
+ decisionId: currentDecision.decisionId,
151
+ unitKey: getUnitKey(),
152
+ });
153
+ }, [client, getUnitKey, trackingMode]);
154
+ const flushEvents = useCallback(async () => {
155
+ if (!client)
156
+ return;
157
+ await client.flushEvents();
158
+ }, [client]);
159
+ return {
160
+ params,
161
+ decision,
162
+ ready,
163
+ error,
164
+ trackExposure,
165
+ track,
166
+ flushEvents,
167
+ };
168
+ }
169
+ // =============================================================================
170
+ // useTrafficalTrack
171
+ // =============================================================================
172
+ export function useTrafficalTrack() {
173
+ const { client, getUnitKey } = useTrafficalContext();
174
+ const track = useCallback((event, properties, options) => {
175
+ if (!client) {
176
+ console.warn("[Traffical] Client not initialized, cannot track event");
177
+ return;
178
+ }
179
+ client.track(event, properties, {
180
+ decisionId: options?.decisionId,
181
+ unitKey: getUnitKey(),
182
+ });
183
+ }, [client, getUnitKey]);
184
+ return track;
185
+ }
186
+ // =============================================================================
187
+ // useTrafficalPlugin
188
+ // =============================================================================
189
+ export function useTrafficalPlugin(name) {
190
+ const { client, ready } = useTrafficalContext();
191
+ if (!client || !ready) {
192
+ return undefined;
193
+ }
194
+ return client.getPlugin(name);
195
+ }
196
+ // =============================================================================
197
+ // useTrafficalClient
198
+ // =============================================================================
199
+ export function useTrafficalClient() {
200
+ const { client, ready, error } = useTrafficalContext();
201
+ return { client, ready, error };
202
+ }
203
+ //# 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,10 @@
1
+ export * from "@traffical/core";
2
+ export { TrafficalClient, type TrafficalClientOptions, type LifecycleProvider, type VisibilityState, type VisibilityCallback, type StorageProvider, MemoryStorageProvider, type TrafficalPlugin, type PluginOptions, ErrorBoundary, type ErrorBoundaryOptions, } from "@traffical/js-client";
3
+ export { TrafficalRNClient, type TrafficalRNClientOptions, } from "./client.js";
4
+ export { createPreloadedAsyncStorage, type PreloadedAsyncStorageProvider, } from "./storage.js";
5
+ export { createRNLifecycleProvider } from "./lifecycle.js";
6
+ export { type DeviceInfo, type DeviceInfoProvider } from "./device-info.js";
7
+ export { TrafficalRNProvider, type TrafficalRNProviderProps, } from "./provider.js";
8
+ export { TrafficalContext, useTrafficalContext, type TrafficalRNProviderConfig, type TrafficalContextValue, } from "./context.js";
9
+ export { useTraffical, type UseTrafficalOptions, type UseTrafficalResult, useTrafficalTrack, useTrafficalPlugin, useTrafficalClient, } from "./hooks.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,GAC9B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,KAAK,6BAA6B,GACnC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Re-export everything from core
2
+ export * from "@traffical/core";
3
+ // Re-export client utilities (excluding browser-specific: DOM plugin, LocalStorageProvider)
4
+ export { TrafficalClient, MemoryStorageProvider, ErrorBoundary, } from "@traffical/js-client";
5
+ // RN-specific exports
6
+ export { TrafficalRNClient, } from "./client.js";
7
+ export { createPreloadedAsyncStorage, } from "./storage.js";
8
+ export { createRNLifecycleProvider } from "./lifecycle.js";
9
+ // Provider and hooks
10
+ export { TrafficalRNProvider, } from "./provider.js";
11
+ export { TrafficalContext, useTrafficalContext, } from "./context.js";
12
+ export { useTraffical, useTrafficalTrack, useTrafficalPlugin, useTrafficalClient, } from "./hooks.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,cAAc,iBAAiB,CAAC;AAEhC,4FAA4F;AAC5F,OAAO,EACL,eAAe,EAMf,qBAAqB,EAGrB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAE9B,sBAAsB;AACtB,OAAO,EACL,iBAAiB,GAElB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,GAE5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,qBAAqB;AACrB,OAAO,EACL,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAGpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EAGZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { LifecycleProvider } from "@traffical/js-client";
2
+ export declare function createRNLifecycleProvider(): LifecycleProvider;
3
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,sBAAsB,CAAC;AAE9B,wBAAgB,yBAAyB,IAAI,iBAAiB,CAoB7D"}
@@ -0,0 +1,23 @@
1
+ import { AppState } from "react-native";
2
+ export function createRNLifecycleProvider() {
3
+ const listeners = [];
4
+ AppState.addEventListener("change", (nextState) => {
5
+ const visibility = nextState === "active" ? "foreground" : "background";
6
+ for (const cb of listeners)
7
+ cb(visibility);
8
+ });
9
+ return {
10
+ onVisibilityChange(callback) {
11
+ listeners.push(callback);
12
+ },
13
+ removeVisibilityListener(callback) {
14
+ const idx = listeners.indexOf(callback);
15
+ if (idx !== -1)
16
+ listeners.splice(idx, 1);
17
+ },
18
+ isUnloading() {
19
+ return false;
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,cAAc,CAAC;AAM7D,MAAM,UAAU,yBAAyB;IACvC,MAAM,SAAS,GAAyB,EAAE,CAAC;IAE3C,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,SAAyB,EAAE,EAAE;QAChE,MAAM,UAAU,GAAG,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;QACxE,KAAK,MAAM,EAAE,IAAI,SAAS;YAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,kBAAkB,CAAC,QAA4B;YAC7C,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,wBAAwB,CAAC,QAA4B;YACnD,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACxC,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,WAAW;YACT,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { type TrafficalRNProviderConfig } from "./context.js";
3
+ export interface TrafficalRNProviderProps {
4
+ config: TrafficalRNProviderConfig;
5
+ children: ReactNode;
6
+ /** Component to render while the SDK is loading (before first resolve completes) */
7
+ loadingComponent?: ReactNode;
8
+ }
9
+ export declare function TrafficalRNProvider({ config, children, loadingComponent, }: TrafficalRNProviderProps): React.ReactElement;
10
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,KAAK,yBAAyB,EAE/B,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,yBAAyB,CAAC;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,QAAQ,EACR,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,KAAK,CAAC,YAAY,CAsI/C"}
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState, useCallback, useMemo, useRef, } from "react";
3
+ import { TrafficalContext, } from "./context.js";
4
+ import { TrafficalRNClient } from "./client.js";
5
+ export function TrafficalRNProvider({ config, children, loadingComponent, }) {
6
+ const [client, setClient] = useState(null);
7
+ const [ready, setReady] = useState(false);
8
+ const [error, setError] = useState(null);
9
+ const clientRef = useRef(null);
10
+ const getUnitKey = useCallback(() => {
11
+ if (config.unitKeyFn) {
12
+ return config.unitKeyFn();
13
+ }
14
+ return clientRef.current?.getStableId() ?? "";
15
+ }, [config.unitKeyFn]);
16
+ const getContext = useCallback(() => {
17
+ const unitKey = getUnitKey();
18
+ const additionalContext = config.contextFn?.() ?? {};
19
+ const deviceInfo = config.deviceInfoProvider?.getDeviceInfo();
20
+ return {
21
+ ...additionalContext,
22
+ ...(deviceInfo ?? {}),
23
+ userId: unitKey,
24
+ deviceId: unitKey,
25
+ anonymousId: unitKey,
26
+ };
27
+ }, [getUnitKey, config.contextFn, config.deviceInfoProvider]);
28
+ useEffect(() => {
29
+ let mounted = true;
30
+ const initClient = async () => {
31
+ try {
32
+ const newClient = new TrafficalRNClient({
33
+ orgId: config.orgId,
34
+ projectId: config.projectId,
35
+ env: config.env,
36
+ apiKey: config.apiKey,
37
+ baseUrl: config.baseUrl,
38
+ localConfig: config.localConfig,
39
+ refreshIntervalMs: config.refreshIntervalMs,
40
+ trackDecisions: config.trackDecisions,
41
+ decisionDeduplicationTtlMs: config.decisionDeduplicationTtlMs,
42
+ exposureSessionTtlMs: config.exposureSessionTtlMs,
43
+ eventBatchSize: config.eventBatchSize,
44
+ eventFlushIntervalMs: config.eventFlushIntervalMs,
45
+ plugins: config.plugins,
46
+ evaluationMode: config.evaluationMode ?? "server",
47
+ deviceInfoProvider: config.deviceInfoProvider,
48
+ cacheMaxAgeMs: config.cacheMaxAgeMs,
49
+ });
50
+ clientRef.current = newClient;
51
+ if (mounted) {
52
+ setClient(newClient);
53
+ if (config.localConfig) {
54
+ setReady(true);
55
+ }
56
+ }
57
+ await newClient.initialize();
58
+ if (mounted) {
59
+ setReady(true);
60
+ }
61
+ }
62
+ catch (err) {
63
+ if (mounted) {
64
+ setError(err instanceof Error ? err : new Error(String(err)));
65
+ setReady(true);
66
+ }
67
+ }
68
+ };
69
+ initClient();
70
+ return () => {
71
+ mounted = false;
72
+ clientRef.current?.destroy();
73
+ clientRef.current = null;
74
+ };
75
+ }, [
76
+ config.orgId,
77
+ config.projectId,
78
+ config.env,
79
+ config.apiKey,
80
+ config.baseUrl,
81
+ config.localConfig,
82
+ config.refreshIntervalMs,
83
+ config.trackDecisions,
84
+ config.decisionDeduplicationTtlMs,
85
+ config.exposureSessionTtlMs,
86
+ config.eventBatchSize,
87
+ config.eventFlushIntervalMs,
88
+ config.plugins,
89
+ config.evaluationMode,
90
+ config.deviceInfoProvider,
91
+ config.cacheMaxAgeMs,
92
+ ]);
93
+ const contextValue = useMemo(() => ({
94
+ client,
95
+ ready,
96
+ error,
97
+ getUnitKey,
98
+ getContext,
99
+ initialParams: config.initialParams,
100
+ localConfig: config.localConfig,
101
+ }), [
102
+ client,
103
+ ready,
104
+ error,
105
+ getUnitKey,
106
+ getContext,
107
+ config.initialParams,
108
+ config.localConfig,
109
+ ]);
110
+ if (!ready && loadingComponent) {
111
+ return (_jsx(TrafficalContext.Provider, { value: contextValue, children: loadingComponent }));
112
+ }
113
+ return (_jsx(TrafficalContext.Provider, { value: contextValue, children: children }));
114
+ }
115
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { StorageProvider } from "@traffical/js-client";
2
+ export interface PreloadedAsyncStorageProvider extends StorageProvider {
3
+ waitUntilReady(): Promise<void>;
4
+ }
5
+ export declare function createPreloadedAsyncStorage(): PreloadedAsyncStorageProvider;
6
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAS5D,MAAM,WAAW,6BAA8B,SAAQ,eAAe;IACpE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,wBAAgB,2BAA2B,IAAI,6BAA6B,CAsE3E"}
@@ -0,0 +1,69 @@
1
+ import AsyncStorage from "@react-native-async-storage/async-storage";
2
+ const STORAGE_PREFIX = "traffical:";
3
+ export function createPreloadedAsyncStorage() {
4
+ const inMemoryStore = {};
5
+ let isReady = false;
6
+ let readyPromise = null;
7
+ function waitUntilReady() {
8
+ if (isReady)
9
+ return Promise.resolve();
10
+ if (!readyPromise) {
11
+ readyPromise = prefetch().then(() => {
12
+ isReady = true;
13
+ });
14
+ }
15
+ return readyPromise;
16
+ }
17
+ async function prefetch() {
18
+ const keys = await AsyncStorage.getAllKeys();
19
+ const trafficalKeys = keys.filter((k) => k.startsWith(STORAGE_PREFIX));
20
+ if (trafficalKeys.length === 0)
21
+ return;
22
+ const entries = await AsyncStorage.multiGet(trafficalKeys);
23
+ for (const [key, value] of entries) {
24
+ if (value != null)
25
+ inMemoryStore[key] = value;
26
+ }
27
+ }
28
+ const provider = {
29
+ waitUntilReady,
30
+ get(key) {
31
+ const raw = inMemoryStore[STORAGE_PREFIX + key];
32
+ if (!raw)
33
+ return null;
34
+ try {
35
+ const stored = JSON.parse(raw);
36
+ if (stored.expiresAt && Date.now() > stored.expiresAt) {
37
+ provider.remove(key);
38
+ return null;
39
+ }
40
+ return stored.value;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ },
46
+ set(key, value, ttlMs) {
47
+ const stored = {
48
+ value,
49
+ ...(ttlMs && { expiresAt: Date.now() + ttlMs }),
50
+ };
51
+ const serialized = JSON.stringify(stored);
52
+ inMemoryStore[STORAGE_PREFIX + key] = serialized;
53
+ AsyncStorage.setItem(STORAGE_PREFIX + key, serialized).catch(() => { });
54
+ },
55
+ remove(key) {
56
+ delete inMemoryStore[STORAGE_PREFIX + key];
57
+ AsyncStorage.removeItem(STORAGE_PREFIX + key).catch(() => { });
58
+ },
59
+ clear() {
60
+ const keys = Object.keys(inMemoryStore).filter((k) => k.startsWith(STORAGE_PREFIX));
61
+ for (const k of keys) {
62
+ delete inMemoryStore[k];
63
+ }
64
+ AsyncStorage.multiRemove(keys).catch(() => { });
65
+ },
66
+ };
67
+ return provider;
68
+ }
69
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@traffical/react-native",
3
+ "version": "0.2.0",
4
+ "description": "Traffical SDK for React Native - server-evaluated feature flags, A/B testing, and adaptive optimization",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "test": "bun test",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@traffical/core": "0.3.0",
26
+ "@traffical/js-client": "0.4.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "@types/react": "^18.2.0",
31
+ "typescript": "^5.3.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^18.0.0 || ^19.0.0",
35
+ "react-native": "*",
36
+ "@react-native-async-storage/async-storage": "1.* || 2.*"
37
+ },
38
+ "keywords": [
39
+ "traffical",
40
+ "feature-flags",
41
+ "experimentation",
42
+ "a/b-testing",
43
+ "react-native"
44
+ ],
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/traffical/js-sdk"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }