@symbiote-native/local-auth 0.0.1 → 0.3.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/LICENSE +21 -0
- package/README.md +252 -24
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +4 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +2 -0
- package/build/core/local-authentication.d.ts +33 -0
- package/build/core/local-authentication.js +75 -0
- package/build/core/native-module.d.ts +13 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +83 -0
- package/build/core/types.js +55 -0
- package/build/react/index.d.ts +1 -0
- package/build/react/index.js +6 -0
- package/build/svelte/index.d.ts +1 -0
- package/build/svelte/index.js +6 -0
- package/build/vue/index.d.ts +1 -0
- package/build/vue/index.js +4 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/local-authentication.d.ts +33 -0
- package/build-ngc/core/local-authentication.js +76 -0
- package/build-ngc/core/local-authentication.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +13 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +83 -0
- package/build-ngc/core/types.js +56 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +17 -0
- package/package.json +110 -3
- package/src/angular/index.ts +4 -0
- package/src/core/index.ts +16 -0
- package/src/core/local-authentication.ts +96 -0
- package/src/core/native-module.ts +27 -0
- package/src/core/types.ts +130 -0
- package/src/react/index.ts +6 -0
- package/src/svelte/index.ts +6 -0
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`, `
|
|
29
|
-
`
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
- `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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,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,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,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,6 @@
|
|
|
1
|
+
// @symbiote-native/local-auth/svelte: the Svelte entry over the framework-agnostic core.
|
|
2
|
+
// Upstream ships free async functions and two enums, no per-instance state and no event stream
|
|
3
|
+
// (unlike the sensor family in @symbiote-native/sensors) — there is nothing for a rune to wrap,
|
|
4
|
+
// so this is a plain re-export. Svelte's lifecycle bucket is `runes/` (adapters/svelte/src/runes
|
|
5
|
+
// — never React's `hooks` or Vue's `composables`), and this package has none to fill.
|
|
6
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -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';
|