@symbiote-native/local-auth 0.0.1 → 0.2.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -24
  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 +2 -0
  7. package/build/core/local-authentication.d.ts +33 -0
  8. package/build/core/local-authentication.js +75 -0
  9. package/build/core/native-module.d.ts +13 -0
  10. package/build/core/native-module.js +3 -0
  11. package/build/core/types.d.ts +83 -0
  12. package/build/core/types.js +55 -0
  13. package/build/react/index.d.ts +1 -0
  14. package/build/react/index.js +6 -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 +3 -0
  22. package/build-ngc/core/index.js.map +1 -0
  23. package/build-ngc/core/local-authentication.d.ts +33 -0
  24. package/build-ngc/core/local-authentication.js +76 -0
  25. package/build-ngc/core/local-authentication.js.map +1 -0
  26. package/build-ngc/core/native-module.d.ts +13 -0
  27. package/build-ngc/core/native-module.js +4 -0
  28. package/build-ngc/core/native-module.js.map +1 -0
  29. package/build-ngc/core/types.d.ts +83 -0
  30. package/build-ngc/core/types.js +56 -0
  31. package/build-ngc/core/types.js.map +1 -0
  32. package/native-link.json +17 -0
  33. package/package.json +96 -3
  34. package/src/angular/index.ts +4 -0
  35. package/src/core/index.ts +16 -0
  36. package/src/core/local-authentication.ts +96 -0
  37. package/src/core/native-module.ts +27 -0
  38. package/src/core/types.ts +130 -0
  39. package/src/react/index.ts +6 -0
  40. 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 CHANGED
@@ -1,40 +1,268 @@
1
1
  # @symbiote-native/local-auth
2
2
 
3
- Planned port of [`expo-local-authentication`](https://docs.expo.dev/versions/latest/sdk/local-authentication/)
4
- for [SymbioteNative](../../README.md) — FaceID/TouchID on iOS, the Fingerprint/Biometric API on
5
- Android, reachable from every adapter (React, Vue, Angular), not just React.
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-local-authentication`](https://github.com/expo/expo/tree/main/packages/expo-local-authentication)
5
+ — FaceID/TouchID on iOS, the Fingerprint/Biometric API on Android — usable from **every**
6
+ adapter, React, Vue, and Angular, not just React. Unlike this repo's other Expo wrapper
7
+ ([`@symbiote-native/sensors`](../sensors), an `EventEmitter` + live-subscription surface),
8
+ every function here is a one-shot async call with no per-instance state, so there is no hook/
9
+ composable/service to wrap — the React, Vue, and Angular entry points are plain re-exports of
10
+ the same `core`.
6
11
 
7
- **Status: skeleton only.** This package is not implemented yet — no source, no native wiring, not
8
- functional. Published early to reserve the npm name. Built the same way as
9
- [`@symbiote-native/sensors`](../sensors), a prior `expo-modules-core`-based wrapper (see the
10
- `symbiote-expo-native-module` project skill for the full mechanism: why `expo-modules-core` is
11
- depended on directly and never the `expo` meta-package, why the upstream JS is hand-ported into
12
- `core/` rather than imported, and how autolinking picks up the native module).
12
+ ## Install
13
13
 
14
- ## Planned API surface
14
+ ```bash
15
+ npm install @symbiote-native/local-auth
16
+ ```
17
+
18
+ `expo-local-authentication` and `expo-modules-core` come along as regular dependencies, pinned
19
+ to exact versions — never install either yourself, and never add the `expo` meta-package to
20
+ your project (it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
21
+
22
+ ## Required one-time step: native autolinking wiring
23
+
24
+ Unlike a plain RN native module, `expo-local-authentication`'s native code is discovered by
25
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
26
+ into the native host app **once**, covering this package and every other
27
+ `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 hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
35
+
36
+ Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
37
+ implementation: `examples/expo-react/ios/Podfile` and
38
+ `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
39
+
40
+ Two platform permission strings ship with the native module itself — nothing to reimplement,
41
+ just add the strings your app's own Info.plist/manifest needs:
42
+
43
+ - iOS — `NSFaceIDUsageDescription` in `Info.plist` (without it, iOS silently falls back to the
44
+ device passcode instead of prompting FaceID).
45
+ - Android — `USE_BIOMETRIC` in `AndroidManifest.xml`.
46
+
47
+ ## Shape
48
+
49
+ ```
50
+ src/core/ hasHardwareAsync / isEnrolledAsync / getEnrolledLevelAsync /
51
+ supportedAuthenticationTypesAsync / authenticateAsync / cancelAuthenticate, plus
52
+ AuthenticationType, SecurityLevel, and the option/result/error types.
53
+ native-module.ts resolves the native module via expo-modules-core's
54
+ requireNativeModule.
55
+ src/react/ @symbiote-native/local-auth/react — export * from '../core'
56
+ src/vue/ @symbiote-native/local-auth/vue — export * from '../core'
57
+ src/angular/ @symbiote-native/local-auth/angular — export * from '../core'
58
+ ```
59
+
60
+ No per-adapter lifecycle wrapper exists because there's nothing to subscribe to or clean up —
61
+ each adapter entry is a single-file re-export.
62
+
63
+ ## Use it
64
+
65
+ ```tsx
66
+ // React
67
+ import { useEffect, useState } from 'react';
68
+ import { Platform, Pressable, Text, View } from '@symbiote-native/react';
69
+ import {
70
+ authenticateAsync,
71
+ cancelAuthenticate,
72
+ hasHardwareAsync,
73
+ isEnrolledAsync,
74
+ } from '@symbiote-native/local-auth/react';
75
+ import type { ILocalAuthenticationResult } from '@symbiote-native/local-auth/react';
76
+
77
+ function LocalAuthScreen() {
78
+ const [hasHardware, setHasHardware] = useState(false);
79
+ const [isEnrolled, setIsEnrolled] = useState(false);
80
+ const [authResult, setAuthResult] = useState<ILocalAuthenticationResult | null>(null);
81
+
82
+ useEffect(() => {
83
+ hasHardwareAsync().then(setHasHardware);
84
+ isEnrolledAsync().then(setIsEnrolled);
85
+ }, []);
86
+
87
+ const handleAuthenticate = () => {
88
+ authenticateAsync({ promptMessage: 'Confirm it is you' }).then(setAuthResult);
89
+ };
90
+
91
+ return (
92
+ <View>
93
+ <Text>{hasHardware && isEnrolled ? 'Ready to authenticate' : 'No biometrics enrolled'}</Text>
94
+ <Pressable onPress={handleAuthenticate}>
95
+ <Text>Authenticate</Text>
96
+ </Pressable>
97
+ {Platform.OS === 'android' && (
98
+ <Pressable onPress={() => cancelAuthenticate()}>
99
+ <Text>Cancel</Text>
100
+ </Pressable>
101
+ )}
102
+ {authResult && <Text>{authResult.success ? 'Success' : `Failed: ${authResult.error}`}</Text>}
103
+ </View>
104
+ );
105
+ }
106
+ ```
107
+
108
+ ```vue
109
+ <!-- Vue -->
110
+ <script setup lang="ts">
111
+ import { onMounted, ref } from 'vue';
112
+ import { Platform, Pressable, Text, View } from '@symbiote-native/vue';
113
+ import {
114
+ authenticateAsync,
115
+ cancelAuthenticate,
116
+ hasHardwareAsync,
117
+ isEnrolledAsync,
118
+ } from '@symbiote-native/local-auth/vue';
119
+ import type { ILocalAuthenticationResult } from '@symbiote-native/local-auth/vue';
120
+
121
+ const hasHardware = ref(false);
122
+ const isEnrolled = ref(false);
123
+ const authResult = ref<ILocalAuthenticationResult | null>(null);
124
+
125
+ onMounted(() => {
126
+ void hasHardwareAsync().then(value => (hasHardware.value = value));
127
+ void isEnrolledAsync().then(value => (isEnrolled.value = value));
128
+ });
129
+
130
+ function handleAuthenticate(): void {
131
+ void authenticateAsync({ promptMessage: 'Confirm it is you' }).then(value => {
132
+ authResult.value = value;
133
+ });
134
+ }
135
+ </script>
136
+
137
+ <template>
138
+ <View>
139
+ <Text>{{ hasHardware && isEnrolled ? 'Ready to authenticate' : 'No biometrics enrolled' }}</Text>
140
+ <Pressable @press="handleAuthenticate">
141
+ <Text>Authenticate</Text>
142
+ </Pressable>
143
+ <Pressable v-if="Platform.OS === 'android'" @press="cancelAuthenticate">
144
+ <Text>Cancel</Text>
145
+ </Pressable>
146
+ <Text v-if="authResult">{{ authResult.success ? 'Success' : `Failed: ${authResult.error}` }}</Text>
147
+ </View>
148
+ </template>
149
+ ```
150
+
151
+ ```ts
152
+ // Angular
153
+ import { Component, signal } from '@angular/core';
154
+ import { Platform, Pressable, Text, View } from '@symbiote-native/angular';
155
+ import {
156
+ authenticateAsync,
157
+ cancelAuthenticate,
158
+ hasHardwareAsync,
159
+ isEnrolledAsync,
160
+ } from '@symbiote-native/local-auth/angular';
161
+ import type { ILocalAuthenticationResult } from '@symbiote-native/local-auth/angular';
162
+
163
+ @Component({
164
+ standalone: true,
165
+ imports: [Pressable, Text, View],
166
+ template: `
167
+ <View>
168
+ <Text>{{ hasHardware() && isEnrolled() ? 'Ready to authenticate' : 'No biometrics enrolled' }}</Text>
169
+ <Pressable (press)="handleAuthenticate()">
170
+ <Text>Authenticate</Text>
171
+ </Pressable>
172
+ @if (Platform.OS === 'android') {
173
+ <Pressable (press)="handleCancel()">
174
+ <Text>Cancel</Text>
175
+ </Pressable>
176
+ }
177
+ @if (authResult(); as result) {
178
+ <Text>{{ result.success ? 'Success' : 'Failed: ' + result.error }}</Text>
179
+ }
180
+ </View>
181
+ `,
182
+ })
183
+ export class LocalAuthScreen {
184
+ readonly Platform = Platform;
185
+ readonly hasHardware = signal(false);
186
+ readonly isEnrolled = signal(false);
187
+ readonly authResult = signal<ILocalAuthenticationResult | null>(null);
188
+
189
+ constructor() {
190
+ hasHardwareAsync().then(value => this.hasHardware.set(value));
191
+ isEnrolledAsync().then(value => this.isEnrolled.set(value));
192
+ }
193
+
194
+ handleAuthenticate(): void {
195
+ authenticateAsync({ promptMessage: 'Confirm it is you' }).then(value => this.authResult.set(value));
196
+ }
197
+
198
+ handleCancel(): void {
199
+ cancelAuthenticate();
200
+ }
201
+ }
202
+ ```
203
+
204
+ There's no per-instance service to `inject()` in the Angular case — every function is a plain
205
+ free function off the core package, called straight from the constructor. All three examples are
206
+ trimmed from the real canary demo screens (`examples/expo-react/screens/LocalAuthScreen.tsx`,
207
+ `examples/expo-vue-sfc/screens/LocalAuthScreen.vue`, `examples/expo-vue-tsx/screens/LocalAuthScreen.tsx`,
208
+ `examples/expo-angular/src/screens/LocalAuthScreen.ts`), which also cover
209
+ `getEnrolledLevelAsync`/`supportedAuthenticationTypesAsync` and render a capabilities card.
210
+
211
+ ## API
15
212
 
16
213
  Free functions, no event stream, no per-instance state — upstream ships a handful of async
17
- functions and two enums, not a subscribable sensor:
214
+ functions and two enums, not a subscribable sensor, so the React/Vue/Angular entry points above
215
+ are plain re-exports of `core` with nothing adapter-specific to add.
18
216
 
19
217
  ```ts
20
218
  hasHardwareAsync(): Promise<boolean>
219
+ supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]>
21
220
  isEnrolledAsync(): Promise<boolean>
22
221
  getEnrolledLevelAsync(): Promise<SecurityLevel>
23
- supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]>
24
- authenticateAsync(options?: LocalAuthenticationOptions): Promise<LocalAuthenticationResult>
222
+ authenticateAsync(options?: ILocalAuthenticationOptions): Promise<ILocalAuthenticationResult>
25
223
  cancelAuthenticate(): Promise<void> // Android only
26
224
  ```
27
225
 
28
- Plus `AuthenticationType`, `SecurityLevel`, `LocalAuthenticationOptions`,
29
- `LocalAuthenticationResult`, `LocalAuthenticationError`, `BiometricsSecurityLevel` — ported
30
- verbatim from upstream's `LocalAuthentication.types.ts`, since they're plain data with no native or
31
- React dependency.
226
+ Plus `AuthenticationType`, `SecurityLevel`, `ILocalAuthenticationOptions`,
227
+ `ILocalAuthenticationResult`, `ILocalAuthenticationError`, `IBiometricsSecurityLevel` — ported
228
+ from upstream's `LocalAuthentication.types.ts`, renamed with this repo's `I`-prefix convention
229
+ for exported types (`ts-js-best-practices`).
230
+
231
+ ```ts
232
+ import { authenticateAsync, hasHardwareAsync } from '@symbiote-native/local-auth';
233
+ // or the framework-scoped entry points — identical surface, re-exported verbatim:
234
+ import { authenticateAsync } from '@symbiote-native/local-auth/react';
235
+ import { authenticateAsync } from '@symbiote-native/local-auth/vue';
236
+ import { authenticateAsync } from '@symbiote-native/local-auth/angular';
237
+ ```
238
+
239
+ ## Notes
240
+
241
+ - **`not_enrolled` on Android almost always means the device's own lock screen has no PIN,
242
+ pattern, or password set.** A real symptom on a fresh emulator or factory-reset device:
243
+ `authenticateAsync` resolves `{ success: false, error: 'not_enrolled', warning:
244
+ 'KeyguardManager#isDeviceSecure() returned false' }`. This is **not** a missing app
245
+ permission — the manifest permission this package needs is an ordinary build-time merge with
246
+ no runtime prompt, so there's nothing for your app to request. The fix lives on the device:
247
+ Settings → Security → Screen lock → set a PIN/pattern/password, then optionally enroll a
248
+ fingerprint (Extended Controls → Fingerprint on an emulator) to exercise the biometric path
249
+ too, not just the passcode fallback.
250
+ - **iOS silently falls back to the device passcode without `NSFaceIDUsageDescription`.** Apple
251
+ requires apps using FaceID to declare why (`Info.plist`); skip it and `authenticateAsync`
252
+ still resolves, just via the passcode prompt instead of FaceID.
253
+
254
+ ## Test it
32
255
 
33
- ## Not yet done
256
+ No Fabric/Descriptor angle at all — every function here is a pure async-function surface, never
257
+ a view or per-instance state. Tests inject a fake native-module object in place of the real
258
+ `requireNativeModule` resolution (`src/core/local-authentication.test.ts`,
259
+ `src/core/types.test.ts`, `vitest`) — no `installFabric()`, no ViewConfig. Native rendering itself
260
+ is verified on-device (see the parent [README](../../README.md) for the project's testing model).
34
261
 
35
- - `src/core` — ported types/enums + the six functions over `requireNativeModule('ExpoLocalAuthentication')`
36
- - `react`/`vue`/`angular` entry points — thin re-exports, no adapter-specific state needed
37
- - Native wiring in an example app (`use_expo_modules!`, Android Gradle autolinking,
38
- `NSFaceIDUsageDescription` in `Info.plist`, `USE_BIOMETRIC`/`USE_FINGERPRINT` on Android)
39
- - Tests, following upstream's own fake-native-module pattern (no Fabric/Descriptor angle — this
40
- wraps no native view)
262
+ Native autolinking wiring is done in the four Expo canary apps
263
+ (`examples/expo-react`, `examples/expo-vue-sfc`, `examples/expo-vue-tsx`, `examples/expo-angular`)
264
+ — iOS Podfile/`AppDelegate.swift` + `NSFaceIDUsageDescription`, Android Gradle/
265
+ `MainApplication.kt` + `USE_BIOMETRIC`, all four confirmed present. The one remaining gap: this
266
+ package isn't yet demoed in the plain, non-Expo `examples/react`/`vue-sfc`/`vue-tsx`/`angular`
267
+ canaries, since an `expo-modules-core` package needs the `expo-modules-autolinking` wiring only
268
+ the `examples/expo-*` apps have set up so far.
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/local-auth/angular: the Angular entry over the framework-agnostic core. Same
2
+ // 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/index.js';
@@ -0,0 +1,2 @@
1
+ export { hasHardwareAsync, supportedAuthenticationTypesAsync, isEnrolledAsync, getEnrolledLevelAsync, authenticateAsync, cancelAuthenticate, } from './local-authentication';
2
+ export { AuthenticationType, SecurityLevel, type IBiometricsSecurityLevel, type ILocalAuthenticationOptions, type ILocalAuthenticationResult, type ILocalAuthenticationError, } from './types';
@@ -0,0 +1,2 @@
1
+ export { hasHardwareAsync, supportedAuthenticationTypesAsync, isEnrolledAsync, getEnrolledLevelAsync, authenticateAsync, cancelAuthenticate, } from './local-authentication.js';
2
+ export { AuthenticationType, SecurityLevel, } from './types.js';
@@ -0,0 +1,33 @@
1
+ import type { AuthenticationType, ILocalAuthenticationOptions, ILocalAuthenticationResult, SecurityLevel } from './types';
2
+ /**
3
+ * Determine whether a face or fingerprint scanner is available on the device.
4
+ */
5
+ export declare function hasHardwareAsync(): Promise<boolean>;
6
+ /**
7
+ * Determine what kinds of authentications are available on the device. Devices can support
8
+ * multiple authentication methods — e.g. `[FINGERPRINT, FACIAL_RECOGNITION]` means the device
9
+ * supports both. Returns an empty array if none are supported.
10
+ */
11
+ export declare function supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]>;
12
+ /**
13
+ * Determine whether the device has saved fingerprints or facial data to use for authentication.
14
+ */
15
+ export declare function isEnrolledAsync(): Promise<boolean>;
16
+ /**
17
+ * Determine what kind of authentication is enrolled on the device.
18
+ * > On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been
19
+ * enrolled, which is not the method `authenticateAsync` prompts.
20
+ */
21
+ export declare function getEnrolledLevelAsync(): Promise<SecurityLevel>;
22
+ /**
23
+ * Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).
24
+ * > Apple requires apps which use FaceID to provide a description of why they use this API
25
+ * (`NSFaceIDUsageDescription` in `Info.plist`). Without it, the module authenticates using the
26
+ * device passcode instead.
27
+ */
28
+ export declare function authenticateAsync(options?: ILocalAuthenticationOptions): Promise<ILocalAuthenticationResult>;
29
+ /**
30
+ * Cancels the authentication flow.
31
+ * @platform android
32
+ */
33
+ export declare function cancelAuthenticate(): Promise<void>;
@@ -0,0 +1,75 @@
1
+ import { UnavailabilityError } from 'expo-modules-core';
2
+ import invariant from 'invariant';
3
+ import { expoLocalAuthentication } from './native-module.js';
4
+ const NATIVE_MODULE_NAME = 'expo-local-authentication';
5
+ const DEFAULT_PROMPT_MESSAGE = 'Authenticate';
6
+ const DEFAULT_CANCEL_LABEL = 'Cancel';
7
+ /**
8
+ * Determine whether a face or fingerprint scanner is available on the device.
9
+ */
10
+ export async function hasHardwareAsync() {
11
+ if (!expoLocalAuthentication.hasHardwareAsync) {
12
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasHardwareAsync');
13
+ }
14
+ return expoLocalAuthentication.hasHardwareAsync();
15
+ }
16
+ /**
17
+ * Determine what kinds of authentications are available on the device. Devices can support
18
+ * multiple authentication methods — e.g. `[FINGERPRINT, FACIAL_RECOGNITION]` means the device
19
+ * supports both. Returns an empty array if none are supported.
20
+ */
21
+ export async function supportedAuthenticationTypesAsync() {
22
+ if (!expoLocalAuthentication.supportedAuthenticationTypesAsync) {
23
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'supportedAuthenticationTypesAsync');
24
+ }
25
+ return expoLocalAuthentication.supportedAuthenticationTypesAsync();
26
+ }
27
+ /**
28
+ * Determine whether the device has saved fingerprints or facial data to use for authentication.
29
+ */
30
+ export async function isEnrolledAsync() {
31
+ if (!expoLocalAuthentication.isEnrolledAsync) {
32
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'isEnrolledAsync');
33
+ }
34
+ return expoLocalAuthentication.isEnrolledAsync();
35
+ }
36
+ /**
37
+ * Determine what kind of authentication is enrolled on the device.
38
+ * > On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been
39
+ * enrolled, which is not the method `authenticateAsync` prompts.
40
+ */
41
+ export async function getEnrolledLevelAsync() {
42
+ if (!expoLocalAuthentication.getEnrolledLevelAsync) {
43
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getEnrolledLevelAsync');
44
+ }
45
+ return expoLocalAuthentication.getEnrolledLevelAsync();
46
+ }
47
+ /**
48
+ * Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).
49
+ * > Apple requires apps which use FaceID to provide a description of why they use this API
50
+ * (`NSFaceIDUsageDescription` in `Info.plist`). Without it, the module authenticates using the
51
+ * device passcode instead.
52
+ */
53
+ export async function authenticateAsync(options = {}) {
54
+ if (!expoLocalAuthentication.authenticateAsync) {
55
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'authenticateAsync');
56
+ }
57
+ if (options.promptMessage !== undefined) {
58
+ invariant(typeof options.promptMessage === 'string' && options.promptMessage.length > 0, 'LocalAuthentication.authenticateAsync: `options.promptMessage` must be a non-empty string.');
59
+ }
60
+ return expoLocalAuthentication.authenticateAsync({
61
+ ...options,
62
+ promptMessage: options.promptMessage || DEFAULT_PROMPT_MESSAGE,
63
+ cancelLabel: options.cancelLabel || DEFAULT_CANCEL_LABEL,
64
+ });
65
+ }
66
+ /**
67
+ * Cancels the authentication flow.
68
+ * @platform android
69
+ */
70
+ export async function cancelAuthenticate() {
71
+ if (!expoLocalAuthentication.cancelAuthenticate) {
72
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'cancelAuthenticate');
73
+ }
74
+ await expoLocalAuthentication.cancelAuthenticate();
75
+ }
@@ -0,0 +1,13 @@
1
+ import type { AuthenticationType, ILocalAuthenticationOptions, ILocalAuthenticationResult, SecurityLevel } from './types';
2
+ export type INativeLocalAuthenticationModule = {
3
+ hasHardwareAsync?(): Promise<boolean>;
4
+ supportedAuthenticationTypesAsync?(): Promise<AuthenticationType[]>;
5
+ isEnrolledAsync?(): Promise<boolean>;
6
+ getEnrolledLevelAsync?(): Promise<SecurityLevel>;
7
+ authenticateAsync?(options: ILocalAuthenticationOptions & {
8
+ promptMessage: string;
9
+ cancelLabel: string;
10
+ }): Promise<ILocalAuthenticationResult>;
11
+ cancelAuthenticate?(): Promise<void>;
12
+ };
13
+ export declare const expoLocalAuthentication: INativeLocalAuthenticationModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_LOCAL_AUTHENTICATION_MODULE_NAME = 'ExpoLocalAuthentication';
3
+ export const expoLocalAuthentication = requireNativeModule(EXPO_LOCAL_AUTHENTICATION_MODULE_NAME);
@@ -0,0 +1,83 @@
1
+ export type ILocalAuthenticationResult = {
2
+ success: true;
3
+ } | {
4
+ success: false;
5
+ error: ILocalAuthenticationError;
6
+ warning?: string;
7
+ };
8
+ export declare enum AuthenticationType {
9
+ /** Indicates fingerprint support. */
10
+ FINGERPRINT = 1,
11
+ /** Indicates facial recognition support. */
12
+ FACIAL_RECOGNITION = 2,
13
+ /**
14
+ * Indicates iris recognition support.
15
+ * @platform android
16
+ */
17
+ IRIS = 3
18
+ }
19
+ export declare enum SecurityLevel {
20
+ /** Indicates no enrolled authentication. */
21
+ NONE = 0,
22
+ /** Indicates non-biometric authentication (e.g. PIN, Pattern). */
23
+ SECRET = 1,
24
+ /**
25
+ * Indicates biometric authentication.
26
+ * @deprecated please use `BIOMETRIC_STRONG` or `BIOMETRIC_WEAK` instead.
27
+ * @hidden
28
+ */
29
+ BIOMETRIC,
30
+ /**
31
+ * Indicates weak biometric authentication. For example, a 2D image-based face unlock. There
32
+ * are currently no weak biometric authentication options on iOS.
33
+ */
34
+ BIOMETRIC_WEAK = 2,
35
+ /** Indicates strong biometric authentication. For example, a fingerprint scan or 3D face unlock. */
36
+ BIOMETRIC_STRONG = 3
37
+ }
38
+ /**
39
+ * Security level of the biometric authentication to allow.
40
+ * @platform android
41
+ */
42
+ export type IBiometricsSecurityLevel = 'weak' | 'strong';
43
+ export type ILocalAuthenticationOptions = {
44
+ /** A message that is shown alongside the TouchID or FaceID prompt. */
45
+ promptMessage?: string;
46
+ /**
47
+ * A subtitle displayed below the prompt message in the authentication prompt.
48
+ * @platform android
49
+ */
50
+ promptSubtitle?: string;
51
+ /**
52
+ * A description displayed in the middle of the authentication prompt.
53
+ * @platform android
54
+ */
55
+ promptDescription?: string;
56
+ /** Allows customizing the default `Cancel` label shown. */
57
+ cancelLabel?: string;
58
+ /**
59
+ * After several failed attempts, the system falls back to the device passcode. This setting
60
+ * allows you to disable this option and instead handle the fallback yourself. Defaults to `false`.
61
+ */
62
+ disableDeviceFallback?: boolean;
63
+ /**
64
+ * Sets a hint to the system for whether to require user confirmation after authentication.
65
+ * Defaults to `true`.
66
+ * @platform android
67
+ */
68
+ requireConfirmation?: boolean;
69
+ /**
70
+ * Sets the security class of biometric authentication to allow. `strong` allows only Android
71
+ * Class 3 biometrics; `weak` allows both Class 3 and Class 2.
72
+ * @platform android
73
+ * @default 'weak'
74
+ */
75
+ biometricsSecurityLevel?: IBiometricsSecurityLevel;
76
+ /**
77
+ * Allows customizing the default `Use Passcode` label shown after several failed attempts. An
78
+ * empty string disables the button.
79
+ * @platform ios
80
+ */
81
+ fallbackLabel?: string;
82
+ };
83
+ export type ILocalAuthenticationError = 'not_enrolled' | 'user_cancel' | 'app_cancel' | 'not_available' | 'lockout' | 'no_space' | 'timeout' | 'unable_to_process' | 'unknown' | 'system_cancel' | 'user_fallback' | 'invalid_context' | 'passcode_not_set' | 'authentication_failed';
@@ -0,0 +1,55 @@
1
+ // expo-local-authentication's own types file imports Platform from the `expo` meta-package. We
2
+ // never depend on `expo` itself (it drags in a second Metro/babel pipeline — see the
3
+ // symbiote-expo-native-module skill) so this pulls Platform straight from expo-modules-core,
4
+ // which is where `expo` re-exports it from — same source device-sensor.ts (packages/sensors)
5
+ // uses for the same reason.
6
+ import { Platform } from 'expo-modules-core';
7
+ export var AuthenticationType;
8
+ (function (AuthenticationType) {
9
+ /** Indicates fingerprint support. */
10
+ AuthenticationType[AuthenticationType["FINGERPRINT"] = 1] = "FINGERPRINT";
11
+ /** Indicates facial recognition support. */
12
+ AuthenticationType[AuthenticationType["FACIAL_RECOGNITION"] = 2] = "FACIAL_RECOGNITION";
13
+ /**
14
+ * Indicates iris recognition support.
15
+ * @platform android
16
+ */
17
+ AuthenticationType[AuthenticationType["IRIS"] = 3] = "IRIS";
18
+ })(AuthenticationType || (AuthenticationType = {}));
19
+ export var SecurityLevel;
20
+ (function (SecurityLevel) {
21
+ /** Indicates no enrolled authentication. */
22
+ SecurityLevel[SecurityLevel["NONE"] = 0] = "NONE";
23
+ /** Indicates non-biometric authentication (e.g. PIN, Pattern). */
24
+ SecurityLevel[SecurityLevel["SECRET"] = 1] = "SECRET";
25
+ /**
26
+ * Indicates biometric authentication.
27
+ * @deprecated please use `BIOMETRIC_STRONG` or `BIOMETRIC_WEAK` instead.
28
+ * @hidden
29
+ */
30
+ SecurityLevel[SecurityLevel["BIOMETRIC"] = Platform.OS === 'android'
31
+ ? SecurityLevel.BIOMETRIC_WEAK
32
+ : SecurityLevel.BIOMETRIC_STRONG] = "BIOMETRIC";
33
+ /**
34
+ * Indicates weak biometric authentication. For example, a 2D image-based face unlock. There
35
+ * are currently no weak biometric authentication options on iOS.
36
+ */
37
+ SecurityLevel[SecurityLevel["BIOMETRIC_WEAK"] = 2] = "BIOMETRIC_WEAK";
38
+ /** Indicates strong biometric authentication. For example, a fingerprint scan or 3D face unlock. */
39
+ SecurityLevel[SecurityLevel["BIOMETRIC_STRONG"] = 3] = "BIOMETRIC_STRONG";
40
+ })(SecurityLevel || (SecurityLevel = {}));
41
+ // Upstream deprecation shim: SecurityLevel.BIOMETRIC used to be a real enum member; it's now a
42
+ // getter aliasing to the platform-correct strong/weak member, so old call sites still resolve
43
+ // but see a console warning steering them to the non-deprecated name.
44
+ Object.defineProperty(SecurityLevel, 'BIOMETRIC', {
45
+ get() {
46
+ const additionalMessage = Platform.OS === 'android'
47
+ ? '. `SecurityLevel.BIOMETRIC` is currently an alias for `SecurityLevel.BIOMETRIC_WEAK` on Android, which might lead to unexpected behaviour.'
48
+ : '';
49
+ console.warn('`SecurityLevel.BIOMETRIC` has been deprecated. Use `SecurityLevel.BIOMETRIC_WEAK` or `SecurityLevel.BIOMETRIC_STRONG` instead' +
50
+ additionalMessage);
51
+ return Platform.OS === 'android'
52
+ ? SecurityLevel.BIOMETRIC_WEAK
53
+ : SecurityLevel.BIOMETRIC_STRONG;
54
+ },
55
+ });
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/local-auth/react: the React entry over the framework-agnostic core.
2
+ // Upstream ships free async functions and two enums, no per-instance state and no event
3
+ // stream (unlike the sensor family in @symbiote-native/sensors) — there is nothing for a hook
4
+ // to wrap, so this is a plain re-export, mirroring how Pedometer's free functions pass through
5
+ // packages/sensors/src/react/index.ts untouched.
6
+ export * from '../core/index.js';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/local-auth/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/index.js';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/local-auth/angular: the Angular entry over the framework-agnostic core. Same
2
+ // 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,gGAAgG;AAChG,0FAA0F;AAC1F,yCAAyC;AACzC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { hasHardwareAsync, supportedAuthenticationTypesAsync, isEnrolledAsync, getEnrolledLevelAsync, authenticateAsync, cancelAuthenticate, } from './local-authentication';
2
+ export { AuthenticationType, SecurityLevel, type IBiometricsSecurityLevel, type ILocalAuthenticationOptions, type ILocalAuthenticationResult, type ILocalAuthenticationError, } from './types';
@@ -0,0 +1,3 @@
1
+ export { hasHardwareAsync, supportedAuthenticationTypesAsync, isEnrolledAsync, getEnrolledLevelAsync, authenticateAsync, cancelAuthenticate, } from './local-authentication';
2
+ export { AuthenticationType, SecurityLevel, } from './types';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iCAAiC,EACjC,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,kBAAkB,EAClB,aAAa,GAKd,MAAM,SAAS,CAAC"}