react-native-nitro-auth 0.5.5 → 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 (82) hide show
  1. package/README.md +22 -17
  2. package/android/proguard-rules.pro +7 -1
  3. package/android/src/main/AndroidManifest.xml +12 -0
  4. package/android/src/main/cpp/PlatformAuth+Android.cpp +260 -67
  5. package/android/src/main/java/com/auth/AuthAdapter.kt +217 -146
  6. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +9 -5
  7. package/cpp/HybridAuth.cpp +79 -64
  8. package/cpp/HybridAuth.hpp +9 -7
  9. package/cpp/JSONSerializer.hpp +3 -0
  10. package/ios/AuthAdapter.swift +173 -60
  11. package/ios/PlatformAuth+iOS.mm +10 -3
  12. package/lib/commonjs/Auth.web.js +50 -10
  13. package/lib/commonjs/Auth.web.js.map +1 -1
  14. package/lib/commonjs/index.web.js +30 -12
  15. package/lib/commonjs/index.web.js.map +1 -1
  16. package/lib/commonjs/service.js +9 -7
  17. package/lib/commonjs/service.js.map +1 -1
  18. package/lib/commonjs/service.web.js +65 -13
  19. package/lib/commonjs/service.web.js.map +1 -1
  20. package/lib/commonjs/ui/social-button.js +19 -14
  21. package/lib/commonjs/ui/social-button.js.map +1 -1
  22. package/lib/commonjs/ui/social-button.web.js +16 -10
  23. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  24. package/lib/commonjs/use-auth.js +13 -5
  25. package/lib/commonjs/use-auth.js.map +1 -1
  26. package/lib/commonjs/utils/logger.js +1 -0
  27. package/lib/commonjs/utils/logger.js.map +1 -1
  28. package/lib/module/Auth.web.js +50 -10
  29. package/lib/module/Auth.web.js.map +1 -1
  30. package/lib/module/global.d.js.map +1 -1
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/index.web.js +2 -1
  33. package/lib/module/index.web.js.map +1 -1
  34. package/lib/module/service.js +9 -7
  35. package/lib/module/service.js.map +1 -1
  36. package/lib/module/service.web.js +65 -13
  37. package/lib/module/service.web.js.map +1 -1
  38. package/lib/module/ui/social-button.js +19 -14
  39. package/lib/module/ui/social-button.js.map +1 -1
  40. package/lib/module/ui/social-button.web.js +16 -10
  41. package/lib/module/ui/social-button.web.js.map +1 -1
  42. package/lib/module/use-auth.js +13 -5
  43. package/lib/module/use-auth.js.map +1 -1
  44. package/lib/module/utils/logger.js +1 -0
  45. package/lib/module/utils/logger.js.map +1 -1
  46. package/lib/typescript/commonjs/Auth.web.d.ts +5 -1
  47. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/index.d.ts +1 -1
  49. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/index.web.d.ts +2 -1
  51. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/service.web.d.ts +2 -18
  54. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  59. package/lib/typescript/module/Auth.web.d.ts +5 -1
  60. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  61. package/lib/typescript/module/index.d.ts +1 -1
  62. package/lib/typescript/module/index.d.ts.map +1 -1
  63. package/lib/typescript/module/index.web.d.ts +2 -1
  64. package/lib/typescript/module/index.web.d.ts.map +1 -1
  65. package/lib/typescript/module/service.d.ts.map +1 -1
  66. package/lib/typescript/module/service.web.d.ts +2 -18
  67. package/lib/typescript/module/service.web.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  69. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  70. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  71. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  72. package/package.json +2 -4
  73. package/src/Auth.web.ts +77 -11
  74. package/src/global.d.ts +0 -11
  75. package/src/index.ts +5 -1
  76. package/src/index.web.ts +6 -1
  77. package/src/service.ts +9 -7
  78. package/src/service.web.ts +84 -15
  79. package/src/ui/social-button.tsx +21 -9
  80. package/src/ui/social-button.web.tsx +17 -4
  81. package/src/use-auth.ts +35 -8
  82. package/src/utils/logger.ts +1 -0
package/README.md CHANGED
@@ -308,12 +308,13 @@ To enable Microsoft Sign-In, you need to register an application in the Azure Po
308
308
  - `nitro_auth_microsoft_client_id`
309
309
  - `nitro_auth_microsoft_tenant` (optional)
310
310
  - `nitro_auth_microsoft_b2c_domain` (optional)
311
- - Add the Microsoft redirect activity to `AndroidManifest.xml`:
311
+ - `MicrosoftAuthActivity` is **automatically declared** by the library manifest — no manual `AndroidManifest.xml` entry is required for basic Microsoft OAuth redirect handling. If you need to customize the intent-filter (e.g., restrict the host/path to your specific package and client ID), you can override it in your app manifest:
312
312
 
313
313
  ```xml
314
314
  <activity
315
315
  android:name="com.auth.MicrosoftAuthActivity"
316
- android:exported="true">
316
+ android:exported="true"
317
+ android:launchMode="singleTask">
317
318
  <intent-filter>
318
319
  <action android:name="android.intent.action.VIEW" />
319
320
  <category android:name="android.intent.category.DEFAULT" />
@@ -693,19 +694,21 @@ try {
693
694
  }
694
695
  ```
695
696
 
696
- | Error Code | Description |
697
- | ---------------------- | ---------------------------------------------- |
698
- | `cancelled` | The user cancelled the sign-in flow |
699
- | `network_error` | A network error occurred |
700
- | `configuration_error` | Missing client IDs or invalid setup |
701
- | `unsupported_provider` | The provider is not supported on this platform |
702
- | `invalid_state` | PKCE state mismatch (possible CSRF) |
703
- | `invalid_nonce` | Nonce mismatch in token response |
704
- | `token_error` | Token exchange failed |
705
- | `no_id_token` | No `id_token` in token response |
706
- | `parse_error` | Failed to parse token response |
707
- | `refresh_failed` | Refresh token flow failed |
708
- | `unknown` | An unknown error occurred |
697
+ | Error Code | Description |
698
+ | ---------------------- | --------------------------------------------------------------- |
699
+ | `cancelled` | The user cancelled the sign-in flow or dismissed the popup |
700
+ | `timeout` | The login popup/flow timed out |
701
+ | `popup_blocked` | The browser blocked the popup window |
702
+ | `network_error` | A network or connectivity error occurred |
703
+ | `configuration_error` | Missing client IDs, invalid tenant, or misconfigured setup |
704
+ | `unsupported_provider` | The provider is not supported on this platform |
705
+ | `invalid_state` | PKCE state mismatch — possible CSRF attack |
706
+ | `invalid_nonce` | Nonce mismatch in token response — possible replay attack |
707
+ | `token_error` | Token exchange or storage failed |
708
+ | `no_id_token` | No `id_token` in token response |
709
+ | `parse_error` | Failed to parse token response |
710
+ | `refresh_failed` | Refresh token flow failed (token may be expired or revoked) |
711
+ | `unknown` | An unknown or unmapped error occurred |
709
712
 
710
713
  ### Native Error Metadata
711
714
 
@@ -729,10 +732,12 @@ The `AuthUser.underlyingError` field carries a raw warning string when the provi
729
732
 
730
733
  ### Troubleshooting
731
734
 
732
- - `configuration_error`: verify client IDs, URL schemes, and redirect URIs are set for the current platform.
733
- - `invalid_state` or `invalid_nonce`: ensure the redirect URI in your provider console matches your app config exactly.
735
+ - `configuration_error`: verify client IDs, URL schemes, and redirect URIs are set for the current platform. On Android, check that the `microsoftTenant` is a non-empty valid value.
736
+ - `invalid_state` or `invalid_nonce`: ensure the redirect URI in your provider console matches your app config exactly. These indicate a potential CSRF or replay attack — do not retry silently.
737
+ - `refresh_failed`: the refresh token is likely expired or revoked. Clear the session and prompt the user to sign in again. On Android and web, structured error details from the provider (e.g. `invalid_grant`) are surfaced via `error.underlyingMessage`.
734
738
  - `hasPlayServices` is false: prompt the user to install/update Google Play Services or disable One-Tap.
735
739
  - Apple web login fails: confirm `appleWebClientId` is set and your domain is registered with Apple.
740
+ - Microsoft Android redirect hangs: confirm the `msauth://` redirect URI in your Azure app matches your package name and client ID.
736
741
 
737
742
  ### Automatic Token Refresh
738
743
 
@@ -1,4 +1,10 @@
1
1
  # Nitro Auth Proguard Rules
2
- -keep class com.auth.** { *; }
2
+ -keep class com.auth.AuthAdapter {
3
+ public static <methods>;
4
+ }
5
+ -keep class com.auth.MicrosoftAuthActivity { *; }
6
+ -keep class com.auth.GoogleSignInActivity { *; }
7
+ -keep class com.auth.NitroAuthModule { *; }
8
+ -keep class com.auth.NitroAuthPackage { *; }
3
9
  -keep class com.margelo.nitro.com.auth.** { *; }
4
10
  -keep class com.google.android.gms.auth.api.signin.** { *; }
@@ -8,5 +8,17 @@
8
8
  android:name="com.auth.GoogleSignInActivity"
9
9
  android:theme="@android:style/Theme.Translucent.NoTitleBar"
10
10
  android:exported="false" />
11
+ <activity
12
+ android:name="com.auth.MicrosoftAuthActivity"
13
+ android:exported="true"
14
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
15
+ android:launchMode="singleTask">
16
+ <intent-filter>
17
+ <action android:name="android.intent.action.VIEW" />
18
+ <category android:name="android.intent.category.DEFAULT" />
19
+ <category android:name="android.intent.category.BROWSABLE" />
20
+ <data android:scheme="msauth" />
21
+ </intent-filter>
22
+ </activity>
11
23
  </application>
12
24
  </manifest>
@@ -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,19 +492,27 @@ 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);
@@ -423,4 +590,30 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshError
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