@variantlab/react-native 0.1.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/dist/index.js ADDED
@@ -0,0 +1,487 @@
1
+ export { Variant, VariantErrorBoundary, VariantLabContext, VariantLabProvider, VariantValue, useExperiment, useRouteExperiments, useSetVariant, useVariant, useVariantLabEngine, useVariantValue } from '@variantlab/react';
2
+ import { Platform, Dimensions, NativeModules } from 'react-native';
3
+
4
+ // src/index.ts
5
+ function getAutoContext(options = {}) {
6
+ const out = {};
7
+ const platform = detectPlatform();
8
+ if (platform !== void 0) out.platform = platform;
9
+ const screenSize = detectScreenSize();
10
+ if (screenSize !== void 0) out.screenSize = screenSize;
11
+ const locale = detectLocale(options.localization);
12
+ if (locale !== void 0) out.locale = locale;
13
+ const appVersion = detectAppVersion(options.constants);
14
+ if (appVersion !== void 0) out.appVersion = appVersion;
15
+ return out;
16
+ }
17
+ function detectPlatform() {
18
+ try {
19
+ const os = Platform.OS;
20
+ if (os === "ios" || os === "android" || os === "web") return os;
21
+ return void 0;
22
+ } catch {
23
+ return void 0;
24
+ }
25
+ }
26
+ function bucketScreenWidth(width) {
27
+ if (width < 360) return "small";
28
+ if (width < 768) return "medium";
29
+ return "large";
30
+ }
31
+ function detectScreenSize() {
32
+ try {
33
+ const { width } = Dimensions.get("window");
34
+ if (typeof width !== "number" || Number.isNaN(width) || width <= 0) return void 0;
35
+ return bucketScreenWidth(width);
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ function detectLocale(localization) {
41
+ if (localization !== void 0 && localization !== null) {
42
+ try {
43
+ const locales = localization.getLocales();
44
+ const first = locales[0];
45
+ if (first !== void 0 && typeof first.languageTag === "string") {
46
+ return first.languageTag;
47
+ }
48
+ } catch {
49
+ }
50
+ }
51
+ try {
52
+ const settings = NativeModules.SettingsManager;
53
+ const apple = settings?.settings?.AppleLocale ?? settings?.settings?.AppleLanguages?.[0];
54
+ if (typeof apple === "string" && apple.length > 0) return normalizeTag(apple);
55
+ const i18n = NativeModules.I18nManager;
56
+ if (typeof i18n?.localeIdentifier === "string" && i18n.localeIdentifier.length > 0) {
57
+ return normalizeTag(i18n.localeIdentifier);
58
+ }
59
+ } catch {
60
+ return void 0;
61
+ }
62
+ return void 0;
63
+ }
64
+ function normalizeTag(raw) {
65
+ return raw.replace(/_/g, "-");
66
+ }
67
+ function detectAppVersion(constants) {
68
+ if (constants === void 0 || constants === null) return void 0;
69
+ try {
70
+ const version = constants.expoConfig?.version;
71
+ if (typeof version === "string" && version.length > 0) return version;
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ return void 0;
76
+ }
77
+
78
+ // src/deep-link/validate.ts
79
+ var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
80
+ var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
81
+ var MAX_OVERRIDES = 100;
82
+ var MAX_PAYLOAD_BYTES = 1024;
83
+ function validatePayload(input, now = Date.now()) {
84
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
85
+ return fail("not-an-object");
86
+ }
87
+ if (hasPollution(input)) return fail("prototype-pollution");
88
+ const obj = input;
89
+ if (obj.v !== 1) return fail("bad-version");
90
+ const overrides = obj.overrides;
91
+ if (overrides === null || typeof overrides !== "object" || Array.isArray(overrides)) {
92
+ return fail("missing-overrides");
93
+ }
94
+ const overrideEntries = Object.entries(overrides);
95
+ if (overrideEntries.length > MAX_OVERRIDES) return fail("overrides-too-large");
96
+ const safeOverrides = /* @__PURE__ */ Object.create(null);
97
+ for (const [key, value] of overrideEntries) {
98
+ if (POLLUTION_KEYS.has(key)) return fail("prototype-pollution");
99
+ if (!ID_RE.test(key)) return fail("bad-override-key");
100
+ if (typeof value !== "string") return fail("bad-override-value");
101
+ if (!ID_RE.test(value)) return fail("bad-override-value");
102
+ safeOverrides[key] = value;
103
+ }
104
+ let context;
105
+ if (obj.context !== void 0) {
106
+ if (obj.context === null || typeof obj.context !== "object" || Array.isArray(obj.context)) {
107
+ return fail("bad-context");
108
+ }
109
+ const sanitized = sanitizeContext(obj.context);
110
+ if (sanitized === null) return fail("bad-context");
111
+ context = sanitized;
112
+ }
113
+ let expires;
114
+ if (obj.expires !== void 0) {
115
+ if (typeof obj.expires !== "number" || !Number.isFinite(obj.expires)) {
116
+ return fail("bad-context");
117
+ }
118
+ if (obj.expires < now) return fail("expired");
119
+ expires = obj.expires;
120
+ }
121
+ const reStringified = JSON.stringify({
122
+ v: 1,
123
+ overrides: safeOverrides,
124
+ ...context !== void 0 ? { context } : {},
125
+ ...expires !== void 0 ? { expires } : {}
126
+ });
127
+ if (reStringified.length > MAX_PAYLOAD_BYTES) return fail("payload-too-large");
128
+ const payload = {
129
+ v: 1,
130
+ overrides: safeOverrides,
131
+ ...context !== void 0 ? { context } : {},
132
+ ...expires !== void 0 ? { expires } : {}
133
+ };
134
+ return { ok: true, payload };
135
+ }
136
+ function hasPollution(input) {
137
+ if (input === null || typeof input !== "object") return false;
138
+ if (Array.isArray(input)) {
139
+ for (const item of input) if (hasPollution(item)) return true;
140
+ return false;
141
+ }
142
+ for (const key of Object.keys(input)) {
143
+ if (POLLUTION_KEYS.has(key)) return true;
144
+ if (hasPollution(input[key])) return true;
145
+ }
146
+ return false;
147
+ }
148
+ function sanitizeContext(input) {
149
+ const out = /* @__PURE__ */ Object.create(null);
150
+ for (const key of Object.keys(input)) {
151
+ if (POLLUTION_KEYS.has(key)) return null;
152
+ const value = input[key];
153
+ if (value === void 0) continue;
154
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
155
+ out[key] = value;
156
+ continue;
157
+ }
158
+ if (key === "attributes" && typeof value === "object" && value !== null && !Array.isArray(value)) {
159
+ const attrs = /* @__PURE__ */ Object.create(null);
160
+ for (const aKey of Object.keys(value)) {
161
+ if (POLLUTION_KEYS.has(aKey)) return null;
162
+ const aVal = value[aKey];
163
+ if (typeof aVal === "string" || typeof aVal === "number" || typeof aVal === "boolean") {
164
+ attrs[aKey] = aVal;
165
+ } else {
166
+ return null;
167
+ }
168
+ }
169
+ out[key] = attrs;
170
+ continue;
171
+ }
172
+ return null;
173
+ }
174
+ return out;
175
+ }
176
+ function fail(reason) {
177
+ return { ok: false, reason };
178
+ }
179
+
180
+ // src/deep-link/encode.ts
181
+ var PREFIX_RAW = 0;
182
+ function encodeSharePayload(payload) {
183
+ const validated = validatePayload(payload);
184
+ if (!validated.ok) {
185
+ throw new Error(`Cannot encode invalid share payload: ${validated.reason}`);
186
+ }
187
+ const json = JSON.stringify(validated.payload);
188
+ const utf8 = new TextEncoder().encode(json);
189
+ const framed = new Uint8Array(utf8.length + 1);
190
+ framed[0] = PREFIX_RAW;
191
+ framed.set(utf8, 1);
192
+ return bytesToBase64Url(framed);
193
+ }
194
+ function decodeSharePayload(encoded, now) {
195
+ let bytes;
196
+ try {
197
+ bytes = base64UrlToBytes(encoded);
198
+ } catch {
199
+ return { ok: false, reason: "not-an-object" };
200
+ }
201
+ if (bytes.length === 0) return { ok: false, reason: "not-an-object" };
202
+ let jsonBytes;
203
+ if (bytes[0] === PREFIX_RAW) {
204
+ jsonBytes = bytes.subarray(1);
205
+ } else {
206
+ jsonBytes = bytes;
207
+ }
208
+ let parsed;
209
+ try {
210
+ const json = new TextDecoder("utf-8", { fatal: true }).decode(jsonBytes);
211
+ parsed = JSON.parse(json);
212
+ } catch {
213
+ return { ok: false, reason: "not-an-object" };
214
+ }
215
+ return validatePayload(parsed, now);
216
+ }
217
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
218
+ var DECODE_TABLE = (() => {
219
+ const table = new Int8Array(128);
220
+ table.fill(-1);
221
+ for (let i = 0; i < ALPHABET.length; i++) {
222
+ table[ALPHABET.charCodeAt(i)] = i;
223
+ }
224
+ table["+".charCodeAt(0)] = 62;
225
+ table["/".charCodeAt(0)] = 63;
226
+ return table;
227
+ })();
228
+ function bytesToBase64Url(bytes) {
229
+ let out = "";
230
+ let i = 0;
231
+ for (; i + 3 <= bytes.length; i += 3) {
232
+ const b0 = bytes[i];
233
+ const b1 = bytes[i + 1];
234
+ const b2 = bytes[i + 2];
235
+ out += ALPHABET[b0 >> 2];
236
+ out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
237
+ out += ALPHABET[(b1 & 15) << 2 | b2 >> 6];
238
+ out += ALPHABET[b2 & 63];
239
+ }
240
+ if (i < bytes.length) {
241
+ const b0 = bytes[i];
242
+ out += ALPHABET[b0 >> 2];
243
+ if (i + 1 < bytes.length) {
244
+ const b1 = bytes[i + 1];
245
+ out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
246
+ out += ALPHABET[(b1 & 15) << 2];
247
+ } else {
248
+ out += ALPHABET[(b0 & 3) << 4];
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ function base64UrlToBytes(input) {
254
+ let s = input;
255
+ while (s.length > 0 && s[s.length - 1] === "=") s = s.slice(0, -1);
256
+ if (s.length === 0) return new Uint8Array(0);
257
+ const remainder = s.length & 3;
258
+ if (remainder === 1) throw new Error("Invalid base64url length");
259
+ const fullGroups = s.length - remainder >> 2;
260
+ const outLen = fullGroups * 3 + (remainder === 0 ? 0 : remainder - 1);
261
+ const out = new Uint8Array(outLen);
262
+ let outIdx = 0;
263
+ let i = 0;
264
+ for (let g = 0; g < fullGroups; g++, i += 4) {
265
+ const c0 = decodeChar(s.charCodeAt(i));
266
+ const c1 = decodeChar(s.charCodeAt(i + 1));
267
+ const c2 = decodeChar(s.charCodeAt(i + 2));
268
+ const c3 = decodeChar(s.charCodeAt(i + 3));
269
+ out[outIdx++] = c0 << 2 | c1 >> 4;
270
+ out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
271
+ out[outIdx++] = (c2 & 3) << 6 | c3;
272
+ }
273
+ if (remainder >= 2) {
274
+ const c0 = decodeChar(s.charCodeAt(i));
275
+ const c1 = decodeChar(s.charCodeAt(i + 1));
276
+ out[outIdx++] = c0 << 2 | c1 >> 4;
277
+ if (remainder === 3) {
278
+ const c2 = decodeChar(s.charCodeAt(i + 2));
279
+ out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+ function decodeChar(code) {
285
+ if (code >= 128) throw new Error("Invalid base64url character");
286
+ const v = DECODE_TABLE[code] ?? -1;
287
+ if (v < 0) throw new Error("Invalid base64url character");
288
+ return v;
289
+ }
290
+
291
+ // src/deep-link/handler.ts
292
+ function registerDeepLinkHandler(engine, linking, options = {}) {
293
+ const param = options.param ?? "p";
294
+ const minInterval = options.minIntervalMs ?? 1e3;
295
+ let lastApplyAt = 0;
296
+ let disposed = false;
297
+ const handle = (url) => {
298
+ if (disposed || url === null || url.length === 0) return;
299
+ if (!schemeMatches(url, options.scheme, options.host)) {
300
+ options.onError?.("wrong-scheme");
301
+ return;
302
+ }
303
+ const encoded = extractQueryParam(url, param);
304
+ if (encoded === null) {
305
+ options.onError?.("no-payload");
306
+ return;
307
+ }
308
+ const result = decodeSharePayload(encoded);
309
+ if (!result.ok) {
310
+ options.onError?.(result.reason);
311
+ return;
312
+ }
313
+ const now = Date.now();
314
+ if (now - lastApplyAt < minInterval) {
315
+ return;
316
+ }
317
+ lastApplyAt = now;
318
+ applyPayload(engine, result.payload);
319
+ options.onApply?.(result.payload);
320
+ };
321
+ const subscription = linking.addEventListener("url", (event) => handle(event.url));
322
+ void linking.getInitialURL().then(handle).catch(() => void 0);
323
+ return () => {
324
+ if (disposed) return;
325
+ disposed = true;
326
+ subscription.remove();
327
+ };
328
+ }
329
+ function applyPayload(engine, payload) {
330
+ if (payload.context !== void 0) {
331
+ engine.updateContext(payload.context);
332
+ }
333
+ for (const id of Object.keys(payload.overrides)) {
334
+ const variantId = payload.overrides[id];
335
+ if (variantId === void 0) continue;
336
+ engine.setVariant(id, variantId, "deeplink");
337
+ }
338
+ }
339
+ function schemeMatches(url, scheme, host) {
340
+ const colon = url.indexOf("://");
341
+ if (colon < 0) return false;
342
+ const urlScheme = url.slice(0, colon);
343
+ if (scheme !== void 0 && urlScheme !== scheme) return false;
344
+ if (host !== void 0) {
345
+ const rest = url.slice(colon + 3);
346
+ const slash = rest.indexOf("/");
347
+ const q = rest.indexOf("?");
348
+ let endIdx = rest.length;
349
+ if (slash >= 0) endIdx = slash;
350
+ if (q >= 0 && q < endIdx) endIdx = q;
351
+ const urlHost = rest.slice(0, endIdx);
352
+ if (urlHost !== host) return false;
353
+ }
354
+ return true;
355
+ }
356
+ function extractQueryParam(url, name) {
357
+ const q = url.indexOf("?");
358
+ if (q < 0) return null;
359
+ let query = url.slice(q + 1);
360
+ const hash = query.indexOf("#");
361
+ if (hash >= 0) query = query.slice(0, hash);
362
+ for (const part of query.split("&")) {
363
+ const eq = part.indexOf("=");
364
+ if (eq < 0) continue;
365
+ const key = decodeURIComponent(part.slice(0, eq));
366
+ if (key !== name) continue;
367
+ try {
368
+ return decodeURIComponent(part.slice(eq + 1));
369
+ } catch {
370
+ return null;
371
+ }
372
+ }
373
+ return null;
374
+ }
375
+
376
+ // src/storage/async-storage.ts
377
+ function createAsyncStorageAdapter(asyncStorage) {
378
+ return {
379
+ async getItem(key) {
380
+ return asyncStorage.getItem(key);
381
+ },
382
+ async setItem(key, value) {
383
+ await asyncStorage.setItem(key, value);
384
+ },
385
+ async removeItem(key) {
386
+ await asyncStorage.removeItem(key);
387
+ },
388
+ async keys() {
389
+ const all = await asyncStorage.getAllKeys();
390
+ return Array.from(all);
391
+ },
392
+ async clear() {
393
+ if (typeof asyncStorage.clear === "function") {
394
+ await asyncStorage.clear();
395
+ }
396
+ }
397
+ };
398
+ }
399
+
400
+ // src/storage/memory.ts
401
+ function createMemoryStorage() {
402
+ const map = /* @__PURE__ */ new Map();
403
+ return {
404
+ getItem(key) {
405
+ return map.get(key) ?? null;
406
+ },
407
+ setItem(key, value) {
408
+ map.set(key, value);
409
+ },
410
+ removeItem(key) {
411
+ map.delete(key);
412
+ },
413
+ keys() {
414
+ return Array.from(map.keys());
415
+ },
416
+ clear() {
417
+ map.clear();
418
+ }
419
+ };
420
+ }
421
+
422
+ // src/storage/mmkv.ts
423
+ function createMMKVStorageAdapter(mmkv) {
424
+ return {
425
+ getItem(key) {
426
+ const value = mmkv.getString(key);
427
+ return value === void 0 ? null : value;
428
+ },
429
+ setItem(key, value) {
430
+ mmkv.set(key, value);
431
+ },
432
+ removeItem(key) {
433
+ mmkv.delete(key);
434
+ },
435
+ keys() {
436
+ return mmkv.getAllKeys();
437
+ },
438
+ clear() {
439
+ if (typeof mmkv.clearAll === "function") {
440
+ mmkv.clearAll();
441
+ }
442
+ }
443
+ };
444
+ }
445
+
446
+ // src/storage/secure-store.ts
447
+ function createSecureStoreAdapter(secureStore) {
448
+ return {
449
+ async getItem(key) {
450
+ return secureStore.getItemAsync(safeKey(key));
451
+ },
452
+ async setItem(key, value) {
453
+ await secureStore.setItemAsync(safeKey(key), value);
454
+ },
455
+ async removeItem(key) {
456
+ await secureStore.deleteItemAsync(safeKey(key));
457
+ }
458
+ };
459
+ }
460
+ function safeKey(key) {
461
+ let out = "";
462
+ for (let i = 0; i < key.length; i++) {
463
+ const c = key.charCodeAt(i);
464
+ const isAlnum = c >= 48 && c <= 57 || // 0-9
465
+ c >= 65 && c <= 90 || // A-Z
466
+ c >= 97 && c <= 122;
467
+ if (isAlnum || c === 46 || c === 95 || c === 45) {
468
+ out += key[i];
469
+ } else {
470
+ out += `_${c.toString(16)}_`;
471
+ }
472
+ }
473
+ return out;
474
+ }
475
+
476
+ // src/storage/types.ts
477
+ var STORAGE_KEY_PREFIX = "variantlab:";
478
+ function buildKey(name) {
479
+ return STORAGE_KEY_PREFIX + name;
480
+ }
481
+
482
+ // src/index.ts
483
+ var VERSION = "0.0.0";
484
+
485
+ export { STORAGE_KEY_PREFIX, VERSION, applyPayload, base64UrlToBytes, bucketScreenWidth, buildKey, bytesToBase64Url, createAsyncStorageAdapter, createMMKVStorageAdapter, createMemoryStorage, createSecureStoreAdapter, decodeSharePayload, encodeSharePayload, getAutoContext, registerDeepLinkHandler, validatePayload };
486
+ //# sourceMappingURL=index.js.map
487
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/auto-context.ts","../src/deep-link/validate.ts","../src/deep-link/encode.ts","../src/deep-link/handler.ts","../src/storage/async-storage.ts","../src/storage/memory.ts","../src/storage/mmkv.ts","../src/storage/secure-store.ts","../src/storage/types.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `getAutoContext()` — best-effort detection of the runtime context\n * for variantlab targeting on React Native.\n *\n * The goal is to fill in `platform`, `screenSize`, and `locale` from\n * native modules so that targeting like `{ platform: [\"ios\"] }` and\n * `{ screenSize: [\"small\"] }` Just Works without the user having to\n * import any RN modules themselves. `appVersion` is filled in when\n * `expo-constants` is installed (it almost always is on Expo apps).\n *\n * Implementation rules:\n *\n * - Every native module is imported through a guarded `try / require`\n * so a missing optional peer never crashes. Failing to read any\n * individual field returns `undefined` for that field — never throws.\n * - The function is **synchronous** because variantlab's resolution\n * hot path is synchronous; we cannot wait on a Promise here.\n * - Locale detection prefers `expo-localization` (the only RN-supported\n * way that works in production). Falls back to `NativeModules` for\n * bare RN apps and finally to `undefined`.\n *\n * The screen-size buckets follow the same thresholds as the rest of\n * variantlab (see `targeting-dsl.md`):\n *\n * - small → < 360 pt wide (iPhone SE, older Androids)\n * - medium → 360–767 pt wide (most phones)\n * - large → ≥ 768 pt wide (tablets, foldables in unfolded mode)\n */\n\nimport type { VariantContext } from \"@variantlab/core\";\nimport { Dimensions, NativeModules, Platform } from \"react-native\";\n\nexport interface AutoContextOptions {\n /** Inject `expo-constants` for `appVersion`. Optional. */\n readonly constants?: { expoConfig?: { version?: string } | null } | null;\n /** Inject `expo-localization` for `locale`. Optional. */\n readonly localization?: { getLocales: () => Array<{ languageTag: string }> } | null;\n}\n\nexport function getAutoContext(options: AutoContextOptions = {}): VariantContext {\n // `VariantContext`'s fields are `readonly`, so we build the object\n // immutably rather than mutating in place. The intermediate\n // `Partial<Mutable>` lets us omit unset fields without `undefined`\n // values leaking through under `exactOptionalPropertyTypes`.\n const out: {\n -readonly [K in keyof VariantContext]?: VariantContext[K];\n } = {};\n const platform = detectPlatform();\n if (platform !== undefined) out.platform = platform;\n const screenSize = detectScreenSize();\n if (screenSize !== undefined) out.screenSize = screenSize;\n const locale = detectLocale(options.localization);\n if (locale !== undefined) out.locale = locale;\n const appVersion = detectAppVersion(options.constants);\n if (appVersion !== undefined) out.appVersion = appVersion;\n return out;\n}\n\nfunction detectPlatform(): VariantContext[\"platform\"] | undefined {\n try {\n const os = Platform.OS;\n if (os === \"ios\" || os === \"android\" || os === \"web\") return os;\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nexport type ScreenSizeBucket = NonNullable<VariantContext[\"screenSize\"]>;\n\nexport function bucketScreenWidth(width: number): ScreenSizeBucket {\n if (width < 360) return \"small\";\n if (width < 768) return \"medium\";\n return \"large\";\n}\n\nfunction detectScreenSize(): ScreenSizeBucket | undefined {\n try {\n const { width } = Dimensions.get(\"window\");\n if (typeof width !== \"number\" || Number.isNaN(width) || width <= 0) return undefined;\n return bucketScreenWidth(width);\n } catch {\n return undefined;\n }\n}\n\nfunction detectLocale(localization: AutoContextOptions[\"localization\"]): string | undefined {\n // Prefer the dependency-injected expo-localization module.\n if (localization !== undefined && localization !== null) {\n try {\n const locales = localization.getLocales();\n const first = locales[0];\n if (first !== undefined && typeof first.languageTag === \"string\") {\n return first.languageTag;\n }\n } catch {\n // fall through to native modules\n }\n }\n\n // Bare RN: read from NativeModules. The shape is documented but\n // varies between iOS, Android, and the New Architecture, so we\n // pick the first thing that looks like a BCP-47 tag.\n try {\n const settings = NativeModules.SettingsManager as\n | { settings?: { AppleLocale?: string; AppleLanguages?: string[] } }\n | undefined;\n const apple = settings?.settings?.AppleLocale ?? settings?.settings?.AppleLanguages?.[0];\n if (typeof apple === \"string\" && apple.length > 0) return normalizeTag(apple);\n\n // Android's turbo module exposes `localeIdentifier` on the bridged\n // `I18nManager` native module. We look it up through `NativeModules`\n // rather than the JS-side `I18nManager` export because the type\n // surface of the latter intentionally omits the field.\n const i18n = NativeModules.I18nManager as { localeIdentifier?: string } | undefined;\n if (typeof i18n?.localeIdentifier === \"string\" && i18n.localeIdentifier.length > 0) {\n return normalizeTag(i18n.localeIdentifier);\n }\n } catch {\n return undefined;\n }\n return undefined;\n}\n\nfunction normalizeTag(raw: string): string {\n // Convert iOS `en_US` style to BCP-47 `en-US`.\n return raw.replace(/_/g, \"-\");\n}\n\nfunction detectAppVersion(constants: AutoContextOptions[\"constants\"]): string | undefined {\n if (constants === undefined || constants === null) return undefined;\n try {\n const version = constants.expoConfig?.version;\n if (typeof version === \"string\" && version.length > 0) return version;\n } catch {\n return undefined;\n }\n return undefined;\n}\n","/**\n * Validate a candidate share payload before applying it.\n *\n * Share payloads come from untrusted input — a deep link, a QR code,\n * a clipboard paste — so we treat them as adversarial. The validator\n * walks the structure with `Object.create(null)` semantics in mind:\n *\n * - Reject prototype-pollution keys (`__proto__`, `constructor`,\n * `prototype`) anywhere in the tree.\n * - Enforce strict id regexes on every override key/value so a\n * malformed payload cannot smuggle non-printable bytes into a\n * `setVariant` call.\n * - Cap the override count and total decoded size to bound memory.\n * - Honor an optional `expires` field so QR codes shared earlier\n * in the day cannot be replayed forever.\n *\n * The validator returns a `ValidationResult` discriminated union\n * instead of throwing because callers (deep-link handler, QR scanner)\n * almost always want to silently surface a failure to the UI rather\n * than treat it as a runtime error.\n */\nimport type { SharePayload, ValidationFailure, ValidationResult } from \"./types.js\";\n\n/** Match the same id regex as `experiments.json`. */\nconst ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nconst MAX_OVERRIDES = 100;\nconst MAX_PAYLOAD_BYTES = 1024;\n\nexport function validatePayload(input: unknown, now: number = Date.now()): ValidationResult {\n if (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n return fail(\"not-an-object\");\n }\n\n if (hasPollution(input)) return fail(\"prototype-pollution\");\n\n const obj = input as Record<string, unknown>;\n\n if (obj.v !== 1) return fail(\"bad-version\");\n\n const overrides = obj.overrides;\n if (overrides === null || typeof overrides !== \"object\" || Array.isArray(overrides)) {\n return fail(\"missing-overrides\");\n }\n\n const overrideEntries = Object.entries(overrides as Record<string, unknown>);\n if (overrideEntries.length > MAX_OVERRIDES) return fail(\"overrides-too-large\");\n\n const safeOverrides: Record<string, string> = Object.create(null) as Record<string, string>;\n for (const [key, value] of overrideEntries) {\n if (POLLUTION_KEYS.has(key)) return fail(\"prototype-pollution\");\n if (!ID_RE.test(key)) return fail(\"bad-override-key\");\n if (typeof value !== \"string\") return fail(\"bad-override-value\");\n if (!ID_RE.test(value)) return fail(\"bad-override-value\");\n safeOverrides[key] = value;\n }\n\n let context: SharePayload[\"context\"];\n if (obj.context !== undefined) {\n if (obj.context === null || typeof obj.context !== \"object\" || Array.isArray(obj.context)) {\n return fail(\"bad-context\");\n }\n const sanitized = sanitizeContext(obj.context as Record<string, unknown>);\n if (sanitized === null) return fail(\"bad-context\");\n context = sanitized;\n }\n\n let expires: number | undefined;\n if (obj.expires !== undefined) {\n if (typeof obj.expires !== \"number\" || !Number.isFinite(obj.expires)) {\n return fail(\"bad-context\");\n }\n if (obj.expires < now) return fail(\"expired\");\n expires = obj.expires;\n }\n\n // Re-stringify with the sanitized fields and check the encoded size.\n const reStringified = JSON.stringify({\n v: 1,\n overrides: safeOverrides,\n ...(context !== undefined ? { context } : {}),\n ...(expires !== undefined ? { expires } : {}),\n });\n if (reStringified.length > MAX_PAYLOAD_BYTES) return fail(\"payload-too-large\");\n\n const payload: SharePayload = {\n v: 1,\n overrides: safeOverrides,\n ...(context !== undefined ? { context } : {}),\n ...(expires !== undefined ? { expires } : {}),\n };\n return { ok: true, payload };\n}\n\nfunction hasPollution(input: unknown): boolean {\n if (input === null || typeof input !== \"object\") return false;\n if (Array.isArray(input)) {\n for (const item of input) if (hasPollution(item)) return true;\n return false;\n }\n for (const key of Object.keys(input as object)) {\n if (POLLUTION_KEYS.has(key)) return true;\n if (hasPollution((input as Record<string, unknown>)[key])) return true;\n }\n return false;\n}\n\nfunction sanitizeContext(input: Record<string, unknown>): SharePayload[\"context\"] | null {\n const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const key of Object.keys(input)) {\n if (POLLUTION_KEYS.has(key)) return null;\n const value = input[key];\n if (value === undefined) continue;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n out[key] = value;\n continue;\n }\n if (\n key === \"attributes\" &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const attrs: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const aKey of Object.keys(value as object)) {\n if (POLLUTION_KEYS.has(aKey)) return null;\n const aVal = (value as Record<string, unknown>)[aKey];\n if (typeof aVal === \"string\" || typeof aVal === \"number\" || typeof aVal === \"boolean\") {\n attrs[aKey] = aVal;\n } else {\n return null;\n }\n }\n out[key] = attrs;\n continue;\n }\n return null;\n }\n return out as SharePayload[\"context\"];\n}\n\nfunction fail(reason: ValidationFailure): ValidationResult {\n return { ok: false, reason };\n}\n","/**\n * Encode and decode share payloads for deep links and QR codes.\n *\n * The wire format is documented in `docs/features/qr-sharing.md`:\n *\n * 1. Serialise the payload as compact JSON.\n * 2. Optionally gzip-like compress (deferred — we ship plain base64\n * first; the wire format is forward-compatible because the prefix\n * byte tells the decoder which mode was used).\n * 3. Base64url-encode the bytes (RFC 4648 §5: `+` → `-`, `/` → `_`,\n * no `=` padding).\n *\n * We implement base64url by hand because we cannot rely on\n * `globalThis.btoa` / `Buffer` being present on every RN runtime,\n * and pulling in a base64 polyfill would burst the bundle budget.\n * The encoder/decoder operates on UTF-8 byte arrays via `TextEncoder`\n * / `TextDecoder`, both of which ship in Hermes.\n *\n * The format prefix byte is `0` for \"raw JSON\" and reserved for future\n * compression schemes (`1` = deflate-raw, `2` = brotli, etc.). On\n * decode we tolerate a missing prefix to keep older clients working.\n */\nimport type { SharePayload, ValidationResult } from \"./types.js\";\nimport { validatePayload } from \"./validate.js\";\n\nconst PREFIX_RAW = 0;\n\n/**\n * Encode a payload to its on-the-wire base64url string. Throws on\n * the rare case where validation fails on a payload constructed by\n * the caller themselves — that's a developer error worth surfacing.\n */\nexport function encodeSharePayload(payload: SharePayload): string {\n const validated = validatePayload(payload);\n if (!validated.ok) {\n throw new Error(`Cannot encode invalid share payload: ${validated.reason}`);\n }\n const json = JSON.stringify(validated.payload);\n const utf8 = new TextEncoder().encode(json);\n const framed = new Uint8Array(utf8.length + 1);\n framed[0] = PREFIX_RAW;\n framed.set(utf8, 1);\n return bytesToBase64Url(framed);\n}\n\n/**\n * Decode a base64url string back into a validated payload. Returns\n * a `ValidationResult` so callers can branch on `ok` rather than\n * wrap the call in try/catch.\n */\nexport function decodeSharePayload(encoded: string, now?: number): ValidationResult {\n let bytes: Uint8Array;\n try {\n bytes = base64UrlToBytes(encoded);\n } catch {\n return { ok: false, reason: \"not-an-object\" };\n }\n if (bytes.length === 0) return { ok: false, reason: \"not-an-object\" };\n\n // Detect prefix; tolerate missing prefix for forward compatibility.\n let jsonBytes: Uint8Array;\n if (bytes[0] === PREFIX_RAW) {\n jsonBytes = bytes.subarray(1);\n } else {\n jsonBytes = bytes;\n }\n\n let parsed: unknown;\n try {\n const json = new TextDecoder(\"utf-8\", { fatal: true }).decode(jsonBytes);\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, reason: \"not-an-object\" };\n }\n\n return validatePayload(parsed, now);\n}\n\n// ---------- base64url ------------------------------------------------------\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\nconst DECODE_TABLE: Int8Array = (() => {\n const table = new Int8Array(128);\n table.fill(-1);\n for (let i = 0; i < ALPHABET.length; i++) {\n table[ALPHABET.charCodeAt(i)] = i;\n }\n // Accept the standard b64 variants too so links pasted from a\n // non-url-safe encoder still decode.\n table[\"+\".charCodeAt(0)] = 62;\n table[\"/\".charCodeAt(0)] = 63;\n return table;\n})();\n\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let out = \"\";\n let i = 0;\n for (; i + 3 <= bytes.length; i += 3) {\n const b0 = bytes[i] as number;\n const b1 = bytes[i + 1] as number;\n const b2 = bytes[i + 2] as number;\n out += ALPHABET[b0 >> 2];\n out += ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];\n out += ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)];\n out += ALPHABET[b2 & 0x3f];\n }\n if (i < bytes.length) {\n const b0 = bytes[i] as number;\n out += ALPHABET[b0 >> 2];\n if (i + 1 < bytes.length) {\n const b1 = bytes[i + 1] as number;\n out += ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];\n out += ALPHABET[(b1 & 0x0f) << 2];\n } else {\n out += ALPHABET[(b0 & 0x03) << 4];\n }\n }\n return out;\n}\n\nexport function base64UrlToBytes(input: string): Uint8Array {\n // Strip optional `=` padding from standard base64.\n let s = input;\n while (s.length > 0 && s[s.length - 1] === \"=\") s = s.slice(0, -1);\n if (s.length === 0) return new Uint8Array(0);\n\n const remainder = s.length & 3;\n if (remainder === 1) throw new Error(\"Invalid base64url length\");\n\n const fullGroups = (s.length - remainder) >> 2;\n const outLen = fullGroups * 3 + (remainder === 0 ? 0 : remainder - 1);\n const out = new Uint8Array(outLen);\n\n let outIdx = 0;\n let i = 0;\n for (let g = 0; g < fullGroups; g++, i += 4) {\n const c0 = decodeChar(s.charCodeAt(i));\n const c1 = decodeChar(s.charCodeAt(i + 1));\n const c2 = decodeChar(s.charCodeAt(i + 2));\n const c3 = decodeChar(s.charCodeAt(i + 3));\n out[outIdx++] = (c0 << 2) | (c1 >> 4);\n out[outIdx++] = ((c1 & 0x0f) << 4) | (c2 >> 2);\n out[outIdx++] = ((c2 & 0x03) << 6) | c3;\n }\n if (remainder >= 2) {\n const c0 = decodeChar(s.charCodeAt(i));\n const c1 = decodeChar(s.charCodeAt(i + 1));\n out[outIdx++] = (c0 << 2) | (c1 >> 4);\n if (remainder === 3) {\n const c2 = decodeChar(s.charCodeAt(i + 2));\n out[outIdx++] = ((c1 & 0x0f) << 4) | (c2 >> 2);\n }\n }\n return out;\n}\n\nfunction decodeChar(code: number): number {\n if (code >= 128) throw new Error(\"Invalid base64url character\");\n const v = DECODE_TABLE[code] ?? -1;\n if (v < 0) throw new Error(\"Invalid base64url character\");\n return v;\n}\n","/**\n * Deep link handler for variantlab share payloads.\n *\n * Wires React Native's `Linking` module to the engine: when the OS\n * delivers a URL with our share scheme, we decode the `?p=...` query\n * parameter, validate it, and apply the overrides.\n *\n * Design notes:\n *\n * - The handler accepts the engine *and* the `Linking` module from\n * the caller. In a real app the caller passes `Linking` from\n * `react-native`; in tests the caller passes a mock that records\n * `addEventListener` and `getInitialURL` calls. This keeps the\n * module free of any real RN imports for the test runner.\n * - URL parsing is hand-rolled because RN's `URL` polyfill is\n * unreliable across versions and we only need to extract one\n * query param.\n * - Each apply emits via `engine.setVariant(id, variantId, \"deeplink\")`\n * so the source field on `variantChanged` events lets debug overlays\n * and telemetry distinguish deep-link writes from manual ones.\n * - We rate-limit deep-link applies to one per second by default\n * (`qr-sharing.md` §Rate limiting). Callers can override the\n * interval but cannot disable it entirely.\n */\nimport type { VariantEngine } from \"@variantlab/core\";\nimport { decodeSharePayload } from \"./encode.js\";\nimport type { SharePayload, ValidationFailure } from \"./types.js\";\n\n/** The minimal Linking surface we depend on. */\nexport interface LinkingLike {\n addEventListener(type: \"url\", handler: (event: { url: string }) => void): { remove: () => void };\n getInitialURL(): Promise<string | null>;\n}\n\nexport interface RegisterDeepLinkOptions {\n /** App URL scheme, e.g. `\"drishtikon\"`. Required to filter foreign URLs. */\n readonly scheme?: string;\n /** Optional host portion, e.g. `\"variantlab\"`. Defaults to any host. */\n readonly host?: string;\n /** Query parameter holding the encoded payload. Default: `\"p\"`. */\n readonly param?: string;\n /** Called after each successful apply with the decoded payload. */\n readonly onApply?: (payload: SharePayload) => void;\n /** Called whenever a candidate link fails validation. */\n readonly onError?: (reason: ValidationFailure | \"wrong-scheme\" | \"no-payload\") => void;\n /** Minimum ms between successful applies. Default: 1000. */\n readonly minIntervalMs?: number;\n}\n\n/**\n * Register the deep-link handler. Returns an unsubscribe function\n * that removes the listener and stops handling pending initial URLs.\n *\n * The handler also fires once for the URL that launched the app\n * (via `getInitialURL`) so cold-launches via QR scan still apply\n * the override on first render.\n */\nexport function registerDeepLinkHandler(\n engine: VariantEngine,\n linking: LinkingLike,\n options: RegisterDeepLinkOptions = {},\n): () => void {\n const param = options.param ?? \"p\";\n const minInterval = options.minIntervalMs ?? 1000;\n let lastApplyAt = 0;\n let disposed = false;\n\n const handle = (url: string | null): void => {\n if (disposed || url === null || url.length === 0) return;\n if (!schemeMatches(url, options.scheme, options.host)) {\n options.onError?.(\"wrong-scheme\");\n return;\n }\n const encoded = extractQueryParam(url, param);\n if (encoded === null) {\n options.onError?.(\"no-payload\");\n return;\n }\n const result = decodeSharePayload(encoded);\n if (!result.ok) {\n options.onError?.(result.reason);\n return;\n }\n const now = Date.now();\n if (now - lastApplyAt < minInterval) {\n // Silently drop — rate limiting is a security feature, not an error.\n return;\n }\n lastApplyAt = now;\n applyPayload(engine, result.payload);\n options.onApply?.(result.payload);\n };\n\n const subscription = linking.addEventListener(\"url\", (event) => handle(event.url));\n // Fire-and-forget the initial URL — RN promises are not awaited at\n // module load time.\n void linking\n .getInitialURL()\n .then(handle)\n .catch(() => undefined);\n\n return () => {\n if (disposed) return;\n disposed = true;\n subscription.remove();\n };\n}\n\n/**\n * Apply a previously-decoded payload to the engine. Exposed publicly\n * so callers that build their own URL-handling pipeline (Next.js\n * middleware, web router) can reuse the same logic.\n */\nexport function applyPayload(engine: VariantEngine, payload: SharePayload): void {\n if (payload.context !== undefined) {\n engine.updateContext(payload.context);\n }\n for (const id of Object.keys(payload.overrides)) {\n const variantId = payload.overrides[id];\n if (variantId === undefined) continue;\n engine.setVariant(id, variantId, \"deeplink\");\n }\n}\n\nfunction schemeMatches(url: string, scheme?: string, host?: string): boolean {\n // Format: `<scheme>://<host>/<path>?<query>`\n // Hand-rolled because RN's URL polyfill is unreliable.\n const colon = url.indexOf(\"://\");\n if (colon < 0) return false;\n const urlScheme = url.slice(0, colon);\n if (scheme !== undefined && urlScheme !== scheme) return false;\n if (host !== undefined) {\n const rest = url.slice(colon + 3);\n const slash = rest.indexOf(\"/\");\n const q = rest.indexOf(\"?\");\n let endIdx = rest.length;\n if (slash >= 0) endIdx = slash;\n if (q >= 0 && q < endIdx) endIdx = q;\n const urlHost = rest.slice(0, endIdx);\n if (urlHost !== host) return false;\n }\n return true;\n}\n\nfunction extractQueryParam(url: string, name: string): string | null {\n const q = url.indexOf(\"?\");\n if (q < 0) return null;\n // Strip optional `#fragment`.\n let query = url.slice(q + 1);\n const hash = query.indexOf(\"#\");\n if (hash >= 0) query = query.slice(0, hash);\n for (const part of query.split(\"&\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n const key = decodeURIComponent(part.slice(0, eq));\n if (key !== name) continue;\n try {\n return decodeURIComponent(part.slice(eq + 1));\n } catch {\n return null;\n }\n }\n return null;\n}\n","/**\n * `Storage` adapter backed by `@react-native-async-storage/async-storage`.\n *\n * AsyncStorage is the de-facto standard KV store on React Native and\n * the only one we recommend by default — it's bundled with every\n * Expo project, ships in the New Architecture, and works on iOS,\n * Android, web, and tvOS. It is, however, async on every method, so\n * the returned `Storage` is a fully async implementation.\n *\n * The adapter is constructed lazily: callers pass in their AsyncStorage\n * module rather than us importing it. This keeps `@variantlab/react-native`\n * itself free of any RN package imports at module load — the user's\n * bundler resolves the peer, we never see it. The factory accepts the\n * module directly so the surface is dependency-injected and trivially\n * testable.\n */\nimport type { Storage } from \"./types.js\";\n\n/** Subset of `AsyncStorageStatic` we actually use. */\nexport interface AsyncStorageLike {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n getAllKeys(): Promise<readonly string[]>;\n clear?(): Promise<void>;\n}\n\nexport function createAsyncStorageAdapter(asyncStorage: AsyncStorageLike): Storage {\n return {\n async getItem(key) {\n return asyncStorage.getItem(key);\n },\n async setItem(key, value) {\n await asyncStorage.setItem(key, value);\n },\n async removeItem(key) {\n await asyncStorage.removeItem(key);\n },\n async keys() {\n const all = await asyncStorage.getAllKeys();\n return Array.from(all);\n },\n async clear() {\n if (typeof asyncStorage.clear === \"function\") {\n await asyncStorage.clear();\n }\n },\n };\n}\n","/**\n * In-memory `Storage` implementation. Useful for tests, SSR fallbacks,\n * and any code path where the host platform's native KV is unavailable.\n *\n * The implementation is intentionally trivial: a `Map` keyed on the\n * fully-qualified key, no namespacing logic, no async wrappers. Real\n * adapters wrap a native module and may be async; this one is sync to\n * keep tests deterministic.\n */\nimport type { Storage } from \"./types.js\";\n\nexport function createMemoryStorage(): Storage {\n const map = new Map<string, string>();\n return {\n getItem(key) {\n return map.get(key) ?? null;\n },\n setItem(key, value) {\n map.set(key, value);\n },\n removeItem(key) {\n map.delete(key);\n },\n keys() {\n return Array.from(map.keys());\n },\n clear() {\n map.clear();\n },\n };\n}\n","/**\n * `Storage` adapter backed by `react-native-mmkv`.\n *\n * MMKV is a fully synchronous KV store implemented in C++ on top of\n * mmap. It's the fastest option on RN by a wide margin and is\n * particularly nice for variantlab because variant resolution is\n * synchronous on the hot path — using MMKV means storage round trips\n * never block on a JS-bridge call.\n *\n * Like `async-storage`, the underlying instance is dependency-injected\n * rather than imported here. The caller constructs an `MMKV` instance\n * (typically with their own `id` namespace and optional encryption key)\n * and hands it in. We only require a tiny subset of the surface so\n * users can also pass any compatible mock in tests.\n */\nimport type { Storage } from \"./types.js\";\n\n/** Subset of the real MMKV instance we depend on. */\nexport interface MMKVLike {\n set(key: string, value: string): void;\n getString(key: string): string | undefined;\n contains(key: string): boolean;\n delete(key: string): void;\n getAllKeys(): string[];\n clearAll?(): void;\n}\n\nexport function createMMKVStorageAdapter(mmkv: MMKVLike): Storage {\n return {\n getItem(key) {\n const value = mmkv.getString(key);\n return value === undefined ? null : value;\n },\n setItem(key, value) {\n mmkv.set(key, value);\n },\n removeItem(key) {\n mmkv.delete(key);\n },\n keys() {\n return mmkv.getAllKeys();\n },\n clear() {\n if (typeof mmkv.clearAll === \"function\") {\n mmkv.clearAll();\n }\n },\n };\n}\n","/**\n * `Storage` adapter backed by `expo-secure-store`.\n *\n * SecureStore writes through to Keychain (iOS) and the EncryptedSharedPreferences\n * /Keystore pair (Android). It's appropriate when the override payload\n * is itself sensitive — e.g. an experiment that toggles an internal\n * staging endpoint, an enterprise feature flag, or a beta-channel key.\n *\n * SecureStore has two notable constraints:\n *\n * 1. **No bulk key listing.** The native API does not expose a\n * \"list every key\" operation, so `keys()` is intentionally absent.\n * Callers that need bulk operations should layer an index in a\n * separate AsyncStorage entry, or compose with `createMemoryStorage`.\n * 2. **Slow on Android.** Each read/write hits the native module.\n * Don't use this on hot paths where latency matters.\n *\n * As with the other adapters, the upstream module is injected so this\n * file imports nothing from `expo-secure-store` itself.\n */\nimport type { Storage } from \"./types.js\";\n\n/** Subset of the real `expo-secure-store` namespace we use. */\nexport interface SecureStoreLike {\n getItemAsync(key: string): Promise<string | null>;\n setItemAsync(key: string, value: string): Promise<void>;\n deleteItemAsync(key: string): Promise<void>;\n}\n\nexport function createSecureStoreAdapter(secureStore: SecureStoreLike): Storage {\n return {\n async getItem(key) {\n // SecureStore keys must be alphanumeric / `_` / `-` / `.` only;\n // we transparently base64-encode anything else so callers can\n // use the colon-prefixed `variantlab:` namespace.\n return secureStore.getItemAsync(safeKey(key));\n },\n async setItem(key, value) {\n await secureStore.setItemAsync(safeKey(key), value);\n },\n async removeItem(key) {\n await secureStore.deleteItemAsync(safeKey(key));\n },\n };\n}\n\nfunction safeKey(key: string): string {\n // SecureStore allows /^[A-Za-z0-9._-]+$/. Substitute disallowed\n // characters with a deterministic underscore-encoded form so the\n // round trip is stable and reversible.\n let out = \"\";\n for (let i = 0; i < key.length; i++) {\n const c = key.charCodeAt(i);\n const isAlnum =\n (c >= 48 && c <= 57) || // 0-9\n (c >= 65 && c <= 90) || // A-Z\n (c >= 97 && c <= 122); // a-z\n if (isAlnum || c === 46 || c === 95 || c === 45) {\n out += key[i];\n } else {\n // Encode the codepoint as `_<hex>_`.\n out += `_${c.toString(16)}_`;\n }\n }\n return out;\n}\n","/**\n * The pluggable storage interface used by every adapter in this package.\n *\n * Mirrors the surface in `API.md` §`Storage interface` so that callers\n * can persist engine overrides to whichever native KV store fits their\n * app — AsyncStorage by default, MMKV when bundle/perf matter, or\n * SecureStore when the override list itself is sensitive.\n *\n * Methods may be sync or async to match the underlying backing store.\n * `@variantlab/core` does not yet wire this into `EngineOptions`; for\n * now adapters expose them as standalone factories so that callers can\n * implement persistence at the app boundary (e.g. by serialising\n * `engine.getHistory()` after every `variantChanged` event).\n */\nexport interface Storage {\n getItem(key: string): string | null | Promise<string | null>;\n setItem(key: string, value: string): void | Promise<void>;\n removeItem(key: string): void | Promise<void>;\n /** Optional bulk-key listing for debug overlay export and tests. */\n keys?(): string[] | Promise<string[]>;\n /** Optional clear-all helper used by `engine.resetAll()` integrations. */\n clear?(): void | Promise<void>;\n}\n\n/** Prefix every variantlab key with this so storage stays sandboxed. */\nexport const STORAGE_KEY_PREFIX = \"variantlab:\";\n\n/** Build a fully-qualified storage key from a logical name. */\nexport function buildKey(name: string): string {\n return STORAGE_KEY_PREFIX + name;\n}\n","/**\n * `@variantlab/react-native` — public barrel.\n *\n * This entrypoint deliberately **re-exports everything** from\n * `@variantlab/react` so a React Native app can\n *\n * import { VariantLabProvider, useVariant } from \"@variantlab/react-native\";\n *\n * without the user having to remember that the hook layer lives in\n * the web-React package. The hooks and components are 100% compatible\n * with React Native — they rely only on React's own APIs.\n *\n * RN-specific surface sits alongside: storage adapters, auto-context\n * detection, and the deep-link handler. The debug overlay and the\n * QR helpers are intentionally split into their own sub-entrypoints\n * (`/debug`, `/qr`) so tree-shaking works at the package level:\n * production bundles that never import `/debug` do not pay for it.\n */\n\nexport const VERSION = \"0.0.0\";\n\n// ---------- re-exports from @variantlab/react -----------------------------\nexport {\n type UseExperimentResult,\n useExperiment,\n useRouteExperiments,\n useSetVariant,\n useVariant,\n useVariantLabEngine,\n useVariantValue,\n Variant,\n VariantErrorBoundary,\n type VariantErrorBoundaryProps,\n VariantLabContext,\n VariantLabProvider,\n type VariantLabProviderProps,\n type VariantProps,\n VariantValue,\n type VariantValueProps,\n} from \"@variantlab/react\";\n\n// ---------- auto-context ---------------------------------------------------\nexport {\n type AutoContextOptions,\n bucketScreenWidth,\n getAutoContext,\n type ScreenSizeBucket,\n} from \"./context/auto-context.js\";\n// ---------- deep linking ---------------------------------------------------\nexport {\n applyPayload,\n base64UrlToBytes,\n bytesToBase64Url,\n decodeSharePayload,\n encodeSharePayload,\n type LinkingLike,\n type RegisterDeepLinkOptions,\n registerDeepLinkHandler,\n type SharePayload,\n type ShareVersion,\n type ValidationFailure,\n type ValidationResult,\n validatePayload,\n} from \"./deep-link/index.js\";\n// ---------- storage --------------------------------------------------------\nexport {\n type AsyncStorageLike,\n buildKey,\n createAsyncStorageAdapter,\n createMemoryStorage,\n createMMKVStorageAdapter,\n createSecureStoreAdapter,\n type MMKVLike,\n type SecureStoreLike,\n STORAGE_KEY_PREFIX,\n type Storage,\n} from \"./storage/index.js\";\n"]}