react-native-credentials-manager 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +35 -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 +50 -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 +49 -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 +82 -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,
|
|
@@ -36,26 +36,33 @@ class CredentialHandler(
|
|
|
36
36
|
jsonString: String,
|
|
37
37
|
preferImmediatelyAvailableCredentials: Boolean,
|
|
38
38
|
): ReadableMap? {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
Log.d("CredentialManager", "Creating passkey with request: $jsonString")
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
val request =
|
|
43
|
+
CreatePublicKeyCredentialRequest(
|
|
44
|
+
requestJson = jsonString,
|
|
45
|
+
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
val response =
|
|
49
|
+
credentialManager.createCredential(
|
|
50
|
+
context,
|
|
51
|
+
request,
|
|
52
|
+
) as CreatePublicKeyCredentialResponse
|
|
53
|
+
|
|
54
|
+
return response.data.getString("androidx.credentials.BUNDLE_KEY_REGISTRATION_RESPONSE_JSON")?.let { json ->
|
|
55
|
+
val jsonObject = Arguments.createMap()
|
|
56
|
+
val parsedObject = JSONObject(json)
|
|
57
|
+
|
|
58
|
+
parsedObject.keys().forEach { key ->
|
|
59
|
+
jsonObject.putString(key, parsedObject.getString(key))
|
|
60
|
+
}
|
|
61
|
+
jsonObject
|
|
57
62
|
}
|
|
58
|
-
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
Log.e("CredentialManager", "Error creating passkey", e)
|
|
65
|
+
throw e
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
@@ -63,8 +70,15 @@ class CredentialHandler(
|
|
|
63
70
|
username: String,
|
|
64
71
|
password: String,
|
|
65
72
|
) {
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
Log.d("CredentialManager", "Creating password credential for username: $username")
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
|
|
77
|
+
credentialManager.createCredential(context, createPasswordRequest)
|
|
78
|
+
} catch (e: Exception) {
|
|
79
|
+
Log.e("CredentialManager", "Error creating password credential", e)
|
|
80
|
+
throw e
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
suspend fun signIn(
|
|
@@ -49,14 +49,34 @@ class CredentialsManagerModule(
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
override fun signUpWithPassword(credObject: ReadableMap) {
|
|
52
|
+
override fun signUpWithPassword(credObject: ReadableMap, promise: Promise) {
|
|
53
53
|
val username = credObject.getString("username") ?: ""
|
|
54
54
|
val password = credObject.getString("password") ?: ""
|
|
55
|
+
|
|
56
|
+
if (username.isEmpty()) {
|
|
57
|
+
promise.reject("INVALID_USERNAME", "Username cannot be empty")
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (password.isEmpty()) {
|
|
62
|
+
promise.reject("INVALID_PASSWORD", "Password cannot be empty")
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
coroutineScope.launch {
|
|
56
67
|
try {
|
|
57
68
|
credentialHandler.createPassword(username, password)
|
|
69
|
+
|
|
70
|
+
// Create success response
|
|
71
|
+
val result = mapOf(
|
|
72
|
+
"type" to "password",
|
|
73
|
+
"username" to username,
|
|
74
|
+
"success" to true
|
|
75
|
+
)
|
|
76
|
+
promise.resolve(result)
|
|
58
77
|
} catch (e: CreateCredentialException) {
|
|
59
78
|
ErrorHandler.handleCredentialError(e)
|
|
79
|
+
promise.reject("CREDENTIAL_ERROR", e.message.toString())
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
82
|
}
|
|
@@ -140,4 +160,11 @@ class CredentialsManagerModule(
|
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
162
|
}
|
|
163
|
+
|
|
164
|
+
override fun signUpWithApple(params: ReadableMap, promise: Promise) {
|
|
165
|
+
promise.reject(
|
|
166
|
+
"PLATFORM_NOT_SUPPORTED",
|
|
167
|
+
"Sign up with Apple is only supported on iOS devices"
|
|
168
|
+
)
|
|
169
|
+
}
|
|
143
170
|
}
|
|
@@ -55,14 +55,34 @@ class CredentialsManagerModule(
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
@ReactMethod
|
|
58
|
-
fun signUpWithPassword(credObject: ReadableMap) {
|
|
58
|
+
fun signUpWithPassword(credObject: ReadableMap, promise: Promise) {
|
|
59
59
|
val username = credObject.getString("username") ?: ""
|
|
60
60
|
val password = credObject.getString("password") ?: ""
|
|
61
|
+
|
|
62
|
+
if (username.isEmpty()) {
|
|
63
|
+
promise.reject("INVALID_USERNAME", "Username cannot be empty")
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (password.isEmpty()) {
|
|
68
|
+
promise.reject("INVALID_PASSWORD", "Password cannot be empty")
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
coroutineScope.launch {
|
|
62
73
|
try {
|
|
63
74
|
credentialHandler.createPassword(username, password)
|
|
75
|
+
|
|
76
|
+
// Create success response
|
|
77
|
+
val result = mapOf(
|
|
78
|
+
"type" to "password",
|
|
79
|
+
"username" to username,
|
|
80
|
+
"success" to true
|
|
81
|
+
)
|
|
82
|
+
promise.resolve(result)
|
|
64
83
|
} catch (e: CreateCredentialException) {
|
|
65
84
|
ErrorHandler.handleCredentialError(e)
|
|
85
|
+
promise.reject("CREDENTIAL_ERROR", e.message.toString())
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
88
|
}
|
|
@@ -161,4 +181,13 @@ class CredentialsManagerModule(
|
|
|
161
181
|
}
|
|
162
182
|
}
|
|
163
183
|
}
|
|
184
|
+
|
|
185
|
+
@ReactMethod
|
|
186
|
+
fun signUpWithApple(params: ReadableMap, promise: Promise) {
|
|
187
|
+
// Since this is an iOS-specific function, we just reject with an appropriate message on Android
|
|
188
|
+
promise.reject(
|
|
189
|
+
"PLATFORM_NOT_SUPPORTED",
|
|
190
|
+
"Sign up with Apple is only supported on iOS devices"
|
|
191
|
+
)
|
|
192
|
+
}
|
|
164
193
|
}
|