@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 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= YOUR_GITHUB_TOKEN
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 using the same bridge pattern as iOS. The plugin provides a stub implementation that you can extend with the Clerk Android SDK.
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
- ### 1. Sync Capacitor
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
- ### 2. Plugin Structure
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
- ### 3. Gradle Configuration
453
+ ```typescript
454
+ import { ClerkNative } from '@trainon-inc/capacitor-clerk-native';
453
455
 
454
- The plugin uses these default configurations (which can be overridden by your app's `variables.gradle`):
456
+ // Configure Clerk (call once at app startup)
457
+ await ClerkNative.configure({
458
+ publishableKey: 'pk_test_your_clerk_key_here'
459
+ });
455
460
 
456
- | Property | Default | Description |
457
- |----------|---------|-------------|
458
- | `compileSdkVersion` | 35 | Android compile SDK |
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. Build Requirements
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**: 21
468
-
469
- These are configured in the plugin's `build.gradle` and are compatible with Capacitor 7.
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 (1.16.0+) which includes the required `build.gradle` and `AndroidManifest.xml` files. Run:
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
- #### AGP version mismatch
491
- If you see AGP version conflicts, ensure your app's `build.gradle` uses AGP 8.5.0 or higher, matching the plugin's requirements.
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
 
@@ -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.VERSION_21
41
- targetCompatibility JavaVersion.VERSION_21
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"
@@ -1,2 +1,3 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
2
3
  </manifest>
@@ -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.21.0",
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",
@@ -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
-