react-native-nitro-auth 0.5.4 → 0.5.6

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.
Files changed (100) hide show
  1. package/README.md +82 -47
  2. package/android/proguard-rules.pro +7 -1
  3. package/android/src/main/AndroidManifest.xml +12 -0
  4. package/android/src/main/cpp/JniOnLoad.cpp +3 -1
  5. package/android/src/main/cpp/PlatformAuth+Android.cpp +271 -78
  6. package/android/src/main/java/com/auth/AuthAdapter.kt +293 -238
  7. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +9 -5
  8. package/android/src/main/java/com/auth/NitroAuthModule.kt +8 -1
  9. package/cpp/HybridAuth.cpp +79 -64
  10. package/cpp/HybridAuth.hpp +9 -7
  11. package/cpp/JSONSerializer.hpp +3 -0
  12. package/ios/AuthAdapter.swift +226 -79
  13. package/ios/PlatformAuth+iOS.mm +10 -3
  14. package/lib/commonjs/Auth.web.js +50 -10
  15. package/lib/commonjs/Auth.web.js.map +1 -1
  16. package/lib/commonjs/index.js +23 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/index.web.js +30 -12
  19. package/lib/commonjs/index.web.js.map +1 -1
  20. package/lib/commonjs/service.js +36 -9
  21. package/lib/commonjs/service.js.map +1 -1
  22. package/lib/commonjs/service.web.js +65 -13
  23. package/lib/commonjs/service.web.js.map +1 -1
  24. package/lib/commonjs/ui/social-button.js +19 -14
  25. package/lib/commonjs/ui/social-button.js.map +1 -1
  26. package/lib/commonjs/ui/social-button.web.js +16 -10
  27. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  28. package/lib/commonjs/use-auth.js +22 -25
  29. package/lib/commonjs/use-auth.js.map +1 -1
  30. package/lib/commonjs/utils/auth-error.js +37 -0
  31. package/lib/commonjs/utils/auth-error.js.map +1 -0
  32. package/lib/commonjs/utils/logger.js +1 -0
  33. package/lib/commonjs/utils/logger.js.map +1 -1
  34. package/lib/module/Auth.web.js +50 -10
  35. package/lib/module/Auth.web.js.map +1 -1
  36. package/lib/module/global.d.js.map +1 -1
  37. package/lib/module/index.js +1 -0
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/index.web.js +2 -1
  40. package/lib/module/index.web.js.map +1 -1
  41. package/lib/module/service.js +36 -9
  42. package/lib/module/service.js.map +1 -1
  43. package/lib/module/service.web.js +65 -13
  44. package/lib/module/service.web.js.map +1 -1
  45. package/lib/module/ui/social-button.js +19 -14
  46. package/lib/module/ui/social-button.js.map +1 -1
  47. package/lib/module/ui/social-button.web.js +16 -10
  48. package/lib/module/ui/social-button.web.js.map +1 -1
  49. package/lib/module/use-auth.js +22 -25
  50. package/lib/module/use-auth.js.map +1 -1
  51. package/lib/module/utils/auth-error.js +30 -0
  52. package/lib/module/utils/auth-error.js.map +1 -0
  53. package/lib/module/utils/logger.js +1 -0
  54. package/lib/module/utils/logger.js.map +1 -1
  55. package/lib/typescript/commonjs/Auth.web.d.ts +5 -1
  56. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/index.d.ts +1 -0
  58. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/index.web.d.ts +2 -1
  60. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/service.web.d.ts +2 -18
  63. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/use-auth.d.ts +2 -1
  67. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/utils/auth-error.d.ts +16 -0
  69. package/lib/typescript/commonjs/utils/auth-error.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  71. package/lib/typescript/module/Auth.web.d.ts +5 -1
  72. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  73. package/lib/typescript/module/index.d.ts +1 -0
  74. package/lib/typescript/module/index.d.ts.map +1 -1
  75. package/lib/typescript/module/index.web.d.ts +2 -1
  76. package/lib/typescript/module/index.web.d.ts.map +1 -1
  77. package/lib/typescript/module/service.d.ts.map +1 -1
  78. package/lib/typescript/module/service.web.d.ts +2 -18
  79. package/lib/typescript/module/service.web.d.ts.map +1 -1
  80. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  81. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  82. package/lib/typescript/module/use-auth.d.ts +2 -1
  83. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  84. package/lib/typescript/module/utils/auth-error.d.ts +16 -0
  85. package/lib/typescript/module/utils/auth-error.d.ts.map +1 -0
  86. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  87. package/nitrogen/generated/android/NitroAuthOnLoad.cpp +22 -17
  88. package/nitrogen/generated/android/NitroAuthOnLoad.hpp +13 -4
  89. package/package.json +8 -10
  90. package/src/Auth.web.ts +77 -11
  91. package/src/global.d.ts +0 -11
  92. package/src/index.ts +5 -0
  93. package/src/index.web.ts +6 -1
  94. package/src/service.ts +37 -9
  95. package/src/service.web.ts +84 -15
  96. package/src/ui/social-button.tsx +21 -9
  97. package/src/ui/social-button.web.tsx +17 -4
  98. package/src/use-auth.ts +29 -67
  99. package/src/utils/auth-error.ts +49 -0
  100. package/src/utils/logger.ts +1 -0
@@ -13,14 +13,6 @@ namespace margelo::nitro::NitroAuth {
13
13
 
14
14
  using namespace facebook::jni;
15
15
 
16
- struct JContext : JavaClass<JContext> {
17
- static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
18
- };
19
-
20
- struct JAuthAdapter : JavaClass<JAuthAdapter> {
21
- static constexpr auto kJavaDescriptor = "Lcom/auth/AuthAdapter;";
22
- };
23
-
24
16
  static std::shared_ptr<Promise<AuthUser>> gLoginPromise;
25
17
  static std::shared_ptr<Promise<AuthUser>> gScopesPromise;
26
18
  static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
@@ -29,29 +21,84 @@ static std::mutex gMutex;
29
21
  static jclass gAuthAdapterClass = nullptr;
30
22
  static jmethodID gLoginMethod = nullptr;
31
23
  static jmethodID gRequestScopesMethod = nullptr;
24
+ static jmethodID gRefreshMethod = nullptr;
25
+ static jmethodID gRestoreMethod = nullptr;
26
+ static jmethodID gHasPlayMethod = nullptr;
27
+ static jmethodID gLogoutMethod = nullptr;
28
+
29
+ // Call from JNI_OnUnload or dispose to prevent stale refs after a module reload.
30
+ static void clearCachedJniRefs(JNIEnv* env) {
31
+ if (gAuthAdapterClass != nullptr) {
32
+ env->DeleteGlobalRef(gAuthAdapterClass);
33
+ gAuthAdapterClass = nullptr;
34
+ }
35
+ gLoginMethod = nullptr;
36
+ gRequestScopesMethod = nullptr;
37
+ gRefreshMethod = nullptr;
38
+ gRestoreMethod = nullptr;
39
+ gHasPlayMethod = nullptr;
40
+ gLogoutMethod = nullptr;
41
+ }
32
42
 
33
43
  static void ensureAuthAdapterMethods(JNIEnv* env) {
34
- if (gAuthAdapterClass != nullptr && gLoginMethod != nullptr && gRequestScopesMethod != nullptr) {
44
+ if (gAuthAdapterClass != nullptr && gLoginMethod != nullptr
45
+ && gRequestScopesMethod != nullptr && gRefreshMethod != nullptr
46
+ && gRestoreMethod != nullptr && gHasPlayMethod != nullptr
47
+ && gLogoutMethod != nullptr) {
35
48
  return;
36
49
  }
37
50
 
38
- jclass localAdapterClass = env->FindClass("com/auth/AuthAdapter");
39
- if (localAdapterClass == nullptr) {
40
- throw std::runtime_error("Unable to resolve com/auth/AuthAdapter");
51
+ if (gAuthAdapterClass == nullptr) {
52
+ jclass localAdapterClass = env->FindClass("com/auth/AuthAdapter");
53
+ if (localAdapterClass == nullptr) {
54
+ throw std::runtime_error("Unable to resolve com/auth/AuthAdapter");
55
+ }
56
+ gAuthAdapterClass = static_cast<jclass>(env->NewGlobalRef(localAdapterClass));
57
+ env->DeleteLocalRef(localAdapterClass);
41
58
  }
42
- gAuthAdapterClass = static_cast<jclass>(env->NewGlobalRef(localAdapterClass));
43
- env->DeleteLocalRef(localAdapterClass);
44
59
 
45
- gLoginMethod = env->GetStaticMethodID(
46
- gAuthAdapterClass,
47
- "loginSync",
48
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;)V"
49
- );
50
- gRequestScopesMethod = env->GetStaticMethodID(
51
- gAuthAdapterClass,
52
- "requestScopesSync",
53
- "(Landroid/content/Context;[Ljava/lang/String;)V"
54
- );
60
+ if (gLoginMethod == nullptr) {
61
+ gLoginMethod = env->GetStaticMethodID(
62
+ gAuthAdapterClass,
63
+ "loginSync",
64
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;)V"
65
+ );
66
+ }
67
+ if (gRequestScopesMethod == nullptr) {
68
+ gRequestScopesMethod = env->GetStaticMethodID(
69
+ gAuthAdapterClass,
70
+ "requestScopesSync",
71
+ "(Landroid/content/Context;[Ljava/lang/String;)V"
72
+ );
73
+ }
74
+ if (gRefreshMethod == nullptr) {
75
+ gRefreshMethod = env->GetStaticMethodID(
76
+ gAuthAdapterClass,
77
+ "refreshTokenSync",
78
+ "(Landroid/content/Context;)V"
79
+ );
80
+ }
81
+ if (gRestoreMethod == nullptr) {
82
+ gRestoreMethod = env->GetStaticMethodID(
83
+ gAuthAdapterClass,
84
+ "restoreSession",
85
+ "(Landroid/content/Context;)V"
86
+ );
87
+ }
88
+ if (gHasPlayMethod == nullptr) {
89
+ gHasPlayMethod = env->GetStaticMethodID(
90
+ gAuthAdapterClass,
91
+ "hasPlayServices",
92
+ "(Landroid/content/Context;)Z"
93
+ );
94
+ }
95
+ if (gLogoutMethod == nullptr) {
96
+ gLogoutMethod = env->GetStaticMethodID(
97
+ gAuthAdapterClass,
98
+ "logoutSync",
99
+ "(Landroid/content/Context;)V"
100
+ );
101
+ }
55
102
  }
56
103
 
57
104
  std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
@@ -61,11 +108,12 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
61
108
  promise->reject(std::make_exception_ptr(std::runtime_error("Android Context not initialized")));
62
109
  return promise;
63
110
  }
64
-
111
+
65
112
  {
66
113
  std::lock_guard<std::mutex> lock(gMutex);
67
114
  if (gLoginPromise) {
68
- gLoginPromise->reject(std::make_exception_ptr(std::runtime_error("Login request superseded by a newer request")));
115
+ promise->reject(std::make_exception_ptr(std::runtime_error("operation_in_progress")));
116
+ return promise;
69
117
  }
70
118
  gLoginPromise = promise;
71
119
  }
@@ -106,15 +154,20 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
106
154
  try {
107
155
  ensureAuthAdapterMethods(env);
108
156
  } catch (...) {
157
+ {
158
+ std::lock_guard<std::mutex> lock(gMutex);
159
+ gLoginPromise = nullptr;
160
+ }
109
161
  promise->reject(std::current_exception());
110
162
  return promise;
111
163
  }
112
164
  jclass stringClass = env->FindClass("java/lang/String");
113
165
  jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
114
166
  for (size_t i = 0; i < scopes.size(); i++) {
115
- env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
167
+ auto jstr = make_jstring(scopes[i]);
168
+ env->SetObjectArrayElement(jScopes, i, jstr.get());
116
169
  }
117
-
170
+
118
171
  local_ref<JString> providerRef = make_jstring(providerStr);
119
172
  local_ref<JString> loginHintRef;
120
173
  local_ref<JString> tenantRef;
@@ -130,8 +183,8 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
130
183
  promptRef = make_jstring(prompt.value());
131
184
  }
132
185
 
133
- env->CallStaticVoidMethod(gAuthAdapterClass, gLoginMethod,
134
- contextPtr,
186
+ env->CallStaticVoidMethod(gAuthAdapterClass, gLoginMethod,
187
+ contextPtr,
135
188
  providerRef.get(),
136
189
  nullptr,
137
190
  jScopes,
@@ -144,7 +197,18 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
144
197
 
145
198
  env->DeleteLocalRef(jScopes);
146
199
  env->DeleteLocalRef(stringClass);
147
-
200
+
201
+ if (env->ExceptionCheck()) {
202
+ env->ExceptionDescribe();
203
+ env->ExceptionClear();
204
+ {
205
+ std::lock_guard<std::mutex> lock(gMutex);
206
+ gLoginPromise = nullptr;
207
+ }
208
+ promise->reject(std::make_exception_ptr(std::runtime_error("JNI call failed")));
209
+ return promise;
210
+ }
211
+
148
212
  return promise;
149
213
  }
150
214
 
@@ -159,7 +223,8 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
159
223
  {
160
224
  std::lock_guard<std::mutex> lock(gMutex);
161
225
  if (gScopesPromise) {
162
- gScopesPromise->reject(std::make_exception_ptr(std::runtime_error("Scope request superseded by a newer request")));
226
+ promise->reject(std::make_exception_ptr(std::runtime_error("operation_in_progress")));
227
+ return promise;
163
228
  }
164
229
  gScopesPromise = promise;
165
230
  }
@@ -168,18 +233,35 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
168
233
  try {
169
234
  ensureAuthAdapterMethods(env);
170
235
  } catch (...) {
236
+ {
237
+ std::lock_guard<std::mutex> lock(gMutex);
238
+ gScopesPromise = nullptr;
239
+ }
171
240
  promise->reject(std::current_exception());
172
241
  return promise;
173
242
  }
174
243
  jclass stringClass = env->FindClass("java/lang/String");
175
244
  jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
176
245
  for (size_t i = 0; i < scopes.size(); i++) {
177
- env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
246
+ auto jstr = make_jstring(scopes[i]);
247
+ env->SetObjectArrayElement(jScopes, i, jstr.get());
178
248
  }
179
-
249
+
180
250
  env->CallStaticVoidMethod(gAuthAdapterClass, gRequestScopesMethod, contextPtr, jScopes);
181
251
  env->DeleteLocalRef(jScopes);
182
252
  env->DeleteLocalRef(stringClass);
253
+
254
+ if (env->ExceptionCheck()) {
255
+ env->ExceptionDescribe();
256
+ env->ExceptionClear();
257
+ {
258
+ std::lock_guard<std::mutex> lock(gMutex);
259
+ gScopesPromise = nullptr;
260
+ }
261
+ promise->reject(std::make_exception_ptr(std::runtime_error("JNI call failed")));
262
+ return promise;
263
+ }
264
+
183
265
  return promise;
184
266
  }
185
267
 
@@ -194,14 +276,37 @@ std::shared_ptr<Promise<AuthTokens>> PlatformAuth::refreshToken() {
194
276
  {
195
277
  std::lock_guard<std::mutex> lock(gMutex);
196
278
  if (gRefreshPromise) {
197
- gRefreshPromise->reject(std::make_exception_ptr(std::runtime_error("Refresh request superseded by a newer request")));
279
+ promise->reject(std::make_exception_ptr(std::runtime_error("operation_in_progress")));
280
+ return promise;
198
281
  }
199
282
  gRefreshPromise = promise;
200
283
  }
201
284
 
202
- auto jContext = wrap_alias(contextPtr);
203
- static auto refreshMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("refreshTokenSync");
204
- refreshMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
285
+ JNIEnv* env = Environment::current();
286
+ try {
287
+ ensureAuthAdapterMethods(env);
288
+ } catch (...) {
289
+ {
290
+ std::lock_guard<std::mutex> lock(gMutex);
291
+ gRefreshPromise = nullptr;
292
+ }
293
+ promise->reject(std::current_exception());
294
+ return promise;
295
+ }
296
+
297
+ env->CallStaticVoidMethod(gAuthAdapterClass, gRefreshMethod, contextPtr);
298
+
299
+ if (env->ExceptionCheck()) {
300
+ env->ExceptionDescribe();
301
+ env->ExceptionClear();
302
+ {
303
+ std::lock_guard<std::mutex> lock(gMutex);
304
+ gRefreshPromise = nullptr;
305
+ }
306
+ promise->reject(std::make_exception_ptr(std::runtime_error("JNI call failed")));
307
+ return promise;
308
+ }
309
+
205
310
  return promise;
206
311
  }
207
312
 
@@ -216,33 +321,79 @@ std::shared_ptr<Promise<std::optional<AuthUser>>> PlatformAuth::silentRestore()
216
321
  {
217
322
  std::lock_guard<std::mutex> lock(gMutex);
218
323
  if (gSilentPromise) {
219
- gSilentPromise->reject(std::make_exception_ptr(std::runtime_error("Silent restore superseded by a newer request")));
324
+ promise->reject(std::make_exception_ptr(std::runtime_error("operation_in_progress")));
325
+ return promise;
220
326
  }
221
327
  gSilentPromise = promise;
222
328
  }
223
329
 
224
- auto jContext = wrap_alias(contextPtr);
225
- static auto restoreMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("restoreSession");
226
- restoreMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
330
+ JNIEnv* env = Environment::current();
331
+ try {
332
+ ensureAuthAdapterMethods(env);
333
+ } catch (...) {
334
+ {
335
+ std::lock_guard<std::mutex> lock(gMutex);
336
+ gSilentPromise = nullptr;
337
+ }
338
+ promise->reject(std::current_exception());
339
+ return promise;
340
+ }
341
+
342
+ env->CallStaticVoidMethod(gAuthAdapterClass, gRestoreMethod, contextPtr);
343
+
344
+ if (env->ExceptionCheck()) {
345
+ env->ExceptionDescribe();
346
+ env->ExceptionClear();
347
+ {
348
+ std::lock_guard<std::mutex> lock(gMutex);
349
+ gSilentPromise = nullptr;
350
+ }
351
+ promise->reject(std::make_exception_ptr(std::runtime_error("JNI call failed")));
352
+ return promise;
353
+ }
354
+
227
355
  return promise;
228
356
  }
229
357
 
230
358
  bool PlatformAuth::hasPlayServices() {
231
359
  auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
232
360
  if (!contextPtr) return false;
233
-
234
- auto jContext = wrap_alias(contextPtr);
235
- static auto hasPlayMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<jboolean(alias_ref<JContext>)>("hasPlayServices");
236
- return hasPlayMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
361
+
362
+ JNIEnv* env = Environment::current();
363
+ try {
364
+ ensureAuthAdapterMethods(env);
365
+ } catch (...) {
366
+ return false;
367
+ }
368
+
369
+ jboolean result = env->CallStaticBooleanMethod(gAuthAdapterClass, gHasPlayMethod, contextPtr);
370
+
371
+ if (env->ExceptionCheck()) {
372
+ env->ExceptionDescribe();
373
+ env->ExceptionClear();
374
+ return false;
375
+ }
376
+
377
+ return result;
237
378
  }
238
379
 
239
380
  void PlatformAuth::logout() {
240
381
  auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
241
382
  if (!contextPtr) return;
242
383
 
243
- auto jContext = wrap_alias(contextPtr);
244
- static auto logoutMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("logoutSync");
245
- logoutMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
384
+ JNIEnv* env = Environment::current();
385
+ try {
386
+ ensureAuthAdapterMethods(env);
387
+ } catch (...) {
388
+ return;
389
+ }
390
+
391
+ env->CallStaticVoidMethod(gAuthAdapterClass, gLogoutMethod, contextPtr);
392
+
393
+ if (env->ExceptionCheck()) {
394
+ env->ExceptionDescribe();
395
+ env->ExceptionClear();
396
+ }
246
397
  }
247
398
 
248
399
  extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv*, jclass, jobject context) {
@@ -250,22 +401,30 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNI
250
401
  }
251
402
 
252
403
  extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess(
253
- JNIEnv* env, jclass,
254
- jstring provider, jstring email, jstring name, jstring photo, jstring idToken, jstring accessToken, jstring serverAuthCode, jobjectArray scopes, jobject expirationTime) {
255
-
404
+ JNIEnv* env, jclass,
405
+ jstring origin, jstring provider, jstring email, jstring name, jstring photo, jstring idToken, jstring accessToken, jstring serverAuthCode, jobjectArray scopes, jobject expirationTime) {
406
+
407
+ const char* originCStr = env->GetStringUTFChars(origin, nullptr);
408
+ std::string originStr(originCStr);
409
+ env->ReleaseStringUTFChars(origin, originCStr);
410
+
256
411
  std::shared_ptr<Promise<AuthUser>> loginPromise;
257
412
  std::shared_ptr<Promise<AuthUser>> scopesPromise;
258
413
  std::shared_ptr<Promise<std::optional<AuthUser>>> silentPromise;
259
414
  {
260
415
  std::lock_guard<std::mutex> lock(gMutex);
261
- loginPromise = gLoginPromise;
262
- gLoginPromise = nullptr;
263
- scopesPromise = gScopesPromise;
264
- gScopesPromise = nullptr;
265
- silentPromise = gSilentPromise;
266
- gSilentPromise = nullptr;
416
+ if (originStr == "login") {
417
+ loginPromise = gLoginPromise;
418
+ gLoginPromise = nullptr;
419
+ } else if (originStr == "scopes") {
420
+ scopesPromise = gScopesPromise;
421
+ gScopesPromise = nullptr;
422
+ } else if (originStr == "silent") {
423
+ silentPromise = gSilentPromise;
424
+ gSilentPromise = nullptr;
425
+ }
267
426
  }
268
-
427
+
269
428
  AuthUser user;
270
429
  const char* providerCStr = env->GetStringUTFChars(provider, nullptr);
271
430
  std::string providerStr(providerCStr);
@@ -333,37 +492,47 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
333
492
  }
334
493
 
335
494
  extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginError(
336
- JNIEnv* env, jclass, jstring error, jstring underlyingError) {
337
-
495
+ JNIEnv* env, jclass, jstring origin, jstring error, jstring underlyingError) {
496
+
497
+ const char* originCStr = env->GetStringUTFChars(origin, nullptr);
498
+ std::string originStr(originCStr);
499
+ env->ReleaseStringUTFChars(origin, originCStr);
500
+
338
501
  std::shared_ptr<Promise<AuthUser>> loginPromise;
339
502
  std::shared_ptr<Promise<AuthUser>> scopesPromise;
340
503
  std::shared_ptr<Promise<std::optional<AuthUser>>> silentPromise;
341
504
  {
342
505
  std::lock_guard<std::mutex> lock(gMutex);
343
- loginPromise = gLoginPromise;
344
- gLoginPromise = nullptr;
345
- scopesPromise = gScopesPromise;
346
- gScopesPromise = nullptr;
347
- silentPromise = gSilentPromise;
348
- gSilentPromise = nullptr;
506
+ if (originStr == "login") {
507
+ loginPromise = gLoginPromise;
508
+ gLoginPromise = nullptr;
509
+ } else if (originStr == "scopes") {
510
+ scopesPromise = gScopesPromise;
511
+ gScopesPromise = nullptr;
512
+ } else if (originStr == "silent") {
513
+ silentPromise = gSilentPromise;
514
+ gSilentPromise = nullptr;
515
+ }
349
516
  }
350
517
 
351
518
  const char* errorCStr = env->GetStringUTFChars(error, nullptr);
352
519
  std::string errorStr(errorCStr);
353
520
  env->ReleaseStringUTFChars(error, errorCStr);
354
-
355
- std::string finalError = errorStr;
521
+
522
+ // errorStr is the structured AuthErrorCode (e.g. "cancelled", "network_error").
523
+ // underlyingError is a raw platform message for debugging — it must not replace the code.
356
524
  if (underlyingError) {
357
525
  const char* uCStr = env->GetStringUTFChars(underlyingError, nullptr);
358
- finalError = std::string(uCStr);
359
526
  env->ReleaseStringUTFChars(underlyingError, uCStr);
527
+ // underlyingError is intentionally discarded here; the structured code is sufficient
528
+ // for consumers. If richer debugging is needed, add it to the AuthUser.underlyingError field.
360
529
  }
361
530
 
362
- if (loginPromise) loginPromise->reject(std::make_exception_ptr(std::runtime_error(finalError)));
363
- if (scopesPromise) scopesPromise->reject(std::make_exception_ptr(std::runtime_error(finalError)));
531
+ if (loginPromise) loginPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
532
+ if (scopesPromise) scopesPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
364
533
  if (silentPromise) {
365
534
  if (errorStr == "No session") silentPromise->resolve(std::nullopt);
366
- else silentPromise->reject(std::make_exception_ptr(std::runtime_error(finalError)));
535
+ else silentPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
367
536
  }
368
537
  }
369
538
 
@@ -409,18 +578,42 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshError
409
578
  gRefreshPromise = nullptr;
410
579
  }
411
580
  if (refreshPromise) {
412
- std::string finalError;
413
581
  const char* errorCStr = env->GetStringUTFChars(error, nullptr);
414
- finalError = std::string(errorCStr);
582
+ std::string errorStr(errorCStr);
415
583
  env->ReleaseStringUTFChars(error, errorCStr);
416
-
584
+
417
585
  if (underlyingError) {
418
586
  const char* uCStr = env->GetStringUTFChars(underlyingError, nullptr);
419
- finalError = std::string(uCStr);
420
587
  env->ReleaseStringUTFChars(underlyingError, uCStr);
421
588
  }
422
- refreshPromise->reject(std::make_exception_ptr(std::runtime_error(finalError)));
589
+ refreshPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
423
590
  }
424
591
  }
425
592
 
593
+ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeDispose(JNIEnv* env, jclass) {
594
+ std::shared_ptr<Promise<AuthUser>> loginPromise;
595
+ std::shared_ptr<Promise<AuthUser>> scopesPromise;
596
+ std::shared_ptr<Promise<AuthTokens>> refreshPromise;
597
+ std::shared_ptr<Promise<std::optional<AuthUser>>> silentPromise;
598
+ {
599
+ std::lock_guard<std::mutex> lock(gMutex);
600
+ loginPromise = std::move(gLoginPromise);
601
+ scopesPromise = std::move(gScopesPromise);
602
+ refreshPromise = std::move(gRefreshPromise);
603
+ silentPromise = std::move(gSilentPromise);
604
+ gLoginPromise = nullptr;
605
+ gScopesPromise = nullptr;
606
+ gRefreshPromise = nullptr;
607
+ gSilentPromise = nullptr;
608
+ }
609
+
610
+ auto disposed = std::make_exception_ptr(std::runtime_error("disposed"));
611
+ if (loginPromise) loginPromise->reject(disposed);
612
+ if (scopesPromise) scopesPromise->reject(disposed);
613
+ if (refreshPromise) refreshPromise->reject(disposed);
614
+ if (silentPromise) silentPromise->reject(disposed);
615
+
616
+ clearCachedJniRefs(env);
617
+ }
618
+
426
619
  } // namespace margelo::nitro::NitroAuth