@symbiote-native/secure-store 0.0.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/core/index.d.ts +2 -0
  6. package/build/core/index.js +1 -0
  7. package/build/core/native-module.d.ts +17 -0
  8. package/build/core/native-module.js +3 -0
  9. package/build/core/secure-store.d.ts +83 -0
  10. package/build/core/secure-store.js +137 -0
  11. package/build/core/types.d.ts +43 -0
  12. package/build/core/types.js +1 -0
  13. package/build/react/index.d.ts +1 -0
  14. package/build/react/index.js +5 -0
  15. package/build/vue/index.d.ts +1 -0
  16. package/build/vue/index.js +4 -0
  17. package/build-ngc/angular/index.d.ts +1 -0
  18. package/build-ngc/angular/index.js +5 -0
  19. package/build-ngc/angular/index.js.map +1 -0
  20. package/build-ngc/core/index.d.ts +2 -0
  21. package/build-ngc/core/index.js +2 -0
  22. package/build-ngc/core/index.js.map +1 -0
  23. package/build-ngc/core/native-module.d.ts +17 -0
  24. package/build-ngc/core/native-module.js +4 -0
  25. package/build-ngc/core/native-module.js.map +1 -0
  26. package/build-ngc/core/secure-store.d.ts +83 -0
  27. package/build-ngc/core/secure-store.js +138 -0
  28. package/build-ngc/core/secure-store.js.map +1 -0
  29. package/build-ngc/core/types.d.ts +43 -0
  30. package/build-ngc/core/types.js +2 -0
  31. package/build-ngc/core/types.js.map +1 -0
  32. package/native-link.json +21 -0
  33. package/package.json +107 -0
  34. package/src/angular/index.ts +4 -0
  35. package/src/core/index.ts +17 -0
  36. package/src/core/native-module.ts +31 -0
  37. package/src/core/secure-store.test.ts +146 -0
  38. package/src/core/secure-store.ts +177 -0
  39. package/src/core/types.ts +44 -0
  40. package/src/react/index.ts +5 -0
  41. package/src/vue/index.ts +4 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @symbiote-native/secure-store
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-secure-store`](https://github.com/expo/expo/tree/main/packages/expo-secure-store) —
5
+ encrypted key-value storage in the iOS Keychain and the Android Keystore, optionally gated behind
6
+ the device's own biometrics — usable from **every** adapter, React, Vue, and Angular, not just
7
+ React. Built the same way as [`@symbiote-native/local-auth`](../local-auth): an
8
+ `expo-modules-core`-based wrapper (see the `symbiote-expo-native-module` project skill for the
9
+ full mechanism — why `expo-modules-core` is depended on directly and never the `expo`
10
+ meta-package, why the upstream JS is hand-ported into `core/` rather than imported, and how
11
+ autolinking picks up the native module).
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @symbiote-native/secure-store
17
+ ```
18
+
19
+ `expo-secure-store` and `expo-modules-core` come along as regular, pinned dependencies — never
20
+ install either yourself, and never add the `expo` meta-package to this project (it bundles its
21
+ own Metro/Babel pipeline that conflicts with this project's own).
22
+
23
+ ### Required one-time step: native autolinking wiring
24
+
25
+ Unlike a plain RN native module, `expo-secure-store`'s native code is discovered by
26
+ `expo-modules-autolinking` — this needs wiring into the native host app **once**, covering this
27
+ package and every other `expo-modules-core` package with zero further changes:
28
+
29
+ | Platform | Touches |
30
+ |---|---|
31
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
32
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
33
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
34
+ | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a native-module name map |
35
+
36
+ Full mechanics live in the `symbiote-expo-native-module` skill. The per-package half of that
37
+ table — the Gradle dependency, the module map entry, the `NSFaceIDUsageDescription` string, and
38
+ the Android backup rules below — is generated by
39
+ [`@symbiote-native/expo-modules-link`](../expo-modules-link) from this package's
40
+ `native-link.json` on every install.
41
+
42
+ ### Android: Auto Backup must exclude the store
43
+
44
+ `native-link.json` asks the linker to set two attributes on your app's `<application>` element:
45
+
46
+ ```xml
47
+ android:fullBackupContent="@xml/secure_store_backup_rules"
48
+ android:dataExtractionRules="@xml/secure_store_data_extraction_rules"
49
+ ```
50
+
51
+ Both rule files ship inside `expo-secure-store` itself and merge in automatically; only the two
52
+ attributes have to live in your own manifest. They matter: Android Auto Backup would otherwise
53
+ upload the encrypted entries while leaving the Keystore keys that decrypt them behind, and a
54
+ restore onto a new device would hand the app values it can no longer read. If your app already
55
+ sets either attribute, the linker keeps yours and prints a notice — merge the rules yourself in
56
+ that case (Expo's own [SecureStore docs](https://docs.expo.dev/versions/latest/sdk/securestore/)
57
+ describe the rule files).
58
+
59
+ ## Shape
60
+
61
+ ```
62
+ src/core/ the whole API: seven keychain-accessibility constants plus the
63
+ get/set/delete surface. native-module.ts resolves ExpoSecureStore
64
+ through expo-modules-core's requireNativeModule.
65
+ src/react/ @symbiote-native/secure-store/react
66
+ src/vue/ @symbiote-native/secure-store/vue
67
+ src/angular/ @symbiote-native/secure-store/angular
68
+ ```
69
+
70
+ All three adapter entries are plain re-exports of `core/`. Upstream ships free functions and
71
+ constants — no per-instance state, no event stream — so there is nothing for a hook, composable,
72
+ or service to wrap, the same reason [`@symbiote-native/local-auth`](../local-auth) re-exports
73
+ rather than wraps. Import from `@symbiote-native/secure-store` directly if you don't care which
74
+ adapter you're on; the per-adapter subpaths exist so every wrapper package has the same import
75
+ surface.
76
+
77
+ ## Use it
78
+
79
+ ```ts
80
+ import * as SecureStore from '@symbiote-native/secure-store';
81
+
82
+ await SecureStore.setItemAsync('session-token', token);
83
+ const stored = await SecureStore.getItemAsync('session-token'); // string | null
84
+ await SecureStore.deleteItemAsync('session-token');
85
+ ```
86
+
87
+ Behind the device's own biometrics or passcode:
88
+
89
+ ```ts
90
+ if (SecureStore.canUseBiometricAuthentication()) {
91
+ await SecureStore.setItemAsync('session-token', token, {
92
+ requireAuthentication: true,
93
+ authenticationPrompt: 'Unlock your saved session',
94
+ });
95
+ }
96
+ ```
97
+
98
+ The prompt fires at different moments per platform: Android authenticates on every operation, iOS
99
+ only when reading or updating an entry that already exists. A simulator or emulator does not
100
+ enforce it at all — this option can only be verified on a real device.
101
+
102
+ Values are strings. JSON-encode anything else:
103
+
104
+ ```ts
105
+ await SecureStore.setItemAsync('profile', JSON.stringify(profile));
106
+ const profile = JSON.parse((await SecureStore.getItemAsync('profile')) ?? 'null');
107
+ ```
108
+
109
+ ## API
110
+
111
+ | Export | Signature | Notes |
112
+ |---|---|---|
113
+ | `isAvailableAsync` | `() => Promise<boolean>` | `true` on Android and iOS. Says nothing about permissions. |
114
+ | `getItemAsync` | `(key, options?) => Promise<string \| null>` | `null` when there is no entry, or when the key has been invalidated. |
115
+ | `getItem` | `(key, options?) => string \| null` | Blocks the JS thread. |
116
+ | `setItemAsync` | `(key, value, options?) => Promise<void>` | Rejects if the value cannot be stored. |
117
+ | `setItem` | `(key, value, options?) => void` | Blocks the JS thread. |
118
+ | `deleteItemAsync` | `(key, options?) => Promise<void>` | |
119
+ | `canUseBiometricAuthentication` | `() => boolean` | Whether `requireAuthentication` can be used at all. |
120
+ | `AFTER_FIRST_UNLOCK`, `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`, `ALWAYS`, `ALWAYS_THIS_DEVICE_ONLY`, `WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`, `WHEN_UNLOCKED`, `WHEN_UNLOCKED_THIS_DEVICE_ONLY` | `IKeychainAccessibilityConstant \| undefined` | Values for `options.keychainAccessible`. iOS only — Android's native module declares none of them, so they read `undefined` there. `ALWAYS` and `ALWAYS_THIS_DEVICE_ONLY` are deprecated upstream. |
121
+
122
+ `ISecureStoreOptions`: `keychainService`, `requireAuthentication`, `authenticationPrompt`,
123
+ `keychainAccessible` (iOS), `accessGroup` (iOS).
124
+
125
+ ## Notes
126
+
127
+ - **Keys are validated before the native call.** Alphanumerics plus `.`, `-` and `_`, non-empty —
128
+ anything else throws with a readable message rather than failing inside the keychain.
129
+ - **An invalidated key is gone for good.** The system invalidates entries stored with
130
+ `requireAuthentication` whenever enrolled biometrics change (a new fingerprint, a re-registered
131
+ face). `getItemAsync` then resolves `null` — treat it as "the user must sign in again", not as
132
+ an error to retry.
133
+ - **`requireAuthentication` does not combine with a shared `keychainService`.** The full behavior
134
+ needs a freshly generated key, so reuse of a service already holding non-authenticated entries
135
+ gives partial behavior. Upstream documents the same limitation.
136
+
137
+ ## Test it
138
+
139
+ ```bash
140
+ pnpm vitest run packages/secure-store
141
+ ```
142
+
143
+ The core tests fake the native module in place of `requireNativeModule`'s runtime resolution —
144
+ `ExpoSecureStore` only exists on a device, so a headless run would otherwise throw at import.
145
+ Real storage, biometrics, and the backup rules can only be verified on a device or emulator.
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/secure-store/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1,2 @@
1
+ export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
2
+ export type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
@@ -0,0 +1 @@
1
+ export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
@@ -0,0 +1,17 @@
1
+ import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
2
+ export type INativeSecureStoreModule = {
3
+ AFTER_FIRST_UNLOCK?: IKeychainAccessibilityConstant;
4
+ AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
5
+ ALWAYS?: IKeychainAccessibilityConstant;
6
+ ALWAYS_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
7
+ WHEN_PASSCODE_SET_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
8
+ WHEN_UNLOCKED?: IKeychainAccessibilityConstant;
9
+ WHEN_UNLOCKED_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
10
+ getValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<string | null>;
11
+ getValueWithKeySync?(key: string, options: ISecureStoreOptions): string | null;
12
+ setValueWithKeyAsync?(value: string, key: string, options: ISecureStoreOptions): Promise<boolean>;
13
+ setValueWithKeySync?(value: string, key: string, options: ISecureStoreOptions): boolean;
14
+ deleteValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<void>;
15
+ canUseBiometricAuthentication?(): boolean;
16
+ };
17
+ export declare const expoSecureStore: INativeSecureStoreModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_SECURE_STORE_MODULE_NAME = 'ExpoSecureStore';
3
+ export const expoSecureStore = requireNativeModule(EXPO_SECURE_STORE_MODULE_NAME);
@@ -0,0 +1,83 @@
1
+ import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
2
+ /**
3
+ * The item cannot be read after a restart until the device has been unlocked once. Useful for
4
+ * data the app needs while the phone is locked.
5
+ * @platform ios
6
+ */
7
+ export declare const AFTER_FIRST_UNLOCK: IKeychainAccessibilityConstant | undefined;
8
+ /**
9
+ * Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
10
+ * from a backup.
11
+ * @platform ios
12
+ */
13
+ export declare const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
14
+ /**
15
+ * The item can always be read, locked device or not. The least secure option.
16
+ * @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
17
+ * @platform ios
18
+ */
19
+ export declare const ALWAYS: IKeychainAccessibilityConstant | undefined;
20
+ /**
21
+ * Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
22
+ * @deprecated Use a level that offers some user protection, such as
23
+ * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
24
+ * @platform ios
25
+ */
26
+ export declare const ALWAYS_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
27
+ /**
28
+ * Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
29
+ * entry at all. Removing the passcode deletes the entry.
30
+ * @platform ios
31
+ */
32
+ export declare const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
33
+ /**
34
+ * The item can only be read while the device is unlocked.
35
+ * @platform ios
36
+ */
37
+ export declare const WHEN_UNLOCKED: IKeychainAccessibilityConstant | undefined;
38
+ /**
39
+ * Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
40
+ * backup.
41
+ * @platform ios
42
+ */
43
+ export declare const WHEN_UNLOCKED_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
44
+ /**
45
+ * Whether the SecureStore API is usable on this device. Says nothing about app permissions.
46
+ * Resolves `true` on Android and iOS.
47
+ */
48
+ export declare function isAvailableAsync(): Promise<boolean>;
49
+ /**
50
+ * Read the value stored under `key`.
51
+ *
52
+ * Resolves `null` when there is no entry for the key, or when the key has been invalidated —
53
+ * the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
54
+ * change (a new fingerprint, a re-registered face), and an invalidated value can never be read
55
+ * again. Rejects if reading itself fails.
56
+ */
57
+ export declare function getItemAsync(key: string, options?: ISecureStoreOptions): Promise<string | null>;
58
+ /**
59
+ * Read the value stored under `key`, synchronously.
60
+ *
61
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
62
+ * > until the user authenticates.
63
+ */
64
+ export declare function getItem(key: string, options?: ISecureStoreOptions): string | null;
65
+ /**
66
+ * Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
67
+ * Rejects if the value cannot be stored on the device.
68
+ */
69
+ export declare function setItemAsync(key: string, value: string, options?: ISecureStoreOptions): Promise<void>;
70
+ /**
71
+ * Store a key–value pair, synchronously.
72
+ *
73
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
74
+ * > until the user authenticates.
75
+ */
76
+ export declare function setItem(key: string, value: string, options?: ISecureStoreOptions): void;
77
+ /** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
78
+ export declare function deleteItemAsync(key: string, options?: ISecureStoreOptions): Promise<void>;
79
+ /**
80
+ * Whether a value can be stored with `requireAuthentication` — `true` when the device supports
81
+ * biometric authentication and the enrolled method is strong enough.
82
+ */
83
+ export declare function canUseBiometricAuthentication(): boolean;
@@ -0,0 +1,137 @@
1
+ import { UnavailabilityError } from 'expo-modules-core';
2
+ import { expoSecureStore } from './native-module';
3
+ const NATIVE_MODULE_NAME = 'expo-secure-store';
4
+ // Alphanumerics plus ".", "-" and "_": the same set the native side validates against, so an
5
+ // invalid key fails here with a readable message instead of deep inside the keychain call.
6
+ const VALID_KEY_PATTERN = /^[\w.-]+$/;
7
+ /**
8
+ * The item cannot be read after a restart until the device has been unlocked once. Useful for
9
+ * data the app needs while the phone is locked.
10
+ * @platform ios
11
+ */
12
+ export const AFTER_FIRST_UNLOCK = expoSecureStore.AFTER_FIRST_UNLOCK;
13
+ /**
14
+ * Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
15
+ * from a backup.
16
+ * @platform ios
17
+ */
18
+ export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY = expoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
19
+ /**
20
+ * The item can always be read, locked device or not. The least secure option.
21
+ * @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
22
+ * @platform ios
23
+ */
24
+ export const ALWAYS = expoSecureStore.ALWAYS;
25
+ /**
26
+ * Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
27
+ * @deprecated Use a level that offers some user protection, such as
28
+ * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
29
+ * @platform ios
30
+ */
31
+ export const ALWAYS_THIS_DEVICE_ONLY = expoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
32
+ /**
33
+ * Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
34
+ * entry at all. Removing the passcode deletes the entry.
35
+ * @platform ios
36
+ */
37
+ export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY = expoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
38
+ /**
39
+ * The item can only be read while the device is unlocked.
40
+ * @platform ios
41
+ */
42
+ export const WHEN_UNLOCKED = expoSecureStore.WHEN_UNLOCKED;
43
+ /**
44
+ * Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
45
+ * backup.
46
+ * @platform ios
47
+ */
48
+ export const WHEN_UNLOCKED_THIS_DEVICE_ONLY = expoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
49
+ /**
50
+ * Whether the SecureStore API is usable on this device. Says nothing about app permissions.
51
+ * Resolves `true` on Android and iOS.
52
+ */
53
+ export async function isAvailableAsync() {
54
+ return !!expoSecureStore.getValueWithKeyAsync;
55
+ }
56
+ /**
57
+ * Read the value stored under `key`.
58
+ *
59
+ * Resolves `null` when there is no entry for the key, or when the key has been invalidated —
60
+ * the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
61
+ * change (a new fingerprint, a re-registered face), and an invalidated value can never be read
62
+ * again. Rejects if reading itself fails.
63
+ */
64
+ export async function getItemAsync(key, options = {}) {
65
+ ensureValidKey(key);
66
+ if (!expoSecureStore.getValueWithKeyAsync) {
67
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItemAsync');
68
+ }
69
+ return expoSecureStore.getValueWithKeyAsync(key, options);
70
+ }
71
+ /**
72
+ * Read the value stored under `key`, synchronously.
73
+ *
74
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
75
+ * > until the user authenticates.
76
+ */
77
+ export function getItem(key, options = {}) {
78
+ ensureValidKey(key);
79
+ if (!expoSecureStore.getValueWithKeySync) {
80
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItem');
81
+ }
82
+ return expoSecureStore.getValueWithKeySync(key, options);
83
+ }
84
+ /**
85
+ * Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
86
+ * Rejects if the value cannot be stored on the device.
87
+ */
88
+ export async function setItemAsync(key, value, options = {}) {
89
+ ensureValidKey(key);
90
+ ensureValidValue(value);
91
+ if (!expoSecureStore.setValueWithKeyAsync) {
92
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItemAsync');
93
+ }
94
+ await expoSecureStore.setValueWithKeyAsync(value, key, options);
95
+ }
96
+ /**
97
+ * Store a key–value pair, synchronously.
98
+ *
99
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
100
+ * > until the user authenticates.
101
+ */
102
+ export function setItem(key, value, options = {}) {
103
+ ensureValidKey(key);
104
+ ensureValidValue(value);
105
+ if (!expoSecureStore.setValueWithKeySync) {
106
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItem');
107
+ }
108
+ expoSecureStore.setValueWithKeySync(value, key, options);
109
+ }
110
+ /** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
111
+ export async function deleteItemAsync(key, options = {}) {
112
+ ensureValidKey(key);
113
+ if (!expoSecureStore.deleteValueWithKeyAsync) {
114
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'deleteItemAsync');
115
+ }
116
+ await expoSecureStore.deleteValueWithKeyAsync(key, options);
117
+ }
118
+ /**
119
+ * Whether a value can be stored with `requireAuthentication` — `true` when the device supports
120
+ * biometric authentication and the enrolled method is strong enough.
121
+ */
122
+ export function canUseBiometricAuthentication() {
123
+ if (!expoSecureStore.canUseBiometricAuthentication) {
124
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'canUseBiometricAuthentication');
125
+ }
126
+ return expoSecureStore.canUseBiometricAuthentication();
127
+ }
128
+ function ensureValidKey(key) {
129
+ if (typeof key !== 'string' || !VALID_KEY_PATTERN.test(key)) {
130
+ throw new Error('Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, ".", "-", and "_".');
131
+ }
132
+ }
133
+ function ensureValidValue(value) {
134
+ if (typeof value !== 'string') {
135
+ throw new Error('Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.');
136
+ }
137
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * An iOS [`kSecAttrAccessible`](https://developer.apple.com/documentation/security/ksecattraccessible/)
3
+ * value, as exposed by the native module. Opaque on purpose — pass one of the constants this
4
+ * package exports rather than a literal.
5
+ */
6
+ export type IKeychainAccessibilityConstant = number;
7
+ export type ISecureStoreOptions = {
8
+ /**
9
+ * - Android: equivalent of the public/private key pair `Alias`.
10
+ * - iOS: the item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
11
+ *
12
+ * An item stored with a `keychainService` needs the same one to be read back.
13
+ */
14
+ keychainService?: string;
15
+ /**
16
+ * Require the device's own authentication (biometrics or passcode) to reach the stored value.
17
+ * - Android: [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)).
18
+ * - iOS: [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
19
+ *
20
+ * The two platforms prompt at different moments: Android authenticates on every operation,
21
+ * iOS only when reading or updating an existing value, never when creating one. The full
22
+ * behavior needs a freshly generated key, so it does not combine with a `keychainService`
23
+ * already used for non-authenticated entries.
24
+ *
25
+ * > Emulators and simulators do not enforce the prompt when retrieving a secret — testing this
26
+ * > option means testing on a real device.
27
+ */
28
+ requireAuthentication?: boolean;
29
+ /** Message shown to the user in the prompt raised by `requireAuthentication`. */
30
+ authenticationPrompt?: string;
31
+ /**
32
+ * When the stored entry is accessible, via iOS's `kSecAttrAccessible` property.
33
+ * @default WHEN_UNLOCKED
34
+ * @platform ios
35
+ */
36
+ keychainAccessible?: IKeychainAccessibilityConstant;
37
+ /**
38
+ * The [access group](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps)
39
+ * the stored entry belongs to.
40
+ * @platform ios
41
+ */
42
+ accessGroup?: string;
43
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/secure-store/react: the React entry over the framework-agnostic core.
2
+ // Upstream ships free functions and seven constants, no per-instance state and no event stream
3
+ // — there is nothing for a hook to wrap, so this is a plain re-export, the same shape
4
+ // packages/local-auth's React entry has for the same reason.
5
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/secure-store/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/secure-store/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,yCAAyC;AACzC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
2
+ export type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
@@ -0,0 +1,2 @@
1
+ export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mCAAmC,EACnC,MAAM,EACN,uBAAuB,EACvB,kCAAkC,EAClC,aAAa,EACb,8BAA8B,EAC9B,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,OAAO,EACP,eAAe,EACf,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
2
+ export type INativeSecureStoreModule = {
3
+ AFTER_FIRST_UNLOCK?: IKeychainAccessibilityConstant;
4
+ AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
5
+ ALWAYS?: IKeychainAccessibilityConstant;
6
+ ALWAYS_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
7
+ WHEN_PASSCODE_SET_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
8
+ WHEN_UNLOCKED?: IKeychainAccessibilityConstant;
9
+ WHEN_UNLOCKED_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
10
+ getValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<string | null>;
11
+ getValueWithKeySync?(key: string, options: ISecureStoreOptions): string | null;
12
+ setValueWithKeyAsync?(value: string, key: string, options: ISecureStoreOptions): Promise<boolean>;
13
+ setValueWithKeySync?(value: string, key: string, options: ISecureStoreOptions): boolean;
14
+ deleteValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<void>;
15
+ canUseBiometricAuthentication?(): boolean;
16
+ };
17
+ export declare const expoSecureStore: INativeSecureStoreModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_SECURE_STORE_MODULE_NAME = 'ExpoSecureStore';
3
+ export const expoSecureStore = requireNativeModule(EXPO_SECURE_STORE_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,6BAA6B,GAAG,iBAAiB,CAAC;AAyBxD,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAChD,6BAA6B,CAC9B,CAAC"}
@@ -0,0 +1,83 @@
1
+ import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
2
+ /**
3
+ * The item cannot be read after a restart until the device has been unlocked once. Useful for
4
+ * data the app needs while the phone is locked.
5
+ * @platform ios
6
+ */
7
+ export declare const AFTER_FIRST_UNLOCK: IKeychainAccessibilityConstant | undefined;
8
+ /**
9
+ * Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
10
+ * from a backup.
11
+ * @platform ios
12
+ */
13
+ export declare const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
14
+ /**
15
+ * The item can always be read, locked device or not. The least secure option.
16
+ * @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
17
+ * @platform ios
18
+ */
19
+ export declare const ALWAYS: IKeychainAccessibilityConstant | undefined;
20
+ /**
21
+ * Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
22
+ * @deprecated Use a level that offers some user protection, such as
23
+ * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
24
+ * @platform ios
25
+ */
26
+ export declare const ALWAYS_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
27
+ /**
28
+ * Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
29
+ * entry at all. Removing the passcode deletes the entry.
30
+ * @platform ios
31
+ */
32
+ export declare const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
33
+ /**
34
+ * The item can only be read while the device is unlocked.
35
+ * @platform ios
36
+ */
37
+ export declare const WHEN_UNLOCKED: IKeychainAccessibilityConstant | undefined;
38
+ /**
39
+ * Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
40
+ * backup.
41
+ * @platform ios
42
+ */
43
+ export declare const WHEN_UNLOCKED_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
44
+ /**
45
+ * Whether the SecureStore API is usable on this device. Says nothing about app permissions.
46
+ * Resolves `true` on Android and iOS.
47
+ */
48
+ export declare function isAvailableAsync(): Promise<boolean>;
49
+ /**
50
+ * Read the value stored under `key`.
51
+ *
52
+ * Resolves `null` when there is no entry for the key, or when the key has been invalidated —
53
+ * the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
54
+ * change (a new fingerprint, a re-registered face), and an invalidated value can never be read
55
+ * again. Rejects if reading itself fails.
56
+ */
57
+ export declare function getItemAsync(key: string, options?: ISecureStoreOptions): Promise<string | null>;
58
+ /**
59
+ * Read the value stored under `key`, synchronously.
60
+ *
61
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
62
+ * > until the user authenticates.
63
+ */
64
+ export declare function getItem(key: string, options?: ISecureStoreOptions): string | null;
65
+ /**
66
+ * Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
67
+ * Rejects if the value cannot be stored on the device.
68
+ */
69
+ export declare function setItemAsync(key: string, value: string, options?: ISecureStoreOptions): Promise<void>;
70
+ /**
71
+ * Store a key–value pair, synchronously.
72
+ *
73
+ * > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
74
+ * > until the user authenticates.
75
+ */
76
+ export declare function setItem(key: string, value: string, options?: ISecureStoreOptions): void;
77
+ /** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
78
+ export declare function deleteItemAsync(key: string, options?: ISecureStoreOptions): Promise<void>;
79
+ /**
80
+ * Whether a value can be stored with `requireAuthentication` — `true` when the device supports
81
+ * biometric authentication and the enrolled method is strong enough.
82
+ */
83
+ export declare function canUseBiometricAuthentication(): boolean;