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.
- package/README.md +192 -1
- package/android/build.gradle +6 -5
- package/android/generated/java/com/credentialsmanager/NativeCredentialsManagerSpec.java +5 -1
- package/android/generated/jni/RNCredentialsManagerSpec-generated.cpp +7 -1
- package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
- package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI.h +151 -3
- package/android/src/main/java/com/credentialsmanager/handlers/CredentialHandler.kt +59 -21
- package/android/src/newarch/java/com/credentialsmanager/CredentialsManagerModule.kt +28 -1
- package/android/src/oldarch/java/com/credentialsmanager/CredentialsManagerModule.kt +30 -1
- package/ios/CredentialsManager.h +8 -2
- package/ios/CredentialsManager.mm +396 -4
- package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec-generated.mm +14 -1
- package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec.h +38 -1
- package/ios/generated/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
- package/ios/generated/RNCredentialsManagerSpecJSI.h +151 -3
- package/lib/commonjs/NativeCredentialsManager.js +6 -0
- package/lib/commonjs/NativeCredentialsManager.js.map +1 -1
- package/lib/commonjs/index.js +45 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeCredentialsManager.js +13 -0
- package/lib/module/NativeCredentialsManager.js.map +1 -1
- package/lib/module/index.js +44 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts +49 -7
- package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +10 -4
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeCredentialsManager.d.ts +49 -7
- package/lib/typescript/module/src/NativeCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +10 -4
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-credentials-manager.podspec +4 -1
- package/react-native.config.js +0 -1
- package/src/NativeCredentialsManager.ts +67 -8
- package/src/index.tsx +81 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ React Native Credentials Manager
|
|
|
4
4
|
|
|
5
5
|

|
|
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
|
|
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.
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
104
|
-
|
|
105
|
-
implementation
|
|
106
|
-
|
|
107
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
val activityContext = getActivityContext()
|
|
68
|
+
val response =
|
|
69
|
+
credentialManager.createCredential(
|
|
70
|
+
activityContext,
|
|
71
|
+
request,
|
|
72
|
+
) as CreatePublicKeyCredentialResponse
|
|
50
73
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
78
|
+
parsedObject.keys().forEach { key ->
|
|
79
|
+
jsonObject.putString(key, parsedObject.getString(key))
|
|
80
|
+
}
|
|
81
|
+
jsonObject
|
|
57
82
|
}
|
|
58
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
254
|
+
context = activityContext,
|
|
217
255
|
)
|
|
218
256
|
|
|
219
257
|
return result
|