@trainon-inc/capacitor-clerk-native 1.22.0 → 1.23.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
@@ -339,19 +339,21 @@ function Profile() {
339
339
  └─────────────────────────────────────────────────┘
340
340
  ```
341
341
 
342
- ### Android Architecture
342
+ ### Android Architecture (Web Provider)
343
343
 
344
344
  ```
345
345
  ┌─────────────────────────────────────────────────┐
346
346
  │ JavaScript/React (Capacitor WebView) │
347
- │ - Uses capacitor-clerk-native hooks
348
- └─────────────────┬───────────────────────────────┘
349
- │ Capacitor Bridge
350
- ┌─────────────────▼───────────────────────────────┐
351
- │ ClerkNativePlugin (Gradle Module) │
352
- │ - Android library module │
353
- - Receives calls from JavaScript
354
- │ - Can integrate with Clerk Android SDK
347
+ │ - Uses @clerk/clerk-react (web provider)
348
+ │ - Full Clerk functionality via web SDK │
349
+ └─────────────────────────────────────────────────┘
350
+ (No native plugin needed for auth)
351
+
352
+ ┌─────────────────────────────────────────────────┐
353
+ ClerkNativePlugin (Gradle Module) - Stub
354
+ │ - Exists for Capacitor plugin registration
355
+ │ - Returns "use web provider" for all methods │
356
+ │ - No native Clerk SDK dependency │
355
357
  └─────────────────────────────────────────────────┘
356
358
  ```
357
359
 
@@ -414,80 +416,51 @@ function Profile() {
414
416
 
415
417
  ## Android Setup
416
418
 
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
-
419
- ### Prerequisites
420
-
421
- - ✅ A [Clerk account](https://dashboard.clerk.com/sign-up)
422
- - ✅ A Clerk application set up in the dashboard
423
- - ✅ **Native API enabled** in Clerk Dashboard → Settings → Native Applications
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
428
-
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
437
-
438
- After installing the plugin, sync your Android project:
419
+ **Important**: On Android, this plugin provides a stub implementation. Android WebViews work well with web-based authentication (unlike iOS which has cookie issues), so **Android should use the web Clerk provider (`@clerk/clerk-react`)** instead of the native plugin.
439
420
 
440
- ```bash
441
- npx cap sync android
442
- ```
421
+ ### Why Web Provider for Android?
443
422
 
444
- This will:
445
- - Add the plugin to `capacitor.settings.gradle`
446
- - Add the plugin dependency to `capacitor.build.gradle`
447
- - Include the Clerk Android SDK automatically
423
+ - ✅ Android WebViews handle cookies correctly - no authentication issues
424
+ - The Clerk Android SDK is still in early stages (v0.1.x) with evolving APIs
425
+ - Using `@clerk/clerk-react` provides a stable, well-tested experience
426
+ - Simpler setup - no native configuration required
448
427
 
449
- ### 3. Configure Clerk in Your App
428
+ ### Recommended App Setup
450
429
 
451
- The plugin automatically handles Clerk initialization when you call `configure()` from JavaScript. No additional Android-specific setup is required!
430
+ Configure your app to use the native plugin only on iOS:
452
431
 
453
432
  ```typescript
454
- import { ClerkNative } from '@trainon-inc/capacitor-clerk-native';
455
-
456
- // Configure Clerk (call once at app startup)
457
- await ClerkNative.configure({
458
- publishableKey: 'pk_test_your_clerk_key_here'
459
- });
433
+ import { Capacitor } from "@capacitor/core";
434
+ import { ClerkProvider as WebClerkProvider } from "@clerk/clerk-react";
435
+ import { ClerkProvider as NativeClerkProvider } from "@trainon-inc/capacitor-clerk-native";
436
+
437
+ // Use native Clerk only on iOS (due to WebView cookie issues)
438
+ // Android WebViews work fine with web Clerk
439
+ const isIOS = Capacitor.getPlatform() === "ios";
440
+ const ClerkProvider = isIOS ? NativeClerkProvider : WebClerkProvider;
441
+
442
+ export function App() {
443
+ const clerkProps = isIOS
444
+ ? { publishableKey: "pk_test_..." }
445
+ : {
446
+ publishableKey: "pk_test_...",
447
+ signInFallbackRedirectUrl: "/home",
448
+ signUpFallbackRedirectUrl: "/home",
449
+ };
460
450
 
461
- // Load and check for existing session
462
- const { user } = await ClerkNative.load();
451
+ return (
452
+ <ClerkProvider {...clerkProps}>
453
+ <YourApp />
454
+ </ClerkProvider>
455
+ );
456
+ }
463
457
  ```
464
458
 
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
459
+ ### Build Requirements
487
460
 
488
461
  - **Gradle**: 8.11.1+
489
- - **Android Gradle Plugin**: 8.7.2+
490
- - **Java/Kotlin**: 17+
462
+ - **Android Gradle Plugin**: 8.5.0+
463
+ - **Java**: 17+
491
464
  - **Min SDK**: 23 (Android 6.0)
492
465
  - **Target SDK**: 35 (Android 15)
493
466
 
@@ -499,14 +472,14 @@ Could not resolve project :trainon-inc-capacitor-clerk-native
499
472
  No matching variant of project was found. No variants exist.
500
473
  ```
501
474
 
502
- **Solution**: Ensure you have the latest version of the plugin which includes the required build files:
475
+ **Solution**: Update to the latest plugin version:
503
476
  ```bash
504
477
  npm update @trainon-inc/capacitor-clerk-native
505
478
  npx cap sync android
506
479
  ```
507
480
 
508
481
  #### "invalid source release: 21" error
509
- The plugin uses Java 17 by default. Ensure your Android Studio uses JDK 17+:
482
+ The plugin uses Java 17. Ensure your Android Studio uses JDK 17+:
510
483
  - **File → Project Structure → SDK Location → Gradle JDK** → Select JDK 17+
511
484
 
512
485
  #### Gradle sync fails
@@ -514,12 +487,6 @@ The plugin uses Java 17 by default. Ensure your Android Studio uses JDK 17+:
514
487
  - Invalidate caches: **File → Invalidate Caches / Restart**
515
488
  - Delete `.gradle` folder and re-sync
516
489
 
517
- #### Network errors
518
- Ensure INTERNET permission is in your `AndroidManifest.xml`:
519
- ```xml
520
- <uses-permission android:name="android.permission.INTERNET" />
521
- ```
522
-
523
490
  ## Contributing
524
491
 
525
492
  Contributions are welcome! This plugin was created to solve a real problem we encountered, and we'd love to make it better.
@@ -3,7 +3,6 @@ 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'
7
6
  }
8
7
 
9
8
  buildscript {
@@ -13,12 +12,10 @@ buildscript {
13
12
  }
14
13
  dependencies {
15
14
  classpath 'com.android.tools.build:gradle:8.7.2'
16
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
17
15
  }
18
16
  }
19
17
 
20
18
  apply plugin: 'com.android.library'
21
- apply plugin: 'kotlin-android'
22
19
 
23
20
  android {
24
21
  namespace "com.trainon.capacitor.clerk"
@@ -43,9 +40,6 @@ android {
43
40
  sourceCompatibility JavaVersion.VERSION_17
44
41
  targetCompatibility JavaVersion.VERSION_17
45
42
  }
46
- kotlinOptions {
47
- jvmTarget = '17'
48
- }
49
43
  }
50
44
 
51
45
  repositories {
@@ -57,15 +51,6 @@ dependencies {
57
51
  implementation fileTree(dir: 'libs', include: ['*.jar'])
58
52
  implementation project(':capacitor-android')
59
53
  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
69
54
  testImplementation "junit:junit:$junitVersion"
70
55
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
71
56
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -0,0 +1,92 @@
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
+ /**
9
+ * Android stub for Clerk Native plugin.
10
+ *
11
+ * On Android, authentication is handled by the web Clerk provider (@clerk/clerk-react)
12
+ * because Android WebViews work well with web-based auth (unlike iOS which has cookie issues).
13
+ *
14
+ * This plugin exists to satisfy Capacitor's plugin registration but all methods
15
+ * will return errors indicating to use the web provider instead.
16
+ */
17
+ @CapacitorPlugin(name = "ClerkNative")
18
+ public class ClerkNativePlugin extends Plugin {
19
+
20
+ private static final String USE_WEB_MESSAGE = "Android uses web Clerk provider. Configure your app to use @clerk/clerk-react on Android.";
21
+
22
+ @PluginMethod
23
+ public void configure(PluginCall call) {
24
+ // Allow configure to succeed silently - the web provider will handle auth
25
+ call.resolve();
26
+ }
27
+
28
+ @PluginMethod
29
+ public void load(PluginCall call) {
30
+ call.reject(USE_WEB_MESSAGE);
31
+ }
32
+
33
+ @PluginMethod
34
+ public void signInWithEmail(PluginCall call) {
35
+ call.reject(USE_WEB_MESSAGE);
36
+ }
37
+
38
+ @PluginMethod
39
+ public void verifyEmailCode(PluginCall call) {
40
+ call.reject(USE_WEB_MESSAGE);
41
+ }
42
+
43
+ @PluginMethod
44
+ public void signInWithPassword(PluginCall call) {
45
+ call.reject(USE_WEB_MESSAGE);
46
+ }
47
+
48
+ @PluginMethod
49
+ public void signUp(PluginCall call) {
50
+ call.reject(USE_WEB_MESSAGE);
51
+ }
52
+
53
+ @PluginMethod
54
+ public void verifySignUpEmail(PluginCall call) {
55
+ call.reject(USE_WEB_MESSAGE);
56
+ }
57
+
58
+ @PluginMethod
59
+ public void getUser(PluginCall call) {
60
+ call.reject(USE_WEB_MESSAGE);
61
+ }
62
+
63
+ @PluginMethod
64
+ public void getToken(PluginCall call) {
65
+ call.reject(USE_WEB_MESSAGE);
66
+ }
67
+
68
+ @PluginMethod
69
+ public void signOut(PluginCall call) {
70
+ call.reject(USE_WEB_MESSAGE);
71
+ }
72
+
73
+ @PluginMethod
74
+ public void updateUser(PluginCall call) {
75
+ call.reject(USE_WEB_MESSAGE);
76
+ }
77
+
78
+ @PluginMethod
79
+ public void requestPasswordReset(PluginCall call) {
80
+ call.reject(USE_WEB_MESSAGE);
81
+ }
82
+
83
+ @PluginMethod
84
+ public void resetPassword(PluginCall call) {
85
+ call.reject(USE_WEB_MESSAGE);
86
+ }
87
+
88
+ @PluginMethod
89
+ public void refreshSession(PluginCall call) {
90
+ call.reject(USE_WEB_MESSAGE);
91
+ }
92
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainon-inc/capacitor-clerk-native",
3
- "version": "1.22.0",
3
+ "version": "1.23.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,509 +0,0 @@
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
- }