@aparajita/capacitor-biometric-auth 1.0.7 → 2.0.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.
@@ -0,0 +1,262 @@
1
+ package com.aparajita.capacitor.biometricauth;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.app.Activity;
5
+ import android.content.Intent;
6
+ import android.content.pm.PackageManager;
7
+ import androidx.activity.result.ActivityResult;
8
+ import androidx.biometric.BiometricManager;
9
+ import androidx.biometric.BiometricPrompt;
10
+ import com.getcapacitor.JSObject;
11
+ import com.getcapacitor.Plugin;
12
+ import com.getcapacitor.PluginCall;
13
+ import com.getcapacitor.PluginMethod;
14
+ import com.getcapacitor.annotation.ActivityCallback;
15
+ import com.getcapacitor.annotation.CapacitorPlugin;
16
+ import java.util.HashMap;
17
+
18
+ @SuppressLint("RestrictedApi")
19
+ @CapacitorPlugin(name = "BiometricAuth")
20
+ public class BiometricAuth extends Plugin {
21
+
22
+ public static final String RESULT_TYPE = "type";
23
+ public static final String RESULT_ERROR_CODE = "errorCode";
24
+ public static final String RESULT_ERROR_MESSAGE = "errorMessage";
25
+ public static final String TITLE = "androidTitle";
26
+ public static final String SUBTITLE = "androidSubtitle";
27
+ public static final String REASON = "reason";
28
+ public static final String CANCEL_TITLE = "cancelTitle";
29
+ public static final String DEVICE_CREDENTIAL = "allowDeviceCredential";
30
+ public static final String MAX_ATTEMPTS = "androidMaxAttempts";
31
+ public static final int DEFAULT_MAX_ATTEMPTS = 3;
32
+ // Error code when biometry is not recognized
33
+ public static final String BIOMETRIC_FAILURE = "authenticationFailed";
34
+ // Maps biometry error numbers to string error codes
35
+ private static final HashMap<Integer, String> biometryErrorCodeMap;
36
+ private static final HashMap<BiometryType, String> biometryNameMap;
37
+ private static final String INVALID_CONTEXT_ERROR = "invalidContext";
38
+ public static String RESULT_EXTRA_PREFIX;
39
+
40
+ static {
41
+ biometryErrorCodeMap = new HashMap<>();
42
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_CANCELED, "systemCancel");
43
+ biometryErrorCodeMap.put(
44
+ BiometricPrompt.ERROR_HW_NOT_PRESENT,
45
+ "biometryNotAvailable"
46
+ );
47
+ biometryErrorCodeMap.put(
48
+ BiometricPrompt.ERROR_HW_UNAVAILABLE,
49
+ "biometryNotAvailable"
50
+ );
51
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_LOCKOUT, "biometryLockout");
52
+ biometryErrorCodeMap.put(
53
+ BiometricPrompt.ERROR_LOCKOUT_PERMANENT,
54
+ "biometryLockout"
55
+ );
56
+ biometryErrorCodeMap.put(
57
+ BiometricPrompt.ERROR_NEGATIVE_BUTTON,
58
+ "userCancel"
59
+ );
60
+ biometryErrorCodeMap.put(
61
+ BiometricPrompt.ERROR_NO_BIOMETRICS,
62
+ "biometryNotEnrolled"
63
+ );
64
+ biometryErrorCodeMap.put(
65
+ BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL,
66
+ "noDeviceCredential"
67
+ );
68
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_NO_SPACE, "systemCancel");
69
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_TIMEOUT, "systemCancel");
70
+ biometryErrorCodeMap.put(
71
+ BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
72
+ "systemCancel"
73
+ );
74
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_USER_CANCELED, "userCancel");
75
+ biometryErrorCodeMap.put(BiometricPrompt.ERROR_VENDOR, "systemCancel");
76
+ }
77
+
78
+ static {
79
+ biometryNameMap = new HashMap<>();
80
+ biometryNameMap.put(BiometryType.NONE, "No Authentication");
81
+ biometryNameMap.put(BiometryType.FINGERPRINT, "Fingerprint Authentication");
82
+ biometryNameMap.put(BiometryType.FACE, "Face Authentication");
83
+ biometryNameMap.put(BiometryType.IRIS, "Iris Authentication");
84
+ }
85
+
86
+ private BiometryType biometryType;
87
+
88
+ /**
89
+ * Check the device's availability and type of biometric authentication.
90
+ */
91
+ @PluginMethod
92
+ public void checkBiometry(PluginCall call) {
93
+ BiometricManager manager = BiometricManager.from(getContext());
94
+
95
+ int result = manager.canAuthenticate();
96
+
97
+ JSObject ret = new JSObject();
98
+ ret.put("isAvailable", result == BiometricManager.BIOMETRIC_SUCCESS);
99
+ biometryType = getDeviceBiometryType();
100
+ ret.put("biometryType", biometryType.getType());
101
+
102
+ String reason = "";
103
+
104
+ switch (result) {
105
+ case BiometricManager.BIOMETRIC_SUCCESS:
106
+ break;
107
+ case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
108
+ reason = "Biometry hardware is present, but currently unavailable.";
109
+ break;
110
+ case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
111
+ reason = "The user does not have any biometrics enrolled.";
112
+ break;
113
+ case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
114
+ reason = "There is no biometric hardware on this device.";
115
+ break;
116
+ }
117
+
118
+ ret.put("reason", reason);
119
+ call.resolve(ret);
120
+ }
121
+
122
+ private BiometryType getDeviceBiometryType() {
123
+ PackageManager manager = getContext().getPackageManager();
124
+
125
+ if (manager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
126
+ return BiometryType.FINGERPRINT;
127
+ }
128
+
129
+ if (manager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
130
+ return BiometryType.FACE;
131
+ }
132
+
133
+ if (manager.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
134
+ return BiometryType.IRIS;
135
+ }
136
+
137
+ return BiometryType.NONE;
138
+ }
139
+
140
+ /**
141
+ * Prompt the user for biometric authentication.
142
+ */
143
+ @PluginMethod
144
+ public void authenticate(final PluginCall call) {
145
+ // The result of an intent is supposed to have the package name as a prefix
146
+ RESULT_EXTRA_PREFIX = getContext().getPackageName() + ".";
147
+
148
+ Intent intent = new Intent(getContext(), AuthActivity.class);
149
+
150
+ // Pass the options to the activity
151
+ intent.putExtra(
152
+ TITLE,
153
+ call.getString(TITLE, biometryNameMap.get(biometryType))
154
+ );
155
+ intent.putExtra(SUBTITLE, call.getString(SUBTITLE));
156
+ intent.putExtra(REASON, call.getString(REASON));
157
+ intent.putExtra(CANCEL_TITLE, call.getString(CANCEL_TITLE));
158
+ intent.putExtra(
159
+ DEVICE_CREDENTIAL,
160
+ call.getBoolean(DEVICE_CREDENTIAL, false)
161
+ );
162
+
163
+ // Just in case the developer does something dumb like using a number < 1...
164
+ Integer maxAttemptsConfig = call.getInt(MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
165
+ int maxAttempts = Math.max(
166
+ maxAttemptsConfig == null ? 0 : maxAttemptsConfig,
167
+ 1
168
+ );
169
+ intent.putExtra(MAX_ATTEMPTS, maxAttempts);
170
+
171
+ startActivityForResult(call, intent, "authenticateResult");
172
+ }
173
+
174
+ @ActivityCallback
175
+ protected void authenticateResult(PluginCall call, ActivityResult result) {
176
+ int resultCode = result.getResultCode();
177
+
178
+ // If the system canceled the activity, we might get RESULT_CANCELED in resultCode.
179
+ // In that case return that immediately, because there won't be any data.
180
+ if (resultCode == Activity.RESULT_CANCELED) {
181
+ call.reject(
182
+ "The system canceled authentication",
183
+ biometryErrorCodeMap.get(BiometricPrompt.ERROR_CANCELED)
184
+ );
185
+ return;
186
+ }
187
+
188
+ // Convert the string result type to an enum
189
+ Intent data = result.getData();
190
+ String resultTypeName = null;
191
+
192
+ if (data != null) {
193
+ resultTypeName =
194
+ data.getStringExtra(RESULT_EXTRA_PREFIX + BiometricAuth.RESULT_TYPE);
195
+ }
196
+
197
+ if (resultTypeName == null) {
198
+ call.reject(
199
+ "Missing data in the result of the activity",
200
+ INVALID_CONTEXT_ERROR
201
+ );
202
+ return;
203
+ }
204
+
205
+ BiometryResultType resultType;
206
+
207
+ try {
208
+ resultType = BiometryResultType.valueOf(resultTypeName);
209
+ } catch (IllegalArgumentException e) {
210
+ call.reject(
211
+ "Invalid data in the result of the activity",
212
+ INVALID_CONTEXT_ERROR
213
+ );
214
+ return;
215
+ }
216
+
217
+ int errorCode = data.getIntExtra(
218
+ RESULT_EXTRA_PREFIX + BiometricAuth.RESULT_ERROR_CODE,
219
+ 0
220
+ );
221
+ String errorMessage = data.getStringExtra(
222
+ RESULT_EXTRA_PREFIX + BiometricAuth.RESULT_ERROR_MESSAGE
223
+ );
224
+
225
+ switch (resultType) {
226
+ case SUCCESS:
227
+ call.resolve();
228
+ break;
229
+ case FAILURE:
230
+ // Biometry was successfully presented but was not recognized
231
+ call.reject(errorMessage, BIOMETRIC_FAILURE);
232
+ break;
233
+ case ERROR:
234
+ // The user cancelled, the system cancelled, or some error occurred.
235
+ // If the user cancelled, errorMessage is the text of the "negative" button,
236
+ // which is not especially descriptive.
237
+ if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
238
+ errorMessage = "Cancel button was pressed";
239
+ }
240
+
241
+ call.reject(errorMessage, biometryErrorCodeMap.get(errorCode));
242
+ break;
243
+ }
244
+ }
245
+
246
+ enum BiometryType {
247
+ NONE(0),
248
+ FINGERPRINT(3),
249
+ FACE(4),
250
+ IRIS(5);
251
+
252
+ private final int type;
253
+
254
+ BiometryType(int type) {
255
+ this.type = type;
256
+ }
257
+
258
+ public int getType() {
259
+ return this.type;
260
+ }
261
+ }
262
+ }
@@ -1,7 +1,7 @@
1
1
  package com.aparajita.capacitor.biometricauth;
2
2
 
3
3
  public enum BiometryResultType {
4
- SUCCESS,
5
- FAILURE,
6
- ERROR
4
+ SUCCESS,
5
+ FAILURE,
6
+ ERROR
7
7
  }
@@ -1,9 +1,5 @@
1
- declare module '@capacitor/core' {
2
- interface PluginRegistry {
3
- WSBiometricAuth: WSBiometricAuthPlugin;
4
- }
5
- }
6
- import { PluginResultError } from '@capacitor/core';
1
+ import type { DecoratedNativePlugin } from '@aparajita/capacitor-native-decorator';
2
+ import type { PluginListenerHandle, PluginResultError } from '@capacitor/core';
7
3
  export declare enum BiometryType {
8
4
  /**
9
5
  * No biometry is available
@@ -32,8 +28,7 @@ export declare enum BiometryType {
32
28
  }
33
29
  export interface AuthenticateOptions {
34
30
  /**
35
- * The reason for requesting authentication. Displays in the authentication dialog presented to the user.
36
- * If not supplied, a default message is displayed.
31
+ * The reason for requesting authentication. Displays in the authentication dialog presented to the user. If not supplied, a default message is displayed.
37
32
  */
38
33
  reason?: string;
39
34
  /**
@@ -53,27 +48,27 @@ export interface AuthenticateOptions {
53
48
  * Default: "Cancel"
54
49
  */
55
50
  cancelTitle?: string;
56
- /***
51
+ /**
57
52
  * If true, allows for authentication using device unlock credentials. Default is false.
58
53
  *
59
54
  * iOS:
60
55
  * If biometry is available, enrolled, and not disabled, the system uses that first.
61
- * After the first Touch ID failure or second Face ID failure, if iosFallbackTitle
56
+ * After the first Touch ID failure or second Face ID failure, if `iosFallbackTitle`
62
57
  * is not an empty string, a fallback button appears in the authentication dialog.
63
58
  * If the user taps the fallback button, the system prompts the user for the device
64
- * passcode or the user’s password. If iosFallbackTitle is an empty string, no
59
+ * passcode or the user’s password. If `iosFallbackTitle` is an empty string, no
65
60
  * fallback button will appear.
66
61
  *
67
62
  * If biometry is not available, enrolled and enabled, and a passcode is set,
68
63
  * the system immediately prompts the user for the device passcode or user’s password.
69
- * Authentication fails with the error code passcodeNotSet if the device passcode isn’t enabled.
64
+ * Authentication fails with the error code `passcodeNotSet` if the device passcode isn’t enabled.
70
65
  *
71
- * If a passcode is not set on the device, a passcodeNotSet error is returned.
66
+ * If a passcode is not set on the device, a `passcodeNotSet` error is returned.
72
67
  *
73
68
  * The system disables passcode authentication after 6 unsuccessful attempts, with progressively
74
69
  * increasing delays between attempts.
75
70
  *
76
- * The title of the fallback button may be customized by setting iosFallbackTitle to
71
+ * The title of the fallback button may be customized by setting `iosFallbackTitle` to
77
72
  * a non-empty string.
78
73
  *
79
74
  * Android:
@@ -88,12 +83,12 @@ export interface AuthenticateOptions {
88
83
  * — for example, because the system doesn’t recognize the presented finger,
89
84
  * or after several failed attempts to recognize the user’s face.
90
85
  *
91
- * If allowDeviceCredential is false, tapping this button dismisses the
86
+ * If `allowDeviceCredential` is false, tapping this button dismisses the
92
87
  * authentication dialog and returns the error code userFallback. If undefined,
93
88
  * the localized systetm default title is used. Passing an empty string
94
89
  * hides the fallback button completely.
95
90
  *
96
- * If allowDeviceCredential is true and this is undefined,
91
+ * If `allowDeviceCredential` is true and this is undefined,
97
92
  * the localized system default title is used.
98
93
  */
99
94
  iosFallbackTitle?: string;
@@ -107,12 +102,12 @@ export interface AuthenticateOptions {
107
102
  androidSubtitle?: string;
108
103
  /**
109
104
  * The maximum number of failed biometric verification attempts before
110
- * returning BiometryError.authenticationFailed. The default is 3.
105
+ * returning `BiometryError.authenticationFailed`. The default is 3.
111
106
  */
112
107
  androidMaxAttempts?: number;
113
108
  }
114
109
  /**
115
- * If the authenticate() method throws an exception, the error object
110
+ * If the `authenticate()` method throws an exception, the error object
116
111
  * contains a .code property which will contain one of these strings,
117
112
  * indicating what the error was.
118
113
  *
@@ -158,42 +153,39 @@ export interface CheckBiometryResult {
158
153
  reason: string;
159
154
  }
160
155
  /**
161
- * The signature of the callback passed to addResumeListener().
156
+ * The signature of the callback passed to `addResumeListener()`.
162
157
  */
163
158
  export declare type ResumeListener = (info: CheckBiometryResult) => void;
164
- export interface WSBiometricAuthPlugin {
159
+ export interface BiometricAuthPlugin extends DecoratedNativePlugin {
165
160
  /**
166
161
  * Check to see what biometry type (if any) is available.
167
- * For testing on the web, a BiometryType name (case-sensitive)
168
- * may be specified as an environment variable. For example:
169
- *
170
- * WS_BIOMETRY_TYPE=touchId
171
162
  */
172
- checkBiometry(): Promise<CheckBiometryResult>;
163
+ checkBiometry: () => Promise<CheckBiometryResult>;
173
164
  /**
174
165
  * web only
175
166
  *
176
167
  * On the web, this method allows you to dynamically simulate
177
- * different types of biometry. You may either pass a BiometryType enum
178
- * or the string name of a BiometryType. If a string is passed and
179
- * it isn't a valid
168
+ * different types of biometry. You may either pass a `BiometryType` enum
169
+ * or the string name of a `BiometryType`. If a string is passed and
170
+ * it isn't a valid value, nothing happens.
180
171
  */
181
- setBiometryType(type: BiometryType | string | undefined): void;
172
+ setBiometryType: (type: BiometryType | string | undefined) => void;
182
173
  /**
183
174
  * Prompt the user for authentication. If authorization fails for any reason,
184
- * the promise is rejected with a BiometryError.
175
+ * the promise is rejected with a `BiometryError`.
185
176
  *
186
177
  * @param {AuthenticateOptions} options
187
178
  * @returns {Promise<void>}
188
179
  * @rejects {BiometryError}
189
180
  */
190
- authenticate(options?: AuthenticateOptions): Promise<void>;
181
+ authenticate: (options?: AuthenticateOptions) => Promise<void>;
191
182
  /**
192
183
  * Register a function that will be called when the app resumes.
193
- * The function will be passed the result of checkBiometry().
184
+ * The function will be passed the result of `checkBiometry()`.
194
185
  *
195
186
  * @param {ResumeListener} listener
196
187
  * @returns {boolean} true if the listener is successfully added
197
188
  */
198
- addResumeListener(listener: ResumeListener): void;
189
+ addResumeListener: (listener: ResumeListener) => Promise<PluginListenerHandle>;
199
190
  }
191
+ export declare const kPluginName = "BiometricAuth";
@@ -26,7 +26,7 @@ export var BiometryType;
26
26
  BiometryType[BiometryType["irisAuthentication"] = 5] = "irisAuthentication";
27
27
  })(BiometryType || (BiometryType = {}));
28
28
  /**
29
- * If the authenticate() method throws an exception, the error object
29
+ * If the `authenticate()` method throws an exception, the error object
30
30
  * contains a .code property which will contain one of these strings,
31
31
  * indicating what the error was.
32
32
  *
@@ -54,4 +54,5 @@ export class BiometryError {
54
54
  this.code = BiometryErrorType[code];
55
55
  }
56
56
  }
57
+ export const kPluginName = 'BiometricAuth';
57
58
  //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAGA,MAAM,CAAN,IAAY,YA8BX;AA9BD,WAAY,YAAY;IACtB;;OAEG;IACH,+CAAI,CAAA;IAEJ;;OAEG;IACH,qDAAO,CAAA;IAEP;;OAEG;IACH,mDAAM,CAAA;IAEN;;OAEG;IACH,yFAAyB,CAAA;IAEzB;;OAEG;IACH,2EAAkB,CAAA;IAElB;;OAEG;IACH,2EAAkB,CAAA;AACpB,CAAC,EA9BW,YAAY,KAAZ,YAAY,QA8BvB;AAyFD;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,iBAaX;AAbD,WAAY,iBAAiB;IAC3B,mEAAS,CAAA;IACT,yFAAoB,CAAA;IACpB,6EAAc,CAAA;IACd,6EAAc,CAAA;IACd,6EAAc,CAAA;IACd,yEAAY,CAAA;IACZ,qEAAU,CAAA;IACV,yEAAY,CAAA;IACZ,+EAAe,CAAA;IACf,yFAAoB,CAAA;IACpB,wFAAmB,CAAA;IACnB,sFAAkB,CAAA;AACpB,CAAC,EAbW,iBAAiB,KAAjB,iBAAiB,QAa5B;AAMD,MAAM,OAAO,aAAa;IAIxB,YAAY,OAAe,EAAE,IAAuB;QAClD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC;CACF;AAuED,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAA","sourcesContent":["import type { DecoratedNativePlugin } from '@aparajita/capacitor-native-decorator'\nimport type { PluginListenerHandle, PluginResultError } from '@capacitor/core'\n\nexport enum BiometryType {\n /**\n * No biometry is available\n */\n none,\n\n /**\n * iOS Touch ID is available\n */\n touchId,\n\n /**\n * iOS Face ID is available\n */\n faceId,\n\n /**\n * Android fingerprint authentication is available\n */\n fingerprintAuthentication,\n\n /**\n * Android face authentication is available\n */\n faceAuthentication,\n\n /**\n * Android iris authentication is available\n */\n irisAuthentication\n}\n\nexport interface AuthenticateOptions {\n /**\n * The reason for requesting authentication. Displays in the authentication dialog presented to the user. If not supplied, a default message is displayed.\n */\n reason?: string\n\n /**\n * iOS:\n * The system presents a cancel button during biometric authentication\n * to let the user abort the authentication attempt. The button appears\n * every time the system asks the user to present a finger registered with\n * Touch ID. For Face ID, the button only appears if authentication fails\n * and the user is prompted to try again. Either way, the user can stop\n * trying to authenticate by tapping the button.\n *\n * Android:\n * The text for the negative button. This would typically be used as a \"Cancel\" button,\n * but may be also used to show an alternative method for authentication, such as a\n * screen that asks for a backup password.\n *\n * Default: \"Cancel\"\n */\n cancelTitle?: string\n\n /**\n * If true, allows for authentication using device unlock credentials. Default is false.\n *\n * iOS:\n * If biometry is available, enrolled, and not disabled, the system uses that first.\n * After the first Touch ID failure or second Face ID failure, if `iosFallbackTitle`\n * is not an empty string, a fallback button appears in the authentication dialog.\n * If the user taps the fallback button, the system prompts the user for the device\n * passcode or the user’s password. If `iosFallbackTitle` is an empty string, no\n * fallback button will appear.\n *\n * If biometry is not available, enrolled and enabled, and a passcode is set,\n * the system immediately prompts the user for the device passcode or user’s password.\n * Authentication fails with the error code `passcodeNotSet` if the device passcode isn’t enabled.\n *\n * If a passcode is not set on the device, a `passcodeNotSet` error is returned.\n *\n * The system disables passcode authentication after 6 unsuccessful attempts, with progressively\n * increasing delays between attempts.\n *\n * The title of the fallback button may be customized by setting `iosFallbackTitle` to\n * a non-empty string.\n *\n * Android:\n * The user will first be prompted to authenticate with biometrics, but also given\n * the option to authenticate with their device PIN, pattern, or password by tapping\n * a button in the authentication dialog. The title of the button is supplied by\n * the system.\n */\n allowDeviceCredential?: boolean\n\n /**\n * The system presents a fallback button when biometric authentication fails\n * — for example, because the system doesn’t recognize the presented finger,\n * or after several failed attempts to recognize the user’s face.\n *\n * If `allowDeviceCredential` is false, tapping this button dismisses the\n * authentication dialog and returns the error code userFallback. If undefined,\n * the localized systetm default title is used. Passing an empty string\n * hides the fallback button completely.\n *\n * If `allowDeviceCredential` is true and this is undefined,\n * the localized system default title is used.\n */\n iosFallbackTitle?: string\n\n /**\n * Title for the Android dialog. If not supplied, the system default is used.\n */\n androidTitle?: string\n\n /**\n * Subtitle for the Android dialog. If not supplied, the system default is used.\n */\n androidSubtitle?: string\n\n /**\n * The maximum number of failed biometric verification attempts before\n * returning `BiometryError.authenticationFailed`. The default is 3.\n */\n androidMaxAttempts?: number\n}\n\n/**\n * If the `authenticate()` method throws an exception, the error object\n * contains a .code property which will contain one of these strings,\n * indicating what the error was.\n *\n * See https://developer.apple.com/documentation/localauthentication/laerror\n * for a description of each error code.\n */\nexport enum BiometryErrorType {\n appCancel,\n authenticationFailed,\n invalidContext,\n notInteractive,\n passcodeNotSet,\n systemCancel,\n userCancel,\n userFallback,\n biometryLockout,\n biometryNotAvailable,\n biometryNotEnrolled,\n noDeviceCredential\n}\n\nexport interface ResultError extends PluginResultError {\n code: string\n}\n\nexport class BiometryError implements ResultError {\n message: string\n code: string\n\n constructor(message: string, code: BiometryErrorType) {\n this.message = message\n this.code = BiometryErrorType[code]\n }\n}\n\nexport interface CheckBiometryResult {\n /**\n * True if the device has biometric authentication capability\n * and the current user has enrolled in biometry.\n */\n isAvailable: boolean\n\n /**\n * The type of biometry available on the device.\n */\n biometryType: BiometryType\n\n /**\n * If biometry is not available and the system gives a reason why,\n * it will be returned here. Otherwise it's an empty string.\n */\n reason: string\n}\n\n/**\n * The signature of the callback passed to `addResumeListener()`.\n */\nexport type ResumeListener = (info: CheckBiometryResult) => void\n\nexport interface BiometricAuthPlugin extends DecoratedNativePlugin {\n /**\n * Check to see what biometry type (if any) is available.\n */\n checkBiometry: () => Promise<CheckBiometryResult>\n\n /**\n * web only\n *\n * On the web, this method allows you to dynamically simulate\n * different types of biometry. You may either pass a `BiometryType` enum\n * or the string name of a `BiometryType`. If a string is passed and\n * it isn't a valid value, nothing happens.\n */\n setBiometryType: (type: BiometryType | string | undefined) => void\n\n /**\n * Prompt the user for authentication. If authorization fails for any reason,\n * the promise is rejected with a `BiometryError`.\n *\n * @param {AuthenticateOptions} options\n * @returns {Promise<void>}\n * @rejects {BiometryError}\n */\n authenticate: (options?: AuthenticateOptions) => Promise<void>\n\n /**\n * Register a function that will be called when the app resumes.\n * The function will be passed the result of `checkBiometry()`.\n *\n * @param {ResumeListener} listener\n * @returns {boolean} true if the listener is successfully added\n */\n addResumeListener: (listener: ResumeListener) => Promise<PluginListenerHandle>\n}\n\n/**\n * Return a human-readable name for a `BiometryType`.\n *\n * @param {BiometryType} type\n * @returns {string}\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare function getBiometryName(type: BiometryType): string\n\nexport const kPluginName = 'BiometricAuth'\n"]}
@@ -1,2 +1,5 @@
1
+ import type { BiometricAuthPlugin } from './definitions';
2
+ declare const biometricAuth: BiometricAuthPlugin;
1
3
  export * from './definitions';
2
- export * from './web';
4
+ export { biometricAuth as BiometricAuth };
5
+ export { getBiometryName } from './web';
package/dist/esm/index.js CHANGED
@@ -1,3 +1,17 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ import { kPluginName } from './definitions';
3
+ import { BiometricAuth } from './web';
4
+ // Because we are using @aparajita/capacitor-native-decorator,
5
+ // we have one version of the TS code to rule them all, and there
6
+ // is no need to lazy load. 😁
7
+ const plugin = new BiometricAuth();
8
+ // eslint-disable-next-line @typescript-eslint/naming-convention
9
+ const biometricAuth = registerPlugin(kPluginName, {
10
+ web: plugin,
11
+ ios: plugin,
12
+ android: plugin
13
+ });
1
14
  export * from './definitions';
2
- export * from './web';
15
+ export { biometricAuth as BiometricAuth };
16
+ export { getBiometryName } from './web';
3
17
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAErC,8DAA8D;AAC9D,iEAAiE;AACjE,8BAA8B;AAC9B,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAA;AAElC,gEAAgE;AAChE,MAAM,aAAa,GAAG,cAAc,CAAsB,WAAW,EAAE;IACrE,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,OAAO,EAAE,MAAM;CAChB,CAAC,CAAA;AAEF,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,aAAa,IAAI,aAAa,EAAE,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA","sourcesContent":["import { registerPlugin } from '@capacitor/core'\nimport { kPluginName } from './definitions'\nimport type { BiometricAuthPlugin } from './definitions'\nimport { BiometricAuth } from './web'\n\n// Because we are using @aparajita/capacitor-native-decorator,\n// we have one version of the TS code to rule them all, and there\n// is no need to lazy load. 😁\nconst plugin = new BiometricAuth()\n\n// eslint-disable-next-line @typescript-eslint/naming-convention\nconst biometricAuth = registerPlugin<BiometricAuthPlugin>(kPluginName, {\n web: plugin,\n ios: plugin,\n android: plugin\n})\n\nexport * from './definitions'\nexport { biometricAuth as BiometricAuth }\nexport { getBiometryName } from './web'\n"]}
package/dist/esm/web.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { WebPlugin } from '@capacitor/core';
2
- import { CheckBiometryResult, AuthenticateOptions, BiometryType, WSBiometricAuthPlugin, ResumeListener } from './definitions';
3
- export declare class WSBiometricAuthWeb extends WebPlugin implements WSBiometricAuthPlugin {
2
+ import type { PluginListenerHandle } from '@capacitor/core';
3
+ import type { AuthenticateOptions, BiometricAuthPlugin, CheckBiometryResult, ResumeListener } from './definitions';
4
+ import { BiometryType } from './definitions';
5
+ export declare class BiometricAuth extends WebPlugin implements BiometricAuthPlugin {
4
6
  private biometryType;
5
- constructor();
7
+ getRegisteredPluginName(): string;
6
8
  setBiometryType(type: BiometryType | string | undefined): void;
7
9
  checkBiometry(): Promise<CheckBiometryResult>;
8
10
  authenticate(options?: AuthenticateOptions): Promise<void>;
9
- addResumeListener(listener: ResumeListener): void;
11
+ addResumeListener(listener: ResumeListener): Promise<PluginListenerHandle> & PluginListenerHandle;
10
12
  }
11
13
  /**
12
14
  * Return a human-readable name for a BiometryType.
@@ -15,5 +17,3 @@ export declare class WSBiometricAuthWeb extends WebPlugin implements WSBiometric
15
17
  * @returns {string}
16
18
  */
17
19
  export declare function getBiometryName(type: BiometryType): string;
18
- declare const WSBiometricAuth: WSBiometricAuthWeb;
19
- export { WSBiometricAuth };
package/dist/esm/web.js CHANGED
@@ -1,30 +1,37 @@
1
- import { __decorate } from "tslib";
2
- import { Plugins, registerWebPlugin, WebPlugin } from '@capacitor/core';
3
- import { BiometryError, BiometryErrorType, BiometryType, } from './definitions';
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
4
7
  import { native } from '@aparajita/capacitor-native-decorator';
8
+ import { App } from '@capacitor/app';
9
+ import { WebPlugin } from '@capacitor/core';
10
+ import { BiometryError, BiometryErrorType, BiometryType, kPluginName } from './definitions';
5
11
  const kBiometryTypeNameMap = {
6
12
  [BiometryType.none]: '',
7
13
  [BiometryType.touchId]: 'Touch ID',
8
14
  [BiometryType.faceId]: 'Face ID',
9
15
  [BiometryType.fingerprintAuthentication]: 'Fingerprint Authentication',
10
16
  [BiometryType.faceAuthentication]: 'Face Authentication',
11
- [BiometryType.irisAuthentication]: 'Iris Authentication',
17
+ [BiometryType.irisAuthentication]: 'Iris Authentication'
12
18
  };
13
- export class WSBiometricAuthWeb extends WebPlugin {
19
+ export class BiometricAuth extends WebPlugin {
14
20
  constructor() {
15
- super({
16
- name: 'WSBiometricAuth',
17
- platforms: ['web', 'ios', 'android'],
18
- });
21
+ super(...arguments);
19
22
  this.biometryType = BiometryType.none;
20
- this.setBiometryType(process.env.WS_BIOMETRY_TYPE);
23
+ }
24
+ getRegisteredPluginName() {
25
+ return kPluginName;
21
26
  }
22
27
  setBiometryType(type) {
23
28
  if (typeof type === 'undefined') {
24
29
  return;
25
30
  }
26
31
  if (typeof type === 'string') {
27
- if (type && BiometryType.hasOwnProperty(type)) {
32
+ // eslint-disable-next-line no-prototype-builtins
33
+ if (BiometryType.hasOwnProperty(type)) {
34
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
28
35
  this.biometryType = BiometryType[type];
29
36
  }
30
37
  }
@@ -32,17 +39,19 @@ export class WSBiometricAuthWeb extends WebPlugin {
32
39
  this.biometryType = type;
33
40
  }
34
41
  }
35
- checkBiometry() {
42
+ async checkBiometry() {
36
43
  return Promise.resolve({
37
44
  isAvailable: this.biometryType !== BiometryType.none,
38
45
  biometryType: this.biometryType,
39
- reason: '',
46
+ reason: ''
40
47
  });
41
48
  }
42
- authenticate(options) {
49
+ async authenticate(options) {
43
50
  return this.checkBiometry().then(({ isAvailable, biometryType }) => {
44
51
  if (isAvailable) {
45
- if (confirm((options === null || options === void 0 ? void 0 : options.reason) ||
52
+ if (
53
+ // eslint-disable-next-line no-alert
54
+ confirm((options === null || options === void 0 ? void 0 : options.reason) ||
46
55
  `Authenticate with ${kBiometryTypeNameMap[biometryType]}?`)) {
47
56
  return;
48
57
  }
@@ -52,23 +61,25 @@ export class WSBiometricAuthWeb extends WebPlugin {
52
61
  });
53
62
  }
54
63
  addResumeListener(listener) {
55
- const app = Plugins.App;
56
- if (app) {
57
- app.addListener('appStateChange', async (state) => {
58
- if (state.isActive) {
59
- const info = await this.checkBiometry();
64
+ return App.addListener('appStateChange', ({ isActive }) => {
65
+ if (isActive) {
66
+ this.checkBiometry()
67
+ .then((info) => {
60
68
  listener(info);
61
- }
62
- });
63
- }
69
+ })
70
+ .catch((error) => {
71
+ console.error(error.message);
72
+ });
73
+ }
74
+ });
64
75
  }
65
76
  }
66
77
  __decorate([
67
78
  native()
68
- ], WSBiometricAuthWeb.prototype, "checkBiometry", null);
79
+ ], BiometricAuth.prototype, "checkBiometry", null);
69
80
  __decorate([
70
81
  native()
71
- ], WSBiometricAuthWeb.prototype, "authenticate", null);
82
+ ], BiometricAuth.prototype, "authenticate", null);
72
83
  /**
73
84
  * Return a human-readable name for a BiometryType.
74
85
  *
@@ -78,7 +89,4 @@ __decorate([
78
89
  export function getBiometryName(type) {
79
90
  return kBiometryTypeNameMap[type] || '';
80
91
  }
81
- const WSBiometricAuth = new WSBiometricAuthWeb();
82
- export { WSBiometricAuth };
83
- registerWebPlugin(WSBiometricAuth);
84
92
  //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { native } from '@aparajita/capacitor-native-decorator'\nimport { App } from '@capacitor/app'\nimport { WebPlugin } from '@capacitor/core'\nimport type { PluginListenerHandle } from '@capacitor/core'\nimport type {\n AuthenticateOptions,\n BiometricAuthPlugin,\n CheckBiometryResult,\n ResumeListener\n} from './definitions'\nimport {\n BiometryError,\n BiometryErrorType,\n BiometryType,\n kPluginName\n} from './definitions'\n\nconst kBiometryTypeNameMap = {\n [BiometryType.none]: '',\n [BiometryType.touchId]: 'Touch ID',\n [BiometryType.faceId]: 'Face ID',\n [BiometryType.fingerprintAuthentication]: 'Fingerprint Authentication',\n [BiometryType.faceAuthentication]: 'Face Authentication',\n [BiometryType.irisAuthentication]: 'Iris Authentication'\n}\n\nexport class BiometricAuth extends WebPlugin implements BiometricAuthPlugin {\n private biometryType: BiometryType = BiometryType.none\n\n getRegisteredPluginName(): string {\n return kPluginName\n }\n\n setBiometryType(type: BiometryType | string | undefined): void {\n if (typeof type === 'undefined') {\n return\n }\n\n if (typeof type === 'string') {\n // eslint-disable-next-line no-prototype-builtins\n if (BiometryType.hasOwnProperty(type)) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n this.biometryType = BiometryType[type as keyof typeof BiometryType]\n }\n } else {\n this.biometryType = type\n }\n }\n\n @native()\n async checkBiometry(): Promise<CheckBiometryResult> {\n return Promise.resolve({\n isAvailable: this.biometryType !== BiometryType.none,\n biometryType: this.biometryType,\n reason: ''\n })\n }\n\n @native()\n async authenticate(options?: AuthenticateOptions): Promise<void> {\n return this.checkBiometry().then(({ isAvailable, biometryType }) => {\n if (isAvailable) {\n if (\n // eslint-disable-next-line no-alert\n confirm(\n options?.reason ||\n `Authenticate with ${kBiometryTypeNameMap[biometryType]}?`\n )\n ) {\n return\n }\n\n throw new BiometryError('User cancelled', BiometryErrorType.userCancel)\n }\n\n throw new BiometryError(\n 'Biometry not available',\n BiometryErrorType.biometryNotAvailable\n )\n })\n }\n\n addResumeListener(\n listener: ResumeListener\n ): Promise<PluginListenerHandle> & PluginListenerHandle {\n return App.addListener('appStateChange', ({ isActive }): void => {\n if (isActive) {\n this.checkBiometry()\n .then((info: CheckBiometryResult) => {\n listener(info)\n })\n .catch((error: Error) => {\n console.error(error.message)\n })\n }\n })\n }\n}\n\n/**\n * Return a human-readable name for a BiometryType.\n *\n * @param {BiometryType} type\n * @returns {string}\n */\nexport function getBiometryName(type: BiometryType): string {\n return kBiometryTypeNameMap[type] || ''\n}\n"]}