@pollar/core 0.7.0 → 0.8.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 CHANGED
@@ -2,10 +2,17 @@
2
2
 
3
3
  Core SDK for [Pollar](https://pollar.xyz) — authentication and transaction utilities for Stellar-based applications.
4
4
 
5
- > **0.7.0 ships sender-constrained tokens via DPoP (RFC 9449), pluggable storage and key managers, automatic
6
- refresh-on-401, and removes PII from persisted storage.** This is a breaking change — read
7
- > the [CHANGELOG](../../CHANGELOG.md) before upgrading. Requires HTTPS and
8
- `sdk-api` Phase 5.
5
+ > **0.8.0** routes every `submitTx` (custodial **and** external wallets) through
6
+ > `/tx/submit` so the dashboard sees every transaction and idempotency is
7
+ > tracked end-to-end. Adds proactive token refresh with a visibility-aware
8
+ > scheduler, an `onStorageDegrade` telemetry hook, and a timeout around the
9
+ > wallet adapter resolver. External-wallet callers may now observe `pending`
10
+ > outcomes where they only ever got `success` / `error` before. Read the
11
+ > [CHANGELOG](../../CHANGELOG.md) before upgrading.
12
+ >
13
+ > **0.7.0** ships sender-constrained tokens via DPoP (RFC 9449), pluggable
14
+ > storage and key managers, automatic refresh-on-401, and removes PII from
15
+ > persisted storage. Requires HTTPS and `sdk-api` ≥ Phase 5.
9
16
 
10
17
  ## Installation
11
18
 
@@ -123,7 +130,7 @@ client.login({ provider: 'email', email: 'user@example.com' });
123
130
  client.verifyEmailCode('123456');
124
131
 
125
132
  // After success
126
- const profile = client.getUserProfile(); // PII (memory-only)
133
+ const profile = client.getUserProfile(); // PII (memory-only)
127
134
  const sessions = await client.listSessions();
128
135
  ```
129
136
 
@@ -131,16 +138,16 @@ const sessions = await client.listSessions();
131
138
 
132
139
  ### `new PollarClient(config)`
133
140
 
134
- | Option | Type | Required | Description |
135
- |--------------------|--------------------------|----------|----------------------------------------------------------------------------------------------------------|
136
- | `apiKey` | `string` | Yes | Your Pollar API key |
137
- | `baseUrl` | `string` | No | Override the default API endpoint |
138
- | `stellarNetwork` | `'mainnet' \| 'testnet'` | No | Target Stellar network (default: `testnet`) |
139
- | `storage` | `Storage` | No | Pluggable storage adapter. Web autodetects `localStorage` with in-memory fallback; RN must inject one |
140
- | `keyManager` | `KeyManager` | No | Pluggable DPoP key manager. Web picks `WebCryptoKeyManager`; otherwise `NobleKeyManager` |
141
+ | Option | Type | Required | Description |
142
+ | ------------------ | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
143
+ | `apiKey` | `string` | Yes | Your Pollar API key |
144
+ | `baseUrl` | `string` | No | Override the default API endpoint |
145
+ | `stellarNetwork` | `'mainnet' \| 'testnet'` | No | Target Stellar network (default: `testnet`) |
146
+ | `storage` | `Storage` | No | Pluggable storage adapter. Web autodetects `localStorage` with in-memory fallback; RN must inject one |
147
+ | `keyManager` | `KeyManager` | No | Pluggable DPoP key manager. Web picks `WebCryptoKeyManager`; otherwise `NobleKeyManager` |
141
148
  | `walletAdapter` | `WalletAdapterResolver` | No | External wallet stack (e.g. Stellar Wallets Kit). Falls back to built-in `FreighterAdapter`/`AlbedoAdapter` |
142
- | `deviceLabel` | `string` | No | UI-friendly device label sent at `/auth/login` time and shown in `listSessions()` rows |
143
- | `onStorageDegrade` | `OnStorageDegrade` | No | Notified the first time `localStorage` falls back to in-memory mode (SSR, private browsing, quota, …) |
149
+ | `deviceLabel` | `string` | No | UI-friendly device label sent at `/auth/login` time and shown in `listSessions()` rows |
150
+ | `onStorageDegrade` | `OnStorageDegrade` | No | Notified the first time `localStorage` falls back to in-memory mode (SSR, private browsing, quota, …) |
144
151
 
145
152
  ---
146
153
 
@@ -183,8 +190,8 @@ Server-side revokes the refresh-token family via `POST /v1/auth/logout`, then cl
183
190
  keypair. Server revocation is best-effort: a failed POST still clears local state.
184
191
 
185
192
  ```ts
186
- await client.logout(); // sign out this device
187
- await client.logout({ everywhere: true }); // revoke every active session for this user
193
+ await client.logout(); // sign out this device
194
+ await client.logout({ everywhere: true }); // revoke every active session for this user
188
195
  ```
189
196
 
190
197
  > Returns `Promise<void>` (was `void` pre-0.7.0). Existing fire-and-forget call sites keep working, but `await` it if
@@ -282,10 +289,18 @@ const unsubAuth = client.onAuthStateChange((state) => {
282
289
  // state.errorCode — AuthErrorCode (when step === 'error')
283
290
  });
284
291
 
285
- const unsubTx = client.onTransactionStateChange((s) => { /* build → sign → submit */ });
286
- const unsubHistory = client.onTxHistoryStateChange((s) => { /* paginated rows */ });
287
- const unsubBalance = client.onWalletBalanceStateChange((s) => { /* balances */ });
288
- const unsubNetwork = client.onNetworkStateChange((s) => { /* mainnet / testnet */ });
292
+ const unsubTx = client.onTransactionStateChange((s) => {
293
+ /* build sign submit */
294
+ });
295
+ const unsubHistory = client.onTxHistoryStateChange((s) => {
296
+ /* paginated rows */
297
+ });
298
+ const unsubBalance = client.onWalletBalanceStateChange((s) => {
299
+ /* balances */
300
+ });
301
+ const unsubNetwork = client.onNetworkStateChange((s) => {
302
+ /* mainnet / testnet */
303
+ });
289
304
 
290
305
  unsubAuth();
291
306
  ```
@@ -378,7 +393,7 @@ import type { AdapterFn, PollarAdapter, PollarAdapters } from '@pollar/core';
378
393
 
379
394
  const trustlessWork: PollarAdapter = {
380
395
  initialize: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
381
- release: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
396
+ release: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
382
397
  };
383
398
 
384
399
  const adapters: PollarAdapters = { trustlessWork };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/expo-secure-store.ts"],"names":[],"mappings":";;;AAqCO,IAAM,4BAAA,GAA+B;AAW5C,eAAe,eAAA,GAA2C;AACxD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,mBAAmB,CAAA;AAC5C,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,mJAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAE/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AAEzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,wBAAA,CAAyB,OAAA,GAAqC,EAAC,EAAqB;AACxG,EAAA,MAAM,WAAA,GAAc,MAAM,eAAA,EAAgB;AAE1C,EAAA,MAAM,aACJ,OAAA,CAAQ,kBAAA,KAAuB,MAAA,GAAY,OAAA,CAAQ,qBAAqB,WAAA,CAAY,8BAAA;AAEtF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,OAAO,WAAA,CAAY,aAAa,GAAG,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,4BAAA,EAA8B;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,qCAAqC,4BAA4B,CAAA;AAAA,SACvH;AAAA,MACF;AACA,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,GAAA,EAAK,KAAA,EAAO,UAAA,KAAe,SAAY,EAAE,kBAAA,EAAoB,UAAA,EAAW,GAAI,MAAS,CAAA;AAAA,IACtH,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,WAAA,CAAY,gBAAgB,GAAG,CAAA;AAAA,IACvC;AAAA,GACF;AACF","file":"expo-secure-store.js","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`expo-secure-store`](https://docs.expo.dev/versions/latest/sdk/securestore/).\n *\n * `expo-secure-store` is an optional peer dependency; install it in your Expo\n * project with `npx expo install expo-secure-store`.\n *\n * The module is loaded lazily via dynamic `import('expo-secure-store')` so web\n * bundlers strip the dependency from web builds entirely.\n */\n\n/**\n * Minimal structural type for the parts of `expo-secure-store` we use. We\n * type the surface here instead of importing the package's types because the\n * package is an optional peer dependency and may not be installed when this\n * SDK is type-checked (e.g. web-only consumers).\n */\ntype SecureStoreApi = {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string, options?: { keychainAccessible?: number }) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n /**\n * Default we use: requires the device to be unlocked and disables iCloud\n * Keychain backup of the value (so a stolen iCloud backup cannot exfiltrate\n * the SDK's private key material to another device).\n */\n WHEN_UNLOCKED_THIS_DEVICE_ONLY?: number;\n};\n\n/**\n * Hard cap per stored value. Generously above what the SDK actually writes\n * (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within\n * iOS Keychain's practical limit. Refuses oversized writes loudly rather\n * than letting the platform truncate or silently fail.\n */\nexport const SECURE_STORE_MAX_VALUE_BYTES = 4096;\n\nexport interface SecureStoreAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.\n * On Android this is a no-op (the platform manages access via Keystore).\n */\n keychainAccessible?: number;\n}\n\nasync function loadSecureStore(): Promise<SecureStoreApi> {\n try {\n // @ts-ignore -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in Expo / RN apps.\n const mod = await import('expo-secure-store');\n return mod as unknown as SecureStoreApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'expo-secure-store'. ` +\n `Install it in your Expo app: \\`npx expo install expo-secure-store\\`. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n // Fallback: count UTF-8 bytes manually for environments without TextEncoder.\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n // Surrogate pair → 4 bytes; advance the index.\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by Expo SecureStore.\n *\n * Throws synchronously (via the returned Promise) at construction time if\n * `expo-secure-store` cannot be loaded.\n */\nexport async function createSecureStoreAdapter(options: SecureStoreAdapterOptions = {}): Promise<Storage> {\n const SecureStore = await loadSecureStore();\n\n const accessible =\n options.keychainAccessible !== undefined ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\n return {\n async get(key) {\n return SecureStore.getItemAsync(key);\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > SECURE_STORE_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`,\n );\n }\n await SecureStore.setItemAsync(key, value, accessible !== undefined ? { keychainAccessible: accessible } : undefined);\n },\n async remove(key) {\n await SecureStore.deleteItemAsync(key);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/adapters/expo-secure-store.ts"],"names":[],"mappings":";;;AAqCO,IAAM,4BAAA,GAA+B;AAW5C,eAAe,eAAA,GAA2C;AACxD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,mBAAmB,CAAA;AAC5C,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,mJAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAE/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AAEzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,wBAAA,CAAyB,OAAA,GAAqC,EAAC,EAAqB;AACxG,EAAA,MAAM,WAAA,GAAc,MAAM,eAAA,EAAgB;AAE1C,EAAA,MAAM,aACJ,OAAA,CAAQ,kBAAA,KAAuB,MAAA,GAAY,OAAA,CAAQ,qBAAqB,WAAA,CAAY,8BAAA;AAEtF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,OAAO,WAAA,CAAY,aAAa,GAAG,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,4BAAA,EAA8B;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,qCAAqC,4BAA4B,CAAA;AAAA,SACvH;AAAA,MACF;AACA,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,GAAA,EAAK,KAAA,EAAO,UAAA,KAAe,SAAY,EAAE,kBAAA,EAAoB,UAAA,EAAW,GAAI,MAAS,CAAA;AAAA,IACtH,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,WAAA,CAAY,gBAAgB,GAAG,CAAA;AAAA,IACvC;AAAA,GACF;AACF","file":"expo-secure-store.js","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`expo-secure-store`](https://docs.expo.dev/versions/latest/sdk/securestore/).\n *\n * `expo-secure-store` is an optional peer dependency; install it in your Expo\n * project with `npx expo install expo-secure-store`.\n *\n * The module is loaded lazily via dynamic `import('expo-secure-store')` so web\n * bundlers strip the dependency from web builds entirely.\n */\n\n/**\n * Minimal structural type for the parts of `expo-secure-store` we use. We\n * type the surface here instead of importing the package's types because the\n * package is an optional peer dependency and may not be installed when this\n * SDK is type-checked (e.g. web-only consumers).\n */\ntype SecureStoreApi = {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string, options?: { keychainAccessible?: number }) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n /**\n * Default we use: requires the device to be unlocked and disables iCloud\n * Keychain backup of the value (so a stolen iCloud backup cannot exfiltrate\n * the SDK's private key material to another device).\n */\n WHEN_UNLOCKED_THIS_DEVICE_ONLY?: number;\n};\n\n/**\n * Hard cap per stored value. Generously above what the SDK actually writes\n * (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within\n * iOS Keychain's practical limit. Refuses oversized writes loudly rather\n * than letting the platform truncate or silently fail.\n */\nexport const SECURE_STORE_MAX_VALUE_BYTES = 4096;\n\nexport interface SecureStoreAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.\n * On Android this is a no-op (the platform manages access via Keystore).\n */\n keychainAccessible?: number;\n}\n\nasync function loadSecureStore(): Promise<SecureStoreApi> {\n try {\n // @ts-expect-error -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in Expo / RN apps.\n const mod = await import('expo-secure-store');\n return mod as unknown as SecureStoreApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'expo-secure-store'. ` +\n `Install it in your Expo app: \\`npx expo install expo-secure-store\\`. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n // Fallback: count UTF-8 bytes manually for environments without TextEncoder.\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n // Surrogate pair → 4 bytes; advance the index.\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by Expo SecureStore.\n *\n * Throws synchronously (via the returned Promise) at construction time if\n * `expo-secure-store` cannot be loaded.\n */\nexport async function createSecureStoreAdapter(options: SecureStoreAdapterOptions = {}): Promise<Storage> {\n const SecureStore = await loadSecureStore();\n\n const accessible =\n options.keychainAccessible !== undefined ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\n return {\n async get(key) {\n return SecureStore.getItemAsync(key);\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > SECURE_STORE_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`,\n );\n }\n await SecureStore.setItemAsync(key, value, accessible !== undefined ? { keychainAccessible: accessible } : undefined);\n },\n async remove(key) {\n await SecureStore.deleteItemAsync(key);\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/expo-secure-store.ts"],"names":[],"mappings":";AAqCO,IAAM,4BAAA,GAA+B;AAW5C,eAAe,eAAA,GAA2C;AACxD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,mBAAmB,CAAA;AAC5C,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,mJAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAE/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AAEzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,wBAAA,CAAyB,OAAA,GAAqC,EAAC,EAAqB;AACxG,EAAA,MAAM,WAAA,GAAc,MAAM,eAAA,EAAgB;AAE1C,EAAA,MAAM,aACJ,OAAA,CAAQ,kBAAA,KAAuB,MAAA,GAAY,OAAA,CAAQ,qBAAqB,WAAA,CAAY,8BAAA;AAEtF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,OAAO,WAAA,CAAY,aAAa,GAAG,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,4BAAA,EAA8B;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,qCAAqC,4BAA4B,CAAA;AAAA,SACvH;AAAA,MACF;AACA,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,GAAA,EAAK,KAAA,EAAO,UAAA,KAAe,SAAY,EAAE,kBAAA,EAAoB,UAAA,EAAW,GAAI,MAAS,CAAA;AAAA,IACtH,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,WAAA,CAAY,gBAAgB,GAAG,CAAA;AAAA,IACvC;AAAA,GACF;AACF","file":"expo-secure-store.mjs","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`expo-secure-store`](https://docs.expo.dev/versions/latest/sdk/securestore/).\n *\n * `expo-secure-store` is an optional peer dependency; install it in your Expo\n * project with `npx expo install expo-secure-store`.\n *\n * The module is loaded lazily via dynamic `import('expo-secure-store')` so web\n * bundlers strip the dependency from web builds entirely.\n */\n\n/**\n * Minimal structural type for the parts of `expo-secure-store` we use. We\n * type the surface here instead of importing the package's types because the\n * package is an optional peer dependency and may not be installed when this\n * SDK is type-checked (e.g. web-only consumers).\n */\ntype SecureStoreApi = {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string, options?: { keychainAccessible?: number }) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n /**\n * Default we use: requires the device to be unlocked and disables iCloud\n * Keychain backup of the value (so a stolen iCloud backup cannot exfiltrate\n * the SDK's private key material to another device).\n */\n WHEN_UNLOCKED_THIS_DEVICE_ONLY?: number;\n};\n\n/**\n * Hard cap per stored value. Generously above what the SDK actually writes\n * (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within\n * iOS Keychain's practical limit. Refuses oversized writes loudly rather\n * than letting the platform truncate or silently fail.\n */\nexport const SECURE_STORE_MAX_VALUE_BYTES = 4096;\n\nexport interface SecureStoreAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.\n * On Android this is a no-op (the platform manages access via Keystore).\n */\n keychainAccessible?: number;\n}\n\nasync function loadSecureStore(): Promise<SecureStoreApi> {\n try {\n // @ts-ignore -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in Expo / RN apps.\n const mod = await import('expo-secure-store');\n return mod as unknown as SecureStoreApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'expo-secure-store'. ` +\n `Install it in your Expo app: \\`npx expo install expo-secure-store\\`. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n // Fallback: count UTF-8 bytes manually for environments without TextEncoder.\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n // Surrogate pair → 4 bytes; advance the index.\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by Expo SecureStore.\n *\n * Throws synchronously (via the returned Promise) at construction time if\n * `expo-secure-store` cannot be loaded.\n */\nexport async function createSecureStoreAdapter(options: SecureStoreAdapterOptions = {}): Promise<Storage> {\n const SecureStore = await loadSecureStore();\n\n const accessible =\n options.keychainAccessible !== undefined ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\n return {\n async get(key) {\n return SecureStore.getItemAsync(key);\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > SECURE_STORE_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`,\n );\n }\n await SecureStore.setItemAsync(key, value, accessible !== undefined ? { keychainAccessible: accessible } : undefined);\n },\n async remove(key) {\n await SecureStore.deleteItemAsync(key);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/adapters/expo-secure-store.ts"],"names":[],"mappings":";AAqCO,IAAM,4BAAA,GAA+B;AAW5C,eAAe,eAAA,GAA2C;AACxD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,mBAAmB,CAAA;AAC5C,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,mJAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAE/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AAEzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,wBAAA,CAAyB,OAAA,GAAqC,EAAC,EAAqB;AACxG,EAAA,MAAM,WAAA,GAAc,MAAM,eAAA,EAAgB;AAE1C,EAAA,MAAM,aACJ,OAAA,CAAQ,kBAAA,KAAuB,MAAA,GAAY,OAAA,CAAQ,qBAAqB,WAAA,CAAY,8BAAA;AAEtF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,OAAO,WAAA,CAAY,aAAa,GAAG,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,4BAAA,EAA8B;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,qCAAqC,4BAA4B,CAAA;AAAA,SACvH;AAAA,MACF;AACA,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,GAAA,EAAK,KAAA,EAAO,UAAA,KAAe,SAAY,EAAE,kBAAA,EAAoB,UAAA,EAAW,GAAI,MAAS,CAAA;AAAA,IACtH,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,WAAA,CAAY,gBAAgB,GAAG,CAAA;AAAA,IACvC;AAAA,GACF;AACF","file":"expo-secure-store.mjs","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`expo-secure-store`](https://docs.expo.dev/versions/latest/sdk/securestore/).\n *\n * `expo-secure-store` is an optional peer dependency; install it in your Expo\n * project with `npx expo install expo-secure-store`.\n *\n * The module is loaded lazily via dynamic `import('expo-secure-store')` so web\n * bundlers strip the dependency from web builds entirely.\n */\n\n/**\n * Minimal structural type for the parts of `expo-secure-store` we use. We\n * type the surface here instead of importing the package's types because the\n * package is an optional peer dependency and may not be installed when this\n * SDK is type-checked (e.g. web-only consumers).\n */\ntype SecureStoreApi = {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string, options?: { keychainAccessible?: number }) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n /**\n * Default we use: requires the device to be unlocked and disables iCloud\n * Keychain backup of the value (so a stolen iCloud backup cannot exfiltrate\n * the SDK's private key material to another device).\n */\n WHEN_UNLOCKED_THIS_DEVICE_ONLY?: number;\n};\n\n/**\n * Hard cap per stored value. Generously above what the SDK actually writes\n * (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within\n * iOS Keychain's practical limit. Refuses oversized writes loudly rather\n * than letting the platform truncate or silently fail.\n */\nexport const SECURE_STORE_MAX_VALUE_BYTES = 4096;\n\nexport interface SecureStoreAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.\n * On Android this is a no-op (the platform manages access via Keystore).\n */\n keychainAccessible?: number;\n}\n\nasync function loadSecureStore(): Promise<SecureStoreApi> {\n try {\n // @ts-expect-error -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in Expo / RN apps.\n const mod = await import('expo-secure-store');\n return mod as unknown as SecureStoreApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'expo-secure-store'. ` +\n `Install it in your Expo app: \\`npx expo install expo-secure-store\\`. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n // Fallback: count UTF-8 bytes manually for environments without TextEncoder.\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n // Surrogate pair → 4 bytes; advance the index.\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by Expo SecureStore.\n *\n * Throws synchronously (via the returned Promise) at construction time if\n * `expo-secure-store` cannot be loaded.\n */\nexport async function createSecureStoreAdapter(options: SecureStoreAdapterOptions = {}): Promise<Storage> {\n const SecureStore = await loadSecureStore();\n\n const accessible =\n options.keychainAccessible !== undefined ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\n return {\n async get(key) {\n return SecureStore.getItemAsync(key);\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > SECURE_STORE_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`,\n );\n }\n await SecureStore.setItemAsync(key, value, accessible !== undefined ? { keychainAccessible: accessible } : undefined);\n },\n async remove(key) {\n await SecureStore.deleteItemAsync(key);\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/react-native-keychain.ts"],"names":[],"mappings":";;;AA8CO,IAAM,wBAAA,GAA2B;AAYxC,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,uBAAuB,CAAA;AAChD,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,+KAE2C,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AACnG,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAC/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AACzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,qBAAA,CAAsB,OAAA,GAAkC,EAAC,EAAqB;AAClG,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,UAAA,GACJ,QAAQ,UAAA,KAAe,MAAA,GAAY,QAAQ,UAAA,GAAa,QAAA,CAAS,aAAa,gCAAgC,CAAA;AAEhH,EAAA,SAAS,aAAa,GAAA,EAA8B;AAClD,IAAA,MAAM,IAAA,GAAwB,EAAE,OAAA,EAAS,GAAA,EAAI;AAC7C,IAAA,IAAI,UAAA,KAAe,MAAA,EAAW,IAAA,CAAK,UAAA,GAAa,UAAA;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,MAAM,SAAS,MAAM,QAAA,CAAS,mBAAmB,EAAE,OAAA,EAAS,KAAK,CAAA;AACjE,MAAA,IAAI,MAAA,KAAW,OAAO,OAAO,IAAA;AAC7B,MAAA,OAAO,MAAA,CAAO,QAAA;AAAA,IAChB,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,wBAAA,EAA0B;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,kCAAkC,wBAAwB,CAAA;AAAA,SAChH;AAAA,MACF;AAGA,MAAA,MAAM,SAAS,kBAAA,CAAmB,GAAA,EAAK,KAAA,EAAO,YAAA,CAAa,GAAG,CAAC,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,QAAA,CAAS,oBAAA,CAAqB,EAAE,OAAA,EAAS,KAAK,CAAA;AAAA,IACtD;AAAA,GACF;AACF","file":"react-native-keychain.js","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`react-native-keychain`](https://github.com/oblador/react-native-keychain).\n *\n * `react-native-keychain` is an optional peer dependency; install it in your\n * React Native project with `npm i react-native-keychain` (and follow its\n * iOS pod-install / Android linking instructions).\n *\n * The module is loaded lazily via dynamic `import('react-native-keychain')`\n * so web bundlers strip the dependency from web builds entirely.\n *\n * Storage model: one Keychain `service` per logical key. Each `Storage.set(k, v)`\n * call writes a separate Keychain entry under `service = k`; this keeps the\n * adapter simple but means the number of distinct keys you write should stay\n * bounded (the SDK uses 2–3 keys per `apiKeyHash`).\n */\n\ntype KeychainOptions = {\n service?: string;\n accessible?: string;\n};\n\ntype KeychainCredentials = {\n username: string;\n password: string;\n service: string;\n storage?: string;\n};\n\ntype KeychainApi = {\n setGenericPassword: (\n username: string,\n password: string,\n options?: KeychainOptions,\n ) => Promise<false | { service: string; storage?: string }>;\n getGenericPassword: (options?: KeychainOptions) => Promise<false | KeychainCredentials>;\n resetGenericPassword: (options?: KeychainOptions) => Promise<boolean>;\n ACCESSIBLE?: Record<string, string | undefined>;\n};\n\n/**\n * Hard cap per stored value. iOS Keychain has no formal byte limit but\n * practical limits sit a few KB; we refuse oversized writes loudly.\n */\nexport const KEYCHAIN_MAX_VALUE_BYTES = 4096;\n\nexport interface KeychainAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded\n * module — that prevents iCloud Keychain backup from carrying the SDK's\n * private key material to another device.\n */\n accessible?: string;\n}\n\nasync function loadKeychain(): Promise<KeychainApi> {\n try {\n // @ts-ignore -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in React Native apps.\n const mod = await import('react-native-keychain');\n return mod as unknown as KeychainApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'react-native-keychain'. ` +\n `Install it in your React Native app: \\`npm i react-native-keychain\\` ` +\n `(plus iOS pod install). Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by `react-native-keychain`.\n *\n * Throws (via the returned Promise) at construction time if the package\n * cannot be loaded.\n */\nexport async function createKeychainAdapter(options: KeychainAdapterOptions = {}): Promise<Storage> {\n const Keychain = await loadKeychain();\n\n const accessible: string | undefined =\n options.accessible !== undefined ? options.accessible : Keychain.ACCESSIBLE?.['WHEN_UNLOCKED_THIS_DEVICE_ONLY'];\n\n function buildOptions(key: string): KeychainOptions {\n const opts: KeychainOptions = { service: key };\n if (accessible !== undefined) opts.accessible = accessible;\n return opts;\n }\n\n return {\n async get(key) {\n const result = await Keychain.getGenericPassword({ service: key });\n if (result === false) return null;\n return result.password;\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > KEYCHAIN_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds Keychain limit ${KEYCHAIN_MAX_VALUE_BYTES}`,\n );\n }\n // Use the storage key as both the username and the service so a\n // (service, account) lookup is unambiguous on both platforms.\n await Keychain.setGenericPassword(key, value, buildOptions(key));\n },\n async remove(key) {\n await Keychain.resetGenericPassword({ service: key });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/adapters/react-native-keychain.ts"],"names":[],"mappings":";;;AA8CO,IAAM,wBAAA,GAA2B;AAYxC,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,uBAAuB,CAAA;AAChD,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,+KAE2C,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AACnG,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAC/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AACzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,qBAAA,CAAsB,OAAA,GAAkC,EAAC,EAAqB;AAClG,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,UAAA,GACJ,QAAQ,UAAA,KAAe,MAAA,GAAY,QAAQ,UAAA,GAAa,QAAA,CAAS,aAAa,gCAAgC,CAAA;AAEhH,EAAA,SAAS,aAAa,GAAA,EAA8B;AAClD,IAAA,MAAM,IAAA,GAAwB,EAAE,OAAA,EAAS,GAAA,EAAI;AAC7C,IAAA,IAAI,UAAA,KAAe,MAAA,EAAW,IAAA,CAAK,UAAA,GAAa,UAAA;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,MAAM,SAAS,MAAM,QAAA,CAAS,mBAAmB,EAAE,OAAA,EAAS,KAAK,CAAA;AACjE,MAAA,IAAI,MAAA,KAAW,OAAO,OAAO,IAAA;AAC7B,MAAA,OAAO,MAAA,CAAO,QAAA;AAAA,IAChB,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,wBAAA,EAA0B;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,kCAAkC,wBAAwB,CAAA;AAAA,SAChH;AAAA,MACF;AAGA,MAAA,MAAM,SAAS,kBAAA,CAAmB,GAAA,EAAK,KAAA,EAAO,YAAA,CAAa,GAAG,CAAC,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,QAAA,CAAS,oBAAA,CAAqB,EAAE,OAAA,EAAS,KAAK,CAAA;AAAA,IACtD;AAAA,GACF;AACF","file":"react-native-keychain.js","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`react-native-keychain`](https://github.com/oblador/react-native-keychain).\n *\n * `react-native-keychain` is an optional peer dependency; install it in your\n * React Native project with `npm i react-native-keychain` (and follow its\n * iOS pod-install / Android linking instructions).\n *\n * The module is loaded lazily via dynamic `import('react-native-keychain')`\n * so web bundlers strip the dependency from web builds entirely.\n *\n * Storage model: one Keychain `service` per logical key. Each `Storage.set(k, v)`\n * call writes a separate Keychain entry under `service = k`; this keeps the\n * adapter simple but means the number of distinct keys you write should stay\n * bounded (the SDK uses 2–3 keys per `apiKeyHash`).\n */\n\ntype KeychainOptions = {\n service?: string;\n accessible?: string;\n};\n\ntype KeychainCredentials = {\n username: string;\n password: string;\n service: string;\n storage?: string;\n};\n\ntype KeychainApi = {\n setGenericPassword: (\n username: string,\n password: string,\n options?: KeychainOptions,\n ) => Promise<false | { service: string; storage?: string }>;\n getGenericPassword: (options?: KeychainOptions) => Promise<false | KeychainCredentials>;\n resetGenericPassword: (options?: KeychainOptions) => Promise<boolean>;\n ACCESSIBLE?: Record<string, string | undefined>;\n};\n\n/**\n * Hard cap per stored value. iOS Keychain has no formal byte limit but\n * practical limits sit a few KB; we refuse oversized writes loudly.\n */\nexport const KEYCHAIN_MAX_VALUE_BYTES = 4096;\n\nexport interface KeychainAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded\n * module — that prevents iCloud Keychain backup from carrying the SDK's\n * private key material to another device.\n */\n accessible?: string;\n}\n\nasync function loadKeychain(): Promise<KeychainApi> {\n try {\n // @ts-expect-error -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in React Native apps.\n const mod = await import('react-native-keychain');\n return mod as unknown as KeychainApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'react-native-keychain'. ` +\n `Install it in your React Native app: \\`npm i react-native-keychain\\` ` +\n `(plus iOS pod install). Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by `react-native-keychain`.\n *\n * Throws (via the returned Promise) at construction time if the package\n * cannot be loaded.\n */\nexport async function createKeychainAdapter(options: KeychainAdapterOptions = {}): Promise<Storage> {\n const Keychain = await loadKeychain();\n\n const accessible: string | undefined =\n options.accessible !== undefined ? options.accessible : Keychain.ACCESSIBLE?.['WHEN_UNLOCKED_THIS_DEVICE_ONLY'];\n\n function buildOptions(key: string): KeychainOptions {\n const opts: KeychainOptions = { service: key };\n if (accessible !== undefined) opts.accessible = accessible;\n return opts;\n }\n\n return {\n async get(key) {\n const result = await Keychain.getGenericPassword({ service: key });\n if (result === false) return null;\n return result.password;\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > KEYCHAIN_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds Keychain limit ${KEYCHAIN_MAX_VALUE_BYTES}`,\n );\n }\n // Use the storage key as both the username and the service so a\n // (service, account) lookup is unambiguous on both platforms.\n await Keychain.setGenericPassword(key, value, buildOptions(key));\n },\n async remove(key) {\n await Keychain.resetGenericPassword({ service: key });\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/react-native-keychain.ts"],"names":[],"mappings":";AA8CO,IAAM,wBAAA,GAA2B;AAYxC,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,uBAAuB,CAAA;AAChD,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,+KAE2C,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AACnG,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAC/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AACzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,qBAAA,CAAsB,OAAA,GAAkC,EAAC,EAAqB;AAClG,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,UAAA,GACJ,QAAQ,UAAA,KAAe,MAAA,GAAY,QAAQ,UAAA,GAAa,QAAA,CAAS,aAAa,gCAAgC,CAAA;AAEhH,EAAA,SAAS,aAAa,GAAA,EAA8B;AAClD,IAAA,MAAM,IAAA,GAAwB,EAAE,OAAA,EAAS,GAAA,EAAI;AAC7C,IAAA,IAAI,UAAA,KAAe,MAAA,EAAW,IAAA,CAAK,UAAA,GAAa,UAAA;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,MAAM,SAAS,MAAM,QAAA,CAAS,mBAAmB,EAAE,OAAA,EAAS,KAAK,CAAA;AACjE,MAAA,IAAI,MAAA,KAAW,OAAO,OAAO,IAAA;AAC7B,MAAA,OAAO,MAAA,CAAO,QAAA;AAAA,IAChB,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,wBAAA,EAA0B;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,kCAAkC,wBAAwB,CAAA;AAAA,SAChH;AAAA,MACF;AAGA,MAAA,MAAM,SAAS,kBAAA,CAAmB,GAAA,EAAK,KAAA,EAAO,YAAA,CAAa,GAAG,CAAC,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,QAAA,CAAS,oBAAA,CAAqB,EAAE,OAAA,EAAS,KAAK,CAAA;AAAA,IACtD;AAAA,GACF;AACF","file":"react-native-keychain.mjs","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`react-native-keychain`](https://github.com/oblador/react-native-keychain).\n *\n * `react-native-keychain` is an optional peer dependency; install it in your\n * React Native project with `npm i react-native-keychain` (and follow its\n * iOS pod-install / Android linking instructions).\n *\n * The module is loaded lazily via dynamic `import('react-native-keychain')`\n * so web bundlers strip the dependency from web builds entirely.\n *\n * Storage model: one Keychain `service` per logical key. Each `Storage.set(k, v)`\n * call writes a separate Keychain entry under `service = k`; this keeps the\n * adapter simple but means the number of distinct keys you write should stay\n * bounded (the SDK uses 2–3 keys per `apiKeyHash`).\n */\n\ntype KeychainOptions = {\n service?: string;\n accessible?: string;\n};\n\ntype KeychainCredentials = {\n username: string;\n password: string;\n service: string;\n storage?: string;\n};\n\ntype KeychainApi = {\n setGenericPassword: (\n username: string,\n password: string,\n options?: KeychainOptions,\n ) => Promise<false | { service: string; storage?: string }>;\n getGenericPassword: (options?: KeychainOptions) => Promise<false | KeychainCredentials>;\n resetGenericPassword: (options?: KeychainOptions) => Promise<boolean>;\n ACCESSIBLE?: Record<string, string | undefined>;\n};\n\n/**\n * Hard cap per stored value. iOS Keychain has no formal byte limit but\n * practical limits sit a few KB; we refuse oversized writes loudly.\n */\nexport const KEYCHAIN_MAX_VALUE_BYTES = 4096;\n\nexport interface KeychainAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded\n * module — that prevents iCloud Keychain backup from carrying the SDK's\n * private key material to another device.\n */\n accessible?: string;\n}\n\nasync function loadKeychain(): Promise<KeychainApi> {\n try {\n // @ts-ignore -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in React Native apps.\n const mod = await import('react-native-keychain');\n return mod as unknown as KeychainApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'react-native-keychain'. ` +\n `Install it in your React Native app: \\`npm i react-native-keychain\\` ` +\n `(plus iOS pod install). Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by `react-native-keychain`.\n *\n * Throws (via the returned Promise) at construction time if the package\n * cannot be loaded.\n */\nexport async function createKeychainAdapter(options: KeychainAdapterOptions = {}): Promise<Storage> {\n const Keychain = await loadKeychain();\n\n const accessible: string | undefined =\n options.accessible !== undefined ? options.accessible : Keychain.ACCESSIBLE?.['WHEN_UNLOCKED_THIS_DEVICE_ONLY'];\n\n function buildOptions(key: string): KeychainOptions {\n const opts: KeychainOptions = { service: key };\n if (accessible !== undefined) opts.accessible = accessible;\n return opts;\n }\n\n return {\n async get(key) {\n const result = await Keychain.getGenericPassword({ service: key });\n if (result === false) return null;\n return result.password;\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > KEYCHAIN_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds Keychain limit ${KEYCHAIN_MAX_VALUE_BYTES}`,\n );\n }\n // Use the storage key as both the username and the service so a\n // (service, account) lookup is unambiguous on both platforms.\n await Keychain.setGenericPassword(key, value, buildOptions(key));\n },\n async remove(key) {\n await Keychain.resetGenericPassword({ service: key });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/adapters/react-native-keychain.ts"],"names":[],"mappings":";AA8CO,IAAM,wBAAA,GAA2B;AAYxC,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,uBAAuB,CAAA;AAChD,IAAA,OAAO,GAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,+KAE2C,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AACnG,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA;AAC/E,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,GAAO,KAAM,KAAA,IAAS,CAAA;AAAA,SAAA,IACjB,IAAA,GAAO,MAAO,KAAA,IAAS,CAAA;AAAA,SAAA,IACvB,IAAA,IAAQ,KAAA,IAAU,IAAA,IAAQ,KAAA,EAAQ;AACzC,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,CAAA,EAAA;AAAA,IACF,OAAO,KAAA,IAAS,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAQA,eAAsB,qBAAA,CAAsB,OAAA,GAAkC,EAAC,EAAqB;AAClG,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,UAAA,GACJ,QAAQ,UAAA,KAAe,MAAA,GAAY,QAAQ,UAAA,GAAa,QAAA,CAAS,aAAa,gCAAgC,CAAA;AAEhH,EAAA,SAAS,aAAa,GAAA,EAA8B;AAClD,IAAA,MAAM,IAAA,GAAwB,EAAE,OAAA,EAAS,GAAA,EAAI;AAC7C,IAAA,IAAI,UAAA,KAAe,MAAA,EAAW,IAAA,CAAK,UAAA,GAAa,UAAA;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,GAAA,EAAK;AACb,MAAA,MAAM,SAAS,MAAM,QAAA,CAAS,mBAAmB,EAAE,OAAA,EAAS,KAAK,CAAA;AACjE,MAAA,IAAI,MAAA,KAAW,OAAO,OAAO,IAAA;AAC7B,MAAA,OAAO,MAAA,CAAO,QAAA;AAAA,IAChB,CAAA;AAAA,IACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO;AACpB,MAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,MAAA,IAAI,OAAO,wBAAA,EAA0B;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,GAAG,CAAA,KAAA,EAAQ,IAAI,kCAAkC,wBAAwB,CAAA;AAAA,SAChH;AAAA,MACF;AAGA,MAAA,MAAM,SAAS,kBAAA,CAAmB,GAAA,EAAK,KAAA,EAAO,YAAA,CAAa,GAAG,CAAC,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,MAAM,OAAO,GAAA,EAAK;AAChB,MAAA,MAAM,QAAA,CAAS,oBAAA,CAAqB,EAAE,OAAA,EAAS,KAAK,CAAA;AAAA,IACtD;AAAA,GACF;AACF","file":"react-native-keychain.mjs","sourcesContent":["import type { Storage } from '../storage/types';\n\n/**\n * Adapter that persists session and key material in the iOS Keychain / Android\n * Keystore via [`react-native-keychain`](https://github.com/oblador/react-native-keychain).\n *\n * `react-native-keychain` is an optional peer dependency; install it in your\n * React Native project with `npm i react-native-keychain` (and follow its\n * iOS pod-install / Android linking instructions).\n *\n * The module is loaded lazily via dynamic `import('react-native-keychain')`\n * so web bundlers strip the dependency from web builds entirely.\n *\n * Storage model: one Keychain `service` per logical key. Each `Storage.set(k, v)`\n * call writes a separate Keychain entry under `service = k`; this keeps the\n * adapter simple but means the number of distinct keys you write should stay\n * bounded (the SDK uses 2–3 keys per `apiKeyHash`).\n */\n\ntype KeychainOptions = {\n service?: string;\n accessible?: string;\n};\n\ntype KeychainCredentials = {\n username: string;\n password: string;\n service: string;\n storage?: string;\n};\n\ntype KeychainApi = {\n setGenericPassword: (\n username: string,\n password: string,\n options?: KeychainOptions,\n ) => Promise<false | { service: string; storage?: string }>;\n getGenericPassword: (options?: KeychainOptions) => Promise<false | KeychainCredentials>;\n resetGenericPassword: (options?: KeychainOptions) => Promise<boolean>;\n ACCESSIBLE?: Record<string, string | undefined>;\n};\n\n/**\n * Hard cap per stored value. iOS Keychain has no formal byte limit but\n * practical limits sit a few KB; we refuse oversized writes loudly.\n */\nexport const KEYCHAIN_MAX_VALUE_BYTES = 4096;\n\nexport interface KeychainAdapterOptions {\n /**\n * Override the iOS Keychain accessibility class. Defaults to\n * `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded\n * module — that prevents iCloud Keychain backup from carrying the SDK's\n * private key material to another device.\n */\n accessible?: string;\n}\n\nasync function loadKeychain(): Promise<KeychainApi> {\n try {\n // @ts-expect-error -- optional peer dep; not present when the SDK is built or\n // when the SDK runs on web. Resolved at runtime in React Native apps.\n const mod = await import('react-native-keychain');\n return mod as unknown as KeychainApi;\n } catch (error) {\n const message =\n `[PollarClient:storage] Failed to load 'react-native-keychain'. ` +\n `Install it in your React Native app: \\`npm i react-native-keychain\\` ` +\n `(plus iOS pod install). Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(value).length;\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++;\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Create a `Storage` adapter backed by `react-native-keychain`.\n *\n * Throws (via the returned Promise) at construction time if the package\n * cannot be loaded.\n */\nexport async function createKeychainAdapter(options: KeychainAdapterOptions = {}): Promise<Storage> {\n const Keychain = await loadKeychain();\n\n const accessible: string | undefined =\n options.accessible !== undefined ? options.accessible : Keychain.ACCESSIBLE?.['WHEN_UNLOCKED_THIS_DEVICE_ONLY'];\n\n function buildOptions(key: string): KeychainOptions {\n const opts: KeychainOptions = { service: key };\n if (accessible !== undefined) opts.accessible = accessible;\n return opts;\n }\n\n return {\n async get(key) {\n const result = await Keychain.getGenericPassword({ service: key });\n if (result === false) return null;\n return result.password;\n },\n async set(key, value) {\n const size = utf8ByteLength(value);\n if (size > KEYCHAIN_MAX_VALUE_BYTES) {\n throw new Error(\n `[PollarClient:storage] Value for \"${key}\" is ${size} bytes, exceeds Keychain limit ${KEYCHAIN_MAX_VALUE_BYTES}`,\n );\n }\n // Use the storage key as both the username and the service so a\n // (service, account) lookup is unambiguous on both platforms.\n await Keychain.setGenericPassword(key, value, buildOptions(key));\n },\n async remove(key) {\n await Keychain.resetGenericPassword({ service: key });\n },\n };\n}\n"]}