@trainon-inc/capacitor-clerk-native 1.21.0 → 1.22.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 +61 -31
- package/android/build.gradle +17 -2
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/trainon/capacitor/clerk/ClerkNativePlugin.kt +509 -0
- package/package.json +1 -1
- package/android/src/main/java/com/trainon/capacitor/clerk/.gitkeep +0 -0
- package/android/src/main/java/com/trainon/capacitor/clerk/ClerkNativePlugin.java +0 -81
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Alternatively, install from GitHub Packages:
|
|
|
49
49
|
1. Create a `.npmrc` file in your project root:
|
|
50
50
|
```
|
|
51
51
|
@trainon-inc:registry=https://npm.pkg.github.com
|
|
52
|
-
//npm.pkg.github.com/:_authToken=
|
|
52
|
+
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
2. Install the package:
|
|
@@ -414,15 +414,26 @@ function Profile() {
|
|
|
414
414
|
|
|
415
415
|
## Android Setup
|
|
416
416
|
|
|
417
|
-
Android is supported
|
|
417
|
+
Android is fully supported with the native Clerk Android SDK integration. Unlike iOS, Android doesn't require a bridge pattern - the plugin directly integrates with the Clerk Android SDK.
|
|
418
418
|
|
|
419
419
|
### Prerequisites
|
|
420
420
|
|
|
421
421
|
- ✅ A [Clerk account](https://dashboard.clerk.com/sign-up)
|
|
422
422
|
- ✅ A Clerk application set up in the dashboard
|
|
423
|
+
- ✅ **Native API enabled** in Clerk Dashboard → Settings → Native Applications
|
|
423
424
|
- ✅ Android Studio with Gradle 8.7+ and AGP 8.5+
|
|
425
|
+
- ✅ JDK 17 or higher
|
|
426
|
+
|
|
427
|
+
### 1. Register Your Android App with Clerk
|
|
424
428
|
|
|
425
|
-
|
|
429
|
+
1. Go to the [**Native Applications**](https://dashboard.clerk.com/last-active?path=native-applications) page in Clerk Dashboard
|
|
430
|
+
2. Click **"Add Application"**
|
|
431
|
+
3. Select **Android** tab
|
|
432
|
+
4. Enter your Android app details:
|
|
433
|
+
- **Package Name**: Your app's package name (e.g., `com.trainon.member`)
|
|
434
|
+
- **SHA256 Fingerprint**: Your app's signing certificate fingerprint (get it with `./gradlew signingReport`)
|
|
435
|
+
|
|
436
|
+
### 2. Sync Capacitor
|
|
426
437
|
|
|
427
438
|
After installing the plugin, sync your Android project:
|
|
428
439
|
|
|
@@ -433,40 +444,52 @@ npx cap sync android
|
|
|
433
444
|
This will:
|
|
434
445
|
- Add the plugin to `capacitor.settings.gradle`
|
|
435
446
|
- Add the plugin dependency to `capacitor.build.gradle`
|
|
447
|
+
- Include the Clerk Android SDK automatically
|
|
436
448
|
|
|
437
|
-
###
|
|
438
|
-
|
|
439
|
-
The plugin includes:
|
|
449
|
+
### 3. Configure Clerk in Your App
|
|
440
450
|
|
|
441
|
-
|
|
442
|
-
android/
|
|
443
|
-
├── build.gradle # Gradle build configuration
|
|
444
|
-
└── src/
|
|
445
|
-
└── main/
|
|
446
|
-
├── AndroidManifest.xml # Android manifest
|
|
447
|
-
└── java/
|
|
448
|
-
└── com/trainon/capacitor/clerk/
|
|
449
|
-
└── ClerkNativePlugin.java
|
|
450
|
-
```
|
|
451
|
+
The plugin automatically handles Clerk initialization when you call `configure()` from JavaScript. No additional Android-specific setup is required!
|
|
451
452
|
|
|
452
|
-
|
|
453
|
+
```typescript
|
|
454
|
+
import { ClerkNative } from '@trainon-inc/capacitor-clerk-native';
|
|
453
455
|
|
|
454
|
-
|
|
456
|
+
// Configure Clerk (call once at app startup)
|
|
457
|
+
await ClerkNative.configure({
|
|
458
|
+
publishableKey: 'pk_test_your_clerk_key_here'
|
|
459
|
+
});
|
|
455
460
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
| `minSdkVersion` | 23 | Minimum Android version |
|
|
460
|
-
| `targetSdkVersion` | 35 | Target Android version |
|
|
461
|
-
| `junitVersion` | 4.13.2 | JUnit test version |
|
|
461
|
+
// Load and check for existing session
|
|
462
|
+
const { user } = await ClerkNative.load();
|
|
463
|
+
```
|
|
462
464
|
|
|
463
|
-
### 4.
|
|
465
|
+
### 4. Plugin Features
|
|
466
|
+
|
|
467
|
+
The Android implementation supports all authentication methods:
|
|
468
|
+
|
|
469
|
+
| Method | Description |
|
|
470
|
+
|--------|-------------|
|
|
471
|
+
| `configure()` | Initialize Clerk with publishable key |
|
|
472
|
+
| `load()` | Load Clerk and check for existing session |
|
|
473
|
+
| `signInWithPassword()` | Sign in with email and password |
|
|
474
|
+
| `signInWithEmail()` | Start email code sign in flow |
|
|
475
|
+
| `verifyEmailCode()` | Verify email code |
|
|
476
|
+
| `signUp()` | Create a new account |
|
|
477
|
+
| `verifySignUpEmail()` | Verify sign up email |
|
|
478
|
+
| `getUser()` | Get current user |
|
|
479
|
+
| `getToken()` | Get authentication token |
|
|
480
|
+
| `signOut()` | Sign out current user |
|
|
481
|
+
| `updateUser()` | Update user profile |
|
|
482
|
+
| `requestPasswordReset()` | Request password reset |
|
|
483
|
+
| `resetPassword()` | Reset password with code |
|
|
484
|
+
| `refreshSession()` | Refresh session token |
|
|
485
|
+
|
|
486
|
+
### 5. Build Requirements
|
|
464
487
|
|
|
465
488
|
- **Gradle**: 8.11.1+
|
|
466
489
|
- **Android Gradle Plugin**: 8.7.2+
|
|
467
|
-
- **Java**:
|
|
468
|
-
|
|
469
|
-
|
|
490
|
+
- **Java/Kotlin**: 17+
|
|
491
|
+
- **Min SDK**: 23 (Android 6.0)
|
|
492
|
+
- **Target SDK**: 35 (Android 15)
|
|
470
493
|
|
|
471
494
|
### Troubleshooting Android
|
|
472
495
|
|
|
@@ -476,19 +499,26 @@ Could not resolve project :trainon-inc-capacitor-clerk-native
|
|
|
476
499
|
No matching variant of project was found. No variants exist.
|
|
477
500
|
```
|
|
478
501
|
|
|
479
|
-
**Solution**: Ensure you have the latest version of the plugin
|
|
502
|
+
**Solution**: Ensure you have the latest version of the plugin which includes the required build files:
|
|
480
503
|
```bash
|
|
481
504
|
npm update @trainon-inc/capacitor-clerk-native
|
|
482
505
|
npx cap sync android
|
|
483
506
|
```
|
|
484
507
|
|
|
508
|
+
#### "invalid source release: 21" error
|
|
509
|
+
The plugin uses Java 17 by default. Ensure your Android Studio uses JDK 17+:
|
|
510
|
+
- **File → Project Structure → SDK Location → Gradle JDK** → Select JDK 17+
|
|
511
|
+
|
|
485
512
|
#### Gradle sync fails
|
|
486
513
|
- Clean the project: **Build → Clean Project**
|
|
487
514
|
- Invalidate caches: **File → Invalidate Caches / Restart**
|
|
488
515
|
- Delete `.gradle` folder and re-sync
|
|
489
516
|
|
|
490
|
-
####
|
|
491
|
-
|
|
517
|
+
#### Network errors
|
|
518
|
+
Ensure INTERNET permission is in your `AndroidManifest.xml`:
|
|
519
|
+
```xml
|
|
520
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
521
|
+
```
|
|
492
522
|
|
|
493
523
|
## Contributing
|
|
494
524
|
|
package/android/build.gradle
CHANGED
|
@@ -3,6 +3,7 @@ ext {
|
|
|
3
3
|
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
|
|
4
4
|
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
|
|
5
5
|
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
|
|
6
|
+
kotlinVersion = project.hasProperty('kotlinVersion') ? rootProject.ext.kotlinVersion : '1.9.25'
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
buildscript {
|
|
@@ -12,10 +13,12 @@ buildscript {
|
|
|
12
13
|
}
|
|
13
14
|
dependencies {
|
|
14
15
|
classpath 'com.android.tools.build:gradle:8.7.2'
|
|
16
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
apply plugin: 'com.android.library'
|
|
21
|
+
apply plugin: 'kotlin-android'
|
|
19
22
|
|
|
20
23
|
android {
|
|
21
24
|
namespace "com.trainon.capacitor.clerk"
|
|
@@ -37,8 +40,11 @@ android {
|
|
|
37
40
|
abortOnError false
|
|
38
41
|
}
|
|
39
42
|
compileOptions {
|
|
40
|
-
sourceCompatibility JavaVersion.
|
|
41
|
-
targetCompatibility JavaVersion.
|
|
43
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
44
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
45
|
+
}
|
|
46
|
+
kotlinOptions {
|
|
47
|
+
jvmTarget = '17'
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -51,6 +57,15 @@ dependencies {
|
|
|
51
57
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
52
58
|
implementation project(':capacitor-android')
|
|
53
59
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
60
|
+
|
|
61
|
+
// Kotlin
|
|
62
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
|
63
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
64
|
+
|
|
65
|
+
// Clerk Android SDK
|
|
66
|
+
implementation "com.clerk:clerk-android:0.8.0"
|
|
67
|
+
|
|
68
|
+
// Testing
|
|
54
69
|
testImplementation "junit:junit:$junitVersion"
|
|
55
70
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
56
71
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
package com.trainon.capacitor.clerk
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.clerk.android.Clerk
|
|
5
|
+
import com.clerk.android.models.Session
|
|
6
|
+
import com.clerk.android.models.SignIn
|
|
7
|
+
import com.clerk.android.models.SignUp
|
|
8
|
+
import com.clerk.android.models.User
|
|
9
|
+
import com.getcapacitor.JSObject
|
|
10
|
+
import com.getcapacitor.Plugin
|
|
11
|
+
import com.getcapacitor.PluginCall
|
|
12
|
+
import com.getcapacitor.PluginMethod
|
|
13
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
14
|
+
import kotlinx.coroutines.CoroutineScope
|
|
15
|
+
import kotlinx.coroutines.Dispatchers
|
|
16
|
+
import kotlinx.coroutines.SupervisorJob
|
|
17
|
+
import kotlinx.coroutines.launch
|
|
18
|
+
|
|
19
|
+
@CapacitorPlugin(name = "ClerkNative")
|
|
20
|
+
class ClerkNativePlugin : Plugin() {
|
|
21
|
+
|
|
22
|
+
private val TAG = "ClerkNativePlugin"
|
|
23
|
+
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
24
|
+
private var isConfigured = false
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
fun configure(call: PluginCall) {
|
|
28
|
+
val publishableKey = call.getString("publishableKey")
|
|
29
|
+
|
|
30
|
+
if (publishableKey.isNullOrEmpty()) {
|
|
31
|
+
call.reject("Must provide publishableKey")
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Initialize Clerk with the publishable key
|
|
37
|
+
Clerk.configure(context, publishableKey)
|
|
38
|
+
isConfigured = true
|
|
39
|
+
Log.d(TAG, "Clerk configured successfully")
|
|
40
|
+
call.resolve()
|
|
41
|
+
} catch (e: Exception) {
|
|
42
|
+
Log.e(TAG, "Failed to configure Clerk", e)
|
|
43
|
+
call.reject("Failed to configure Clerk: ${e.message}")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@PluginMethod
|
|
48
|
+
fun load(call: PluginCall) {
|
|
49
|
+
if (!isConfigured) {
|
|
50
|
+
call.reject("Clerk not configured. Call configure() first.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
scope.launch {
|
|
55
|
+
try {
|
|
56
|
+
val user = Clerk.user
|
|
57
|
+
val result = JSObject()
|
|
58
|
+
if (user != null) {
|
|
59
|
+
result.put("user", userToJSObject(user))
|
|
60
|
+
} else {
|
|
61
|
+
result.put("user", null)
|
|
62
|
+
}
|
|
63
|
+
call.resolve(result)
|
|
64
|
+
} catch (e: Exception) {
|
|
65
|
+
Log.e(TAG, "Failed to load Clerk", e)
|
|
66
|
+
call.reject("Failed to load Clerk: ${e.message}")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@PluginMethod
|
|
72
|
+
fun signInWithEmail(call: PluginCall) {
|
|
73
|
+
val email = call.getString("email")
|
|
74
|
+
|
|
75
|
+
if (email.isNullOrEmpty()) {
|
|
76
|
+
call.reject("Must provide email")
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
scope.launch {
|
|
81
|
+
try {
|
|
82
|
+
val signIn = SignIn.create(identifier = email)
|
|
83
|
+
signIn.onSuccess { si ->
|
|
84
|
+
// Prepare email code verification
|
|
85
|
+
si.prepareFirstFactor(
|
|
86
|
+
strategy = SignIn.PrepareFirstFactorStrategy.EmailCode(
|
|
87
|
+
emailAddressId = si.supportedFirstFactors
|
|
88
|
+
?.filterIsInstance<SignIn.Factor.EmailCode>()
|
|
89
|
+
?.firstOrNull()
|
|
90
|
+
?.emailAddressId ?: ""
|
|
91
|
+
)
|
|
92
|
+
).onSuccess {
|
|
93
|
+
val result = JSObject()
|
|
94
|
+
result.put("requiresCode", true)
|
|
95
|
+
call.resolve(result)
|
|
96
|
+
}.onFailure { error ->
|
|
97
|
+
call.reject("Failed to prepare email code: ${error.message}")
|
|
98
|
+
}
|
|
99
|
+
}.onFailure { error ->
|
|
100
|
+
call.reject("Sign in with email failed: ${error.message}")
|
|
101
|
+
}
|
|
102
|
+
} catch (e: Exception) {
|
|
103
|
+
Log.e(TAG, "Sign in with email failed", e)
|
|
104
|
+
call.reject("Sign in with email failed: ${e.message}")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@PluginMethod
|
|
110
|
+
fun verifyEmailCode(call: PluginCall) {
|
|
111
|
+
val code = call.getString("code")
|
|
112
|
+
|
|
113
|
+
if (code.isNullOrEmpty()) {
|
|
114
|
+
call.reject("Must provide code")
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
scope.launch {
|
|
119
|
+
try {
|
|
120
|
+
val signIn = Clerk.client?.signIn
|
|
121
|
+
if (signIn == null) {
|
|
122
|
+
call.reject("No active sign in session")
|
|
123
|
+
return@launch
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
signIn.attemptFirstFactor(
|
|
127
|
+
strategy = SignIn.AttemptFirstFactorStrategy.EmailCode(code = code)
|
|
128
|
+
).onSuccess { si ->
|
|
129
|
+
if (si.status == SignIn.Status.COMPLETE) {
|
|
130
|
+
// Set active session
|
|
131
|
+
Clerk.client?.activeSessions?.firstOrNull()?.let { session ->
|
|
132
|
+
Clerk.setActive(sessionId = session.id)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
val user = Clerk.user
|
|
136
|
+
val result = JSObject()
|
|
137
|
+
result.put("user", user?.let { userToJSObject(it) })
|
|
138
|
+
call.resolve(result)
|
|
139
|
+
} else {
|
|
140
|
+
call.reject("Verification incomplete")
|
|
141
|
+
}
|
|
142
|
+
}.onFailure { error ->
|
|
143
|
+
call.reject("Email code verification failed: ${error.message}")
|
|
144
|
+
}
|
|
145
|
+
} catch (e: Exception) {
|
|
146
|
+
Log.e(TAG, "Email code verification failed", e)
|
|
147
|
+
call.reject("Email code verification failed: ${e.message}")
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@PluginMethod
|
|
153
|
+
fun signInWithPassword(call: PluginCall) {
|
|
154
|
+
val email = call.getString("email")
|
|
155
|
+
val password = call.getString("password")
|
|
156
|
+
|
|
157
|
+
if (email.isNullOrEmpty() || password.isNullOrEmpty()) {
|
|
158
|
+
call.reject("Must provide email and password")
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
scope.launch {
|
|
163
|
+
try {
|
|
164
|
+
val signIn = SignIn.create(identifier = email)
|
|
165
|
+
signIn.onSuccess { si ->
|
|
166
|
+
si.attemptFirstFactor(
|
|
167
|
+
strategy = SignIn.AttemptFirstFactorStrategy.Password(password = password)
|
|
168
|
+
).onSuccess { completedSignIn ->
|
|
169
|
+
if (completedSignIn.status == SignIn.Status.COMPLETE) {
|
|
170
|
+
// Set active session
|
|
171
|
+
completedSignIn.createdSessionId?.let { sessionId ->
|
|
172
|
+
Clerk.setActive(sessionId = sessionId)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Get user after sign in
|
|
176
|
+
val user = Clerk.user
|
|
177
|
+
val result = JSObject()
|
|
178
|
+
result.put("user", user?.let { userToJSObject(it) })
|
|
179
|
+
call.resolve(result)
|
|
180
|
+
} else {
|
|
181
|
+
call.reject("Sign in incomplete")
|
|
182
|
+
}
|
|
183
|
+
}.onFailure { error ->
|
|
184
|
+
call.reject("Sign in failed: ${error.message}")
|
|
185
|
+
}
|
|
186
|
+
}.onFailure { error ->
|
|
187
|
+
call.reject("Sign in failed: ${error.message}")
|
|
188
|
+
}
|
|
189
|
+
} catch (e: Exception) {
|
|
190
|
+
Log.e(TAG, "Sign in with password failed", e)
|
|
191
|
+
call.reject("Sign in with password failed: ${e.message}")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@PluginMethod
|
|
197
|
+
fun signUp(call: PluginCall) {
|
|
198
|
+
val emailAddress = call.getString("emailAddress")
|
|
199
|
+
val password = call.getString("password")
|
|
200
|
+
val firstName = call.getString("firstName")
|
|
201
|
+
val lastName = call.getString("lastName")
|
|
202
|
+
|
|
203
|
+
if (emailAddress.isNullOrEmpty() || password.isNullOrEmpty()) {
|
|
204
|
+
call.reject("Must provide emailAddress and password")
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
scope.launch {
|
|
209
|
+
try {
|
|
210
|
+
val signUp = SignUp.create(
|
|
211
|
+
emailAddress = emailAddress,
|
|
212
|
+
password = password,
|
|
213
|
+
firstName = firstName,
|
|
214
|
+
lastName = lastName
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
signUp.onSuccess { su ->
|
|
218
|
+
when (su.status) {
|
|
219
|
+
SignUp.Status.COMPLETE -> {
|
|
220
|
+
// Sign up complete, set active session
|
|
221
|
+
su.createdSessionId?.let { sessionId ->
|
|
222
|
+
Clerk.setActive(sessionId = sessionId)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
val user = Clerk.user
|
|
226
|
+
val result = JSObject()
|
|
227
|
+
result.put("user", user?.let { userToJSObject(it) })
|
|
228
|
+
result.put("requiresVerification", false)
|
|
229
|
+
call.resolve(result)
|
|
230
|
+
}
|
|
231
|
+
SignUp.Status.MISSING_REQUIREMENTS -> {
|
|
232
|
+
// Needs email verification
|
|
233
|
+
su.prepareEmailAddressVerification().onSuccess {
|
|
234
|
+
val result = JSObject()
|
|
235
|
+
result.put("user", null)
|
|
236
|
+
result.put("requiresVerification", true)
|
|
237
|
+
call.resolve(result)
|
|
238
|
+
}.onFailure { error ->
|
|
239
|
+
call.reject("Failed to prepare email verification: ${error.message}")
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else -> {
|
|
243
|
+
call.reject("Sign up incomplete: ${su.status}")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}.onFailure { error ->
|
|
247
|
+
call.reject("Sign up failed: ${error.message}")
|
|
248
|
+
}
|
|
249
|
+
} catch (e: Exception) {
|
|
250
|
+
Log.e(TAG, "Sign up failed", e)
|
|
251
|
+
call.reject("Sign up failed: ${e.message}")
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@PluginMethod
|
|
257
|
+
fun verifySignUpEmail(call: PluginCall) {
|
|
258
|
+
val code = call.getString("code")
|
|
259
|
+
|
|
260
|
+
if (code.isNullOrEmpty()) {
|
|
261
|
+
call.reject("Must provide code")
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
scope.launch {
|
|
266
|
+
try {
|
|
267
|
+
val signUp = Clerk.client?.signUp
|
|
268
|
+
if (signUp == null) {
|
|
269
|
+
call.reject("No active sign up session")
|
|
270
|
+
return@launch
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
signUp.attemptEmailAddressVerification(code = code)
|
|
274
|
+
.onSuccess { su ->
|
|
275
|
+
if (su.status == SignUp.Status.COMPLETE) {
|
|
276
|
+
su.createdSessionId?.let { sessionId ->
|
|
277
|
+
Clerk.setActive(sessionId = sessionId)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
val user = Clerk.user
|
|
281
|
+
val result = JSObject()
|
|
282
|
+
result.put("user", user?.let { userToJSObject(it) })
|
|
283
|
+
call.resolve(result)
|
|
284
|
+
} else {
|
|
285
|
+
call.reject("Verification incomplete")
|
|
286
|
+
}
|
|
287
|
+
}.onFailure { error ->
|
|
288
|
+
call.reject("Email verification failed: ${error.message}")
|
|
289
|
+
}
|
|
290
|
+
} catch (e: Exception) {
|
|
291
|
+
Log.e(TAG, "Email verification failed", e)
|
|
292
|
+
call.reject("Email verification failed: ${e.message}")
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@PluginMethod
|
|
298
|
+
fun getUser(call: PluginCall) {
|
|
299
|
+
scope.launch {
|
|
300
|
+
try {
|
|
301
|
+
val user = Clerk.user
|
|
302
|
+
val result = JSObject()
|
|
303
|
+
if (user != null) {
|
|
304
|
+
result.put("user", userToJSObject(user))
|
|
305
|
+
} else {
|
|
306
|
+
result.put("user", null)
|
|
307
|
+
}
|
|
308
|
+
call.resolve(result)
|
|
309
|
+
} catch (e: Exception) {
|
|
310
|
+
Log.e(TAG, "Get user failed", e)
|
|
311
|
+
call.reject("Get user failed: ${e.message}")
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@PluginMethod
|
|
317
|
+
fun getToken(call: PluginCall) {
|
|
318
|
+
scope.launch {
|
|
319
|
+
try {
|
|
320
|
+
val session = Clerk.session
|
|
321
|
+
if (session == null) {
|
|
322
|
+
val result = JSObject()
|
|
323
|
+
result.put("token", null)
|
|
324
|
+
call.resolve(result)
|
|
325
|
+
return@launch
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
session.getToken().onSuccess { tokenResult ->
|
|
329
|
+
val result = JSObject()
|
|
330
|
+
result.put("token", tokenResult?.jwt)
|
|
331
|
+
call.resolve(result)
|
|
332
|
+
}.onFailure { error ->
|
|
333
|
+
call.reject("Get token failed: ${error.message}")
|
|
334
|
+
}
|
|
335
|
+
} catch (e: Exception) {
|
|
336
|
+
Log.e(TAG, "Get token failed", e)
|
|
337
|
+
call.reject("Get token failed: ${e.message}")
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@PluginMethod
|
|
343
|
+
fun signOut(call: PluginCall) {
|
|
344
|
+
scope.launch {
|
|
345
|
+
try {
|
|
346
|
+
val session = Clerk.session
|
|
347
|
+
if (session != null) {
|
|
348
|
+
session.revoke().onSuccess {
|
|
349
|
+
call.resolve()
|
|
350
|
+
}.onFailure { error ->
|
|
351
|
+
call.reject("Sign out failed: ${error.message}")
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
// No active session, consider it signed out
|
|
355
|
+
call.resolve()
|
|
356
|
+
}
|
|
357
|
+
} catch (e: Exception) {
|
|
358
|
+
Log.e(TAG, "Sign out failed", e)
|
|
359
|
+
call.reject("Sign out failed: ${e.message}")
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@PluginMethod
|
|
365
|
+
fun updateUser(call: PluginCall) {
|
|
366
|
+
val firstName = call.getString("firstName")
|
|
367
|
+
val lastName = call.getString("lastName")
|
|
368
|
+
|
|
369
|
+
scope.launch {
|
|
370
|
+
try {
|
|
371
|
+
val user = Clerk.user
|
|
372
|
+
if (user == null) {
|
|
373
|
+
call.reject("No user signed in")
|
|
374
|
+
return@launch
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
user.update(
|
|
378
|
+
firstName = firstName,
|
|
379
|
+
lastName = lastName
|
|
380
|
+
).onSuccess { updatedUser ->
|
|
381
|
+
val result = JSObject()
|
|
382
|
+
result.put("user", userToJSObject(updatedUser))
|
|
383
|
+
call.resolve(result)
|
|
384
|
+
}.onFailure { error ->
|
|
385
|
+
call.reject("Update user failed: ${error.message}")
|
|
386
|
+
}
|
|
387
|
+
} catch (e: Exception) {
|
|
388
|
+
Log.e(TAG, "Update user failed", e)
|
|
389
|
+
call.reject("Update user failed: ${e.message}")
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@PluginMethod
|
|
395
|
+
fun requestPasswordReset(call: PluginCall) {
|
|
396
|
+
val email = call.getString("email")
|
|
397
|
+
|
|
398
|
+
if (email.isNullOrEmpty()) {
|
|
399
|
+
call.reject("Must provide email")
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
scope.launch {
|
|
404
|
+
try {
|
|
405
|
+
val signIn = SignIn.create(identifier = email)
|
|
406
|
+
signIn.onSuccess { si ->
|
|
407
|
+
si.prepareFirstFactor(
|
|
408
|
+
strategy = SignIn.PrepareFirstFactorStrategy.ResetPasswordEmailCode(
|
|
409
|
+
emailAddressId = si.supportedFirstFactors
|
|
410
|
+
?.filterIsInstance<SignIn.Factor.ResetPasswordEmailCode>()
|
|
411
|
+
?.firstOrNull()
|
|
412
|
+
?.emailAddressId ?: ""
|
|
413
|
+
)
|
|
414
|
+
).onSuccess {
|
|
415
|
+
call.resolve()
|
|
416
|
+
}.onFailure { error ->
|
|
417
|
+
call.reject("Failed to request password reset: ${error.message}")
|
|
418
|
+
}
|
|
419
|
+
}.onFailure { error ->
|
|
420
|
+
call.reject("Failed to request password reset: ${error.message}")
|
|
421
|
+
}
|
|
422
|
+
} catch (e: Exception) {
|
|
423
|
+
Log.e(TAG, "Request password reset failed", e)
|
|
424
|
+
call.reject("Request password reset failed: ${e.message}")
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@PluginMethod
|
|
430
|
+
fun resetPassword(call: PluginCall) {
|
|
431
|
+
val code = call.getString("code")
|
|
432
|
+
val newPassword = call.getString("newPassword")
|
|
433
|
+
|
|
434
|
+
if (code.isNullOrEmpty() || newPassword.isNullOrEmpty()) {
|
|
435
|
+
call.reject("Must provide code and newPassword")
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
scope.launch {
|
|
440
|
+
try {
|
|
441
|
+
val signIn = Clerk.client?.signIn
|
|
442
|
+
if (signIn == null) {
|
|
443
|
+
call.reject("No active sign in session")
|
|
444
|
+
return@launch
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
signIn.attemptFirstFactor(
|
|
448
|
+
strategy = SignIn.AttemptFirstFactorStrategy.ResetPasswordEmailCode(code = code)
|
|
449
|
+
).onSuccess { si ->
|
|
450
|
+
si.resetPassword(newPassword = newPassword).onSuccess { completedSignIn ->
|
|
451
|
+
if (completedSignIn.status == SignIn.Status.COMPLETE) {
|
|
452
|
+
completedSignIn.createdSessionId?.let { sessionId ->
|
|
453
|
+
Clerk.setActive(sessionId = sessionId)
|
|
454
|
+
}
|
|
455
|
+
call.resolve()
|
|
456
|
+
} else {
|
|
457
|
+
call.reject("Password reset incomplete")
|
|
458
|
+
}
|
|
459
|
+
}.onFailure { error ->
|
|
460
|
+
call.reject("Failed to reset password: ${error.message}")
|
|
461
|
+
}
|
|
462
|
+
}.onFailure { error ->
|
|
463
|
+
call.reject("Failed to verify reset code: ${error.message}")
|
|
464
|
+
}
|
|
465
|
+
} catch (e: Exception) {
|
|
466
|
+
Log.e(TAG, "Reset password failed", e)
|
|
467
|
+
call.reject("Reset password failed: ${e.message}")
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
@PluginMethod
|
|
473
|
+
fun refreshSession(call: PluginCall) {
|
|
474
|
+
scope.launch {
|
|
475
|
+
try {
|
|
476
|
+
val session = Clerk.session
|
|
477
|
+
if (session == null) {
|
|
478
|
+
val result = JSObject()
|
|
479
|
+
result.put("token", null)
|
|
480
|
+
call.resolve(result)
|
|
481
|
+
return@launch
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Force refresh the token
|
|
485
|
+
session.getToken(forceRefresh = true).onSuccess { tokenResult ->
|
|
486
|
+
val result = JSObject()
|
|
487
|
+
result.put("token", tokenResult?.jwt)
|
|
488
|
+
call.resolve(result)
|
|
489
|
+
}.onFailure { error ->
|
|
490
|
+
call.reject("Refresh session failed: ${error.message}")
|
|
491
|
+
}
|
|
492
|
+
} catch (e: Exception) {
|
|
493
|
+
Log.e(TAG, "Refresh session failed", e)
|
|
494
|
+
call.reject("Refresh session failed: ${e.message}")
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private fun userToJSObject(user: User): JSObject {
|
|
500
|
+
val jsObject = JSObject()
|
|
501
|
+
jsObject.put("id", user.id)
|
|
502
|
+
jsObject.put("firstName", user.firstName)
|
|
503
|
+
jsObject.put("lastName", user.lastName)
|
|
504
|
+
jsObject.put("emailAddress", user.primaryEmailAddress?.emailAddress)
|
|
505
|
+
jsObject.put("imageUrl", user.imageUrl)
|
|
506
|
+
jsObject.put("username", user.username)
|
|
507
|
+
return jsObject
|
|
508
|
+
}
|
|
509
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trainon-inc/capacitor-clerk-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Capacitor plugin for Clerk native authentication using bridge pattern to integrate Clerk iOS/Android SDKs with CocoaPods/Gradle",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
File without changes
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
package com.trainon.capacitor.clerk;
|
|
2
|
-
|
|
3
|
-
import com.getcapacitor.Plugin;
|
|
4
|
-
import com.getcapacitor.PluginCall;
|
|
5
|
-
import com.getcapacitor.PluginMethod;
|
|
6
|
-
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
7
|
-
|
|
8
|
-
@CapacitorPlugin(name = "ClerkNative")
|
|
9
|
-
public class ClerkNativePlugin extends Plugin {
|
|
10
|
-
|
|
11
|
-
@PluginMethod
|
|
12
|
-
public void configure(PluginCall call) {
|
|
13
|
-
call.reject("Android not yet implemented");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
@PluginMethod
|
|
17
|
-
public void load(PluginCall call) {
|
|
18
|
-
call.reject("Android not yet implemented");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@PluginMethod
|
|
22
|
-
public void signInWithEmail(PluginCall call) {
|
|
23
|
-
call.reject("Android not yet implemented");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@PluginMethod
|
|
27
|
-
public void verifyEmailCode(PluginCall call) {
|
|
28
|
-
call.reject("Android not yet implemented");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@PluginMethod
|
|
32
|
-
public void signInWithPassword(PluginCall call) {
|
|
33
|
-
call.reject("Android not yet implemented");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@PluginMethod
|
|
37
|
-
public void signUp(PluginCall call) {
|
|
38
|
-
call.reject("Android not yet implemented");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@PluginMethod
|
|
42
|
-
public void verifySignUpEmail(PluginCall call) {
|
|
43
|
-
call.reject("Android not yet implemented");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@PluginMethod
|
|
47
|
-
public void getUser(PluginCall call) {
|
|
48
|
-
call.reject("Android not yet implemented");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@PluginMethod
|
|
52
|
-
public void getToken(PluginCall call) {
|
|
53
|
-
call.reject("Android not yet implemented");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@PluginMethod
|
|
57
|
-
public void signOut(PluginCall call) {
|
|
58
|
-
call.reject("Android not yet implemented");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@PluginMethod
|
|
62
|
-
public void updateUser(PluginCall call) {
|
|
63
|
-
call.reject("Android not yet implemented");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@PluginMethod
|
|
67
|
-
public void requestPasswordReset(PluginCall call) {
|
|
68
|
-
call.reject("Android not yet implemented");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@PluginMethod
|
|
72
|
-
public void resetPassword(PluginCall call) {
|
|
73
|
-
call.reject("Android not yet implemented");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
@PluginMethod
|
|
77
|
-
public void refreshSession(PluginCall call) {
|
|
78
|
-
call.reject("Android not yet implemented");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|