@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 +404 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +89 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +10 -0
- package/dist/context.js.map +1 -0
- package/dist/device-info.d.ts +17 -0
- package/dist/device-info.d.ts.map +1 -0
- package/dist/device-info.js +2 -0
- package/dist/device-info.js.map +1 -0
- package/dist/hooks.d.ts +36 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +203 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +3 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +23 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +115 -0
- package/dist/provider.js.map +1 -0
- package/dist/storage.d.ts +6 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +69 -0
- package/dist/storage.js.map +1 -0
- package/package.json +53 -0
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
|
package/dist/client.d.ts
ADDED
|
@@ -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":"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"}
|
|
@@ -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"}
|
package/dist/context.js
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"device-info.js","sourceRoot":"","sources":["../src/device-info.ts"],"names":[],"mappings":""}
|
package/dist/hooks.d.ts
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/provider.js
ADDED
|
@@ -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"}
|
package/dist/storage.js
ADDED
|
@@ -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
|
+
}
|