@trainon-inc/capacitor-clerk-native 1.15.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
@@ -32,15 +32,8 @@ This allows your Capacitor app to use Clerk's native iOS/Android SDKs (following
32
32
 
33
33
  ## Installation
34
34
 
35
- ### From GitHub Packages (Recommended)
35
+ ### From npm (Recommended)
36
36
 
37
- 1. Create a `.npmrc` file in your project root:
38
- ```
39
- @trainon-inc:registry=https://npm.pkg.github.com
40
- //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
41
- ```
42
-
43
- 2. Install the package:
44
37
  ```bash
45
38
  npm install @trainon-inc/capacitor-clerk-native
46
39
  # or
@@ -49,15 +42,23 @@ pnpm add @trainon-inc/capacitor-clerk-native
49
42
  yarn add @trainon-inc/capacitor-clerk-native
50
43
  ```
51
44
 
52
- > **Note**: You'll need a GitHub Personal Access Token with `read:packages` permission. [Create one here](https://github.com/settings/tokens/new?scopes=read:packages)
45
+ ### From GitHub Packages
53
46
 
54
- ### From NPM (Future)
47
+ Alternatively, install from GitHub Packages:
55
48
 
56
- Once published to npm:
49
+ 1. Create a `.npmrc` file in your project root:
50
+ ```
51
+ @trainon-inc:registry=https://npm.pkg.github.com
52
+ //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
53
+ ```
54
+
55
+ 2. Install the package:
57
56
  ```bash
58
- npm install capacitor-clerk-native
57
+ npm install @trainon-inc/capacitor-clerk-native
59
58
  ```
60
59
 
60
+ > **Note**: You'll need a GitHub Personal Access Token with `read:packages` permission. [Create one here](https://github.com/settings/tokens/new?scopes=read:packages)
61
+
61
62
  ## iOS Setup
62
63
 
63
64
  ### Prerequisites
@@ -315,6 +316,8 @@ function Profile() {
315
316
 
316
317
  ## Architecture
317
318
 
319
+ ### iOS Architecture (Bridge Pattern)
320
+
318
321
  ```
319
322
  ┌─────────────────────────────────────────────────┐
320
323
  │ JavaScript/React (Capacitor WebView) │
@@ -336,6 +339,22 @@ function Profile() {
336
339
  └─────────────────────────────────────────────────┘
337
340
  ```
338
341
 
342
+ ### Android Architecture
343
+
344
+ ```
345
+ ┌─────────────────────────────────────────────────┐
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 │
355
+ └─────────────────────────────────────────────────┘
356
+ ```
357
+
339
358
  ## API
340
359
 
341
360
  ### Methods
@@ -393,9 +412,113 @@ function Profile() {
393
412
  - 💬 [Clerk Discord Community](https://clerk.com/discord)
394
413
  - 🐛 [Report Issues](https://github.com/TrainOn-Inc/capacitor-clerk-native/issues)
395
414
 
396
- ## Android Support
415
+ ## Android Setup
397
416
 
398
- Android support is planned. The bridge pattern will work similarly with Gradle and 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
+
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:
439
+
440
+ ```bash
441
+ npx cap sync android
442
+ ```
443
+
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
448
+
449
+ ### 3. Configure Clerk in Your App
450
+
451
+ The plugin automatically handles Clerk initialization when you call `configure()` from JavaScript. No additional Android-specific setup is required!
452
+
453
+ ```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
+ });
460
+
461
+ // Load and check for existing session
462
+ const { user } = await ClerkNative.load();
463
+ ```
464
+
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
487
+
488
+ - **Gradle**: 8.11.1+
489
+ - **Android Gradle Plugin**: 8.7.2+
490
+ - **Java/Kotlin**: 17+
491
+ - **Min SDK**: 23 (Android 6.0)
492
+ - **Target SDK**: 35 (Android 15)
493
+
494
+ ### Troubleshooting Android
495
+
496
+ #### "No matching variant" error
497
+ ```
498
+ Could not resolve project :trainon-inc-capacitor-clerk-native
499
+ No matching variant of project was found. No variants exist.
500
+ ```
501
+
502
+ **Solution**: Ensure you have the latest version of the plugin which includes the required build files:
503
+ ```bash
504
+ npm update @trainon-inc/capacitor-clerk-native
505
+ npx cap sync android
506
+ ```
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
+
512
+ #### Gradle sync fails
513
+ - Clean the project: **Build → Clean Project**
514
+ - Invalidate caches: **File → Invalidate Caches / Restart**
515
+ - Delete `.gradle` folder and re-sync
516
+
517
+ #### Network errors
518
+ Ensure INTERNET permission is in your `AndroidManifest.xml`:
519
+ ```xml
520
+ <uses-permission android:name="android.permission.INTERNET" />
521
+ ```
399
522
 
400
523
  ## Contributing
401
524
 
@@ -411,7 +534,6 @@ Created by the TrainOn Team to solve CocoaPods ↔ SPM conflicts when integratin
411
534
 
412
535
  ## Support
413
536
 
414
- - [GitHub Issues](https://github.com/Trainon-Inc/capacitor-clerk-native/issues)
537
+ - [GitHub Issues](https://github.com/TrainOn-Inc/capacitor-clerk-native/issues)
415
538
  - [Clerk Documentation](https://clerk.com/docs)
416
539
  - [Capacitor Documentation](https://capacitorjs.com/docs)
417
- # Test CI/CD for npm publishing
@@ -0,0 +1,72 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6
+ kotlinVersion = project.hasProperty('kotlinVersion') ? rootProject.ext.kotlinVersion : '1.9.25'
7
+ }
8
+
9
+ buildscript {
10
+ repositories {
11
+ google()
12
+ mavenCentral()
13
+ }
14
+ dependencies {
15
+ classpath 'com.android.tools.build:gradle:8.7.2'
16
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
17
+ }
18
+ }
19
+
20
+ apply plugin: 'com.android.library'
21
+ apply plugin: 'kotlin-android'
22
+
23
+ android {
24
+ namespace "com.trainon.capacitor.clerk"
25
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
26
+ defaultConfig {
27
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
28
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
29
+ versionCode 1
30
+ versionName "1.0"
31
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
32
+ }
33
+ buildTypes {
34
+ release {
35
+ minifyEnabled false
36
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
37
+ }
38
+ }
39
+ lintOptions {
40
+ abortOnError false
41
+ }
42
+ compileOptions {
43
+ sourceCompatibility JavaVersion.VERSION_17
44
+ targetCompatibility JavaVersion.VERSION_17
45
+ }
46
+ kotlinOptions {
47
+ jvmTarget = '17'
48
+ }
49
+ }
50
+
51
+ repositories {
52
+ google()
53
+ mavenCentral()
54
+ }
55
+
56
+ dependencies {
57
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
58
+ implementation project(':capacitor-android')
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
69
+ testImplementation "junit:junit:$junitVersion"
70
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
71
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
72
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
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.15.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",
@@ -18,6 +18,7 @@
18
18
  "files": [
19
19
  "android/src/main/",
20
20
  "android/build.gradle",
21
+ "android/src/main/AndroidManifest.xml",
21
22
  "dist/",
22
23
  "ios/Plugin/",
23
24
  "TrainonIncCapacitorClerkNative.podspec",
@@ -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
-