react-native-credentials-manager 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +192 -1
  2. package/android/build.gradle +6 -5
  3. package/android/generated/java/com/credentialsmanager/NativeCredentialsManagerSpec.java +5 -1
  4. package/android/generated/jni/RNCredentialsManagerSpec-generated.cpp +7 -1
  5. package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
  6. package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI.h +151 -3
  7. package/android/src/main/java/com/credentialsmanager/handlers/CredentialHandler.kt +59 -21
  8. package/android/src/newarch/java/com/credentialsmanager/CredentialsManagerModule.kt +28 -1
  9. package/android/src/oldarch/java/com/credentialsmanager/CredentialsManagerModule.kt +30 -1
  10. package/ios/CredentialsManager.h +8 -2
  11. package/ios/CredentialsManager.mm +396 -4
  12. package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec-generated.mm +14 -1
  13. package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec.h +38 -1
  14. package/ios/generated/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
  15. package/ios/generated/RNCredentialsManagerSpecJSI.h +151 -3
  16. package/lib/commonjs/NativeCredentialsManager.js +6 -0
  17. package/lib/commonjs/NativeCredentialsManager.js.map +1 -1
  18. package/lib/commonjs/index.js +45 -3
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/module/NativeCredentialsManager.js +13 -0
  21. package/lib/module/NativeCredentialsManager.js.map +1 -1
  22. package/lib/module/index.js +44 -3
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts +49 -7
  25. package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/src/index.d.ts +10 -4
  27. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  28. package/lib/typescript/module/src/NativeCredentialsManager.d.ts +49 -7
  29. package/lib/typescript/module/src/NativeCredentialsManager.d.ts.map +1 -1
  30. package/lib/typescript/module/src/index.d.ts +10 -4
  31. package/lib/typescript/module/src/index.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/react-native-credentials-manager.podspec +4 -1
  34. package/react-native.config.js +0 -1
  35. package/src/NativeCredentialsManager.ts +67 -8
  36. package/src/index.tsx +81 -5
package/README.md CHANGED
@@ -4,7 +4,7 @@ React Native Credentials Manager
4
4
 
5
5
  ![App Screens](IMG/flow.png)
6
6
 
7
- A React Native library that implements the [Credential Manager](https://developer.android.com/identity/sign-in/credential-manager) API for Android. This library allows you to manage passwords, passkeys and google signin in your React Native applications.
7
+ A React Native library that implements the [Credential Manager](https://developer.android.com/identity/sign-in/credential-manager) API for Android and [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices) for iOS. This library allows you to manage passwords, passkeys and platform-specific sign-in (Google Sign-In on Android, Apple Sign In on iOS) in your React Native applications.
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/react-native-credentials-manager">
@@ -18,5 +18,196 @@ A React Native library that implements the [Credential Manager](https://develope
18
18
  </a>
19
19
  </p>
20
20
 
21
+ ## Platform Support
22
+
23
+ - ✅ **Android**: Full implementation with Credential Manager API (Android 14+ / API 34+)
24
+ - ✅ **iOS**: Full implementation with AuthenticationServices (iOS 16.0+)
25
+
26
+ ### Platform-Specific Features
27
+
28
+ | Feature | Android | iOS |
29
+ | ------------------------- | ------------------------- | --------------------------------- |
30
+ | Passkeys | ✅ Credential Manager API | ✅ AuthenticationServices |
31
+ | AutoFill Password Support | ✅ Credential Manager API | ✅ AuthenticationServices |
32
+ | Manual Password Storage | ✅ Credential Manager API | ❌ Not supported (iOS limitation) |
33
+ | Third-party Sign In | ✅ Google Sign In | ✅ Apple Sign In |
34
+
35
+ > [!NOTE] > **iOS Implementation**: This library strictly follows Apple's Authentication Services framework. Manual password storage is not supported on iOS as it's not part of Apple's official Authentication Services APIs. Use AutoFill passwords instead.
36
+
21
37
  > [!IMPORTANT]
22
38
  > 📚 **Documentation has moved!** The complete documentation is now available at [https://docs.benjamineruvieru.com/docs/react-native-credentials-manager/](https://docs.benjamineruvieru.com/docs/react-native-credentials-manager/)
39
+
40
+ ## Platform-Specific Parameters
41
+
42
+ When using this library, be aware that some parameters are platform-specific:
43
+
44
+ | Function | Parameter | Platform Support | Notes |
45
+ | ---------------------- | --------------------------------------- | ---------------- | ----------------------------------------------------------------- |
46
+ | `signUpWithPasskeys()` | `preferImmediatelyAvailableCredentials` | Android only | This parameter is ignored on iOS |
47
+ | `signUpWithPassword()` | All parameters | Android only | This function is not supported on iOS and will throw an error |
48
+ | `signIn()` | `googleSignIn` | Android only | This parameter is ignored on iOS |
49
+ | `signIn()` | `appleSignIn` | iOS only | This parameter is ignored on Android |
50
+ | `signUpWithGoogle()` | All parameters | Cross-platform | Uses Google Sign-In on Android, Apple Sign-In on iOS |
51
+ | `signUpWithApple()` | All parameters | iOS only | This function is not supported on Android and will throw an error |
52
+
53
+ ### Handling Platform Differences
54
+
55
+ To handle these platform differences, you can use conditional code:
56
+
57
+ ```typescript
58
+ // For passkey registration
59
+ await signUpWithPasskeys(
60
+ requestJson,
61
+ Platform.OS === 'android' ? true : false // preferImmediatelyAvailableCredentials
62
+ );
63
+
64
+ // For sign-in
65
+ await signIn(
66
+ [
67
+ 'passkeys',
68
+ 'password',
69
+ Platform.OS === 'android' ? 'google-signin' : 'apple-signin',
70
+ ],
71
+ {
72
+ passkeys: passkeyParams,
73
+ ...(Platform.OS === 'android'
74
+ ? { googleSignIn: { serverClientId: 'your-client-id' } }
75
+ : { appleSignIn: { requestedScopes: ['fullName', 'email'] } }),
76
+ }
77
+ );
78
+ ```
79
+
80
+ ## iOS Setup Requirements
81
+
82
+ ### 1. Associated Domains
83
+
84
+ Add the Associated Domains capability to your iOS app:
85
+
86
+ 1. In Xcode, select your project
87
+ 2. Go to Signing & Capabilities
88
+ 3. Add "Associated Domains" capability
89
+ 4. Add your domain with the `webcredentials` service: `webcredentials:yourdomain.com`
90
+
91
+ ### 2. Apple App Site Association (AASA)
92
+
93
+ Ensure your domain has a proper AASA file at `https://yourdomain.com/.well-known/apple-app-site-association`:
94
+
95
+ ```json
96
+ {
97
+ "webcredentials": {
98
+ "apps": ["TEAMID.com.yourcompany.yourapp"]
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### 3. Apple Sign In Setup (Optional)
104
+
105
+ If using Apple Sign In, configure it in your Apple Developer account:
106
+
107
+ 1. Enable "Sign In with Apple" capability in Xcode
108
+ 2. Configure Sign In with Apple in your Apple Developer account
109
+ 3. Add your app's bundle identifier to the Sign In with Apple configuration
110
+
111
+ ## Quick Example
112
+
113
+ ```typescript
114
+ import {
115
+ signUpWithPasskeys,
116
+ signUpWithPassword, // Android only - throws error on iOS
117
+ signUpWithGoogle, // Cross-platform: Google on Android, Apple on iOS
118
+ signUpWithApple, // iOS-specific function
119
+ signIn,
120
+ signOut,
121
+ type Credential,
122
+ type AppleCredential,
123
+ type GoogleCredential,
124
+ type AppleSignInParams,
125
+ type GoogleSignInParams,
126
+ } from 'react-native-credentials-manager';
127
+
128
+ // Sign up with passkey (works on both platforms)
129
+ const passkeyResult = await signUpWithPasskeys({
130
+ challenge: 'base64-challenge',
131
+ rp: { name: 'Your App', id: 'yourdomain.com' },
132
+ user: { id: 'user-id', name: 'username', displayName: 'User Name' },
133
+ // ... other WebAuthn options
134
+ });
135
+
136
+ // Password handling - platform differences
137
+ try {
138
+ // This will work on Android but throw an error on iOS
139
+ await signUpWithPassword({ username: 'user', password: 'pass' });
140
+ } catch (error) {
141
+ if (Platform.OS === 'ios') {
142
+ console.log(
143
+ 'Manual password storage not supported on iOS. Use AutoFill instead.'
144
+ );
145
+ }
146
+ }
147
+
148
+ // Unified sign in (supports passkeys, AutoFill passwords, and platform sign-in)
149
+ const credential = await signIn(
150
+ ['passkeys', 'password', 'google-signin'], // 'google-signin' becomes 'apple-signin' on iOS
151
+ {
152
+ passkeys: { challenge: 'base64-challenge', rpId: 'yourdomain.com' },
153
+ googleSignIn: { serverClientId: 'your-client-id' },
154
+ }
155
+ );
156
+
157
+ // Platform-specific sign-up methods
158
+ if (Platform.OS === 'ios') {
159
+ // Direct Apple Sign In (iOS only) - uses AuthenticationServices
160
+ const appleCredential = await signUpWithApple({
161
+ requestedScopes: ['fullName', 'email'],
162
+ });
163
+ } else {
164
+ // Google Sign In (Android only)
165
+ const googleCredential = await signUpWithGoogle({
166
+ serverClientId: 'your-client-id',
167
+ });
168
+ }
169
+
170
+ // Cross-platform sign-up (automatically uses the appropriate method)
171
+ const credential = await signUpWithGoogle({
172
+ serverClientId: 'your-client-id', // Used on Android, ignored on iOS
173
+ });
174
+ // Returns GoogleCredential on Android, AppleCredential on iOS
175
+
176
+ // Handle different credential types
177
+ if (credential.type === 'passkey') {
178
+ console.log('Passkey authentication:', credential.authenticationResponseJson);
179
+ } else if (credential.type === 'password') {
180
+ console.log('AutoFill Password:', credential.username, credential.password);
181
+ } else if (credential.type === 'google-signin') {
182
+ console.log('Google Sign In:', credential.idToken);
183
+ } else if (credential.type === 'apple-signin') {
184
+ console.log('Apple Sign In:', credential.idToken, credential.email);
185
+ }
186
+ ```
187
+
188
+ ## Cross-Platform Compatibility
189
+
190
+ The library provides excellent cross-platform compatibility while respecting platform limitations:
191
+
192
+ - **`signUpWithGoogle()`**: Cross-platform function
193
+ - Android: Uses Google Sign In
194
+ - iOS: Automatically uses Apple Sign In (AuthenticationServices)
195
+ - **`signUpWithApple()`**: iOS-specific function
196
+ - iOS: Uses Apple Sign In (AuthenticationServices)
197
+ - Android: Rejects with clear error message
198
+ - **`signUpWithPassword()`**: Platform-specific behavior
199
+ - Android: Uses Credential Manager API
200
+ - iOS: Rejects (manual storage not supported by AuthenticationServices)
201
+ - **AutoFill Passwords**: Available on both platforms through `signIn` method
202
+ - **Error handling**: Platform-specific errors are handled gracefully
203
+
204
+ ## iOS AuthenticationServices Integration
205
+
206
+ The iOS implementation strictly follows Apple's Authentication Services framework:
207
+
208
+ - **Passkeys**: `ASAuthorizationPlatformPublicKeyCredentialProvider`
209
+ - **AutoFill Passwords**: `ASAuthorizationPasswordProvider`
210
+ - **Apple Sign In**: `ASAuthorizationAppleIDProvider`
211
+ - **No Custom Keychain**: Manual credential storage is handled by the system
212
+
213
+ This ensures compliance with Apple's security guidelines and provides the best user experience on iOS.
@@ -100,11 +100,12 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
100
100
  dependencies {
101
101
  implementation "com.facebook.react:react-android"
102
102
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
103
- implementation "androidx.credentials:credentials:1.5.0-rc01"
104
- implementation "androidx.credentials:credentials-play-services-auth:1.5.0-rc01"
105
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
106
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
107
- implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
103
+ implementation("androidx.credentials:credentials:1.6.0-alpha02")
104
+
105
+ implementation("androidx.credentials:credentials-play-services-auth:1.6.0-alpha02")
106
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
107
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
108
+ implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
108
109
  }
109
110
 
110
111
  if (isNewArchitectureEnabled()) {
@@ -40,7 +40,7 @@ public abstract class NativeCredentialsManagerSpec extends ReactContextBaseJavaM
40
40
 
41
41
  @ReactMethod
42
42
  @DoNotStrip
43
- public abstract void signUpWithPassword(ReadableMap credObject);
43
+ public abstract void signUpWithPassword(ReadableMap credObject, Promise promise);
44
44
 
45
45
  @ReactMethod
46
46
  @DoNotStrip
@@ -50,6 +50,10 @@ public abstract class NativeCredentialsManagerSpec extends ReactContextBaseJavaM
50
50
  @DoNotStrip
51
51
  public abstract void signUpWithGoogle(ReadableMap params, Promise promise);
52
52
 
53
+ @ReactMethod
54
+ @DoNotStrip
55
+ public abstract void signUpWithApple(ReadableMap params, Promise promise);
56
+
53
57
  @ReactMethod
54
58
  @DoNotStrip
55
59
  public abstract void signOut(Promise promise);
@@ -19,7 +19,7 @@ static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signU
19
19
 
20
20
  static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithPassword(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
21
21
  static jmethodID cachedMethodId = nullptr;
22
- return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, "signUpWithPassword", "(Lcom/facebook/react/bridge/ReadableMap;)V", args, count, cachedMethodId);
22
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "signUpWithPassword", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
23
23
  }
24
24
 
25
25
  static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signIn(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
@@ -32,6 +32,11 @@ static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signU
32
32
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "signUpWithGoogle", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
33
33
  }
34
34
 
35
+ static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithApple(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
36
+ static jmethodID cachedMethodId = nullptr;
37
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "signUpWithApple", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
38
+ }
39
+
35
40
  static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signOut(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
36
41
  static jmethodID cachedMethodId = nullptr;
37
42
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "signOut", "(Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
@@ -43,6 +48,7 @@ NativeCredentialsManagerSpecJSI::NativeCredentialsManagerSpecJSI(const JavaTurbo
43
48
  methodMap_["signUpWithPassword"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithPassword};
44
49
  methodMap_["signIn"] = MethodMetadata {2, __hostFunction_NativeCredentialsManagerSpecJSI_signIn};
45
50
  methodMap_["signUpWithGoogle"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithGoogle};
51
+ methodMap_["signUpWithApple"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithApple};
46
52
  methodMap_["signOut"] = MethodMetadata {0, __hostFunction_NativeCredentialsManagerSpecJSI_signOut};
47
53
  }
48
54
 
@@ -19,11 +19,10 @@ static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithPa
19
19
  );
20
20
  }
21
21
  static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithPassword(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
22
- static_cast<NativeCredentialsManagerCxxSpecJSI *>(&turboModule)->signUpWithPassword(
22
+ return static_cast<NativeCredentialsManagerCxxSpecJSI *>(&turboModule)->signUpWithPassword(
23
23
  rt,
24
24
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt)
25
25
  );
26
- return jsi::Value::undefined();
27
26
  }
28
27
  static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signIn(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
29
28
  return static_cast<NativeCredentialsManagerCxxSpecJSI *>(&turboModule)->signIn(
@@ -38,6 +37,12 @@ static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithGo
38
37
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt)
39
38
  );
40
39
  }
40
+ static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithApple(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
41
+ return static_cast<NativeCredentialsManagerCxxSpecJSI *>(&turboModule)->signUpWithApple(
42
+ rt,
43
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt)
44
+ );
45
+ }
41
46
  static jsi::Value __hostFunction_NativeCredentialsManagerCxxSpecJSI_signOut(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
42
47
  return static_cast<NativeCredentialsManagerCxxSpecJSI *>(&turboModule)->signOut(
43
48
  rt
@@ -50,6 +55,7 @@ NativeCredentialsManagerCxxSpecJSI::NativeCredentialsManagerCxxSpecJSI(std::shar
50
55
  methodMap_["signUpWithPassword"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithPassword};
51
56
  methodMap_["signIn"] = MethodMetadata {2, __hostFunction_NativeCredentialsManagerCxxSpecJSI_signIn};
52
57
  methodMap_["signUpWithGoogle"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithGoogle};
58
+ methodMap_["signUpWithApple"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerCxxSpecJSI_signUpWithApple};
53
59
  methodMap_["signOut"] = MethodMetadata {0, __hostFunction_NativeCredentialsManagerCxxSpecJSI_signOut};
54
60
  }
55
61
 
@@ -16,6 +16,145 @@ namespace facebook::react {
16
16
 
17
17
 
18
18
 
19
+ #pragma mark - NativeCredentialsManagerAppleCredential
20
+
21
+ template <typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6>
22
+ struct NativeCredentialsManagerAppleCredential {
23
+ P0 type;
24
+ P1 id;
25
+ P2 idToken;
26
+ P3 displayName;
27
+ P4 familyName;
28
+ P5 givenName;
29
+ P6 email;
30
+ bool operator==(const NativeCredentialsManagerAppleCredential &other) const {
31
+ return type == other.type && id == other.id && idToken == other.idToken && displayName == other.displayName && familyName == other.familyName && givenName == other.givenName && email == other.email;
32
+ }
33
+ };
34
+
35
+ template <typename T>
36
+ struct NativeCredentialsManagerAppleCredentialBridging {
37
+ static T types;
38
+
39
+ static T fromJs(
40
+ jsi::Runtime &rt,
41
+ const jsi::Object &value,
42
+ const std::shared_ptr<CallInvoker> &jsInvoker) {
43
+ T result{
44
+ bridging::fromJs<decltype(types.type)>(rt, value.getProperty(rt, "type"), jsInvoker),
45
+ bridging::fromJs<decltype(types.id)>(rt, value.getProperty(rt, "id"), jsInvoker),
46
+ bridging::fromJs<decltype(types.idToken)>(rt, value.getProperty(rt, "idToken"), jsInvoker),
47
+ bridging::fromJs<decltype(types.displayName)>(rt, value.getProperty(rt, "displayName"), jsInvoker),
48
+ bridging::fromJs<decltype(types.familyName)>(rt, value.getProperty(rt, "familyName"), jsInvoker),
49
+ bridging::fromJs<decltype(types.givenName)>(rt, value.getProperty(rt, "givenName"), jsInvoker),
50
+ bridging::fromJs<decltype(types.email)>(rt, value.getProperty(rt, "email"), jsInvoker)};
51
+ return result;
52
+ }
53
+
54
+ #ifdef DEBUG
55
+ static jsi::String typeToJs(jsi::Runtime &rt, decltype(types.type) value) {
56
+ return bridging::toJs(rt, value);
57
+ }
58
+
59
+ static jsi::String idToJs(jsi::Runtime &rt, decltype(types.id) value) {
60
+ return bridging::toJs(rt, value);
61
+ }
62
+
63
+ static jsi::String idTokenToJs(jsi::Runtime &rt, decltype(types.idToken) value) {
64
+ return bridging::toJs(rt, value);
65
+ }
66
+
67
+ static jsi::String displayNameToJs(jsi::Runtime &rt, decltype(types.displayName) value) {
68
+ return bridging::toJs(rt, value);
69
+ }
70
+
71
+ static jsi::String familyNameToJs(jsi::Runtime &rt, decltype(types.familyName) value) {
72
+ return bridging::toJs(rt, value);
73
+ }
74
+
75
+ static jsi::String givenNameToJs(jsi::Runtime &rt, decltype(types.givenName) value) {
76
+ return bridging::toJs(rt, value);
77
+ }
78
+
79
+ static jsi::String emailToJs(jsi::Runtime &rt, decltype(types.email) value) {
80
+ return bridging::toJs(rt, value);
81
+ }
82
+ #endif
83
+
84
+ static jsi::Object toJs(
85
+ jsi::Runtime &rt,
86
+ const T &value,
87
+ const std::shared_ptr<CallInvoker> &jsInvoker) {
88
+ auto result = facebook::jsi::Object(rt);
89
+ result.setProperty(rt, "type", bridging::toJs(rt, value.type, jsInvoker));
90
+ result.setProperty(rt, "id", bridging::toJs(rt, value.id, jsInvoker));
91
+ result.setProperty(rt, "idToken", bridging::toJs(rt, value.idToken, jsInvoker));
92
+ if (value.displayName) {
93
+ result.setProperty(rt, "displayName", bridging::toJs(rt, value.displayName.value(), jsInvoker));
94
+ }
95
+ if (value.familyName) {
96
+ result.setProperty(rt, "familyName", bridging::toJs(rt, value.familyName.value(), jsInvoker));
97
+ }
98
+ if (value.givenName) {
99
+ result.setProperty(rt, "givenName", bridging::toJs(rt, value.givenName.value(), jsInvoker));
100
+ }
101
+ if (value.email) {
102
+ result.setProperty(rt, "email", bridging::toJs(rt, value.email.value(), jsInvoker));
103
+ }
104
+ return result;
105
+ }
106
+ };
107
+
108
+
109
+
110
+ #pragma mark - NativeCredentialsManagerAppleSignInParams
111
+
112
+ template <typename P0, typename P1>
113
+ struct NativeCredentialsManagerAppleSignInParams {
114
+ P0 nonce;
115
+ P1 requestedScopes;
116
+ bool operator==(const NativeCredentialsManagerAppleSignInParams &other) const {
117
+ return nonce == other.nonce && requestedScopes == other.requestedScopes;
118
+ }
119
+ };
120
+
121
+ template <typename T>
122
+ struct NativeCredentialsManagerAppleSignInParamsBridging {
123
+ static T types;
124
+
125
+ static T fromJs(
126
+ jsi::Runtime &rt,
127
+ const jsi::Object &value,
128
+ const std::shared_ptr<CallInvoker> &jsInvoker) {
129
+ T result{
130
+ bridging::fromJs<decltype(types.nonce)>(rt, value.getProperty(rt, "nonce"), jsInvoker),
131
+ bridging::fromJs<decltype(types.requestedScopes)>(rt, value.getProperty(rt, "requestedScopes"), jsInvoker)};
132
+ return result;
133
+ }
134
+
135
+ #ifdef DEBUG
136
+ static jsi::String nonceToJs(jsi::Runtime &rt, decltype(types.nonce) value) {
137
+ return bridging::toJs(rt, value);
138
+ }
139
+
140
+ static jsi::Array requestedScopesToJs(jsi::Runtime &rt, decltype(types.requestedScopes) value) {
141
+ return bridging::toJs(rt, value);
142
+ }
143
+ #endif
144
+
145
+ static jsi::Object toJs(
146
+ jsi::Runtime &rt,
147
+ const T &value,
148
+ const std::shared_ptr<CallInvoker> &jsInvoker) {
149
+ auto result = facebook::jsi::Object(rt);
150
+ result.setProperty(rt, "nonce", bridging::toJs(rt, value.nonce, jsInvoker));
151
+ result.setProperty(rt, "requestedScopes", bridging::toJs(rt, value.requestedScopes, jsInvoker));
152
+ return result;
153
+ }
154
+ };
155
+
156
+
157
+
19
158
  #pragma mark - NativeCredentialsManagerCredObject
20
159
 
21
160
  template <typename P0, typename P1>
@@ -223,9 +362,10 @@ protected:
223
362
 
224
363
  public:
225
364
  virtual jsi::Value signUpWithPasskeys(jsi::Runtime &rt, jsi::Object requestJson, bool preferImmediatelyAvailableCredentials) = 0;
226
- virtual void signUpWithPassword(jsi::Runtime &rt, jsi::Object credObject) = 0;
365
+ virtual jsi::Value signUpWithPassword(jsi::Runtime &rt, jsi::Object credObject) = 0;
227
366
  virtual jsi::Value signIn(jsi::Runtime &rt, jsi::Array options, jsi::Object params) = 0;
228
367
  virtual jsi::Value signUpWithGoogle(jsi::Runtime &rt, jsi::Object params) = 0;
368
+ virtual jsi::Value signUpWithApple(jsi::Runtime &rt, jsi::Object params) = 0;
229
369
  virtual jsi::Value signOut(jsi::Runtime &rt) = 0;
230
370
 
231
371
  };
@@ -265,12 +405,12 @@ private:
265
405
  return bridging::callFromJs<jsi::Value>(
266
406
  rt, &T::signUpWithPasskeys, jsInvoker_, instance_, std::move(requestJson), std::move(preferImmediatelyAvailableCredentials));
267
407
  }
268
- void signUpWithPassword(jsi::Runtime &rt, jsi::Object credObject) override {
408
+ jsi::Value signUpWithPassword(jsi::Runtime &rt, jsi::Object credObject) override {
269
409
  static_assert(
270
410
  bridging::getParameterCount(&T::signUpWithPassword) == 2,
271
411
  "Expected signUpWithPassword(...) to have 2 parameters");
272
412
 
273
- return bridging::callFromJs<void>(
413
+ return bridging::callFromJs<jsi::Value>(
274
414
  rt, &T::signUpWithPassword, jsInvoker_, instance_, std::move(credObject));
275
415
  }
276
416
  jsi::Value signIn(jsi::Runtime &rt, jsi::Array options, jsi::Object params) override {
@@ -289,6 +429,14 @@ private:
289
429
  return bridging::callFromJs<jsi::Value>(
290
430
  rt, &T::signUpWithGoogle, jsInvoker_, instance_, std::move(params));
291
431
  }
432
+ jsi::Value signUpWithApple(jsi::Runtime &rt, jsi::Object params) override {
433
+ static_assert(
434
+ bridging::getParameterCount(&T::signUpWithApple) == 2,
435
+ "Expected signUpWithApple(...) to have 2 parameters");
436
+
437
+ return bridging::callFromJs<jsi::Value>(
438
+ rt, &T::signUpWithApple, jsInvoker_, instance_, std::move(params));
439
+ }
292
440
  jsi::Value signOut(jsi::Runtime &rt) override {
293
441
  static_assert(
294
442
  bridging::getParameterCount(&T::signOut) == 1,
@@ -1,5 +1,6 @@
1
1
  package com.credentialsmanager.handlers
2
2
 
3
+ import android.app.Activity
3
4
  import android.content.Context
4
5
  import android.util.Log
5
6
  import androidx.credentials.ClearCredentialStateRequest
@@ -16,6 +17,7 @@ import androidx.credentials.GetPublicKeyCredentialOption
16
17
  import androidx.credentials.PasswordCredential
17
18
  import androidx.credentials.PublicKeyCredential
18
19
  import com.facebook.react.bridge.Arguments
20
+ import com.facebook.react.bridge.ReactApplicationContext
19
21
  import com.facebook.react.bridge.ReadableArray
20
22
  import com.facebook.react.bridge.ReadableMap
21
23
  import com.google.android.libraries.identity.googleid.GetGoogleIdOption
@@ -28,6 +30,23 @@ class CredentialHandler(
28
30
  ) {
29
31
  private val credentialManager = CredentialManager.create(context)
30
32
 
33
+ // Helper function to get activity context
34
+ private fun getActivityContext(): Context {
35
+ if (context is ReactApplicationContext) {
36
+ val activity = context.currentActivity
37
+ if (activity != null) {
38
+ return activity
39
+ } else {
40
+ Log.w("CredentialManager", "No current activity found. UI operations may fail - ensure you're calling from the main/UI thread.")
41
+ }
42
+ } else {
43
+ Log.w("CredentialManager", "Context is not ReactApplicationContext. UI operations may fail.")
44
+ }
45
+ // If we can't get an activity context, use the original context
46
+ // This might still cause issues for UI operations, but it's better than null
47
+ return context
48
+ }
49
+
31
50
  suspend fun signOut() {
32
51
  credentialManager.clearCredentialState(ClearCredentialStateRequest())
33
52
  }
@@ -36,26 +55,34 @@ class CredentialHandler(
36
55
  jsonString: String,
37
56
  preferImmediatelyAvailableCredentials: Boolean,
38
57
  ): ReadableMap? {
39
- val request =
40
- CreatePublicKeyCredentialRequest(
41
- requestJson = jsonString,
42
- preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
43
- )
58
+ Log.d("CredentialManager", "Creating passkey with request: $jsonString")
59
+
60
+ try {
61
+ val request =
62
+ CreatePublicKeyCredentialRequest(
63
+ requestJson = jsonString,
64
+ preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
65
+ )
44
66
 
45
- val response =
46
- credentialManager.createCredential(
47
- context,
48
- request,
49
- ) as CreatePublicKeyCredentialResponse
67
+ val activityContext = getActivityContext()
68
+ val response =
69
+ credentialManager.createCredential(
70
+ activityContext,
71
+ request,
72
+ ) as CreatePublicKeyCredentialResponse
50
73
 
51
- return response.data.getString("androidx.credentials.BUNDLE_KEY_REGISTRATION_RESPONSE_JSON")?.let { json ->
52
- val jsonObject = Arguments.createMap()
53
- val parsedObject = JSONObject(json)
74
+ return response.data.getString("androidx.credentials.BUNDLE_KEY_REGISTRATION_RESPONSE_JSON")?.let { json ->
75
+ val jsonObject = Arguments.createMap()
76
+ val parsedObject = JSONObject(json)
54
77
 
55
- parsedObject.keys().forEach { key ->
56
- jsonObject.putString(key, parsedObject.getString(key))
78
+ parsedObject.keys().forEach { key ->
79
+ jsonObject.putString(key, parsedObject.getString(key))
80
+ }
81
+ jsonObject
57
82
  }
58
- jsonObject
83
+ } catch (e: Exception) {
84
+ Log.e("CredentialManager", "Error creating passkey", e)
85
+ throw e
59
86
  }
60
87
  }
61
88
 
@@ -63,8 +90,16 @@ class CredentialHandler(
63
90
  username: String,
64
91
  password: String,
65
92
  ) {
66
- val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
67
- credentialManager.createCredential(context, createPasswordRequest)
93
+ Log.d("CredentialManager", "Creating password credential for username: $username")
94
+
95
+ try {
96
+ val activityContext = getActivityContext()
97
+ val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
98
+ credentialManager.createCredential(activityContext, createPasswordRequest)
99
+ } catch (e: Exception) {
100
+ Log.e("CredentialManager", "Error creating password credential", e)
101
+ throw e
102
+ }
68
103
  }
69
104
 
70
105
  suspend fun signIn(
@@ -106,17 +141,19 @@ class CredentialHandler(
106
141
  }
107
142
 
108
143
  val request = GetCredentialRequest(credentialOptions)
109
- val result = credentialManager.getCredential(context, request)
144
+ val activityContext = getActivityContext()
145
+ val result = credentialManager.getCredential(activityContext, request)
110
146
  return handleSignInResult(result)
111
147
  }
112
148
 
113
149
  suspend fun getSavedCredentials(jsonString: String): ReadableMap? {
114
150
  val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(jsonString, null)
115
151
  val getPasswordOption = GetPasswordOption()
152
+ val activityContext = getActivityContext()
116
153
 
117
154
  val result =
118
155
  credentialManager.getCredential(
119
- context,
156
+ activityContext,
120
157
  GetCredentialRequest(
121
158
  listOf(
122
159
  getPublicKeyCredentialOption,
@@ -210,10 +247,11 @@ class CredentialHandler(
210
247
  .addCredentialOption(googleIdOption)
211
248
  .build()
212
249
 
250
+ val activityContext = getActivityContext()
213
251
  val result =
214
252
  credentialManager.getCredential(
215
253
  request = request,
216
- context = context,
254
+ context = activityContext,
217
255
  )
218
256
 
219
257
  return result