@pollar/core 0.5.3 → 0.7.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 +276 -42
- package/dist/adapters/expo-secure-store.d.mts +26 -0
- package/dist/adapters/expo-secure-store.d.ts +26 -0
- package/dist/adapters/expo-secure-store.js +53 -0
- package/dist/adapters/expo-secure-store.js.map +1 -0
- package/dist/adapters/expo-secure-store.mjs +50 -0
- package/dist/adapters/expo-secure-store.mjs.map +1 -0
- package/dist/adapters/react-native-keychain.d.mts +25 -0
- package/dist/adapters/react-native-keychain.d.ts +25 -0
- package/dist/adapters/react-native-keychain.js +60 -0
- package/dist/adapters/react-native-keychain.js.map +1 -0
- package/dist/adapters/react-native-keychain.mjs +57 -0
- package/dist/adapters/react-native-keychain.mjs.map +1 -0
- package/dist/index.d.mts +743 -36
- package/dist/index.d.ts +743 -36
- package/dist/index.js +819 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +811 -125
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +30 -0
- package/dist/index.rn.d.ts +30 -0
- package/dist/index.rn.js +2627 -0
- package/dist/index.rn.js.map +1 -0
- package/dist/index.rn.mjs +2599 -0
- package/dist/index.rn.mjs.map +1 -0
- package/dist/types-DqgJIJBl.d.mts +19 -0
- package/dist/types-DqgJIJBl.d.ts +19 -0
- package/package.json +54 -2
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
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.
|
|
9
|
+
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
7
12
|
```bash
|
|
@@ -12,43 +17,130 @@ pnpm add @pollar/core
|
|
|
12
17
|
yarn add @pollar/core
|
|
13
18
|
```
|
|
14
19
|
|
|
20
|
+
For React Native / Expo, also install one of the storage adapter peer deps:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Expo
|
|
24
|
+
npx expo install expo-secure-store react-native-get-random-values
|
|
25
|
+
|
|
26
|
+
# Bare React Native
|
|
27
|
+
npm i react-native-keychain react-native-get-random-values
|
|
28
|
+
```
|
|
29
|
+
|
|
15
30
|
## Overview
|
|
16
31
|
|
|
17
32
|
`@pollar/core` provides the `PollarClient` class and utilities to:
|
|
18
33
|
|
|
19
34
|
- Authenticate users via **Google**, **GitHub**, **Email (OTP)**, or **Stellar wallets** (Freighter, Albedo)
|
|
35
|
+
- Sign every authenticated request with **DPoP** (RFC 9449), making stolen tokens useless to an attacker without the
|
|
36
|
+
per-session keypair
|
|
20
37
|
- Build and submit Stellar transactions
|
|
21
38
|
- Fetch Stellar account balances
|
|
22
39
|
- React to real-time authentication state changes
|
|
23
40
|
|
|
24
|
-
## Quick Start
|
|
41
|
+
## Quick Start (web)
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { PollarClient } from '@pollar/core';
|
|
45
|
+
|
|
46
|
+
const client = new PollarClient({ apiKey: 'your-api-key' });
|
|
47
|
+
// Storage and KeyManager autodetect:
|
|
48
|
+
// storage → localStorage with in-memory fallback
|
|
49
|
+
// keypair → WebCrypto ECDSA P-256, non-extractable, persisted in IndexedDB
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## React Native (Expo)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// At your app entry — `crypto.getRandomValues` polyfill
|
|
56
|
+
import 'react-native-get-random-values';
|
|
57
|
+
|
|
58
|
+
import { PollarClient } from '@pollar/core';
|
|
59
|
+
import { createSecureStoreAdapter } from '@pollar/core/adapters/expo';
|
|
60
|
+
|
|
61
|
+
// `await`: SecureStore is loaded via dynamic import.
|
|
62
|
+
const storage = await createSecureStoreAdapter({
|
|
63
|
+
// Default: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
64
|
+
// Prevents iCloud Keychain from carrying the key to another device.
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const client = new PollarClient({ apiKey: 'your-api-key', storage });
|
|
68
|
+
// KeyManager autodetects → NobleKeyManager (pure-JS @noble/curves p256).
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## React Native (`react-native-keychain`)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import 'react-native-get-random-values';
|
|
75
|
+
import { PollarClient } from '@pollar/core';
|
|
76
|
+
import { createKeychainAdapter } from '@pollar/core/adapters/react-native-keychain';
|
|
77
|
+
|
|
78
|
+
const storage = await createKeychainAdapter();
|
|
79
|
+
const client = new PollarClient({ apiKey: 'your-api-key', storage });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Preserved-on-disk storage shape
|
|
83
|
+
|
|
84
|
+
0.7.0 persists exactly:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
clientSessionId, userId, status,
|
|
88
|
+
token { accessToken, refreshToken, expiresAt },
|
|
89
|
+
user { id?, ready },
|
|
90
|
+
wallet { publicKey, existsOnStellar?, createdAt? }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
PII (`mail`, `first_name`, `last_name`, `avatar`, `providers.*`) lives **in memory only** on the `PollarClient` instance
|
|
94
|
+
and is fetched after auth. Reach it via:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const profile = client.getUserProfile();
|
|
98
|
+
// { mail, first_name, last_name, avatar, providers } | null
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Storage keys are namespaced by `apiKeyHash` (first 8 hex chars of SHA-256 of your API key) so multiple SDK instances on
|
|
102
|
+
the same origin don't cross-contaminate.
|
|
103
|
+
|
|
104
|
+
## End-to-end example
|
|
25
105
|
|
|
26
106
|
```ts
|
|
27
107
|
import { PollarClient } from '@pollar/core';
|
|
28
108
|
|
|
29
109
|
const client = new PollarClient({ apiKey: 'your-api-key' });
|
|
30
110
|
|
|
31
|
-
//
|
|
32
|
-
client.
|
|
33
|
-
console.log(
|
|
111
|
+
// React to auth state
|
|
112
|
+
const unsubscribe = client.onAuthStateChange((state) => {
|
|
113
|
+
console.log(state.step, state.errorCode ?? '');
|
|
34
114
|
});
|
|
35
115
|
|
|
36
|
-
//
|
|
37
|
-
|
|
116
|
+
// Wait until the keypair is ready and any persisted session has been restored
|
|
117
|
+
await client.ready();
|
|
118
|
+
|
|
119
|
+
// Start an email login
|
|
120
|
+
client.login({ provider: 'email', email: 'user@example.com' });
|
|
121
|
+
|
|
122
|
+
// Submit the OTP — clientSessionId is tracked internally
|
|
123
|
+
client.verifyEmailCode('123456');
|
|
38
124
|
|
|
39
|
-
//
|
|
40
|
-
|
|
125
|
+
// After success
|
|
126
|
+
const profile = client.getUserProfile(); // PII (memory-only)
|
|
127
|
+
const sessions = await client.listSessions();
|
|
41
128
|
```
|
|
42
129
|
|
|
43
130
|
## API Reference
|
|
44
131
|
|
|
45
132
|
### `new PollarClient(config)`
|
|
46
133
|
|
|
47
|
-
| Option
|
|
48
|
-
|
|
49
|
-
| `apiKey`
|
|
50
|
-
| `baseUrl`
|
|
51
|
-
| `stellarNetwork
|
|
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
|
+
| `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, …) |
|
|
52
144
|
|
|
53
145
|
---
|
|
54
146
|
|
|
@@ -72,18 +164,87 @@ client.login({ provider: 'wallet', type: WalletType.FREIGHTER });
|
|
|
72
164
|
client.login({ provider: 'wallet', type: WalletType.ALBEDO });
|
|
73
165
|
```
|
|
74
166
|
|
|
75
|
-
#### `client.verifyEmailCode(
|
|
167
|
+
#### `client.verifyEmailCode(code)`
|
|
168
|
+
|
|
169
|
+
Submits the OTP code for email authentication. The active `clientSessionId` is tracked internally — no need to pass it.
|
|
76
170
|
|
|
77
|
-
|
|
171
|
+
#### `client.loginWallet(walletId)`
|
|
78
172
|
|
|
79
|
-
|
|
173
|
+
Lower-level entry point for wallet flows. Accepts any `WalletId` (`WalletType.FREIGHTER`, `WalletType.ALBEDO`, or an
|
|
174
|
+
opaque string id like `'xbull'` / `'lobstr'` resolved by `walletAdapter`).
|
|
80
175
|
|
|
81
|
-
|
|
176
|
+
#### `client.cancelLogin()`
|
|
177
|
+
|
|
178
|
+
Aborts any in-flight login flow and resets `authState` to `idle`. Safe to call from any step (including `error`).
|
|
179
|
+
|
|
180
|
+
#### `client.logout(options?): Promise<void>`
|
|
181
|
+
|
|
182
|
+
Server-side revokes the refresh-token family via `POST /v1/auth/logout`, then clears local storage and resets the
|
|
183
|
+
keypair. Server revocation is best-effort: a failed POST still clears local state.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
await client.logout(); // sign out this device
|
|
187
|
+
await client.logout({ everywhere: true }); // revoke every active session for this user
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> Returns `Promise<void>` (was `void` pre-0.7.0). Existing fire-and-forget call sites keep working, but `await` it if
|
|
191
|
+
> you want to observe server-side revocation.
|
|
192
|
+
|
|
193
|
+
#### `client.logoutEverywhere(): Promise<void>`
|
|
194
|
+
|
|
195
|
+
Shorthand for `logout({ everywhere: true })`.
|
|
82
196
|
|
|
83
197
|
#### `client.isAuthenticated()`
|
|
84
198
|
|
|
85
199
|
Returns `true` if a valid session with a wallet public key is present.
|
|
86
200
|
|
|
201
|
+
#### `client.getUserProfile(): PollarUserProfile | null`
|
|
202
|
+
|
|
203
|
+
Returns the in-memory profile (`mail`, `first_name`, `last_name`, `avatar`, `providers`). `null` until `/auth/login`
|
|
204
|
+
completes. **This is the only way to read PII as of 0.7.0** — PII is no longer persisted to storage.
|
|
205
|
+
|
|
206
|
+
#### `client.ready(): Promise<void>`
|
|
207
|
+
|
|
208
|
+
Resolves once the keypair is initialized and any persisted session has been restored. Useful in tests and
|
|
209
|
+
server-side rendering.
|
|
210
|
+
|
|
211
|
+
#### `client.destroy(): void`
|
|
212
|
+
|
|
213
|
+
Detaches the cross-tab `storage` listener, aborts in-flight logins, and releases the keypair. Call this on unmount in
|
|
214
|
+
environments that re-instantiate `PollarClient`.
|
|
215
|
+
|
|
216
|
+
#### `client.refresh(): Promise<void>`
|
|
217
|
+
|
|
218
|
+
Forces an access-token refresh. Race-safe: concurrent calls coalesce into a single `/v1/auth/refresh` request.
|
|
219
|
+
Request middleware also calls this automatically on 401 with `DPoP-Nonce` rotation.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Sessions
|
|
224
|
+
|
|
225
|
+
#### `client.listSessions(): Promise<SessionInfo[]>`
|
|
226
|
+
|
|
227
|
+
Returns one row per active refresh-token family for the authenticated user:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
interface SessionInfo {
|
|
231
|
+
familyId: string;
|
|
232
|
+
createdAt: string;
|
|
233
|
+
lastUsedAt: string;
|
|
234
|
+
userAgent: string;
|
|
235
|
+
ipHash: string;
|
|
236
|
+
deviceLabel?: string;
|
|
237
|
+
expiresAt: string;
|
|
238
|
+
current: boolean; // true for the family backing this client
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### `client.revokeSession(familyId): Promise<void>`
|
|
243
|
+
|
|
244
|
+
Revokes a specific refresh-token family. Revoking the **current** family does not immediately clear local state — the
|
|
245
|
+
next 401 triggers an auto-refresh, which fails (family revoked) and clears the session. Call `logout()` for an
|
|
246
|
+
immediate teardown.
|
|
247
|
+
|
|
87
248
|
---
|
|
88
249
|
|
|
89
250
|
### Transactions
|
|
@@ -112,32 +273,36 @@ await client.submitTx(signedXdr);
|
|
|
112
273
|
|
|
113
274
|
### State
|
|
114
275
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Subscribe to state changes. Returns an unsubscribe function.
|
|
276
|
+
Each state domain has its own typed subscriber. All `on*StateChange` methods return an unsubscribe function.
|
|
118
277
|
|
|
119
278
|
```ts
|
|
120
|
-
const
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// entry.data — optional payload
|
|
125
|
-
// entry.level — 'info' | 'warn' | 'error'
|
|
126
|
-
// entry.ts — timestamp (ms)
|
|
279
|
+
const unsubAuth = client.onAuthStateChange((state) => {
|
|
280
|
+
// state.step — 'idle' | 'oauth' | 'email' | 'wallet' | 'success' | 'error'
|
|
281
|
+
// state.session — PollarPersistedSession (when step === 'success')
|
|
282
|
+
// state.errorCode — AuthErrorCode (when step === 'error')
|
|
127
283
|
});
|
|
128
284
|
|
|
129
|
-
|
|
130
|
-
|
|
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 */ });
|
|
289
|
+
|
|
290
|
+
unsubAuth();
|
|
131
291
|
```
|
|
132
292
|
|
|
133
|
-
|
|
293
|
+
Snapshot getters are also available: `getAuthState()`, `getTransactionState()`, `getTxHistoryState()`,
|
|
294
|
+
`getWalletBalanceState()`, `getNetworkState()`.
|
|
295
|
+
|
|
296
|
+
Error codes for the auth flow are surfaced via `AUTH_ERROR_CODES` / `AuthErrorCode`:
|
|
134
297
|
|
|
135
298
|
```ts
|
|
136
|
-
import {
|
|
299
|
+
import { AUTH_ERROR_CODES, type AuthErrorCode } from '@pollar/core';
|
|
137
300
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
301
|
+
// AUTH_ERROR_CODES.EMAIL_CODE_INVALID
|
|
302
|
+
// AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED
|
|
303
|
+
// AUTH_ERROR_CODES.SESSION_CREATE_FAILED
|
|
304
|
+
// AUTH_ERROR_CODES.WALLET_CONNECT_FAILED
|
|
305
|
+
// …see types.ts for the full list
|
|
141
306
|
```
|
|
142
307
|
|
|
143
308
|
---
|
|
@@ -178,6 +343,50 @@ if (available) {
|
|
|
178
343
|
}
|
|
179
344
|
```
|
|
180
345
|
|
|
346
|
+
To plug in external wallet stacks (e.g. Stellar Wallets Kit) without `@pollar/core` having to depend on them, pass a
|
|
347
|
+
`WalletAdapterResolver` to the client:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { PollarClient, WalletType } from '@pollar/core';
|
|
351
|
+
import { stellarWalletsKit } from '@pollar/stellar-wallets-kit-adapter';
|
|
352
|
+
import { Networks } from '@creit.tech/stellar-wallets-kit';
|
|
353
|
+
|
|
354
|
+
const client = new PollarClient({
|
|
355
|
+
apiKey: 'pk_...',
|
|
356
|
+
walletAdapter: stellarWalletsKit({ network: Networks.PUBLIC }),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
client.loginWallet('xbull'); // any string id the kit understands
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The resolver signature is:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
type WalletAdapterResolver = (id: WalletId) => WalletAdapter | Promise<WalletAdapter>;
|
|
366
|
+
type WalletId = WalletType | (string & {});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Custom adapters (`AdapterFn` / `PollarAdapter`)
|
|
372
|
+
|
|
373
|
+
Generic adapter contract for wrapping external signing flows (e.g. Trustless Work SDK). Adapter functions receive
|
|
374
|
+
params and return an unsigned XDR; the client handles signing and submission.
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import type { AdapterFn, PollarAdapter, PollarAdapters } from '@pollar/core';
|
|
378
|
+
|
|
379
|
+
const trustlessWork: PollarAdapter = {
|
|
380
|
+
initialize: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
|
|
381
|
+
release: (async (params) => ({ unsignedTransaction: '...' })) satisfies AdapterFn,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const adapters: PollarAdapters = { trustlessWork };
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
> **Renamed in 0.7.0** — `EscrowFn` → `AdapterFn` and `EscrowAdapter` → `PollarAdapter`. Runtime contract is unchanged;
|
|
388
|
+
> rename your imports.
|
|
389
|
+
|
|
181
390
|
## TypeScript
|
|
182
391
|
|
|
183
392
|
`@pollar/core` is written in TypeScript and ships full type declarations.
|
|
@@ -186,20 +395,45 @@ Key exported types:
|
|
|
186
395
|
|
|
187
396
|
```ts
|
|
188
397
|
import type {
|
|
398
|
+
// Client
|
|
189
399
|
PollarClientConfig,
|
|
190
400
|
PollarLoginOptions,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
401
|
+
PollarPersistedSession,
|
|
402
|
+
PollarUserProfile,
|
|
403
|
+
AuthState,
|
|
404
|
+
AuthErrorCode,
|
|
405
|
+
|
|
406
|
+
// Storage / keys / DPoP
|
|
407
|
+
Storage,
|
|
408
|
+
OnStorageDegrade,
|
|
409
|
+
StorageDegradeReason,
|
|
410
|
+
KeyManager,
|
|
411
|
+
PublicEcJwk,
|
|
412
|
+
BuildProofArgs,
|
|
413
|
+
|
|
414
|
+
// Sessions
|
|
415
|
+
SessionInfo,
|
|
416
|
+
|
|
417
|
+
// Wallets
|
|
418
|
+
WalletType,
|
|
419
|
+
WalletId,
|
|
420
|
+
WalletAdapter,
|
|
421
|
+
WalletAdapterResolver,
|
|
422
|
+
|
|
423
|
+
// Adapters (renamed from Escrow*)
|
|
424
|
+
AdapterFn,
|
|
425
|
+
PollarAdapter,
|
|
426
|
+
PollarAdapters,
|
|
427
|
+
|
|
428
|
+
// Stellar
|
|
197
429
|
StellarNetwork,
|
|
430
|
+
StellarClientConfig,
|
|
198
431
|
StellarBalance,
|
|
199
|
-
GetBalancesResult,
|
|
200
432
|
} from '@pollar/core';
|
|
433
|
+
|
|
434
|
+
import { AUTH_ERROR_CODES } from '@pollar/core';
|
|
201
435
|
```
|
|
202
436
|
|
|
203
437
|
## License
|
|
204
438
|
|
|
205
|
-
MIT
|
|
439
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { S as Storage } from '../types-DqgJIJBl.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hard cap per stored value. Generously above what the SDK actually writes
|
|
5
|
+
* (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within
|
|
6
|
+
* iOS Keychain's practical limit. Refuses oversized writes loudly rather
|
|
7
|
+
* than letting the platform truncate or silently fail.
|
|
8
|
+
*/
|
|
9
|
+
declare const SECURE_STORE_MAX_VALUE_BYTES = 4096;
|
|
10
|
+
interface SecureStoreAdapterOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Override the iOS Keychain accessibility class. Defaults to
|
|
13
|
+
* `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.
|
|
14
|
+
* On Android this is a no-op (the platform manages access via Keystore).
|
|
15
|
+
*/
|
|
16
|
+
keychainAccessible?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a `Storage` adapter backed by Expo SecureStore.
|
|
20
|
+
*
|
|
21
|
+
* Throws synchronously (via the returned Promise) at construction time if
|
|
22
|
+
* `expo-secure-store` cannot be loaded.
|
|
23
|
+
*/
|
|
24
|
+
declare function createSecureStoreAdapter(options?: SecureStoreAdapterOptions): Promise<Storage>;
|
|
25
|
+
|
|
26
|
+
export { SECURE_STORE_MAX_VALUE_BYTES, type SecureStoreAdapterOptions, createSecureStoreAdapter };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { S as Storage } from '../types-DqgJIJBl.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hard cap per stored value. Generously above what the SDK actually writes
|
|
5
|
+
* (sessions ≈ 600–800 bytes, private scalars ≈ 43 chars), and well within
|
|
6
|
+
* iOS Keychain's practical limit. Refuses oversized writes loudly rather
|
|
7
|
+
* than letting the platform truncate or silently fail.
|
|
8
|
+
*/
|
|
9
|
+
declare const SECURE_STORE_MAX_VALUE_BYTES = 4096;
|
|
10
|
+
interface SecureStoreAdapterOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Override the iOS Keychain accessibility class. Defaults to
|
|
13
|
+
* `WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded module.
|
|
14
|
+
* On Android this is a no-op (the platform manages access via Keystore).
|
|
15
|
+
*/
|
|
16
|
+
keychainAccessible?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a `Storage` adapter backed by Expo SecureStore.
|
|
20
|
+
*
|
|
21
|
+
* Throws synchronously (via the returned Promise) at construction time if
|
|
22
|
+
* `expo-secure-store` cannot be loaded.
|
|
23
|
+
*/
|
|
24
|
+
declare function createSecureStoreAdapter(options?: SecureStoreAdapterOptions): Promise<Storage>;
|
|
25
|
+
|
|
26
|
+
export { SECURE_STORE_MAX_VALUE_BYTES, type SecureStoreAdapterOptions, createSecureStoreAdapter };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/expo-secure-store.ts
|
|
4
|
+
var SECURE_STORE_MAX_VALUE_BYTES = 4096;
|
|
5
|
+
async function loadSecureStore() {
|
|
6
|
+
try {
|
|
7
|
+
const mod = await import('expo-secure-store');
|
|
8
|
+
return mod;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
const message = `[PollarClient:storage] Failed to load 'expo-secure-store'. Install it in your Expo app: \`npx expo install expo-secure-store\`. Original error: ${error instanceof Error ? error.message : String(error)}`;
|
|
11
|
+
throw new Error(message);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function utf8ByteLength(value) {
|
|
15
|
+
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(value).length;
|
|
16
|
+
let bytes = 0;
|
|
17
|
+
for (let i = 0; i < value.length; i++) {
|
|
18
|
+
const code = value.charCodeAt(i);
|
|
19
|
+
if (code < 128) bytes += 1;
|
|
20
|
+
else if (code < 2048) bytes += 2;
|
|
21
|
+
else if (code >= 55296 && code <= 56319) {
|
|
22
|
+
bytes += 4;
|
|
23
|
+
i++;
|
|
24
|
+
} else bytes += 3;
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
|
28
|
+
async function createSecureStoreAdapter(options = {}) {
|
|
29
|
+
const SecureStore = await loadSecureStore();
|
|
30
|
+
const accessible = options.keychainAccessible !== void 0 ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
|
|
31
|
+
return {
|
|
32
|
+
async get(key) {
|
|
33
|
+
return SecureStore.getItemAsync(key);
|
|
34
|
+
},
|
|
35
|
+
async set(key, value) {
|
|
36
|
+
const size = utf8ByteLength(value);
|
|
37
|
+
if (size > SECURE_STORE_MAX_VALUE_BYTES) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`[PollarClient:storage] Value for "${key}" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
await SecureStore.setItemAsync(key, value, accessible !== void 0 ? { keychainAccessible: accessible } : void 0);
|
|
43
|
+
},
|
|
44
|
+
async remove(key) {
|
|
45
|
+
await SecureStore.deleteItemAsync(key);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.SECURE_STORE_MAX_VALUE_BYTES = SECURE_STORE_MAX_VALUE_BYTES;
|
|
51
|
+
exports.createSecureStoreAdapter = createSecureStoreAdapter;
|
|
52
|
+
//# sourceMappingURL=expo-secure-store.js.map
|
|
53
|
+
//# sourceMappingURL=expo-secure-store.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/adapters/expo-secure-store.ts
|
|
2
|
+
var SECURE_STORE_MAX_VALUE_BYTES = 4096;
|
|
3
|
+
async function loadSecureStore() {
|
|
4
|
+
try {
|
|
5
|
+
const mod = await import('expo-secure-store');
|
|
6
|
+
return mod;
|
|
7
|
+
} catch (error) {
|
|
8
|
+
const message = `[PollarClient:storage] Failed to load 'expo-secure-store'. Install it in your Expo app: \`npx expo install expo-secure-store\`. Original error: ${error instanceof Error ? error.message : String(error)}`;
|
|
9
|
+
throw new Error(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function utf8ByteLength(value) {
|
|
13
|
+
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(value).length;
|
|
14
|
+
let bytes = 0;
|
|
15
|
+
for (let i = 0; i < value.length; i++) {
|
|
16
|
+
const code = value.charCodeAt(i);
|
|
17
|
+
if (code < 128) bytes += 1;
|
|
18
|
+
else if (code < 2048) bytes += 2;
|
|
19
|
+
else if (code >= 55296 && code <= 56319) {
|
|
20
|
+
bytes += 4;
|
|
21
|
+
i++;
|
|
22
|
+
} else bytes += 3;
|
|
23
|
+
}
|
|
24
|
+
return bytes;
|
|
25
|
+
}
|
|
26
|
+
async function createSecureStoreAdapter(options = {}) {
|
|
27
|
+
const SecureStore = await loadSecureStore();
|
|
28
|
+
const accessible = options.keychainAccessible !== void 0 ? options.keychainAccessible : SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
|
|
29
|
+
return {
|
|
30
|
+
async get(key) {
|
|
31
|
+
return SecureStore.getItemAsync(key);
|
|
32
|
+
},
|
|
33
|
+
async set(key, value) {
|
|
34
|
+
const size = utf8ByteLength(value);
|
|
35
|
+
if (size > SECURE_STORE_MAX_VALUE_BYTES) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[PollarClient:storage] Value for "${key}" is ${size} bytes, exceeds SecureStore limit ${SECURE_STORE_MAX_VALUE_BYTES}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
await SecureStore.setItemAsync(key, value, accessible !== void 0 ? { keychainAccessible: accessible } : void 0);
|
|
41
|
+
},
|
|
42
|
+
async remove(key) {
|
|
43
|
+
await SecureStore.deleteItemAsync(key);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { SECURE_STORE_MAX_VALUE_BYTES, createSecureStoreAdapter };
|
|
49
|
+
//# sourceMappingURL=expo-secure-store.mjs.map
|
|
50
|
+
//# sourceMappingURL=expo-secure-store.mjs.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { S as Storage } from '../types-DqgJIJBl.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hard cap per stored value. iOS Keychain has no formal byte limit but
|
|
5
|
+
* practical limits sit a few KB; we refuse oversized writes loudly.
|
|
6
|
+
*/
|
|
7
|
+
declare const KEYCHAIN_MAX_VALUE_BYTES = 4096;
|
|
8
|
+
interface KeychainAdapterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Override the iOS Keychain accessibility class. Defaults to
|
|
11
|
+
* `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded
|
|
12
|
+
* module — that prevents iCloud Keychain backup from carrying the SDK's
|
|
13
|
+
* private key material to another device.
|
|
14
|
+
*/
|
|
15
|
+
accessible?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a `Storage` adapter backed by `react-native-keychain`.
|
|
19
|
+
*
|
|
20
|
+
* Throws (via the returned Promise) at construction time if the package
|
|
21
|
+
* cannot be loaded.
|
|
22
|
+
*/
|
|
23
|
+
declare function createKeychainAdapter(options?: KeychainAdapterOptions): Promise<Storage>;
|
|
24
|
+
|
|
25
|
+
export { KEYCHAIN_MAX_VALUE_BYTES, type KeychainAdapterOptions, createKeychainAdapter };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { S as Storage } from '../types-DqgJIJBl.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hard cap per stored value. iOS Keychain has no formal byte limit but
|
|
5
|
+
* practical limits sit a few KB; we refuse oversized writes loudly.
|
|
6
|
+
*/
|
|
7
|
+
declare const KEYCHAIN_MAX_VALUE_BYTES = 4096;
|
|
8
|
+
interface KeychainAdapterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Override the iOS Keychain accessibility class. Defaults to
|
|
11
|
+
* `ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY` when available on the loaded
|
|
12
|
+
* module — that prevents iCloud Keychain backup from carrying the SDK's
|
|
13
|
+
* private key material to another device.
|
|
14
|
+
*/
|
|
15
|
+
accessible?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a `Storage` adapter backed by `react-native-keychain`.
|
|
19
|
+
*
|
|
20
|
+
* Throws (via the returned Promise) at construction time if the package
|
|
21
|
+
* cannot be loaded.
|
|
22
|
+
*/
|
|
23
|
+
declare function createKeychainAdapter(options?: KeychainAdapterOptions): Promise<Storage>;
|
|
24
|
+
|
|
25
|
+
export { KEYCHAIN_MAX_VALUE_BYTES, type KeychainAdapterOptions, createKeychainAdapter };
|