@algorandfoundation/react-native-passkey-autofill 1.0.0-canary.19 → 1.0.0-canary.20

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [1.0.0-canary.20](https://github.com/algorandfoundation/react-native-passkey-autofill/compare/v1.0.0-canary.19...v1.0.0-canary.20) (2026-06-10)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **Android:** improve setting intent handling, gradle includes pickFirst for MMKV. ([27af9e4](https://github.com/algorandfoundation/react-native-passkey-autofill/commit/27af9e470fb2d53f375422b246e3f8d72329b2c0))
6
+
1
7
  # [1.0.0-canary.19](https://github.com/algorandfoundation/react-native-passkey-autofill/compare/v1.0.0-canary.18...v1.0.0-canary.19) (2026-06-09)
2
8
 
3
9
  ### Bug Fixes
@@ -52,6 +52,7 @@ android {
52
52
  }
53
53
  packagingOptions {
54
54
  pickFirst '**/libc++_shared.so'
55
+ pickFirst '**/libmmkv.so'
55
56
  }
56
57
  }
57
58
 
@@ -4,12 +4,11 @@ import expo.modules.kotlin.modules.Module
4
4
  import expo.modules.kotlin.modules.ModuleDefinition
5
5
  import co.algorand.passkeyautofill.credentials.CredentialRepository
6
6
  import co.algorand.passkeyautofill.service.PasskeyAutofillCredentialProviderService
7
- import co.algorand.passkeyautofill.service.PasskeyAutofillCredentialProviderService.Companion.KEY_LAST_INVOKED_AT_MS
8
- import com.tencent.mmkv.MMKV
9
7
  import android.content.ComponentName
10
8
  import android.content.Context
11
9
  import android.content.Intent
12
10
  import android.net.Uri
11
+ import android.os.Build
13
12
  import android.provider.Settings
14
13
  import android.util.Log
15
14
  import org.bouncycastle.jce.provider.BouncyCastleProvider
@@ -159,33 +158,43 @@ class ReactNativePasskeyAutofillModule : Module() {
159
158
  * Returns `true` when this app's [PasskeyAutofillCredentialProviderService]
160
159
  * is registered as an active credential provider for the current user.
161
160
  *
162
- * Android stores the enabled credential providers as a colon-separated list
163
- * of flattened [ComponentName]s in the `credential_service` and
164
- * `credential_service_primary` Secure settings (API 34+). However those
165
- * keys are `@hide` on Android 12+, so reading them from a regular app
166
- * throws `SecurityException`. We therefore combine two signals:
161
+ * On API 34+ (UpsideDownCake) we use the official
162
+ * [android.credentials.CredentialManager.isEnabledCredentialProviderService]
163
+ * API, which reflects the user's current toggle in Settings in real time.
167
164
  *
168
- * 1. Best-effort: try [Settings.Secure.getString]; if it returns the
169
- * expected component we know for sure we're the provider.
170
- * 2. Fallback: inspect the MMKV timestamp written by the service itself
171
- * whenever the system routes a `BeginCreate/BeginGetCredentialRequest`
172
- * to it ([PasskeyAutofillCredentialProviderService.KEY_LAST_INVOKED_AT_MS]).
173
- * The Credential Manager only routes requests to *enabled* providers,
174
- * so a non-zero stamp is proof that we were selected at least once.
165
+ * On older devices we fall back to a best-effort read of the `@hide`
166
+ * `credential_service` / `credential_service_primary` Secure settings. These
167
+ * typically throw `SecurityException` for non-system apps on Android 12+,
168
+ * in which case we conservatively return `false` rather than guess.
175
169
  */
176
170
  private fun isProviderEnabled(context: Context): Boolean {
177
- val expected = ComponentName(
171
+ val component = ComponentName(
178
172
  context.packageName,
179
173
  PasskeyAutofillCredentialProviderService::class.java.name,
180
- ).flattenToString()
174
+ )
175
+ val expected = component.flattenToString()
176
+
177
+ // Preferred path (API 34+, UpsideDownCake): ask the platform
178
+ // `CredentialManager` system service whether our component is enabled.
179
+ // This is the official, real-time-accurate signal and is trusted
180
+ // exclusively on supported OS versions.
181
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
182
+ return try {
183
+ val cm = context.getSystemService(android.credentials.CredentialManager::class.java)
184
+ cm != null && cm.isEnabledCredentialProviderService(component)
185
+ } catch (e: Throwable) {
186
+ Log.d("ReactNativePasskeyAutofill", "CredentialManager.isEnabledCredentialProviderService failed: ${e.message}")
187
+ false
188
+ }
189
+ }
190
+
191
+ // Pre-API-34 best-effort: try the (often `@hide`) Secure settings keys.
181
192
  val resolver = context.contentResolver
182
193
  val keys = arrayOf("credential_service", "credential_service_primary")
183
194
  for (key in keys) {
184
195
  val value = try {
185
196
  Settings.Secure.getString(resolver, key)
186
197
  } catch (e: SecurityException) {
187
- // `credential_service[_primary]` are @hide on Android 12+; fall
188
- // through to the MMKV stamp check below.
189
198
  Log.d("ReactNativePasskeyAutofill", "Secure key $key not readable: ${e.message}")
190
199
  null
191
200
  } ?: continue
@@ -194,14 +203,7 @@ class ReactNativePasskeyAutofillModule : Module() {
194
203
  value.contains(expected, ignoreCase = true)
195
204
  if (enabled) return true
196
205
  }
197
- // Fallback: look for a timestamp written by our CredentialProviderService.
198
- return try {
199
- MMKV.initialize(context)
200
- (MMKV.defaultMMKV()?.decodeLong(KEY_LAST_INVOKED_AT_MS, 0L) ?: 0L) > 0L
201
- } catch (e: Exception) {
202
- Log.w("ReactNativePasskeyAutofill", "Failed to read provider stamp: ${e.message}")
203
- false
204
- }
206
+ return false
205
207
  }
206
208
 
207
209
  /**
@@ -210,8 +212,34 @@ class ReactNativePasskeyAutofillModule : Module() {
210
212
  * the credential provider screen is not available on the device.
211
213
  */
212
214
  private fun openCredentialProviderSettings(context: Context): Boolean {
215
+ // The system action for the Credential Manager provider picker is
216
+ // `android.settings.CREDENTIAL_PROVIDER` (`Settings.ACTION_CREDENTIAL_PROVIDER`,
217
+ // API 34+). Some OEM Settings builds additionally accept a
218
+ // `:settings:fragment_args_key` extra so the screen scrolls directly to
219
+ // our app's row instead of the generic list. We also include a legacy
220
+ // autofill-service picker fallback for devices where the Credential
221
+ // Manager screen isn't a directly launchable activity.
222
+ val component = ComponentName(
223
+ context.packageName,
224
+ PasskeyAutofillCredentialProviderService::class.java.name,
225
+ ).flattenToString()
213
226
  val intents = listOf(
227
+ // Preferred: Credential Manager provider settings, deep-linked to our row.
228
+ Intent("android.settings.CREDENTIAL_PROVIDER").apply {
229
+ putExtra(":settings:fragment_args_key", component)
230
+ putExtra(
231
+ ":settings:show_fragment_args",
232
+ android.os.Bundle().apply { putString(":settings:fragment_args_key", component) },
233
+ )
234
+ },
235
+ // Same screen without the deep-link extras (some OEMs ignore them).
214
236
  Intent("android.settings.CREDENTIAL_PROVIDER"),
237
+ // Legacy / fallback: the system autofill provider picker, which on
238
+ // pre-14 devices is the closest "pick a passkey provider" screen.
239
+ Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE).apply {
240
+ data = Uri.parse("package:${context.packageName}")
241
+ },
242
+ // Last-resort fallback: this app's details page.
215
243
  Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
216
244
  data = Uri.fromParts("package", context.packageName, null)
217
245
  },
package/app.plugin.js CHANGED
@@ -3,6 +3,7 @@ const {
3
3
  withEntitlementsPlist,
4
4
  withInfoPlist,
5
5
  withStringsXml,
6
+ withAppBuildGradle,
6
7
  withProjectBuildGradle,
7
8
  withMainApplication,
8
9
  withDangerousMod,
@@ -718,7 +719,34 @@ const withPasskeyAutofill = (config, props = {}) => {
718
719
  return config;
719
720
  });
720
721
 
721
- // 3. Add local Maven repository for local AAR
722
+ // 3. Inject `pickFirst '**/libmmkv.so'` into the consumer app's
723
+ // `android/app/build.gradle`. The native `libmmkv.so` is bundled by both
724
+ // this module (via `io.github.zhongwuzw:mmkv`) and `react-native-mmkv`
725
+ // (which also depends on the same artifact, sometimes pinned to a different
726
+ // version). When two copies end up on the merged-libs path, AGP fails
727
+ // `mergeDebugNativeLibs` with: "2 files found with path
728
+ // 'lib/<abi>/libmmkv.so'". Picking the first occurrence is safe because
729
+ // the `com.tencent.mmkv.MMKV` Java surface we touch is ABI-stable across
730
+ // the 2.x range.
731
+ config = withAppBuildGradle(config, (config) => {
732
+ const marker = "pickFirst '**/libmmkv.so'";
733
+ if (config.modResults.contents.includes(marker)) {
734
+ return config;
735
+ }
736
+ let contents = config.modResults.contents;
737
+ if (/packagingOptions\s*\{/.test(contents)) {
738
+ contents = contents.replace(/packagingOptions\s*\{/, `packagingOptions {\n ${marker}`);
739
+ } else if (/android\s*\{/.test(contents)) {
740
+ contents = contents.replace(
741
+ /android\s*\{/,
742
+ `android {\n packagingOptions {\n ${marker}\n }\n`,
743
+ );
744
+ }
745
+ config.modResults.contents = contents;
746
+ return config;
747
+ });
748
+
749
+ // 4. Add local Maven repository for local AAR
722
750
  config = withProjectBuildGradle(config, (config) => {
723
751
  if (config.modResults.contents.includes("android/libs/repo")) {
724
752
  return config;
@@ -7,14 +7,25 @@ declare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativeP
7
7
  configureIntentActions(getPasskeyAction: string, createPasskeyAction: string): Promise<void>;
8
8
  clearCredentials(): Promise<void>;
9
9
  deleteCredential(credentialId: string): Promise<void>;
10
+ /**
11
+ * iOS: returns the identities currently published to the AutoFill
12
+ * `ASCredentialIdentityStore`. Android: no-op returning `[]` because the
13
+ * native Credential Provider service reads credentials directly from MMKV
14
+ * and there is no separate identity store to query.
15
+ */
10
16
  getStoredCredentials(): Promise<PasskeyAutofillCredentialIdentity[]>;
17
+ /**
18
+ * iOS: returns diagnostic strings from the AutoFill extension.
19
+ * Android: no-op returning `[]`.
20
+ */
11
21
  getDiagnostics(): Promise<string[]>;
12
22
  /**
13
23
  * Replaces the iOS AutoFill passkey identity store with credentials
14
24
  * available to the native Credential Provider extension. Each item must
15
25
  * include a credential id, relying party (`relyingPartyIdentifier`,
16
26
  * `rpId`, or `origin`), user handle, and P-256 private key material.
17
- * Android ignores this method because the provider reads MMKV directly.
27
+ * Android ignores this method (no-op) because the provider reads MMKV
28
+ * directly.
18
29
  */
19
30
  replaceCredentialIdentities(credentials: PasskeyAutofillCredentialIdentity[]): Promise<void>;
20
31
  refreshCredentialIdentities(): Promise<void>;
@@ -33,6 +44,6 @@ declare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativeP
33
44
  */
34
45
  openProviderSettings(): Promise<boolean>;
35
46
  }
36
- declare const _default: ReactNativePasskeyAutofillModule;
37
- export default _default;
47
+ declare const nativeModule: ReactNativePasskeyAutofillModule;
48
+ export default nativeModule;
38
49
  //# sourceMappingURL=ReactNativePasskeyAutofillModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativePasskeyAutofillModule.d.ts","sourceRoot":"","sources":["../src/ReactNativePasskeyAutofillModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,iCAAiC,EACjC,sCAAsC,EACvC,MAAM,oCAAoC,CAAC;AAE5C,OAAO,OAAO,gCAAiC,SAAQ,YAAY,CAAC,sCAAsC,CAAC;IACzG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IACxC,sBAAsB,CAAC,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5F,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IACjC,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD,oBAAoB,IAAI,OAAO,CAAC,iCAAiC,EAAE,CAAC;IACpE,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,2BAA2B,CAAC,WAAW,EAAE,iCAAiC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5F,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IAC5C;;;;;;OAMG;IACH,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IACpC;;;;OAIG;IACH,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;CACzC;;AAGD,wBAAmG"}
1
+ {"version":3,"file":"ReactNativePasskeyAutofillModule.d.ts","sourceRoot":"","sources":["../src/ReactNativePasskeyAutofillModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAGzD,OAAO,EACL,iCAAiC,EACjC,sCAAsC,EACvC,MAAM,oCAAoC,CAAC;AAE5C,OAAO,OAAO,gCAAiC,SAAQ,YAAY,CAAC,sCAAsC,CAAC;IACzG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IACxC,sBAAsB,CAAC,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5F,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IACjC,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO,CAAC,iCAAiC,EAAE,CAAC;IACpE;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IACnC;;;;;;;OAOG;IACH,2BAA2B,CAAC,WAAW,EAAE,iCAAiC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5F,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IAC5C;;;;;;OAMG;IACH,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IACpC;;;;OAIG;IACH,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;CACzC;AAGD,QAAA,MAAM,YAAY,kCAEjB,CAAC;AAoBF,eAAe,YAAY,CAAC"}
@@ -1,4 +1,23 @@
1
1
  import { requireNativeModule } from "expo";
2
+ import { Platform } from "react-native";
2
3
  // This call loads the native module object from the JSI.
3
- export default requireNativeModule("ReactNativePasskeyAutofill");
4
+ const nativeModule = requireNativeModule("ReactNativePasskeyAutofill");
5
+ // A handful of methods are only implemented in the iOS native module because
6
+ // they interact with `ASCredentialIdentityStore`, which has no Android
7
+ // equivalent (the Android Credential Provider service reads credentials
8
+ // directly from MMKV on each request).
9
+ if (Platform.OS === "android") {
10
+ const noops = {
11
+ replaceCredentialIdentities: () => Promise.resolve(),
12
+ refreshCredentialIdentities: () => Promise.resolve(),
13
+ getStoredCredentials: () => Promise.resolve([]),
14
+ getDiagnostics: () => Promise.resolve([]),
15
+ };
16
+ for (const name of Object.keys(noops)) {
17
+ if (typeof nativeModule[name] !== "function") {
18
+ nativeModule[name] = noops[name];
19
+ }
20
+ }
21
+ }
22
+ export default nativeModule;
4
23
  //# sourceMappingURL=ReactNativePasskeyAutofillModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativePasskeyAutofillModule.js","sourceRoot":"","sources":["../src/ReactNativePasskeyAutofillModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAyCzD,yDAAyD;AACzD,eAAe,mBAAmB,CAAmC,4BAA4B,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n PasskeyAutofillCredentialIdentity,\n ReactNativePasskeyAutofillModuleEvents,\n} from \"./ReactNativePasskeyAutofill.types\";\n\ndeclare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativePasskeyAutofillModuleEvents> {\n setMasterKey(secret: string): Promise<void>;\n setHdRootKeyId(id: string): Promise<void>;\n getHdRootKeyId(): Promise<string | null>;\n configureIntentActions(getPasskeyAction: string, createPasskeyAction: string): Promise<void>;\n clearCredentials(): Promise<void>;\n deleteCredential(credentialId: string): Promise<void>;\n getStoredCredentials(): Promise<PasskeyAutofillCredentialIdentity[]>;\n getDiagnostics(): Promise<string[]>;\n /**\n * Replaces the iOS AutoFill passkey identity store with credentials\n * available to the native Credential Provider extension. Each item must\n * include a credential id, relying party (`relyingPartyIdentifier`,\n * `rpId`, or `origin`), user handle, and P-256 private key material.\n * Android ignores this method because the provider reads MMKV directly.\n */\n replaceCredentialIdentities(credentials: PasskeyAutofillCredentialIdentity[]): Promise<void>;\n refreshCredentialIdentities(): Promise<void>;\n /**\n * Resolves to `true` when this app is registered as the active\n * credential/autofill provider on the current device (Android 14+\n * credential provider or iOS AutoFill). Useful for gating passkey UI\n * and for E2E tests that need to confirm the prompt that appears\n * belongs to this provider and not a third party.\n */\n isProviderActive(): Promise<boolean>;\n /**\n * Opens the OS credential/autofill provider settings screen so the\n * user can enable this app as the active provider. Resolves to `true`\n * if a settings screen could be launched, `false` otherwise.\n */\n openProviderSettings(): Promise<boolean>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ReactNativePasskeyAutofillModule>(\"ReactNativePasskeyAutofill\");\n"]}
1
+ {"version":3,"file":"ReactNativePasskeyAutofillModule.js","sourceRoot":"","sources":["../src/ReactNativePasskeyAutofillModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAoDxC,yDAAyD;AACzD,MAAM,YAAY,GAAG,mBAAmB,CACtC,4BAA4B,CAC7B,CAAC;AAEF,6EAA6E;AAC7E,uEAAuE;AACvE,wEAAwE;AACxE,uCAAuC;AACvC,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;IAC9B,MAAM,KAAK,GAA6D;QACtE,2BAA2B,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;QACpD,2BAA2B,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;QACpD,oBAAoB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/C,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;KAC1C,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,IAAI,OAAQ,YAAmD,CAAC,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;YACpF,YAAmD,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;AACH,CAAC;AAED,eAAe,YAAY,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\nimport { Platform } from \"react-native\";\n\nimport {\n PasskeyAutofillCredentialIdentity,\n ReactNativePasskeyAutofillModuleEvents,\n} from \"./ReactNativePasskeyAutofill.types\";\n\ndeclare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativePasskeyAutofillModuleEvents> {\n setMasterKey(secret: string): Promise<void>;\n setHdRootKeyId(id: string): Promise<void>;\n getHdRootKeyId(): Promise<string | null>;\n configureIntentActions(getPasskeyAction: string, createPasskeyAction: string): Promise<void>;\n clearCredentials(): Promise<void>;\n deleteCredential(credentialId: string): Promise<void>;\n /**\n * iOS: returns the identities currently published to the AutoFill\n * `ASCredentialIdentityStore`. Android: no-op returning `[]` because the\n * native Credential Provider service reads credentials directly from MMKV\n * and there is no separate identity store to query.\n */\n getStoredCredentials(): Promise<PasskeyAutofillCredentialIdentity[]>;\n /**\n * iOS: returns diagnostic strings from the AutoFill extension.\n * Android: no-op returning `[]`.\n */\n getDiagnostics(): Promise<string[]>;\n /**\n * Replaces the iOS AutoFill passkey identity store with credentials\n * available to the native Credential Provider extension. Each item must\n * include a credential id, relying party (`relyingPartyIdentifier`,\n * `rpId`, or `origin`), user handle, and P-256 private key material.\n * Android ignores this method (no-op) because the provider reads MMKV\n * directly.\n */\n replaceCredentialIdentities(credentials: PasskeyAutofillCredentialIdentity[]): Promise<void>;\n refreshCredentialIdentities(): Promise<void>;\n /**\n * Resolves to `true` when this app is registered as the active\n * credential/autofill provider on the current device (Android 14+\n * credential provider or iOS AutoFill). Useful for gating passkey UI\n * and for E2E tests that need to confirm the prompt that appears\n * belongs to this provider and not a third party.\n */\n isProviderActive(): Promise<boolean>;\n /**\n * Opens the OS credential/autofill provider settings screen so the\n * user can enable this app as the active provider. Resolves to `true`\n * if a settings screen could be launched, `false` otherwise.\n */\n openProviderSettings(): Promise<boolean>;\n}\n\n// This call loads the native module object from the JSI.\nconst nativeModule = requireNativeModule<ReactNativePasskeyAutofillModule>(\n \"ReactNativePasskeyAutofill\",\n);\n\n// A handful of methods are only implemented in the iOS native module because\n// they interact with `ASCredentialIdentityStore`, which has no Android\n// equivalent (the Android Credential Provider service reads credentials\n// directly from MMKV on each request).\nif (Platform.OS === \"android\") {\n const noops: Record<string, (...args: unknown[]) => Promise<unknown>> = {\n replaceCredentialIdentities: () => Promise.resolve(),\n refreshCredentialIdentities: () => Promise.resolve(),\n getStoredCredentials: () => Promise.resolve([]),\n getDiagnostics: () => Promise.resolve([]),\n };\n for (const name of Object.keys(noops)) {\n if (typeof (nativeModule as unknown as Record<string, unknown>)[name] !== \"function\") {\n (nativeModule as unknown as Record<string, unknown>)[name] = noops[name];\n }\n }\n}\n\nexport default nativeModule;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algorandfoundation/react-native-passkey-autofill",
3
- "version": "1.0.0-canary.19",
3
+ "version": "1.0.0-canary.20",
4
4
  "description": "Passkey AutoFill React Native",
5
5
  "keywords": [
6
6
  "@algorandfoundation/react-native-passkey-autofill",
@@ -51,36 +51,36 @@
51
51
  "@algorandfoundation/react-native-keystore": "1.0.0-canary.12"
52
52
  },
53
53
  "devDependencies": {
54
- "@expo/config-plugins": "~55.0.8",
54
+ "@expo/config-plugins": "~55.0.10",
55
55
  "@expo/npm-proofread": "^1.0.1",
56
56
  "@semantic-release/changelog": "^6.0.3",
57
57
  "@semantic-release/exec": "^7.1.0",
58
58
  "@semantic-release/git": "^10.0.1",
59
- "@semantic-release/github": "^12.0.6",
59
+ "@semantic-release/github": "^12.0.8",
60
60
  "@semantic-release/npm": "^13.1.5",
61
61
  "@types/jest": "^29.5.14",
62
- "@types/node": "^22.19.17",
63
- "@types/react": "~19.2.10",
62
+ "@types/node": "^22.19.20",
63
+ "@types/react": "~19.2.16",
64
64
  "appium": "^2.19.0",
65
65
  "appium-uiautomator2-driver": "^3.10.0",
66
66
  "appium-xcuitest-driver": "^9.10.5",
67
67
  "eslint": "^9.39.4",
68
- "expo": "~55.0.17",
69
- "expo-doctor": "^1.18.19",
68
+ "expo": "55.0.17",
69
+ "expo-doctor": "^1.19.8",
70
70
  "expo-module-scripts": "^55.0.2",
71
71
  "jest": "^29.7.0",
72
72
  "lefthook": "2.1.4",
73
73
  "oxfmt": "^0.43.0",
74
- "oxlint": "^1.60.0",
74
+ "oxlint": "^1.68.0",
75
75
  "prettier": "^3.8.3",
76
76
  "react": "19.2.0",
77
77
  "react-native": "0.83.6",
78
78
  "semantic-release": "^25.0.3",
79
79
  "start-server-and-test": "^2.1.5",
80
- "ts-jest": "^29.4.9",
80
+ "ts-jest": "^29.4.11",
81
81
  "ts-node": "^10.9.2",
82
82
  "typescript": "~5.9.3",
83
- "webdriverio": "^9.27.0"
83
+ "webdriverio": "^9.27.2"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "expo": "*",
@@ -114,26 +114,26 @@
114
114
  "react-native-quick-crypto": "1.0.19",
115
115
  "expo": "55.0.17",
116
116
  "ajv@>=7.0.0-alpha.0 <8.18.0": ">=8.18.0",
117
- "axios@>=1.0.0 <1.12.0": ">=1.12.0",
118
- "axios@>=1.0.0 <1.15.0": ">=1.15.0",
119
- "axios@>=1.0.0 <=1.13.4": ">=1.13.5",
117
+ "axios@>=1.0.0 <1.16.0": ">=1.16.0",
120
118
  "basic-ftp@<=5.2.2": ">=5.3.0",
119
+ "brace-expansion@>=5.0.0 <5.0.6": ">=5.0.6",
121
120
  "diff@>=6.0.0 <8.0.3": ">=8.0.3",
121
+ "fast-uri@<=3.1.1": ">=3.1.2",
122
122
  "fast-xml-parser@<5.7.0": ">=5.7.0",
123
123
  "form-data@>=4.0.0 <4.0.4": ">=4.0.4",
124
124
  "glob@>=10.2.0 <10.5.0": ">=10.5.0",
125
125
  "lodash@<=4.17.23": ">=4.18.0",
126
- "lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
127
- "lodash@>=4.0.0 <=4.17.23": ">=4.18.0",
128
126
  "on-headers@<1.1.0": ">=1.1.0",
129
127
  "path-to-regexp@<0.1.13": "0.1.13",
130
128
  "path-to-regexp@>=8.0.0 <8.4.0": ">=8.4.0",
131
129
  "postcss@<8.5.10": ">=8.5.10",
132
- "qs@<6.14.1": ">=6.14.1",
133
- "qs@>=6.7.0 <=6.14.1": ">=6.14.2",
130
+ "qs@<6.15.2": ">=6.15.2",
131
+ "shell-quote@>=1.1.0 <=1.8.3": ">=1.8.4",
134
132
  "uuid@<14.0.0": ">=14.0.0",
133
+ "ws@>=8.0.0 <8.20.1": ">=8.20.1",
135
134
  "yaml@>=2.0.0 <2.8.3": ">=2.8.3",
136
- "yauzl@=3.2.0": ">=3.2.1"
135
+ "yauzl@=3.2.0": ">=3.2.1",
136
+ "@babel/plugin-transform-modules-systemjs@>=7.12.0 <=7.29.3": ">=7.29.4"
137
137
  }
138
138
  }
139
139
  }
@@ -1,4 +1,5 @@
1
1
  import { NativeModule, requireNativeModule } from "expo";
2
+ import { Platform } from "react-native";
2
3
 
3
4
  import {
4
5
  PasskeyAutofillCredentialIdentity,
@@ -12,14 +13,25 @@ declare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativeP
12
13
  configureIntentActions(getPasskeyAction: string, createPasskeyAction: string): Promise<void>;
13
14
  clearCredentials(): Promise<void>;
14
15
  deleteCredential(credentialId: string): Promise<void>;
16
+ /**
17
+ * iOS: returns the identities currently published to the AutoFill
18
+ * `ASCredentialIdentityStore`. Android: no-op returning `[]` because the
19
+ * native Credential Provider service reads credentials directly from MMKV
20
+ * and there is no separate identity store to query.
21
+ */
15
22
  getStoredCredentials(): Promise<PasskeyAutofillCredentialIdentity[]>;
23
+ /**
24
+ * iOS: returns diagnostic strings from the AutoFill extension.
25
+ * Android: no-op returning `[]`.
26
+ */
16
27
  getDiagnostics(): Promise<string[]>;
17
28
  /**
18
29
  * Replaces the iOS AutoFill passkey identity store with credentials
19
30
  * available to the native Credential Provider extension. Each item must
20
31
  * include a credential id, relying party (`relyingPartyIdentifier`,
21
32
  * `rpId`, or `origin`), user handle, and P-256 private key material.
22
- * Android ignores this method because the provider reads MMKV directly.
33
+ * Android ignores this method (no-op) because the provider reads MMKV
34
+ * directly.
23
35
  */
24
36
  replaceCredentialIdentities(credentials: PasskeyAutofillCredentialIdentity[]): Promise<void>;
25
37
  refreshCredentialIdentities(): Promise<void>;
@@ -40,4 +52,26 @@ declare class ReactNativePasskeyAutofillModule extends NativeModule<ReactNativeP
40
52
  }
41
53
 
42
54
  // This call loads the native module object from the JSI.
43
- export default requireNativeModule<ReactNativePasskeyAutofillModule>("ReactNativePasskeyAutofill");
55
+ const nativeModule = requireNativeModule<ReactNativePasskeyAutofillModule>(
56
+ "ReactNativePasskeyAutofill",
57
+ );
58
+
59
+ // A handful of methods are only implemented in the iOS native module because
60
+ // they interact with `ASCredentialIdentityStore`, which has no Android
61
+ // equivalent (the Android Credential Provider service reads credentials
62
+ // directly from MMKV on each request).
63
+ if (Platform.OS === "android") {
64
+ const noops: Record<string, (...args: unknown[]) => Promise<unknown>> = {
65
+ replaceCredentialIdentities: () => Promise.resolve(),
66
+ refreshCredentialIdentities: () => Promise.resolve(),
67
+ getStoredCredentials: () => Promise.resolve([]),
68
+ getDiagnostics: () => Promise.resolve([]),
69
+ };
70
+ for (const name of Object.keys(noops)) {
71
+ if (typeof (nativeModule as unknown as Record<string, unknown>)[name] !== "function") {
72
+ (nativeModule as unknown as Record<string, unknown>)[name] = noops[name];
73
+ }
74
+ }
75
+ }
76
+
77
+ export default nativeModule;