@trainon-inc/capacitor-clerk-native 1.22.0 → 1.24.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
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
│
|
|
354
|
-
│ -
|
|
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
|
|
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
|
-
|
|
441
|
-
npx cap sync android
|
|
442
|
-
```
|
|
421
|
+
### Why Web Provider for Android?
|
|
443
422
|
|
|
444
|
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
-
|
|
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
|
-
###
|
|
428
|
+
### Recommended App Setup
|
|
450
429
|
|
|
451
|
-
|
|
430
|
+
Configure your app to use the native plugin only on iOS:
|
|
452
431
|
|
|
453
432
|
```typescript
|
|
454
|
-
import {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
462
|
-
|
|
451
|
+
return (
|
|
452
|
+
<ClerkProvider {...clerkProps}>
|
|
453
|
+
<YourApp />
|
|
454
|
+
</ClerkProvider>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
463
457
|
```
|
|
464
458
|
|
|
465
|
-
###
|
|
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.
|
|
490
|
-
- **Java
|
|
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**:
|
|
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
|
|
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.
|
package/android/build.gradle
CHANGED
|
@@ -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 {
|
|
@@ -58,14 +52,9 @@ dependencies {
|
|
|
58
52
|
implementation project(':capacitor-android')
|
|
59
53
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
60
54
|
|
|
61
|
-
//
|
|
62
|
-
implementation
|
|
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"
|
|
55
|
+
// OkHttp for making HTTP requests to Clerk API
|
|
56
|
+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
67
57
|
|
|
68
|
-
// Testing
|
|
69
58
|
testImplementation "junit:junit:$junitVersion"
|
|
70
59
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
71
60
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
package com.trainon.capacitor.clerk;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.SharedPreferences;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import com.getcapacitor.JSObject;
|
|
8
|
+
import com.getcapacitor.Plugin;
|
|
9
|
+
import com.getcapacitor.PluginCall;
|
|
10
|
+
import com.getcapacitor.PluginMethod;
|
|
11
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
12
|
+
|
|
13
|
+
import org.json.JSONArray;
|
|
14
|
+
import org.json.JSONException;
|
|
15
|
+
import org.json.JSONObject;
|
|
16
|
+
|
|
17
|
+
import java.io.IOException;
|
|
18
|
+
import java.util.concurrent.ExecutorService;
|
|
19
|
+
import java.util.concurrent.Executors;
|
|
20
|
+
|
|
21
|
+
import okhttp3.MediaType;
|
|
22
|
+
import okhttp3.OkHttpClient;
|
|
23
|
+
import okhttp3.Request;
|
|
24
|
+
import okhttp3.RequestBody;
|
|
25
|
+
import okhttp3.Response;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Android implementation of Clerk Native plugin.
|
|
29
|
+
*
|
|
30
|
+
* Makes HTTP requests directly to Clerk's Frontend API, bypassing WebView origin restrictions.
|
|
31
|
+
* This allows the custom Clerk proxy domain to work on Android.
|
|
32
|
+
*/
|
|
33
|
+
@CapacitorPlugin(name = "ClerkNative")
|
|
34
|
+
public class ClerkNativePlugin extends Plugin {
|
|
35
|
+
|
|
36
|
+
private static final String TAG = "ClerkNativePlugin";
|
|
37
|
+
private static final String PREFS_NAME = "ClerkNativePrefs";
|
|
38
|
+
private static final String PREF_SESSION_TOKEN = "session_token";
|
|
39
|
+
private static final String PREF_CLIENT_TOKEN = "client_token";
|
|
40
|
+
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
|
41
|
+
|
|
42
|
+
private OkHttpClient client;
|
|
43
|
+
private ExecutorService executor;
|
|
44
|
+
private String publishableKey;
|
|
45
|
+
private String clerkDomain;
|
|
46
|
+
private String clientToken;
|
|
47
|
+
private String sessionToken;
|
|
48
|
+
private JSONObject currentUser;
|
|
49
|
+
|
|
50
|
+
@Override
|
|
51
|
+
public void load() {
|
|
52
|
+
super.load();
|
|
53
|
+
client = new OkHttpClient();
|
|
54
|
+
executor = Executors.newSingleThreadExecutor();
|
|
55
|
+
|
|
56
|
+
// Load saved tokens
|
|
57
|
+
SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
58
|
+
clientToken = prefs.getString(PREF_CLIENT_TOKEN, null);
|
|
59
|
+
sessionToken = prefs.getString(PREF_SESSION_TOKEN, null);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private void saveTokens() {
|
|
63
|
+
SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
64
|
+
SharedPreferences.Editor editor = prefs.edit();
|
|
65
|
+
editor.putString(PREF_CLIENT_TOKEN, clientToken);
|
|
66
|
+
editor.putString(PREF_SESSION_TOKEN, sessionToken);
|
|
67
|
+
editor.apply();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private void clearTokens() {
|
|
71
|
+
clientToken = null;
|
|
72
|
+
sessionToken = null;
|
|
73
|
+
currentUser = null;
|
|
74
|
+
SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
75
|
+
prefs.edit().clear().apply();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private String getClerkApiUrl(String path) {
|
|
79
|
+
return "https://" + clerkDomain + path;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private Request.Builder createRequestBuilder(String url) {
|
|
83
|
+
Request.Builder builder = new Request.Builder()
|
|
84
|
+
.url(url)
|
|
85
|
+
.header("Content-Type", "application/json")
|
|
86
|
+
.header("Authorization", publishableKey)
|
|
87
|
+
.header("Origin", "https://app.trainonapp.com") // Spoof origin to match web
|
|
88
|
+
.header("User-Agent", "Mozilla/5.0 (Linux; Android) ClerkNative/1.0");
|
|
89
|
+
|
|
90
|
+
if (clientToken != null) {
|
|
91
|
+
builder.header("Cookie", "__client=" + clientToken);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return builder;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@PluginMethod
|
|
98
|
+
public void configure(PluginCall call) {
|
|
99
|
+
publishableKey = call.getString("publishableKey");
|
|
100
|
+
|
|
101
|
+
if (publishableKey == null || publishableKey.isEmpty()) {
|
|
102
|
+
call.reject("Publishable key is required");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extract domain from publishable key (base64 decode)
|
|
107
|
+
try {
|
|
108
|
+
String decoded = new String(android.util.Base64.decode(
|
|
109
|
+
publishableKey.replace("pk_live_", "").replace("pk_test_", ""),
|
|
110
|
+
android.util.Base64.DEFAULT
|
|
111
|
+
));
|
|
112
|
+
// Remove trailing $ if present
|
|
113
|
+
clerkDomain = decoded.replace("$", "").trim();
|
|
114
|
+
Log.d(TAG, "Configured with domain: " + clerkDomain);
|
|
115
|
+
} catch (Exception e) {
|
|
116
|
+
// Fallback to default domain
|
|
117
|
+
clerkDomain = "clerk.trainonapp.com";
|
|
118
|
+
Log.w(TAG, "Could not decode domain from key, using default: " + clerkDomain);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
call.resolve();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@PluginMethod
|
|
125
|
+
public void load(PluginCall call) {
|
|
126
|
+
executor.execute(() -> {
|
|
127
|
+
try {
|
|
128
|
+
// First, get or create a client
|
|
129
|
+
if (clientToken == null) {
|
|
130
|
+
createClient(call);
|
|
131
|
+
} else {
|
|
132
|
+
// Verify existing session
|
|
133
|
+
fetchClientAndUser(call);
|
|
134
|
+
}
|
|
135
|
+
} catch (Exception e) {
|
|
136
|
+
Log.e(TAG, "Load error", e);
|
|
137
|
+
call.reject("Failed to load: " + e.getMessage());
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private void createClient(PluginCall call) throws IOException, JSONException {
|
|
143
|
+
String url = getClerkApiUrl("/v1/client?_clerk_js_version=5.117.0");
|
|
144
|
+
|
|
145
|
+
Request request = createRequestBuilder(url)
|
|
146
|
+
.post(RequestBody.create("{}", JSON))
|
|
147
|
+
.build();
|
|
148
|
+
|
|
149
|
+
try (Response response = client.newCall(request).execute()) {
|
|
150
|
+
String body = response.body().string();
|
|
151
|
+
Log.d(TAG, "Create client response: " + response.code());
|
|
152
|
+
|
|
153
|
+
if (!response.isSuccessful()) {
|
|
154
|
+
call.reject("Failed to create client: " + response.code() + " - " + body);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
JSONObject json = new JSONObject(body);
|
|
159
|
+
JSONObject responseObj = json.optJSONObject("response");
|
|
160
|
+
|
|
161
|
+
if (responseObj != null) {
|
|
162
|
+
clientToken = responseObj.optString("id", null);
|
|
163
|
+
|
|
164
|
+
// Check for active session
|
|
165
|
+
JSONArray sessions = responseObj.optJSONArray("sessions");
|
|
166
|
+
if (sessions != null && sessions.length() > 0) {
|
|
167
|
+
JSONObject session = sessions.getJSONObject(0);
|
|
168
|
+
sessionToken = session.optString("last_active_token", null);
|
|
169
|
+
currentUser = session.optJSONObject("user");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
saveTokens();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
JSObject result = new JSObject();
|
|
176
|
+
if (currentUser != null) {
|
|
177
|
+
result.put("user", convertUser(currentUser));
|
|
178
|
+
} else {
|
|
179
|
+
result.put("user", JSObject.NULL);
|
|
180
|
+
}
|
|
181
|
+
call.resolve(result);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private void fetchClientAndUser(PluginCall call) throws IOException, JSONException {
|
|
186
|
+
String url = getClerkApiUrl("/v1/client?_clerk_js_version=5.117.0");
|
|
187
|
+
|
|
188
|
+
Request request = createRequestBuilder(url)
|
|
189
|
+
.get()
|
|
190
|
+
.build();
|
|
191
|
+
|
|
192
|
+
try (Response response = client.newCall(request).execute()) {
|
|
193
|
+
String body = response.body().string();
|
|
194
|
+
Log.d(TAG, "Fetch client response: " + response.code());
|
|
195
|
+
|
|
196
|
+
if (!response.isSuccessful()) {
|
|
197
|
+
// Token might be invalid, clear and retry
|
|
198
|
+
clearTokens();
|
|
199
|
+
createClient(call);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
JSONObject json = new JSONObject(body);
|
|
204
|
+
JSONObject responseObj = json.optJSONObject("response");
|
|
205
|
+
|
|
206
|
+
if (responseObj != null) {
|
|
207
|
+
// Check for active session
|
|
208
|
+
JSONArray sessions = responseObj.optJSONArray("sessions");
|
|
209
|
+
if (sessions != null && sessions.length() > 0) {
|
|
210
|
+
JSONObject session = sessions.getJSONObject(0);
|
|
211
|
+
JSONObject lastToken = session.optJSONObject("last_active_token");
|
|
212
|
+
if (lastToken != null) {
|
|
213
|
+
sessionToken = lastToken.optString("jwt", null);
|
|
214
|
+
}
|
|
215
|
+
currentUser = session.optJSONObject("user");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
saveTokens();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
JSObject result = new JSObject();
|
|
222
|
+
if (currentUser != null) {
|
|
223
|
+
result.put("user", convertUser(currentUser));
|
|
224
|
+
} else {
|
|
225
|
+
result.put("user", JSObject.NULL);
|
|
226
|
+
}
|
|
227
|
+
call.resolve(result);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@PluginMethod
|
|
232
|
+
public void signInWithPassword(PluginCall call) {
|
|
233
|
+
String email = call.getString("email");
|
|
234
|
+
String password = call.getString("password");
|
|
235
|
+
|
|
236
|
+
if (email == null || password == null) {
|
|
237
|
+
call.reject("Email and password are required");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
executor.execute(() -> {
|
|
242
|
+
try {
|
|
243
|
+
// Step 1: Create sign-in attempt
|
|
244
|
+
String createUrl = getClerkApiUrl("/v1/client/sign_ins?_clerk_js_version=5.117.0");
|
|
245
|
+
|
|
246
|
+
JSONObject createBody = new JSONObject();
|
|
247
|
+
createBody.put("identifier", email);
|
|
248
|
+
|
|
249
|
+
Request createRequest = createRequestBuilder(createUrl)
|
|
250
|
+
.post(RequestBody.create(createBody.toString(), JSON))
|
|
251
|
+
.build();
|
|
252
|
+
|
|
253
|
+
String signInId;
|
|
254
|
+
try (Response response = client.newCall(createRequest).execute()) {
|
|
255
|
+
String body = response.body().string();
|
|
256
|
+
Log.d(TAG, "Create sign-in response: " + response.code());
|
|
257
|
+
|
|
258
|
+
if (!response.isSuccessful()) {
|
|
259
|
+
call.reject("Failed to start sign in: " + body);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
JSONObject json = new JSONObject(body);
|
|
264
|
+
JSONObject responseObj = json.optJSONObject("response");
|
|
265
|
+
signInId = responseObj.optString("id");
|
|
266
|
+
|
|
267
|
+
// Update client token from response
|
|
268
|
+
JSONObject clientObj = json.optJSONObject("client");
|
|
269
|
+
if (clientObj != null) {
|
|
270
|
+
clientToken = clientObj.optString("id", clientToken);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Step 2: Attempt with password
|
|
275
|
+
String attemptUrl = getClerkApiUrl("/v1/client/sign_ins/" + signInId + "/attempt_first_factor?_clerk_js_version=5.117.0");
|
|
276
|
+
|
|
277
|
+
JSONObject attemptBody = new JSONObject();
|
|
278
|
+
attemptBody.put("strategy", "password");
|
|
279
|
+
attemptBody.put("password", password);
|
|
280
|
+
|
|
281
|
+
Request attemptRequest = createRequestBuilder(attemptUrl)
|
|
282
|
+
.post(RequestBody.create(attemptBody.toString(), JSON))
|
|
283
|
+
.build();
|
|
284
|
+
|
|
285
|
+
try (Response response = client.newCall(attemptRequest).execute()) {
|
|
286
|
+
String body = response.body().string();
|
|
287
|
+
Log.d(TAG, "Attempt password response: " + response.code());
|
|
288
|
+
|
|
289
|
+
if (!response.isSuccessful()) {
|
|
290
|
+
JSONObject errorJson = new JSONObject(body);
|
|
291
|
+
JSONArray errors = errorJson.optJSONArray("errors");
|
|
292
|
+
if (errors != null && errors.length() > 0) {
|
|
293
|
+
String message = errors.getJSONObject(0).optString("message", "Sign in failed");
|
|
294
|
+
call.reject(message);
|
|
295
|
+
} else {
|
|
296
|
+
call.reject("Sign in failed: " + body);
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
JSONObject json = new JSONObject(body);
|
|
302
|
+
|
|
303
|
+
// Extract session and user from response
|
|
304
|
+
JSONObject clientObj = json.optJSONObject("client");
|
|
305
|
+
if (clientObj != null) {
|
|
306
|
+
clientToken = clientObj.optString("id", clientToken);
|
|
307
|
+
|
|
308
|
+
JSONArray sessions = clientObj.optJSONArray("sessions");
|
|
309
|
+
if (sessions != null && sessions.length() > 0) {
|
|
310
|
+
JSONObject session = sessions.getJSONObject(0);
|
|
311
|
+
JSONObject lastToken = session.optJSONObject("last_active_token");
|
|
312
|
+
if (lastToken != null) {
|
|
313
|
+
sessionToken = lastToken.optString("jwt", null);
|
|
314
|
+
}
|
|
315
|
+
currentUser = session.optJSONObject("user");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
saveTokens();
|
|
320
|
+
|
|
321
|
+
JSObject result = new JSObject();
|
|
322
|
+
if (currentUser != null) {
|
|
323
|
+
result.put("user", convertUser(currentUser));
|
|
324
|
+
} else {
|
|
325
|
+
result.put("user", JSObject.NULL);
|
|
326
|
+
}
|
|
327
|
+
call.resolve(result);
|
|
328
|
+
}
|
|
329
|
+
} catch (Exception e) {
|
|
330
|
+
Log.e(TAG, "Sign in error", e);
|
|
331
|
+
call.reject("Sign in failed: " + e.getMessage());
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@PluginMethod
|
|
337
|
+
public void getUser(PluginCall call) {
|
|
338
|
+
JSObject result = new JSObject();
|
|
339
|
+
if (currentUser != null) {
|
|
340
|
+
try {
|
|
341
|
+
result.put("user", convertUser(currentUser));
|
|
342
|
+
} catch (JSONException e) {
|
|
343
|
+
result.put("user", JSObject.NULL);
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
result.put("user", JSObject.NULL);
|
|
347
|
+
}
|
|
348
|
+
call.resolve(result);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@PluginMethod
|
|
352
|
+
public void getToken(PluginCall call) {
|
|
353
|
+
JSObject result = new JSObject();
|
|
354
|
+
result.put("token", sessionToken != null ? sessionToken : JSObject.NULL);
|
|
355
|
+
call.resolve(result);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@PluginMethod
|
|
359
|
+
public void signOut(PluginCall call) {
|
|
360
|
+
if (clientToken == null) {
|
|
361
|
+
clearTokens();
|
|
362
|
+
call.resolve();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
executor.execute(() -> {
|
|
367
|
+
try {
|
|
368
|
+
String url = getClerkApiUrl("/v1/client/sessions?_clerk_js_version=5.117.0");
|
|
369
|
+
|
|
370
|
+
Request request = createRequestBuilder(url)
|
|
371
|
+
.delete()
|
|
372
|
+
.build();
|
|
373
|
+
|
|
374
|
+
try (Response response = client.newCall(request).execute()) {
|
|
375
|
+
Log.d(TAG, "Sign out response: " + response.code());
|
|
376
|
+
// Clear tokens regardless of response
|
|
377
|
+
clearTokens();
|
|
378
|
+
call.resolve();
|
|
379
|
+
}
|
|
380
|
+
} catch (Exception e) {
|
|
381
|
+
Log.e(TAG, "Sign out error", e);
|
|
382
|
+
clearTokens();
|
|
383
|
+
call.resolve(); // Still resolve since we cleared local state
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@PluginMethod
|
|
389
|
+
public void signInWithEmail(PluginCall call) {
|
|
390
|
+
String email = call.getString("email");
|
|
391
|
+
|
|
392
|
+
if (email == null) {
|
|
393
|
+
call.reject("Email is required");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
executor.execute(() -> {
|
|
398
|
+
try {
|
|
399
|
+
String createUrl = getClerkApiUrl("/v1/client/sign_ins?_clerk_js_version=5.117.0");
|
|
400
|
+
|
|
401
|
+
JSONObject createBody = new JSONObject();
|
|
402
|
+
createBody.put("identifier", email);
|
|
403
|
+
|
|
404
|
+
Request request = createRequestBuilder(createUrl)
|
|
405
|
+
.post(RequestBody.create(createBody.toString(), JSON))
|
|
406
|
+
.build();
|
|
407
|
+
|
|
408
|
+
try (Response response = client.newCall(request).execute()) {
|
|
409
|
+
String body = response.body().string();
|
|
410
|
+
Log.d(TAG, "Sign in with email response: " + response.code());
|
|
411
|
+
|
|
412
|
+
if (!response.isSuccessful()) {
|
|
413
|
+
call.reject("Failed to start sign in: " + body);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// TODO: Prepare email code flow
|
|
418
|
+
// For now, return that code is required
|
|
419
|
+
JSObject result = new JSObject();
|
|
420
|
+
result.put("requiresCode", true);
|
|
421
|
+
call.resolve(result);
|
|
422
|
+
}
|
|
423
|
+
} catch (Exception e) {
|
|
424
|
+
Log.e(TAG, "Sign in with email error", e);
|
|
425
|
+
call.reject("Failed: " + e.getMessage());
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@PluginMethod
|
|
431
|
+
public void verifyEmailCode(PluginCall call) {
|
|
432
|
+
// TODO: Implement email code verification
|
|
433
|
+
call.reject("Email code verification not yet implemented on Android");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@PluginMethod
|
|
437
|
+
public void signUp(PluginCall call) {
|
|
438
|
+
String email = call.getString("emailAddress");
|
|
439
|
+
String password = call.getString("password");
|
|
440
|
+
String firstName = call.getString("firstName");
|
|
441
|
+
String lastName = call.getString("lastName");
|
|
442
|
+
|
|
443
|
+
if (email == null || password == null) {
|
|
444
|
+
call.reject("Email and password are required");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
executor.execute(() -> {
|
|
449
|
+
try {
|
|
450
|
+
String url = getClerkApiUrl("/v1/client/sign_ups?_clerk_js_version=5.117.0");
|
|
451
|
+
|
|
452
|
+
JSONObject body = new JSONObject();
|
|
453
|
+
body.put("email_address", email);
|
|
454
|
+
body.put("password", password);
|
|
455
|
+
if (firstName != null) body.put("first_name", firstName);
|
|
456
|
+
if (lastName != null) body.put("last_name", lastName);
|
|
457
|
+
|
|
458
|
+
Request request = createRequestBuilder(url)
|
|
459
|
+
.post(RequestBody.create(body.toString(), JSON))
|
|
460
|
+
.build();
|
|
461
|
+
|
|
462
|
+
try (Response response = client.newCall(request).execute()) {
|
|
463
|
+
String responseBody = response.body().string();
|
|
464
|
+
Log.d(TAG, "Sign up response: " + response.code());
|
|
465
|
+
|
|
466
|
+
if (!response.isSuccessful()) {
|
|
467
|
+
JSONObject errorJson = new JSONObject(responseBody);
|
|
468
|
+
JSONArray errors = errorJson.optJSONArray("errors");
|
|
469
|
+
if (errors != null && errors.length() > 0) {
|
|
470
|
+
String message = errors.getJSONObject(0).optString("message", "Sign up failed");
|
|
471
|
+
call.reject(message);
|
|
472
|
+
} else {
|
|
473
|
+
call.reject("Sign up failed: " + responseBody);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
JSONObject json = new JSONObject(responseBody);
|
|
479
|
+
|
|
480
|
+
// Check if email verification is required
|
|
481
|
+
JSONObject responseObj = json.optJSONObject("response");
|
|
482
|
+
boolean requiresVerification = false;
|
|
483
|
+
if (responseObj != null) {
|
|
484
|
+
JSONArray verifications = responseObj.optJSONArray("verifications");
|
|
485
|
+
if (verifications != null) {
|
|
486
|
+
requiresVerification = true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
JSObject result = new JSObject();
|
|
491
|
+
result.put("requiresVerification", requiresVerification);
|
|
492
|
+
if (currentUser != null) {
|
|
493
|
+
result.put("user", convertUser(currentUser));
|
|
494
|
+
} else {
|
|
495
|
+
result.put("user", JSObject.NULL);
|
|
496
|
+
}
|
|
497
|
+
call.resolve(result);
|
|
498
|
+
}
|
|
499
|
+
} catch (Exception e) {
|
|
500
|
+
Log.e(TAG, "Sign up error", e);
|
|
501
|
+
call.reject("Sign up failed: " + e.getMessage());
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
@PluginMethod
|
|
507
|
+
public void verifySignUpEmail(PluginCall call) {
|
|
508
|
+
call.reject("Sign up email verification not yet implemented on Android");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
@PluginMethod
|
|
512
|
+
public void updateUser(PluginCall call) {
|
|
513
|
+
call.reject("Update user not yet implemented on Android");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@PluginMethod
|
|
517
|
+
public void requestPasswordReset(PluginCall call) {
|
|
518
|
+
call.reject("Password reset not yet implemented on Android");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@PluginMethod
|
|
522
|
+
public void resetPassword(PluginCall call) {
|
|
523
|
+
call.reject("Password reset not yet implemented on Android");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@PluginMethod
|
|
527
|
+
public void refreshSession(PluginCall call) {
|
|
528
|
+
call.reject("Refresh session not yet implemented on Android");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private JSObject convertUser(JSONObject clerkUser) throws JSONException {
|
|
532
|
+
JSObject user = new JSObject();
|
|
533
|
+
user.put("id", clerkUser.optString("id", null));
|
|
534
|
+
user.put("firstName", clerkUser.optString("first_name", null));
|
|
535
|
+
user.put("lastName", clerkUser.optString("last_name", null));
|
|
536
|
+
user.put("imageUrl", clerkUser.optString("image_url", null));
|
|
537
|
+
user.put("username", clerkUser.optString("username", null));
|
|
538
|
+
|
|
539
|
+
// Get primary email
|
|
540
|
+
JSONArray emailAddresses = clerkUser.optJSONArray("email_addresses");
|
|
541
|
+
if (emailAddresses != null && emailAddresses.length() > 0) {
|
|
542
|
+
String primaryEmailId = clerkUser.optString("primary_email_address_id", null);
|
|
543
|
+
for (int i = 0; i < emailAddresses.length(); i++) {
|
|
544
|
+
JSONObject emailObj = emailAddresses.getJSONObject(i);
|
|
545
|
+
if (primaryEmailId != null && primaryEmailId.equals(emailObj.optString("id"))) {
|
|
546
|
+
user.put("emailAddress", emailObj.optString("email_address", null));
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Fallback to first email if no primary found
|
|
551
|
+
if (user.optString("emailAddress", null) == null) {
|
|
552
|
+
user.put("emailAddress", emailAddresses.getJSONObject(0).optString("email_address", null));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return user;
|
|
557
|
+
}
|
|
558
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trainon-inc/capacitor-clerk-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.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
|
-
}
|