@pollar/core 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -20,11 +27,37 @@ yarn add @pollar/core
20
27
  For React Native / Expo, also install one of the storage adapter peer deps:
21
28
 
22
29
  ```bash
23
- # Expo
30
+ # Expo (react-native-quick-crypto is a native module → requires a dev build, not Expo Go)
24
31
  npx expo install expo-secure-store react-native-get-random-values
32
+ npm i react-native-quick-crypto react-native-polyfill-globals
25
33
 
26
34
  # Bare React Native
27
- npm i react-native-keychain react-native-get-random-values
35
+ npm i react-native-keychain react-native-get-random-values react-native-quick-crypto react-native-polyfill-globals
36
+ ```
37
+
38
+ > **React Native runtime requirements.** The SDK builds a DPoP proof (RFC 9449) for **every** authenticated request.
39
+ > That path uses four standard Web primitives that React Native / Hermes does **not** all ship by default. Register
40
+ > them at the very top of your entry file, **before** any `@pollar/core` import. If any is missing, DPoP proof
41
+ > construction fails and **no authenticated request works** — the SDK is not at fault, the runtime is.
42
+ >
43
+ > | Primitive | Used by | Polyfill |
44
+ > | --- | --- | --- |
45
+ > | `crypto.getRandomValues` | keypair generation (`NobleKeyManager`), DPoP `jti` | [`react-native-get-random-values`](https://github.com/LinusU/react-native-get-random-values) |
46
+ > | `crypto.subtle.digest('SHA-256')` | `sha256` → API-key namespace, DPoP `ath`, `NobleKeyManager.sign()` | [`react-native-quick-crypto`](https://github.com/margelo/react-native-quick-crypto) ≥ 0.7 (native module → Expo **dev build**, not Expo Go) |
47
+ > | `TextEncoder` / `TextDecoder` | DPoP proof encoding, base64url, JWK thumbprint | bundled in [`react-native-polyfill-globals`](https://github.com/acostalima/react-native-polyfill-globals) (or `text-encoding`) |
48
+ > | `URL` (spec-compliant) | DPoP `htu` normalization (`new URL(request.url)` on every proof) | bundled in `react-native-polyfill-globals` (or [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill)) |
49
+ >
50
+ > `react-native-polyfill-globals/auto` is the pragmatic one-liner — it installs `TextEncoder`/`TextDecoder` **and**
51
+ > `URL` together (plus base64 / fetch-streaming you don't strictly need). The SDK does **not** rely on `fetch` response
52
+ > streaming on React Native: it polls the non-streaming `/auth/session/status/{id}/poll` endpoint instead, so you do
53
+ > **not** need a fetch-streaming polyfill for auth — but you still need TextEncoder + URL from that same package.
54
+
55
+ Entry-file setup (e.g. `index.js`), **before importing `@pollar/core`**:
56
+
57
+ ```ts
58
+ import 'react-native-get-random-values'; // crypto.getRandomValues
59
+ import 'react-native-polyfill-globals/auto'; // TextEncoder/TextDecoder + URL
60
+ import 'react-native-quick-crypto'; // crypto.subtle.digest — register its global per the package's setup docs
28
61
  ```
29
62
 
30
63
  ## Overview
@@ -52,8 +85,10 @@ const client = new PollarClient({ apiKey: 'your-api-key' });
52
85
  ## React Native (Expo)
53
86
 
54
87
  ```ts
55
- // At your app entry — `crypto.getRandomValues` polyfill
56
- import 'react-native-get-random-values';
88
+ // At your app entry, BEFORE importing @pollar/core runtime polyfills (see table above):
89
+ import 'react-native-get-random-values'; // crypto.getRandomValues
90
+ import 'react-native-polyfill-globals/auto'; // TextEncoder/TextDecoder + URL
91
+ import 'react-native-quick-crypto'; // crypto.subtle.digest — register its global per the package's setup docs
57
92
 
58
93
  import { PollarClient } from '@pollar/core';
59
94
  import { createSecureStoreAdapter } from '@pollar/core/adapters/expo';
@@ -71,7 +106,9 @@ const client = new PollarClient({ apiKey: 'your-api-key', storage });
71
106
  ## React Native (`react-native-keychain`)
72
107
 
73
108
  ```ts
74
- import 'react-native-get-random-values';
109
+ import 'react-native-get-random-values'; // crypto.getRandomValues
110
+ import 'react-native-polyfill-globals/auto'; // TextEncoder/TextDecoder + URL
111
+ import 'react-native-quick-crypto'; // crypto.subtle.digest — register its global per the package's setup docs
75
112
  import { PollarClient } from '@pollar/core';
76
113
  import { createKeychainAdapter } from '@pollar/core/adapters/react-native-keychain';
77
114
 
@@ -79,6 +116,87 @@ const storage = await createKeychainAdapter();
79
116
  const client = new PollarClient({ apiKey: 'your-api-key', storage });
80
117
  ```
81
118
 
119
+ ## Framework integration
120
+
121
+ `@pollar/core` is framework-agnostic: `PollarClient` is a plain class and the `on*StateChange` methods are
122
+ callback subscriptions. To render reactively, bridge those callbacks into your framework's state primitive. The
123
+ client instance should be a singleton (module scope, context, or DI) — never recreate it on every render.
124
+
125
+ ### React / Next.js
126
+
127
+ `useSyncExternalStore` is the idiomatic bridge. In Next.js, only instantiate in Client Components (`'use client'`)
128
+ — server-side, the SDK degrades to a no-op and warns.
129
+
130
+ ```tsx
131
+ 'use client';
132
+ import { useSyncExternalStore } from 'react';
133
+ import { PollarClient, type AuthState } from '@pollar/core';
134
+
135
+ const client = new PollarClient({ apiKey: 'pk_...' }); // module scope = one instance
136
+
137
+ export function useAuthState(): AuthState {
138
+ return useSyncExternalStore(
139
+ (cb) => client.onAuthStateChange(cb), // returns the unsubscribe fn
140
+ () => client.getAuthState(), // client snapshot
141
+ () => client.getAuthState(), // server snapshot (idle)
142
+ );
143
+ }
144
+
145
+ // const auth = useAuthState(); // re-renders on every auth transition
146
+ ```
147
+
148
+ ### Angular
149
+
150
+ The SDK's callbacks fire **outside Angular's zone**, so wrap state updates in `NgZone.run()` (or use signals) or the
151
+ view won't update. Expose the client through a service.
152
+
153
+ ```ts
154
+ import { Injectable, NgZone, signal } from '@angular/core';
155
+ import { PollarClient, type AuthState } from '@pollar/core';
156
+
157
+ @Injectable({ providedIn: 'root' })
158
+ export class PollarService {
159
+ private client = new PollarClient({ apiKey: 'pk_...' });
160
+ readonly authState = signal<AuthState>(this.client.getAuthState());
161
+
162
+ constructor(private zone: NgZone) {
163
+ this.client.onAuthStateChange((state) => {
164
+ this.zone.run(() => this.authState.set(state)); // re-enter Angular's zone
165
+ });
166
+ }
167
+
168
+ login = (email: string) => this.client.login({ provider: 'email', email });
169
+ }
170
+ ```
171
+
172
+ ### Vue 3
173
+
174
+ Assign the callback payload into a `ref` (or `shallowRef`) inside `onMounted`; unsubscribe in `onUnmounted`.
175
+
176
+ ```ts
177
+ import { ref, shallowRef, onMounted, onUnmounted } from 'vue';
178
+ import { PollarClient, type AuthState } from '@pollar/core';
179
+
180
+ const client = new PollarClient({ apiKey: 'pk_...' });
181
+
182
+ export function useAuth() {
183
+ const authState = shallowRef<AuthState>(client.getAuthState());
184
+ let unsub = () => {
185
+ };
186
+ onMounted(() => {
187
+ unsub = client.onAuthStateChange((s) => (authState.value = s)); // ref assign = reactive
188
+ });
189
+ onUnmounted(() => unsub());
190
+ return { authState, login: client.login.bind(client) };
191
+ }
192
+ ```
193
+
194
+ ### React Native
195
+
196
+ Same as React (`useSyncExternalStore`), plus the entry-file polyfills and injected adapters shown in the
197
+ React Native sections above. OAuth and external-wallet logins require `openAuthUrl` / `walletAdapter` to be injected
198
+ (the built-in popup/extension adapters are web-only).
199
+
82
200
  ## Preserved-on-disk storage shape
83
201
 
84
202
  0.7.0 persists exactly:
@@ -123,7 +241,7 @@ client.login({ provider: 'email', email: 'user@example.com' });
123
241
  client.verifyEmailCode('123456');
124
242
 
125
243
  // After success
126
- const profile = client.getUserProfile(); // PII (memory-only)
244
+ const profile = client.getUserProfile(); // PII (memory-only)
127
245
  const sessions = await client.listSessions();
128
246
  ```
129
247
 
@@ -131,16 +249,16 @@ const sessions = await client.listSessions();
131
249
 
132
250
  ### `new PollarClient(config)`
133
251
 
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` |
252
+ | Option | Type | Required | Description |
253
+ |--------------------|--------------------------|----------|-------------------------------------------------------------------------------------------------------------|
254
+ | `apiKey` | `string` | Yes | Your Pollar API key |
255
+ | `baseUrl` | `string` | No | Override the default API endpoint |
256
+ | `stellarNetwork` | `'mainnet' \| 'testnet'` | No | Target Stellar network (default: `testnet`) |
257
+ | `storage` | `Storage` | No | Pluggable storage adapter. Web autodetects `localStorage` with in-memory fallback; RN must inject one |
258
+ | `keyManager` | `KeyManager` | No | Pluggable DPoP key manager. Web picks `WebCryptoKeyManager`; otherwise `NobleKeyManager` |
141
259
  | `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, …) |
260
+ | `deviceLabel` | `string` | No | UI-friendly device label sent at `/auth/login` time and shown in `listSessions()` rows |
261
+ | `onStorageDegrade` | `OnStorageDegrade` | No | Notified the first time `localStorage` falls back to in-memory mode (SSR, private browsing, quota, …) |
144
262
 
145
263
  ---
146
264
 
@@ -160,6 +278,7 @@ client.login({ provider: 'email', email: 'user@example.com' });
160
278
 
161
279
  // Stellar wallet
162
280
  import { WalletType } from '@pollar/core';
281
+
163
282
  client.login({ provider: 'wallet', type: WalletType.FREIGHTER });
164
283
  client.login({ provider: 'wallet', type: WalletType.ALBEDO });
165
284
  ```
@@ -183,8 +302,8 @@ Server-side revokes the refresh-token family via `POST /v1/auth/logout`, then cl
183
302
  keypair. Server revocation is best-effort: a failed POST still clears local state.
184
303
 
185
304
  ```ts
186
- await client.logout(); // sign out this device
187
- await client.logout({ everywhere: true }); // revoke every active session for this user
305
+ await client.logout(); // sign out this device
306
+ await client.logout({ everywhere: true }); // revoke every active session for this user
188
307
  ```
189
308
 
190
309
  > Returns `Promise<void>` (was `void` pre-0.7.0). Existing fire-and-forget call sites keep working, but `await` it if
@@ -282,10 +401,18 @@ const unsubAuth = client.onAuthStateChange((state) => {
282
401
  // state.errorCode — AuthErrorCode (when step === 'error')
283
402
  });
284
403
 
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 */ });
404
+ const unsubTx = client.onTransactionStateChange((s) => {
405
+ /* build sign submit */
406
+ });
407
+ const unsubHistory = client.onTxHistoryStateChange((s) => {
408
+ /* paginated rows */
409
+ });
410
+ const unsubBalance = client.onWalletBalanceStateChange((s) => {
411
+ /* balances */
412
+ });
413
+ const unsubNetwork = client.onNetworkStateChange((s) => {
414
+ /* mainnet / testnet */
415
+ });
289
416
 
290
417
  unsubAuth();
291
418
  ```
@@ -378,7 +505,7 @@ import type { AdapterFn, PollarAdapter, PollarAdapters } from '@pollar/core';
378
505
 
379
506
  const trustlessWork: PollarAdapter = {
380
507
  initialize: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
381
- release: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
508
+ release: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
382
509
  };
383
510
 
384
511
  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"]}
@@ -0,0 +1,10 @@
1
+ import { V as VisibilityProvider } from '../types-84G_htcn.mjs';
2
+
3
+ /**
4
+ * Create a `VisibilityProvider` backed by React Native's `AppState`.
5
+ *
6
+ * Throws (via the returned Promise) if `react-native` cannot be loaded.
7
+ */
8
+ declare function createAppStateVisibilityProvider(): Promise<VisibilityProvider>;
9
+
10
+ export { createAppStateVisibilityProvider };
@@ -0,0 +1,10 @@
1
+ import { V as VisibilityProvider } from '../types-84G_htcn.js';
2
+
3
+ /**
4
+ * Create a `VisibilityProvider` backed by React Native's `AppState`.
5
+ *
6
+ * Throws (via the returned Promise) if `react-native` cannot be loaded.
7
+ */
8
+ declare function createAppStateVisibilityProvider(): Promise<VisibilityProvider>;
9
+
10
+ export { createAppStateVisibilityProvider };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ // src/adapters/react-native-appstate.ts
4
+ async function loadAppState() {
5
+ try {
6
+ const mod = await import('react-native');
7
+ const AppState = mod.AppState ?? mod.default?.AppState;
8
+ if (!AppState) {
9
+ throw new Error("'react-native' loaded but exposes no AppState export");
10
+ }
11
+ return AppState;
12
+ } catch (error) {
13
+ const message = `[PollarClient:visibility] Failed to load 'react-native' AppState. This adapter only runs inside a React Native app. Original error: ${error instanceof Error ? error.message : String(error)}`;
14
+ throw new Error(message);
15
+ }
16
+ }
17
+ async function createAppStateVisibilityProvider() {
18
+ const AppState = await loadAppState();
19
+ const isActive = (state) => state === "active";
20
+ return {
21
+ isVisible: () => isActive(AppState.currentState),
22
+ onChange: (cb) => {
23
+ let last = isActive(AppState.currentState);
24
+ const subscription = AppState.addEventListener("change", (state) => {
25
+ const next = isActive(state);
26
+ if (next !== last) {
27
+ last = next;
28
+ cb(next);
29
+ }
30
+ });
31
+ return () => subscription.remove();
32
+ }
33
+ };
34
+ }
35
+
36
+ exports.createAppStateVisibilityProvider = createAppStateVisibilityProvider;
37
+ //# sourceMappingURL=react-native-appstate.js.map
38
+ //# sourceMappingURL=react-native-appstate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/react-native-appstate.ts"],"names":[],"mappings":";;;AA8BA,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,cAAc,CAAA;AACvC,IAAA,MAAM,QAAA,GAAY,GAAA,CAAmC,QAAA,IAAa,GAAA,CAAiD,OAAA,EAAS,QAAA;AAC5H,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE;AACA,IAAA,OAAO,QAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,uIAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAOA,eAAsB,gCAAA,GAAgE;AACpF,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAA2B,KAAA,KAAU,QAAA;AAEvD,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,MAAM,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA;AAAA,IAC/C,QAAA,EAAU,CAAC,EAAA,KAAO;AAIhB,MAAA,IAAI,IAAA,GAAO,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA;AACzC,MAAA,MAAM,YAAA,GAAe,QAAA,CAAS,gBAAA,CAAiB,QAAA,EAAU,CAAC,KAAA,KAAU;AAClE,QAAA,MAAM,IAAA,GAAO,SAAS,KAAK,CAAA;AAC3B,QAAA,IAAI,SAAS,IAAA,EAAM;AACjB,UAAA,IAAA,GAAO,IAAA;AACP,UAAA,EAAA,CAAG,IAAI,CAAA;AAAA,QACT;AAAA,MACF,CAAC,CAAA;AACD,MAAA,OAAO,MAAM,aAAa,MAAA,EAAO;AAAA,IACnC;AAAA,GACF;AACF","file":"react-native-appstate.js","sourcesContent":["import type { VisibilityProvider } from '../visibility/types';\n\n/**\n * `AppState`-backed {@link VisibilityProvider} for React Native.\n *\n * Wire it into the silent-refresh scheduler so proactive token renewals are\n * skipped while the app is backgrounded and run the moment it returns to the\n * foreground — matching the web `visibilitychange` behavior and sidestepping\n * RN's aggressive background timer throttling.\n *\n * `react-native` is the consumer's framework (not a dependency of this SDK),\n * so the module is loaded lazily via dynamic `import('react-native')`. That\n * keeps web/Node bundles from ever resolving it. Because loading is async, the\n * factory is async too — mirror the `createSecureStoreAdapter` usage:\n *\n * import { createAppStateVisibilityProvider } from '@pollar/core/adapters/react-native-appstate';\n * const visibilityProvider = await createAppStateVisibilityProvider();\n * new PollarClient({ apiKey, storage, visibilityProvider });\n */\n\n/**\n * Minimal structural type for the slice of `react-native`'s `AppState` we use.\n * Typed here instead of importing the package's types because `react-native`\n * is an optional peer the SDK is not type-checked against.\n */\ntype AppStateApi = {\n currentState: string;\n addEventListener: (type: 'change', handler: (state: string) => void) => { remove: () => void };\n};\n\nasync function loadAppState(): Promise<AppStateApi> {\n try {\n // @ts-expect-error -- optional peer dep; resolved at runtime in RN apps,\n // absent when the SDK is built or run on web/Node.\n const mod = await import('react-native');\n const AppState = (mod as { AppState?: AppStateApi }).AppState ?? (mod as { default?: { AppState?: AppStateApi } }).default?.AppState;\n if (!AppState) {\n throw new Error(\"'react-native' loaded but exposes no AppState export\");\n }\n return AppState;\n } catch (error) {\n const message =\n `[PollarClient:visibility] Failed to load 'react-native' AppState. ` +\n `This adapter only runs inside a React Native app. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\n/**\n * Create a `VisibilityProvider` backed by React Native's `AppState`.\n *\n * Throws (via the returned Promise) if `react-native` cannot be loaded.\n */\nexport async function createAppStateVisibilityProvider(): Promise<VisibilityProvider> {\n const AppState = await loadAppState();\n\n const isActive = (state: string): boolean => state === 'active';\n\n return {\n isVisible: () => isActive(AppState.currentState),\n onChange: (cb) => {\n // Filter duplicate notifications — listeners only see real transitions,\n // matching the web provider's contract. RN also emits 'inactive'\n // (iOS transition state) which we collapse into \"not visible\".\n let last = isActive(AppState.currentState);\n const subscription = AppState.addEventListener('change', (state) => {\n const next = isActive(state);\n if (next !== last) {\n last = next;\n cb(next);\n }\n });\n return () => subscription.remove();\n },\n };\n}\n"]}
@@ -0,0 +1,36 @@
1
+ // src/adapters/react-native-appstate.ts
2
+ async function loadAppState() {
3
+ try {
4
+ const mod = await import('react-native');
5
+ const AppState = mod.AppState ?? mod.default?.AppState;
6
+ if (!AppState) {
7
+ throw new Error("'react-native' loaded but exposes no AppState export");
8
+ }
9
+ return AppState;
10
+ } catch (error) {
11
+ const message = `[PollarClient:visibility] Failed to load 'react-native' AppState. This adapter only runs inside a React Native app. Original error: ${error instanceof Error ? error.message : String(error)}`;
12
+ throw new Error(message);
13
+ }
14
+ }
15
+ async function createAppStateVisibilityProvider() {
16
+ const AppState = await loadAppState();
17
+ const isActive = (state) => state === "active";
18
+ return {
19
+ isVisible: () => isActive(AppState.currentState),
20
+ onChange: (cb) => {
21
+ let last = isActive(AppState.currentState);
22
+ const subscription = AppState.addEventListener("change", (state) => {
23
+ const next = isActive(state);
24
+ if (next !== last) {
25
+ last = next;
26
+ cb(next);
27
+ }
28
+ });
29
+ return () => subscription.remove();
30
+ }
31
+ };
32
+ }
33
+
34
+ export { createAppStateVisibilityProvider };
35
+ //# sourceMappingURL=react-native-appstate.mjs.map
36
+ //# sourceMappingURL=react-native-appstate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/react-native-appstate.ts"],"names":[],"mappings":";AA8BA,eAAe,YAAA,GAAqC;AAClD,EAAA,IAAI;AAGF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,cAAc,CAAA;AACvC,IAAA,MAAM,QAAA,GAAY,GAAA,CAAmC,QAAA,IAAa,GAAA,CAAiD,OAAA,EAAS,QAAA;AAC5H,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE;AACA,IAAA,OAAO,QAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GACJ,uIAEmB,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC3E,IAAA,MAAM,IAAI,MAAM,OAAO,CAAA;AAAA,EACzB;AACF;AAOA,eAAsB,gCAAA,GAAgE;AACpF,EAAA,MAAM,QAAA,GAAW,MAAM,YAAA,EAAa;AAEpC,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAA2B,KAAA,KAAU,QAAA;AAEvD,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,MAAM,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA;AAAA,IAC/C,QAAA,EAAU,CAAC,EAAA,KAAO;AAIhB,MAAA,IAAI,IAAA,GAAO,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA;AACzC,MAAA,MAAM,YAAA,GAAe,QAAA,CAAS,gBAAA,CAAiB,QAAA,EAAU,CAAC,KAAA,KAAU;AAClE,QAAA,MAAM,IAAA,GAAO,SAAS,KAAK,CAAA;AAC3B,QAAA,IAAI,SAAS,IAAA,EAAM;AACjB,UAAA,IAAA,GAAO,IAAA;AACP,UAAA,EAAA,CAAG,IAAI,CAAA;AAAA,QACT;AAAA,MACF,CAAC,CAAA;AACD,MAAA,OAAO,MAAM,aAAa,MAAA,EAAO;AAAA,IACnC;AAAA,GACF;AACF","file":"react-native-appstate.mjs","sourcesContent":["import type { VisibilityProvider } from '../visibility/types';\n\n/**\n * `AppState`-backed {@link VisibilityProvider} for React Native.\n *\n * Wire it into the silent-refresh scheduler so proactive token renewals are\n * skipped while the app is backgrounded and run the moment it returns to the\n * foreground — matching the web `visibilitychange` behavior and sidestepping\n * RN's aggressive background timer throttling.\n *\n * `react-native` is the consumer's framework (not a dependency of this SDK),\n * so the module is loaded lazily via dynamic `import('react-native')`. That\n * keeps web/Node bundles from ever resolving it. Because loading is async, the\n * factory is async too — mirror the `createSecureStoreAdapter` usage:\n *\n * import { createAppStateVisibilityProvider } from '@pollar/core/adapters/react-native-appstate';\n * const visibilityProvider = await createAppStateVisibilityProvider();\n * new PollarClient({ apiKey, storage, visibilityProvider });\n */\n\n/**\n * Minimal structural type for the slice of `react-native`'s `AppState` we use.\n * Typed here instead of importing the package's types because `react-native`\n * is an optional peer the SDK is not type-checked against.\n */\ntype AppStateApi = {\n currentState: string;\n addEventListener: (type: 'change', handler: (state: string) => void) => { remove: () => void };\n};\n\nasync function loadAppState(): Promise<AppStateApi> {\n try {\n // @ts-expect-error -- optional peer dep; resolved at runtime in RN apps,\n // absent when the SDK is built or run on web/Node.\n const mod = await import('react-native');\n const AppState = (mod as { AppState?: AppStateApi }).AppState ?? (mod as { default?: { AppState?: AppStateApi } }).default?.AppState;\n if (!AppState) {\n throw new Error(\"'react-native' loaded but exposes no AppState export\");\n }\n return AppState;\n } catch (error) {\n const message =\n `[PollarClient:visibility] Failed to load 'react-native' AppState. ` +\n `This adapter only runs inside a React Native app. ` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`;\n throw new Error(message);\n }\n}\n\n/**\n * Create a `VisibilityProvider` backed by React Native's `AppState`.\n *\n * Throws (via the returned Promise) if `react-native` cannot be loaded.\n */\nexport async function createAppStateVisibilityProvider(): Promise<VisibilityProvider> {\n const AppState = await loadAppState();\n\n const isActive = (state: string): boolean => state === 'active';\n\n return {\n isVisible: () => isActive(AppState.currentState),\n onChange: (cb) => {\n // Filter duplicate notifications — listeners only see real transitions,\n // matching the web provider's contract. RN also emits 'inactive'\n // (iOS transition state) which we collapse into \"not visible\".\n let last = isActive(AppState.currentState);\n const subscription = AppState.addEventListener('change', (state) => {\n const next = isActive(state);\n if (next !== last) {\n last = next;\n cb(next);\n }\n });\n return () => subscription.remove();\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"]}