react-native-nitro-auth 0.5.1 → 0.5.4

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 (126) hide show
  1. package/README.md +362 -190
  2. package/android/build.gradle +2 -5
  3. package/android/src/main/cpp/PlatformAuth+Android.cpp +84 -18
  4. package/android/src/main/java/com/auth/AuthAdapter.kt +82 -182
  5. package/android/src/main/java/com/auth/NitroAuthPackage.kt +1 -1
  6. package/app.plugin.js +2 -9
  7. package/cpp/AuthCache.cpp +0 -134
  8. package/cpp/AuthCache.hpp +0 -7
  9. package/cpp/HybridAuth.cpp +57 -63
  10. package/cpp/HybridAuth.hpp +3 -4
  11. package/ios/AuthAdapter.swift +23 -25
  12. package/lib/commonjs/Auth.web.js +523 -201
  13. package/lib/commonjs/Auth.web.js.map +1 -1
  14. package/lib/commonjs/index.js +0 -12
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/index.web.js +0 -12
  17. package/lib/commonjs/index.web.js.map +1 -1
  18. package/lib/commonjs/js-storage-adapter.js +2 -0
  19. package/lib/commonjs/js-storage-adapter.js.map +1 -0
  20. package/lib/commonjs/service.js +9 -86
  21. package/lib/commonjs/service.js.map +1 -1
  22. package/lib/commonjs/service.web.js +1 -5
  23. package/lib/commonjs/service.web.js.map +1 -1
  24. package/lib/commonjs/ui/social-button.js +44 -29
  25. package/lib/commonjs/ui/social-button.js.map +1 -1
  26. package/lib/commonjs/ui/social-button.web.js +44 -29
  27. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  28. package/lib/commonjs/use-auth.js +56 -42
  29. package/lib/commonjs/use-auth.js.map +1 -1
  30. package/lib/commonjs/utils/logger.js +12 -4
  31. package/lib/commonjs/utils/logger.js.map +1 -1
  32. package/lib/module/Auth.web.js +523 -201
  33. package/lib/module/Auth.web.js.map +1 -1
  34. package/lib/module/index.js +0 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/index.web.js +0 -1
  37. package/lib/module/index.web.js.map +1 -1
  38. package/lib/module/js-storage-adapter.js +2 -0
  39. package/lib/module/js-storage-adapter.js.map +1 -0
  40. package/lib/module/service.js +9 -86
  41. package/lib/module/service.js.map +1 -1
  42. package/lib/module/service.web.js +1 -5
  43. package/lib/module/service.web.js.map +1 -1
  44. package/lib/module/ui/social-button.js +44 -29
  45. package/lib/module/ui/social-button.js.map +1 -1
  46. package/lib/module/ui/social-button.web.js +44 -29
  47. package/lib/module/ui/social-button.web.js.map +1 -1
  48. package/lib/module/use-auth.js +56 -42
  49. package/lib/module/use-auth.js.map +1 -1
  50. package/lib/module/utils/logger.js +12 -4
  51. package/lib/module/utils/logger.js.map +1 -1
  52. package/lib/typescript/commonjs/Auth.nitro.d.ts +3 -3
  53. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/Auth.web.d.ts +25 -6
  55. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/index.d.ts +1 -2
  57. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/index.web.d.ts +0 -1
  59. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/js-storage-adapter.d.ts +6 -0
  61. package/lib/typescript/commonjs/js-storage-adapter.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/service.d.ts +1 -8
  63. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/service.web.d.ts +1 -8
  65. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/ui/social-button.d.ts +6 -6
  67. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/ui/social-button.web.d.ts +6 -6
  69. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/use-auth.d.ts +4 -4
  71. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/utils/logger.d.ts +4 -4
  73. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  74. package/lib/typescript/module/Auth.nitro.d.ts +3 -3
  75. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  76. package/lib/typescript/module/Auth.web.d.ts +25 -6
  77. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  78. package/lib/typescript/module/index.d.ts +1 -2
  79. package/lib/typescript/module/index.d.ts.map +1 -1
  80. package/lib/typescript/module/index.web.d.ts +0 -1
  81. package/lib/typescript/module/index.web.d.ts.map +1 -1
  82. package/lib/typescript/module/js-storage-adapter.d.ts +6 -0
  83. package/lib/typescript/module/js-storage-adapter.d.ts.map +1 -0
  84. package/lib/typescript/module/service.d.ts +1 -8
  85. package/lib/typescript/module/service.d.ts.map +1 -1
  86. package/lib/typescript/module/service.web.d.ts +1 -8
  87. package/lib/typescript/module/service.web.d.ts.map +1 -1
  88. package/lib/typescript/module/ui/social-button.d.ts +6 -6
  89. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  90. package/lib/typescript/module/ui/social-button.web.d.ts +6 -6
  91. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  92. package/lib/typescript/module/use-auth.d.ts +4 -4
  93. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  94. package/lib/typescript/module/utils/logger.d.ts +4 -4
  95. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  96. package/nitrogen/generated/android/NitroAuth+autolinking.cmake +0 -1
  97. package/nitrogen/generated/shared/c++/AuthTokens.hpp +5 -1
  98. package/nitrogen/generated/shared/c++/AuthUser.hpp +5 -1
  99. package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +0 -1
  100. package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +0 -5
  101. package/package.json +11 -8
  102. package/react-native-nitro-auth.podspec +1 -1
  103. package/src/Auth.nitro.ts +4 -3
  104. package/src/Auth.web.ts +700 -246
  105. package/src/global.d.ts +0 -1
  106. package/src/index.ts +1 -2
  107. package/src/index.web.ts +0 -1
  108. package/src/js-storage-adapter.ts +5 -0
  109. package/src/service.ts +13 -106
  110. package/src/service.web.ts +0 -7
  111. package/src/ui/social-button.tsx +66 -43
  112. package/src/ui/social-button.web.tsx +67 -44
  113. package/src/use-auth.ts +116 -72
  114. package/src/utils/logger.ts +12 -4
  115. package/ios/KeychainStore.swift +0 -43
  116. package/lib/commonjs/AuthStorage.nitro.js +0 -6
  117. package/lib/commonjs/AuthStorage.nitro.js.map +0 -1
  118. package/lib/module/AuthStorage.nitro.js +0 -4
  119. package/lib/module/AuthStorage.nitro.js.map +0 -1
  120. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts +0 -26
  121. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts.map +0 -1
  122. package/lib/typescript/module/AuthStorage.nitro.d.ts +0 -26
  123. package/lib/typescript/module/AuthStorage.nitro.d.ts.map +0 -1
  124. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +0 -23
  125. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +0 -65
  126. package/src/AuthStorage.nitro.ts +0 -26
@@ -93,7 +93,7 @@ dependencies {
93
93
  implementation project(":react-native-nitro-modules")
94
94
 
95
95
  // Google Sign-In SDK (full scope support)
96
- implementation "com.google.android.gms:play-services-auth:21.2.0"
96
+ implementation "com.google.android.gms:play-services-auth:21.5.1"
97
97
 
98
98
  // Activity result APIs
99
99
  implementation "androidx.activity:activity-ktx:1.9.3"
@@ -104,8 +104,5 @@ dependencies {
104
104
  // Google Credential Manager (One-Tap / Passkeys)
105
105
  implementation "androidx.credentials:credentials:1.5.0"
106
106
  implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
107
- implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
108
-
109
- // Secure storage (EncryptedSharedPreferences)
110
- implementation "androidx.security:security-crypto:1.0.0"
107
+ implementation "com.google.android.libraries.identity.googleid:googleid:1.2.0"
111
108
  }
@@ -6,6 +6,8 @@
6
6
  #include <fbjni/fbjni.h>
7
7
  #include <NitroModules/NitroLogger.hpp>
8
8
  #include <NitroModules/Promise.hpp>
9
+ #include <exception>
10
+ #include <stdexcept>
9
11
 
10
12
  namespace margelo::nitro::NitroAuth {
11
13
 
@@ -24,6 +26,33 @@ static std::shared_ptr<Promise<AuthUser>> gScopesPromise;
24
26
  static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
25
27
  static std::shared_ptr<Promise<std::optional<AuthUser>>> gSilentPromise;
26
28
  static std::mutex gMutex;
29
+ static jclass gAuthAdapterClass = nullptr;
30
+ static jmethodID gLoginMethod = nullptr;
31
+ static jmethodID gRequestScopesMethod = nullptr;
32
+
33
+ static void ensureAuthAdapterMethods(JNIEnv* env) {
34
+ if (gAuthAdapterClass != nullptr && gLoginMethod != nullptr && gRequestScopesMethod != nullptr) {
35
+ return;
36
+ }
37
+
38
+ jclass localAdapterClass = env->FindClass("com/auth/AuthAdapter");
39
+ if (localAdapterClass == nullptr) {
40
+ throw std::runtime_error("Unable to resolve com/auth/AuthAdapter");
41
+ }
42
+ gAuthAdapterClass = static_cast<jclass>(env->NewGlobalRef(localAdapterClass));
43
+ env->DeleteLocalRef(localAdapterClass);
44
+
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
+ );
55
+ }
27
56
 
28
57
  std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
29
58
  auto promise = Promise<AuthUser>::create();
@@ -35,6 +64,9 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
35
64
 
36
65
  {
37
66
  std::lock_guard<std::mutex> lock(gMutex);
67
+ if (gLoginPromise) {
68
+ gLoginPromise->reject(std::make_exception_ptr(std::runtime_error("Login request superseded by a newer request")));
69
+ }
38
70
  gLoginPromise = promise;
39
71
  }
40
72
 
@@ -71,30 +103,47 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
71
103
  }
72
104
 
73
105
  JNIEnv* env = Environment::current();
106
+ try {
107
+ ensureAuthAdapterMethods(env);
108
+ } catch (...) {
109
+ promise->reject(std::current_exception());
110
+ return promise;
111
+ }
74
112
  jclass stringClass = env->FindClass("java/lang/String");
75
113
  jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
76
114
  for (size_t i = 0; i < scopes.size(); i++) {
77
115
  env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
78
116
  }
79
117
 
80
- jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
81
- jstring jTenant = tenant.has_value() ? make_jstring(tenant.value()).get() : nullptr;
82
- jstring jPrompt = prompt.has_value() ? make_jstring(prompt.value()).get() : nullptr;
83
-
84
- jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
85
- jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
86
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;)V");
87
- env->CallStaticVoidMethod(adapterClass, loginMethod,
118
+ local_ref<JString> providerRef = make_jstring(providerStr);
119
+ local_ref<JString> loginHintRef;
120
+ local_ref<JString> tenantRef;
121
+ local_ref<JString> promptRef;
122
+
123
+ if (loginHint.has_value()) {
124
+ loginHintRef = make_jstring(loginHint.value());
125
+ }
126
+ if (tenant.has_value()) {
127
+ tenantRef = make_jstring(tenant.value());
128
+ }
129
+ if (prompt.has_value()) {
130
+ promptRef = make_jstring(prompt.value());
131
+ }
132
+
133
+ env->CallStaticVoidMethod(gAuthAdapterClass, gLoginMethod,
88
134
  contextPtr,
89
- make_jstring(providerStr).get(),
135
+ providerRef.get(),
90
136
  nullptr,
91
137
  jScopes,
92
- jLoginHint,
138
+ loginHintRef.get(),
93
139
  (jboolean)useOneTap,
94
140
  (jboolean)forceAccountPicker,
95
141
  (jboolean)useLegacyGoogleSignIn,
96
- jTenant,
97
- jPrompt);
142
+ tenantRef.get(),
143
+ promptRef.get());
144
+
145
+ env->DeleteLocalRef(jScopes);
146
+ env->DeleteLocalRef(stringClass);
98
147
 
99
148
  return promise;
100
149
  }
@@ -109,20 +158,28 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
109
158
 
110
159
  {
111
160
  std::lock_guard<std::mutex> lock(gMutex);
161
+ if (gScopesPromise) {
162
+ gScopesPromise->reject(std::make_exception_ptr(std::runtime_error("Scope request superseded by a newer request")));
163
+ }
112
164
  gScopesPromise = promise;
113
165
  }
114
166
 
115
167
  JNIEnv* env = Environment::current();
168
+ try {
169
+ ensureAuthAdapterMethods(env);
170
+ } catch (...) {
171
+ promise->reject(std::current_exception());
172
+ return promise;
173
+ }
116
174
  jclass stringClass = env->FindClass("java/lang/String");
117
175
  jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
118
176
  for (size_t i = 0; i < scopes.size(); i++) {
119
177
  env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
120
178
  }
121
179
 
122
- jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
123
- jmethodID requestMethod = env->GetStaticMethodID(adapterClass, "requestScopesSync",
124
- "(Landroid/content/Context;[Ljava/lang/String;)V");
125
- env->CallStaticVoidMethod(adapterClass, requestMethod, contextPtr, jScopes);
180
+ env->CallStaticVoidMethod(gAuthAdapterClass, gRequestScopesMethod, contextPtr, jScopes);
181
+ env->DeleteLocalRef(jScopes);
182
+ env->DeleteLocalRef(stringClass);
126
183
  return promise;
127
184
  }
128
185
 
@@ -136,6 +193,9 @@ std::shared_ptr<Promise<AuthTokens>> PlatformAuth::refreshToken() {
136
193
 
137
194
  {
138
195
  std::lock_guard<std::mutex> lock(gMutex);
196
+ if (gRefreshPromise) {
197
+ gRefreshPromise->reject(std::make_exception_ptr(std::runtime_error("Refresh request superseded by a newer request")));
198
+ }
139
199
  gRefreshPromise = promise;
140
200
  }
141
201
 
@@ -155,6 +215,9 @@ std::shared_ptr<Promise<std::optional<AuthUser>>> PlatformAuth::silentRestore()
155
215
 
156
216
  {
157
217
  std::lock_guard<std::mutex> lock(gMutex);
218
+ if (gSilentPromise) {
219
+ gSilentPromise->reject(std::make_exception_ptr(std::runtime_error("Silent restore superseded by a newer request")));
220
+ }
158
221
  gSilentPromise = promise;
159
222
  }
160
223
 
@@ -182,8 +245,8 @@ void PlatformAuth::logout() {
182
245
  logoutMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
183
246
  }
184
247
 
185
- extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv* env, jclass, jobject context) {
186
- AuthCache::setAndroidContext(env->NewGlobalRef(context));
248
+ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv*, jclass, jobject context) {
249
+ AuthCache::setAndroidContext(context);
187
250
  }
188
251
 
189
252
  extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess(
@@ -253,6 +316,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
253
316
  const char* s = env->GetStringUTFChars(jstr, nullptr);
254
317
  scopeVec.push_back(std::string(s));
255
318
  env->ReleaseStringUTFChars(jstr, s);
319
+ env->DeleteLocalRef(jstr);
256
320
  }
257
321
  user.scopes = scopeVec;
258
322
  }
@@ -260,6 +324,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
260
324
  jclass longClass = env->FindClass("java/lang/Long");
261
325
  jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
262
326
  user.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
327
+ env->DeleteLocalRef(longClass);
263
328
  }
264
329
 
265
330
  if (loginPromise) loginPromise->resolve(user);
@@ -328,6 +393,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshSucce
328
393
  jclass longClass = env->FindClass("java/lang/Long");
329
394
  jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
330
395
  tokens.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
396
+ env->DeleteLocalRef(longClass);
331
397
  }
332
398
  refreshPromise->resolve(tokens);
333
399
  }
@@ -3,9 +3,7 @@
3
3
  package com.auth
4
4
 
5
5
  import android.content.Context
6
- import android.content.SharedPreferences
7
6
  import android.os.Bundle
8
- import android.os.Build
9
7
  import android.util.Log
10
8
  import com.google.android.gms.auth.api.signin.GoogleSignIn
11
9
  import com.google.android.gms.auth.api.signin.GoogleSignInAccount
@@ -27,21 +25,20 @@ import android.app.Application
27
25
  import android.content.Intent
28
26
  import android.net.Uri
29
27
  import androidx.browser.customtabs.CustomTabsIntent
30
- import androidx.security.crypto.EncryptedSharedPreferences
31
- import androidx.security.crypto.MasterKeys
32
28
  import java.util.UUID
33
- import org.json.JSONArray
34
29
  import org.json.JSONObject
35
30
  import android.util.Base64
36
31
 
37
32
  object AuthAdapter {
38
33
  private const val TAG = "AuthAdapter"
39
- private const val PREF_NAME = "nitro_auth"
40
- private const val SECURE_PREF_NAME = "nitro_auth_secure"
41
34
 
35
+ @Volatile
36
+ private var isInitialized = false
37
+
42
38
  private var appContext: Context? = null
43
39
  private var currentActivity: Activity? = null
44
40
  private var googleSignInClient: GoogleSignInClient? = null
41
+ private var lifecycleCallbacks: Application.ActivityLifecycleCallbacks? = null
45
42
  private var pendingScopes: List<String> = emptyList()
46
43
  private var pendingMicrosoftScopes: List<String> = emptyList()
47
44
 
@@ -51,47 +48,10 @@ object AuthAdapter {
51
48
  private var pendingMicrosoftTenant: String? = null
52
49
  private var pendingMicrosoftClientId: String? = null
53
50
  private var pendingMicrosoftB2cDomain: String? = null
54
-
55
- private fun getPrefs(context: Context): SharedPreferences {
56
- return try {
57
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
58
- val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
59
- val securePrefs = EncryptedSharedPreferences.create(
60
- SECURE_PREF_NAME,
61
- masterKeyAlias,
62
- context,
63
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
64
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
65
- )
66
- migrateLegacyPrefsIfNeeded(context, securePrefs)
67
- securePrefs
68
- } else {
69
- context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
70
- }
71
- } catch (e: Exception) {
72
- Log.w(TAG, "Failed to initialize encrypted storage, falling back to SharedPreferences", e)
73
- context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
74
- }
75
- }
76
-
77
- private fun migrateLegacyPrefsIfNeeded(context: Context, securePrefs: SharedPreferences) {
78
- val legacyPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
79
- if (legacyPrefs.all.isEmpty() || securePrefs.all.isNotEmpty()) {
80
- return
81
- }
82
- val editor = securePrefs.edit()
83
- for ((key, value) in legacyPrefs.all) {
84
- when (value) {
85
- is String -> editor.putString(key, value)
86
- is Boolean -> editor.putBoolean(key, value)
87
- is Int -> editor.putInt(key, value)
88
- is Long -> editor.putLong(key, value)
89
- is Float -> editor.putFloat(key, value)
90
- }
91
- }
92
- editor.apply()
93
- legacyPrefs.edit().clear().apply()
94
- }
51
+
52
+ private var inMemoryMicrosoftRefreshToken: String? = null
53
+ private var inMemoryMicrosoftScopes: List<String> =
54
+ listOf("openid", "email", "profile", "offline_access", "User.Read")
95
55
 
96
56
  @JvmStatic
97
57
  private external fun nativeInitialize(context: Context)
@@ -118,34 +78,43 @@ object AuthAdapter {
118
78
  @JvmStatic
119
79
  private external fun nativeOnRefreshError(error: String, underlyingError: String?)
120
80
 
81
+ @Synchronized
121
82
  fun initialize(context: Context) {
122
- val app = context.applicationContext as? Application
123
- appContext = app
124
-
125
- app?.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
126
- override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { currentActivity = activity }
127
- override fun onActivityStarted(activity: Activity) { currentActivity = activity }
128
- override fun onActivityResumed(activity: Activity) { currentActivity = activity }
129
- override fun onActivityPaused(activity: Activity) { if (currentActivity == activity) currentActivity = null }
130
- override fun onActivityStopped(activity: Activity) { if (currentActivity == activity) currentActivity = null }
131
- override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
132
- override fun onActivityDestroyed(activity: Activity) { if (currentActivity == activity) currentActivity = null }
133
- })
83
+ if (isInitialized) {
84
+ return
85
+ }
86
+
87
+ val applicationContext = context.applicationContext
88
+ appContext = applicationContext
89
+
90
+ val app = applicationContext as? Application
91
+ if (app != null && lifecycleCallbacks == null) {
92
+ lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
93
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { currentActivity = activity }
94
+ override fun onActivityStarted(activity: Activity) { currentActivity = activity }
95
+ override fun onActivityResumed(activity: Activity) { currentActivity = activity }
96
+ override fun onActivityPaused(activity: Activity) { if (currentActivity == activity) currentActivity = null }
97
+ override fun onActivityStopped(activity: Activity) { if (currentActivity == activity) currentActivity = null }
98
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
99
+ override fun onActivityDestroyed(activity: Activity) { if (currentActivity == activity) currentActivity = null }
100
+ }
101
+ app.registerActivityLifecycleCallbacks(lifecycleCallbacks)
102
+ }
134
103
 
135
104
  try {
136
105
  System.loadLibrary("NitroAuth")
137
- nativeInitialize(appContext!!)
106
+ nativeInitialize(applicationContext)
107
+ isInitialized = true
138
108
  } catch (e: Exception) {
139
109
  Log.e(TAG, "Failed to load NitroAuth library", e)
140
110
  }
141
111
  }
142
112
 
143
113
  fun onSignInSuccess(account: GoogleSignInAccount, scopes: List<String>) {
144
- val ctx = appContext ?: return
145
- saveUser(ctx, "google", account.email, account.displayName,
146
- account.photoUrl?.toString(), account.idToken, account.serverAuthCode, scopes)
114
+ appContext ?: return
115
+ val expirationTime = getJwtExpirationTimeMs(account.idToken)
147
116
  nativeOnLoginSuccess("google", account.email, account.displayName,
148
- account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode, scopes.toTypedArray(), null)
117
+ account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode, scopes.toTypedArray(), expirationTime)
149
118
  }
150
119
 
151
120
  fun onSignInError(errorCode: Int, message: String?) {
@@ -393,12 +362,11 @@ object AuthAdapter {
393
362
  val email = claims["preferred_username"] ?: claims["email"]
394
363
  val name = claims["name"]
395
364
 
396
- val ctx = appContext
397
- if (ctx != null) {
398
- saveUser(ctx, "microsoft", email, name, null, idToken, refreshToken, pendingMicrosoftScopes)
399
- if (refreshToken.isNotEmpty()) {
400
- saveMicrosoftRefreshToken(ctx, refreshToken)
401
- }
365
+ if (refreshToken.isNotEmpty()) {
366
+ inMemoryMicrosoftRefreshToken = refreshToken
367
+ }
368
+ inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
369
+ listOf("openid", "email", "profile", "offline_access", "User.Read")
402
370
  }
403
371
 
404
372
  clearPkceState()
@@ -419,14 +387,12 @@ object AuthAdapter {
419
387
  }
420
388
  }
421
389
 
422
- private fun saveMicrosoftRefreshToken(context: Context, refreshToken: String) {
423
- val prefs = getPrefs(context)
424
- prefs.edit().putString("microsoft_refresh_token", refreshToken).apply()
390
+ private fun saveMicrosoftRefreshToken(refreshToken: String) {
391
+ inMemoryMicrosoftRefreshToken = refreshToken
425
392
  }
426
393
 
427
- private fun getMicrosoftRefreshToken(context: Context): String? {
428
- val prefs = getPrefs(context)
429
- return prefs.getString("microsoft_refresh_token", null)
394
+ private fun getMicrosoftRefreshToken(): String? {
395
+ return inMemoryMicrosoftRefreshToken
430
396
  }
431
397
 
432
398
  private fun clearPkceState() {
@@ -455,6 +421,15 @@ object AuthAdapter {
455
421
  }
456
422
  }
457
423
 
424
+ private fun getJwtExpirationTimeMs(idToken: String?): Long? {
425
+ if (idToken.isNullOrEmpty()) {
426
+ return null
427
+ }
428
+
429
+ val expSeconds = decodeJwt(idToken)["exp"]?.toLongOrNull() ?: return null
430
+ return expSeconds * 1000
431
+ }
432
+
458
433
  private fun getMicrosoftClientIdFromResources(context: Context): String? {
459
434
  val resId = context.resources.getIdentifier("nitro_auth_microsoft_client_id", "string", context.packageName)
460
435
  return if (resId != 0) context.getString(resId) else null
@@ -553,6 +528,7 @@ object AuthAdapter {
553
528
  }
554
529
 
555
530
  if (googleIdTokenCredential != null) {
531
+ val expirationTime = getJwtExpirationTimeMs(googleIdTokenCredential.idToken)
556
532
  nativeOnLoginSuccess(
557
533
  "google",
558
534
  googleIdTokenCredential.id,
@@ -562,7 +538,7 @@ object AuthAdapter {
562
538
  null,
563
539
  null,
564
540
  scopes.toTypedArray(),
565
- null
541
+ expirationTime
566
542
  )
567
543
  } else {
568
544
  Log.w(TAG, "Unsupported credential type: ${credential.type}")
@@ -590,12 +566,8 @@ object AuthAdapter {
590
566
  ctx.startActivity(intent)
591
567
  return
592
568
  }
593
- val userJson = getUserJson(ctx)
594
- if (userJson != null && userJson.contains("\"provider\":\"microsoft\"")) {
595
- val currentScopes = extractScopesFromUserJson(userJson)
596
- val defaultMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
597
- val existing = if (currentScopes.isEmpty()) defaultMicrosoftScopes else currentScopes
598
- val mergedScopes = (existing + scopes.toList()).distinct()
569
+ if (inMemoryMicrosoftRefreshToken != null) {
570
+ val mergedScopes = (inMemoryMicrosoftScopes + scopes.toList()).distinct()
599
571
  val tenant = getMicrosoftTenantFromResources(ctx)
600
572
  loginMicrosoft(ctx, mergedScopes.toTypedArray(), null, tenant, null)
601
573
  return
@@ -624,20 +596,21 @@ object AuthAdapter {
624
596
  googleSignInClient!!.silentSignIn().addOnCompleteListener { task ->
625
597
  if (task.isSuccessful) {
626
598
  val acc = task.result
627
- nativeOnRefreshSuccess(acc?.idToken, null, null)
599
+ nativeOnRefreshSuccess(
600
+ acc?.idToken,
601
+ null,
602
+ getJwtExpirationTimeMs(acc?.idToken),
603
+ )
628
604
  } else {
629
605
  nativeOnRefreshError("network_error", task.exception?.message ?: "Silent sign-in failed")
630
606
  }
631
607
  }
632
608
  return
633
609
  }
634
- val userJson = getUserJson(ctx)
635
- if (userJson != null && userJson.contains("\"provider\":\"microsoft\"")) {
636
- val refreshToken = getMicrosoftRefreshToken(ctx)
637
- if (refreshToken != null) {
638
- refreshMicrosoftTokenForRefresh(ctx, refreshToken)
639
- return
640
- }
610
+ val refreshToken = getMicrosoftRefreshToken()
611
+ if (refreshToken != null) {
612
+ refreshMicrosoftTokenForRefresh(ctx, refreshToken)
613
+ return
641
614
  }
642
615
  nativeOnRefreshError("unknown", "No user logged in")
643
616
  }
@@ -662,7 +635,8 @@ object AuthAdapter {
662
635
  .build()
663
636
  GoogleSignIn.getClient(ctx, gso).signOut()
664
637
  }
665
- clearUser(ctx)
638
+ inMemoryMicrosoftRefreshToken = null
639
+ inMemoryMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
666
640
  }
667
641
 
668
642
  @JvmStatic
@@ -677,7 +651,8 @@ object AuthAdapter {
677
651
  .build()
678
652
  GoogleSignIn.getClient(ctx, gso).revokeAccess()
679
653
  }
680
- clearUser(ctx)
654
+ inMemoryMicrosoftRefreshToken = null
655
+ inMemoryMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
681
656
  }
682
657
 
683
658
  private fun getClientIdFromResources(context: Context): String? {
@@ -685,66 +660,19 @@ object AuthAdapter {
685
660
  return if (resId != 0) context.getString(resId) else null
686
661
  }
687
662
 
688
- @JvmStatic
689
- fun getUserJson(context: Context): String? {
690
- val pref = getPrefs(context)
691
- return pref.getString("user_json", null)
692
- }
693
-
694
- @JvmStatic
695
- fun setUserJson(context: Context, json: String) {
696
- val pref = getPrefs(context)
697
- pref.edit().putString("user_json", json).apply()
698
- }
699
-
700
- @JvmStatic
701
- fun clearUser(context: Context) {
702
- val pref = getPrefs(context)
703
- pref.edit().clear().apply()
704
- }
705
-
706
663
  @JvmStatic
707
664
  fun restoreSession(context: Context) {
708
665
  val ctx = context.applicationContext ?: appContext ?: context
709
666
  val account = GoogleSignIn.getLastSignedInAccount(ctx)
710
667
  if (account != null) {
668
+ val expirationTime = getJwtExpirationTimeMs(account.idToken)
711
669
  nativeOnLoginSuccess("google", account.email, account.displayName,
712
670
  account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode,
713
- account.grantedScopes?.map { it.scopeUri }?.toTypedArray(), null)
671
+ account.grantedScopes?.map { it.scopeUri }?.toTypedArray(), expirationTime)
714
672
  } else {
715
- val json = getUserJson(ctx)
716
- if (json != null) {
717
- val provider = try {
718
- val parsed = JSONObject(json)
719
- parsed.optString("provider")
720
- } catch (_: Exception) {
721
- ""
722
- }
723
- val effectiveProvider = when (provider) {
724
- "google" -> "google"
725
- "microsoft" -> "microsoft"
726
- "apple" -> "apple"
727
- else -> "apple"
728
- }
729
-
730
- if (effectiveProvider == "microsoft") {
731
- val refreshToken = getMicrosoftRefreshToken(ctx)
732
- if (refreshToken != null) {
733
- refreshMicrosoftToken(ctx, refreshToken)
734
- } else {
735
- val email = extractJsonValue(json, "email")
736
- val name = extractJsonValue(json, "name")
737
- val idToken = extractJsonValue(json, "idToken")
738
- nativeOnLoginSuccess(effectiveProvider, email, name, null, idToken, null, null, null, null)
739
- }
740
- } else {
741
- val email = extractJsonValue(json, "email")
742
- val name = extractJsonValue(json, "name")
743
- val photo = extractJsonValue(json, "photo")
744
- val idToken = extractJsonValue(json, "idToken")
745
- val serverAuthCode = extractJsonValue(json, "serverAuthCode")
746
- nativeOnLoginSuccess(effectiveProvider, email, name, photo, idToken, null, serverAuthCode, null, null)
747
- }
673
+ val refreshToken = getMicrosoftRefreshToken()
674
+ if (refreshToken != null) {
675
+ refreshMicrosoftToken(ctx, refreshToken)
748
676
  } else {
749
677
  nativeOnLoginError("unknown", "No session")
750
678
  }
@@ -801,9 +729,11 @@ object AuthAdapter {
801
729
  val name = claims["name"]
802
730
 
803
731
  if (newRefreshToken.isNotEmpty()) {
804
- saveMicrosoftRefreshToken(context, newRefreshToken)
732
+ saveMicrosoftRefreshToken(newRefreshToken)
733
+ }
734
+ inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
735
+ listOf("openid", "email", "profile", "offline_access", "User.Read")
805
736
  }
806
- saveUser(context, "microsoft", email, name, null, newIdToken, newRefreshToken, null)
807
737
 
808
738
  nativeOnLoginSuccess("microsoft", email, name, null, newIdToken, newAccessToken, null, null, expirationTime)
809
739
  } else {
@@ -859,9 +789,11 @@ object AuthAdapter {
859
789
  val email = claims["preferred_username"] ?: claims["email"]
860
790
  val name = claims["name"]
861
791
  if (newRefreshToken.isNotEmpty()) {
862
- saveMicrosoftRefreshToken(context, newRefreshToken)
792
+ saveMicrosoftRefreshToken(newRefreshToken)
793
+ }
794
+ inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
795
+ listOf("openid", "email", "profile", "offline_access", "User.Read")
863
796
  }
864
- saveUser(context, "microsoft", email, name, null, newIdToken, newRefreshToken, null)
865
797
  nativeOnRefreshSuccess(
866
798
  newIdToken.ifEmpty { null },
867
799
  newAccessToken.ifEmpty { null },
@@ -879,36 +811,4 @@ object AuthAdapter {
879
811
  }
880
812
  }
881
813
 
882
- private fun extractJsonValue(json: String, key: String): String? {
883
- return try {
884
- val value = JSONObject(json).optString(key, "")
885
- if (value.isEmpty()) null else value
886
- } catch (_: Exception) {
887
- null
888
- }
889
- }
890
-
891
- private fun extractScopesFromUserJson(json: String): List<String> {
892
- return try {
893
- val jsonObj = JSONObject(json)
894
- val arr = jsonObj.optJSONArray("scopes") ?: return emptyList()
895
- (0 until arr.length()).mapNotNull { i -> arr.optString(i).takeIf { it.isNotEmpty() } }
896
- } catch (_: Exception) {
897
- emptyList()
898
- }
899
- }
900
-
901
- private fun saveUser(context: Context, provider: String, email: String?, name: String?,
902
- photo: String?, idToken: String?, serverAuthCode: String?, scopes: List<String>?) {
903
- val pref = getPrefs(context)
904
- val json = JSONObject()
905
- json.put("provider", provider)
906
- if (email != null) json.put("email", email)
907
- if (name != null) json.put("name", name)
908
- if (photo != null) json.put("photo", photo)
909
- if (idToken != null) json.put("idToken", idToken)
910
- if (serverAuthCode != null) json.put("serverAuthCode", serverAuthCode)
911
- if (scopes != null) json.put("scopes", JSONArray(scopes))
912
- pref.edit().putString("user_json", json.toString()).apply()
913
- }
914
814
  }
@@ -1,4 +1,4 @@
1
- @file:Suppress("DEPRECATION")
1
+ @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
2
2
 
3
3
  package com.auth
4
4
 
package/app.plugin.js CHANGED
@@ -6,7 +6,6 @@ const {
6
6
  AndroidConfig,
7
7
  createRunOncePlugin,
8
8
  } = require("@expo/config-plugins");
9
-
10
9
  const pkg = require("./package.json");
11
10
 
12
11
  const withNitroAuth = (config, props = {}) => {
@@ -157,9 +156,7 @@ const withNitroAuth = (config, props = {}) => {
157
156
  ],
158
157
  };
159
158
  const existingMsalActivity = application.activity.find(
160
- (a) =>
161
- a.$?.["android:name"] ===
162
- "com.auth.MicrosoftAuthActivity",
159
+ (a) => a.$?.["android:name"] === "com.auth.MicrosoftAuthActivity",
163
160
  );
164
161
  if (!existingMsalActivity) {
165
162
  application.activity.push(msalActivity);
@@ -172,8 +169,4 @@ const withNitroAuth = (config, props = {}) => {
172
169
  return config;
173
170
  };
174
171
 
175
- module.exports = createRunOncePlugin(
176
- withNitroAuth,
177
- pkg.name,
178
- pkg.version,
179
- );
172
+ module.exports = createRunOncePlugin(withNitroAuth, pkg.name, pkg.version);