@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 +138 -16
- package/android/build.gradle +72 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/trainon/capacitor/clerk/ClerkNativePlugin.kt +509 -0
- package/package.json +2 -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
|
@@ -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
|
|
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
|
-
|
|
45
|
+
### From GitHub Packages
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
Alternatively, install from GitHub Packages:
|
|
55
48
|
|
|
56
|
-
|
|
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
|
|
415
|
+
## Android Setup
|
|
397
416
|
|
|
398
|
-
Android
|
|
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/
|
|
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,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",
|
|
@@ -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",
|
|
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
|
-
|