react-native-nitro-auth 0.1.3 → 0.1.5

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 (66) hide show
  1. package/README.md +62 -13
  2. package/android/build.gradle +8 -3
  3. package/android/src/main/cpp/PlatformAuth+Android.cpp +14 -3
  4. package/android/src/main/java/com/auth/AuthAdapter.kt +100 -3
  5. package/cpp/HybridAuth.cpp +64 -13
  6. package/cpp/HybridAuth.hpp +10 -0
  7. package/cpp/PlatformAuth.hpp +2 -1
  8. package/ios/PlatformAuth+iOS.mm +22 -4
  9. package/lib/commonjs/Auth.web.js +91 -23
  10. package/lib/commonjs/Auth.web.js.map +1 -1
  11. package/lib/commonjs/AuthStorage.nitro.js +6 -0
  12. package/lib/commonjs/AuthStorage.nitro.js.map +1 -0
  13. package/lib/commonjs/index.js +12 -0
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/index.web.js +12 -0
  16. package/lib/commonjs/index.web.js.map +1 -1
  17. package/lib/commonjs/ui/social-button.js +2 -2
  18. package/lib/commonjs/ui/social-button.js.map +1 -1
  19. package/lib/commonjs/ui/social-button.web.js +2 -2
  20. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  21. package/lib/commonjs/utils/logger.js +17 -0
  22. package/lib/commonjs/utils/logger.js.map +1 -0
  23. package/lib/module/Auth.web.js +91 -23
  24. package/lib/module/Auth.web.js.map +1 -1
  25. package/lib/module/AuthStorage.nitro.js +4 -0
  26. package/lib/module/AuthStorage.nitro.js.map +1 -0
  27. package/lib/module/index.js +1 -0
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/module/index.web.js +1 -0
  30. package/lib/module/index.web.js.map +1 -1
  31. package/lib/module/ui/social-button.js +2 -2
  32. package/lib/module/ui/social-button.js.map +1 -1
  33. package/lib/module/ui/social-button.web.js +2 -2
  34. package/lib/module/ui/social-button.web.js.map +1 -1
  35. package/lib/module/utils/logger.js +13 -0
  36. package/lib/module/utils/logger.js.map +1 -0
  37. package/lib/typescript/Auth.nitro.d.ts +5 -0
  38. package/lib/typescript/Auth.nitro.d.ts.map +1 -1
  39. package/lib/typescript/Auth.web.d.ts +9 -1
  40. package/lib/typescript/Auth.web.d.ts.map +1 -1
  41. package/lib/typescript/AuthStorage.nitro.d.ts +19 -0
  42. package/lib/typescript/AuthStorage.nitro.d.ts.map +1 -0
  43. package/lib/typescript/index.d.ts +1 -0
  44. package/lib/typescript/index.d.ts.map +1 -1
  45. package/lib/typescript/index.web.d.ts +1 -0
  46. package/lib/typescript/index.web.d.ts.map +1 -1
  47. package/lib/typescript/ui/social-button.d.ts.map +1 -1
  48. package/lib/typescript/ui/social-button.web.d.ts.map +1 -1
  49. package/lib/typescript/utils/logger.d.ts +8 -0
  50. package/lib/typescript/utils/logger.d.ts.map +1 -0
  51. package/nitrogen/generated/android/NitroAuth+autolinking.cmake +1 -0
  52. package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +3 -0
  53. package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +7 -0
  54. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +23 -0
  55. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +65 -0
  56. package/nitrogen/generated/shared/c++/LoginOptions.hpp +6 -2
  57. package/package.json +3 -4
  58. package/react-native-nitro-auth.podspec +1 -1
  59. package/src/Auth.nitro.ts +6 -0
  60. package/src/Auth.web.ts +114 -24
  61. package/src/AuthStorage.nitro.ts +17 -0
  62. package/src/index.ts +1 -0
  63. package/src/index.web.ts +1 -0
  64. package/src/ui/social-button.tsx +3 -3
  65. package/src/ui/social-button.web.tsx +3 -3
  66. package/src/utils/logger.ts +11 -0
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # react-native-nitro-auth
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/react-native-nitro-auth?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-auth)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+ [![Nitro Modules](https://img.shields.io/badge/Powered%20by-Nitro%20Modules-blueviolet?style=flat-square)](https://nitro.margelo.com)
6
+
3
7
  🚀 **High-performance, JSI-powered Authentication for React Native.**
4
8
 
5
9
  Nitro Auth is a modern authentication library for React Native built on top of [Nitro Modules](https://github.com/mrousavy/nitro). It provides a unified, type-safe API for Google and Apple Sign-In with zero-bridge overhead.
@@ -8,21 +12,24 @@ Nitro Auth is a modern authentication library for React Native built on top of [
8
12
 
9
13
  Nitro Auth is designed to replace legacy modules like `@react-native-google-signin/google-signin` with a modern, high-performance architecture.
10
14
 
11
- | Feature | Legacy Modules | Nitro Auth |
12
- | :--- | :--- | :--- |
15
+ | Feature | Legacy Modules | Nitro Auth |
16
+ | :-------------- | :--------------------------- | :----------------------------- |
13
17
  | **Performance** | Async bridge overhead (JSON) | **Direct JSI C++ (Zero-copy)** |
14
- | **Persistence** | Varies / Manual | **Built-in & Automatic** |
15
- | **Setup** | Manual async initialization | **Sync & declarative plugins** |
16
- | **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
18
+ | **Persistence** | Varies / Manual | **Built-in & Automatic** |
19
+ | **Setup** | Manual async initialization | **Sync & declarative plugins** |
20
+ | **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
17
21
 
18
22
  ## Features
19
23
 
20
- - 🏎️ **Ultra-fast**: Direct C++ calls using JSI (no JSON serialization).
21
- - 🛡️ **Fully Type-Safe**: Shared types between TypeScript, C++, Swift, and Kotlin.
22
- - �� **Incremental Auth**: Request additional OAuth scopes on the fly.
23
- - 📦 **Expo Ready**: Comes with a powerful Config Plugin for zero-config setup.
24
- - 📱 **Cross-Platform**: Unified API for iOS, Android, and Web.
25
- - 🔁 **Auto-Refresh**: Synchronous access to tokens with automatic silent refresh.
24
+ - **Ultra-fast**: Direct C++ calls using JSI (no JSON serialization).
25
+ - **Fully Type-Safe**: Shared types between TypeScript, C++, Swift, and Kotlin.
26
+ - **Incremental Auth**: Request additional OAuth scopes on the fly.
27
+ - **Expo Ready**: Comes with a powerful Config Plugin for zero-config setup.
28
+ - **Cross-Platform**: Unified API for iOS, Android, and Web.
29
+ - **Auto-Refresh**: Synchronous access to tokens with automatic silent refresh.
30
+ - **Google One-Tap**: Modern login experience on Android using Credential Manager.
31
+ - **Custom Storage**: Pluggable storage adapters for secure persistence (e.g., Keychain).
32
+ - **Refresh Interceptors**: Listen to token updates globally.
26
33
 
27
34
  ## Installation
28
35
 
@@ -57,8 +64,7 @@ Add the plugin to `app.json`:
57
64
  }
58
65
  ```
59
66
 
60
- > [!NOTE]
61
- > `googleServerClientId` is only required if you need a `serverAuthCode` for backend integration.
67
+ > [!NOTE] > `googleServerClientId` is only required if you need a `serverAuthCode` for backend integration.
62
68
 
63
69
  ### Bare React Native
64
70
 
@@ -183,6 +189,49 @@ if (user?.serverAuthCode) {
183
189
  }
184
190
  ```
185
191
 
192
+ ### Custom Storage Adapter
193
+
194
+ By default, Nitro Auth uses standard local storage. You can provide a custom adapter for better security (e.g., using `react-native-keychain`):
195
+
196
+ ```ts
197
+ import { AuthService, AuthStorageAdapter } from "react-native-nitro-auth";
198
+
199
+ const myStorage: AuthStorageAdapter = {
200
+ save: (key, value) => {
201
+ /* Save to Keychain */
202
+ },
203
+ load: (key) => {
204
+ /* Load from Keychain */
205
+ },
206
+ remove: (key) => {
207
+ /* Clear from Keychain */
208
+ },
209
+ };
210
+
211
+ AuthService.setStorageAdapter(myStorage);
212
+ ```
213
+
214
+ ### Token Refresh Listeners
215
+
216
+ Perfect for updating your API client (e.g., Axios/Fetch) whenever tokens are refreshed in the background:
217
+
218
+ ```ts
219
+ AuthService.onTokensRefreshed((tokens) => {
220
+ console.log("Tokens were updated!", tokens.accessToken);
221
+ apiClient.defaults.headers.common[
222
+ "Authorization"
223
+ ] = `Bearer ${tokens.accessToken}`;
224
+ });
225
+ ```
226
+
227
+ ### Google One-Tap (Android)
228
+
229
+ Explicitly enable the modern One-Tap flow on Android:
230
+
231
+ ```ts
232
+ await login("google", { useOneTap: true });
233
+ ```
234
+
186
235
  ## API Reference
187
236
 
188
237
  ### useAuth Hook
@@ -45,6 +45,7 @@ android {
45
45
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
46
46
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
47
47
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
48
+ consumerProguardFiles "proguard-rules.pro"
48
49
 
49
50
  externalNativeBuild {
50
51
  cmake {
@@ -63,7 +64,6 @@ android {
63
64
 
64
65
  buildFeatures {
65
66
  buildConfig true
66
- consumerProguardFiles "proguard-rules.pro"
67
67
  prefab true
68
68
  }
69
69
 
@@ -89,12 +89,17 @@ repositories {
89
89
 
90
90
  dependencies {
91
91
  implementation "com.facebook.react:react-native:+"
92
- implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0"
92
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.24"
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.3.0"
96
+ implementation "com.google.android.gms:play-services-auth:21.2.0"
97
97
 
98
98
  // Activity result APIs
99
99
  implementation "androidx.activity:activity-ktx:1.9.3"
100
+
101
+ // Google Credential Manager (One-Tap / Passkeys)
102
+ implementation "androidx.credentials:credentials:1.5.0"
103
+ implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
104
+ implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
100
105
  }
@@ -24,7 +24,7 @@ static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
24
24
  static std::shared_ptr<Promise<std::optional<AuthUser>>> gSilentPromise;
25
25
  static std::mutex gMutex;
26
26
 
27
- std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::vector<std::string>& scopes, const std::optional<std::string>& loginHint) {
27
+ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
28
28
  auto promise = Promise<AuthUser>::create();
29
29
  auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
30
30
  if (!contextPtr) {
@@ -38,6 +38,16 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
38
38
  }
39
39
 
40
40
  std::string providerStr = (provider == AuthProvider::GOOGLE) ? "google" : "apple";
41
+ std::vector<std::string> scopes = {"email", "profile"};
42
+ std::optional<std::string> loginHint;
43
+ bool useOneTap = false;
44
+
45
+ if (options) {
46
+ if (options->scopes) scopes = *options->scopes;
47
+ loginHint = options->loginHint;
48
+ useOneTap = options->useOneTap.value_or(false);
49
+ }
50
+
41
51
  JNIEnv* env = Environment::current();
42
52
  jclass stringClass = env->FindClass("java/lang/String");
43
53
  jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
@@ -48,13 +58,14 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
48
58
  jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
49
59
  jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
50
60
  jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
51
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V");
61
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Z)V");
52
62
  env->CallStaticVoidMethod(adapterClass, loginMethod,
53
63
  contextPtr,
54
64
  make_jstring(providerStr).get(),
55
65
  nullptr,
56
66
  jScopes,
57
- jLoginHint);
67
+ jLoginHint,
68
+ (jboolean)useOneTap);
58
69
 
59
70
  return promise;
60
71
  }
@@ -10,12 +10,23 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
10
10
  import com.google.android.gms.common.GoogleApiAvailability
11
11
  import com.google.android.gms.common.ConnectionResult
12
12
  import com.google.android.gms.common.api.Scope
13
+ import androidx.credentials.CredentialManager
14
+ import androidx.credentials.GetCredentialRequest
15
+ import androidx.credentials.GetCredentialResponse
16
+ import com.google.android.libraries.identity.googleid.GetGoogleIdOption
17
+ import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
18
+ import kotlinx.coroutines.CoroutineScope
19
+ import kotlinx.coroutines.Dispatchers
20
+ import kotlinx.coroutines.launch
21
+ import android.app.Activity
22
+ import android.app.Application
13
23
 
14
24
  object AuthAdapter {
15
25
  private const val TAG = "AuthAdapter"
16
26
  private const val PREF_NAME = "nitro_auth"
17
27
 
18
28
  private var appContext: Context? = null
29
+ private var currentActivity: Activity? = null
19
30
  private var googleSignInClient: GoogleSignInClient? = null
20
31
  private var pendingScopes: List<String> = emptyList()
21
32
 
@@ -45,7 +56,19 @@ object AuthAdapter {
45
56
  private external fun nativeOnRefreshError(error: String)
46
57
 
47
58
  fun initialize(context: Context) {
48
- appContext = context.applicationContext
59
+ val app = context.applicationContext as? Application
60
+ appContext = app
61
+
62
+ app?.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
63
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { currentActivity = activity }
64
+ override fun onActivityStarted(activity: Activity) { currentActivity = activity }
65
+ override fun onActivityResumed(activity: Activity) { currentActivity = activity }
66
+ override fun onActivityPaused(activity: Activity) { if (currentActivity == activity) currentActivity = null }
67
+ override fun onActivityStopped(activity: Activity) { if (currentActivity == activity) currentActivity = null }
68
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
69
+ override fun onActivityDestroyed(activity: Activity) { if (currentActivity == activity) currentActivity = null }
70
+ })
71
+
49
72
  try {
50
73
  System.loadLibrary("NitroAuth")
51
74
  nativeInitialize(appContext!!)
@@ -73,7 +96,7 @@ object AuthAdapter {
73
96
  }
74
97
 
75
98
  @JvmStatic
76
- fun loginSync(context: Context, provider: String, googleClientId: String?, scopes: Array<String>?, loginHint: String?) {
99
+ fun loginSync(context: Context, provider: String, googleClientId: String?, scopes: Array<String>?, loginHint: String?, useOneTap: Boolean) {
77
100
  if (provider == "apple") {
78
101
  nativeOnLoginError("Apple Sign-In is not supported on Android.")
79
102
  return
@@ -94,10 +117,84 @@ object AuthAdapter {
94
117
  val requestedScopes = scopes?.toList() ?: listOf("email", "profile")
95
118
  pendingScopes = requestedScopes
96
119
 
97
- val intent = GoogleSignInActivity.createIntent(ctx, clientId, requestedScopes.toTypedArray(), loginHint)
120
+ if (useOneTap) {
121
+ loginOneTap(context, clientId, requestedScopes)
122
+ } else {
123
+ val intent = GoogleSignInActivity.createIntent(ctx, clientId, requestedScopes.toTypedArray(), loginHint)
124
+ intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
125
+ ctx.startActivity(intent)
126
+ }
127
+ }
128
+
129
+ private fun loginOneTap(context: Context, clientId: String, scopes: List<String>) {
130
+ val activity = currentActivity ?: context as? Activity
131
+ if (activity == null) {
132
+ Log.w(TAG, "No Activity context available for One-Tap, falling back to legacy")
133
+ return loginLegacy(context, clientId, scopes)
134
+ }
135
+
136
+ val credentialManager = CredentialManager.create(activity)
137
+ val googleIdOption = GetGoogleIdOption.Builder()
138
+ .setFilterByAuthorizedAccounts(false)
139
+ .setServerClientId(clientId)
140
+ .setAutoSelectEnabled(false) // Disable auto-select for testing so the sheet always shows
141
+ .build()
142
+
143
+ val request = GetCredentialRequest.Builder()
144
+ .addCredentialOption(googleIdOption)
145
+ .build()
146
+
147
+ CoroutineScope(Dispatchers.Main).launch {
148
+ try {
149
+ val result = credentialManager.getCredential(context = activity, request = request)
150
+ handleCredentialResponse(result, scopes)
151
+ } catch (e: Exception) {
152
+ Log.w(TAG, "One-Tap failed, falling back to legacy: ${e.message}")
153
+ loginLegacy(context, clientId, scopes)
154
+ }
155
+ }
156
+ }
157
+
158
+ private fun loginLegacy(context: Context, clientId: String, scopes: List<String>) {
159
+ val ctx = appContext ?: context.applicationContext
160
+ val intent = GoogleSignInActivity.createIntent(ctx, clientId, scopes.toTypedArray(), null)
161
+ intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
98
162
  ctx.startActivity(intent)
99
163
  }
100
164
 
165
+ private fun handleCredentialResponse(response: GetCredentialResponse, scopes: List<String>) {
166
+ val credential = response.credential
167
+ val googleIdTokenCredential = try {
168
+ if (credential is GoogleIdTokenCredential) {
169
+ credential
170
+ } else if (credential.type == "com.google.android.libraries.identity.googleid.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL") {
171
+ GoogleIdTokenCredential.createFrom(credential.data)
172
+ } else {
173
+ null
174
+ }
175
+ } catch (e: Exception) {
176
+ Log.e(TAG, "Failed to parse Google ID token credential: ${e.message}")
177
+ null
178
+ }
179
+
180
+ if (googleIdTokenCredential != null) {
181
+ nativeOnLoginSuccess(
182
+ "google",
183
+ googleIdTokenCredential.id,
184
+ googleIdTokenCredential.displayName,
185
+ googleIdTokenCredential.profilePictureUri?.toString(),
186
+ googleIdTokenCredential.idToken,
187
+ null,
188
+ null,
189
+ scopes.toTypedArray(),
190
+ null
191
+ )
192
+ } else {
193
+ Log.w(TAG, "Unsupported credential type: ${credential.type}")
194
+ nativeOnLoginError("Unsupported credential type: ${credential.type}")
195
+ }
196
+ }
197
+
101
198
  @JvmStatic
102
199
  fun requestScopesSync(context: Context, scopes: Array<String>) {
103
200
  val ctx = appContext ?: context.applicationContext
@@ -6,6 +6,8 @@
6
6
  #include <chrono>
7
7
 
8
8
  namespace margelo::nitro::NitroAuth {
9
+
10
+ bool HybridAuth::sLoggingEnabled = false;
9
11
 
10
12
  HybridAuth::HybridAuth() : HybridObject(TAG) {
11
13
  loadFromCache();
@@ -27,7 +29,14 @@ bool HybridAuth::getHasPlayServices() {
27
29
 
28
30
  void HybridAuth::loadFromCache() {
29
31
  std::lock_guard<std::mutex> lock(_mutex);
30
- auto json = AuthCache::getUserJson();
32
+ std::optional<std::string> json;
33
+
34
+ if (_storageAdapter) {
35
+ json = _storageAdapter->load("nitro_auth_user");
36
+ } else {
37
+ json = AuthCache::getUserJson();
38
+ }
39
+
31
40
  if (json) {
32
41
  _currentUser = JSONSerializer::deserialize(*json);
33
42
  if (_currentUser && _currentUser->scopes) {
@@ -39,9 +48,17 @@ void HybridAuth::loadFromCache() {
39
48
  void HybridAuth::saveToCache(const std::optional<AuthUser>& user) {
40
49
  if (user) {
41
50
  auto json = JSONSerializer::serialize(*user);
42
- AuthCache::setUserJson(json);
51
+ if (_storageAdapter) {
52
+ _storageAdapter->save("nitro_auth_user", json);
53
+ } else {
54
+ AuthCache::setUserJson(json);
55
+ }
43
56
  } else {
44
- AuthCache::clear();
57
+ if (_storageAdapter) {
58
+ _storageAdapter->remove("nitro_auth_user");
59
+ } else {
60
+ AuthCache::clear();
61
+ }
45
62
  }
46
63
  }
47
64
 
@@ -71,6 +88,17 @@ std::function<void()> HybridAuth::onAuthStateChanged(const std::function<void(co
71
88
  };
72
89
  }
73
90
 
91
+ std::function<void()> HybridAuth::onTokensRefreshed(const std::function<void(const AuthTokens&)>& callback) {
92
+ std::lock_guard<std::mutex> lock(_mutex);
93
+ int id = _nextTokenListenerId++;
94
+ _tokenListeners[id] = callback;
95
+
96
+ return [this, id]() {
97
+ std::lock_guard<std::mutex> lock(_mutex);
98
+ _tokenListeners.erase(id);
99
+ };
100
+ }
101
+
74
102
  void HybridAuth::logout() {
75
103
  {
76
104
  std::lock_guard<std::mutex> lock(_mutex);
@@ -84,20 +112,16 @@ void HybridAuth::logout() {
84
112
 
85
113
  std::shared_ptr<Promise<void>> HybridAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
86
114
  auto promise = Promise<void>::create();
87
- std::vector<std::string> scopes;
88
- std::optional<std::string> loginHint;
89
- if (options) {
90
- if (options->scopes) scopes = *options->scopes;
91
- loginHint = options->loginHint;
92
- }
93
115
 
94
- auto loginPromise = PlatformAuth::login(provider, scopes, loginHint);
95
- loginPromise->addOnResolvedListener([this, promise, scopes](const AuthUser& user) {
116
+ auto loginPromise = PlatformAuth::login(provider, options);
117
+ loginPromise->addOnResolvedListener([this, promise, options](const AuthUser& user) {
96
118
  {
97
119
  std::lock_guard<std::mutex> lock(_mutex);
98
120
  _currentUser = user;
99
- _grantedScopes = scopes;
100
- if (_currentUser) _currentUser->scopes = scopes;
121
+ if (options && options->scopes) {
122
+ _grantedScopes = *options->scopes;
123
+ }
124
+ if (_currentUser) _currentUser->scopes = _grantedScopes;
101
125
  saveToCache(_currentUser);
102
126
  }
103
127
  notifyAuthStateChanged();
@@ -200,6 +224,7 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
200
224
  saveToCache(_currentUser);
201
225
  }
202
226
  }
227
+ notifyTokensRefreshed(tokens);
203
228
  notifyAuthStateChanged();
204
229
  promise->resolve(tokens);
205
230
  });
@@ -209,5 +234,31 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
209
234
  });
210
235
  return promise;
211
236
  }
237
+
238
+ void HybridAuth::setLoggingEnabled(bool enabled) {
239
+ sLoggingEnabled = enabled;
240
+ }
241
+
242
+ void HybridAuth::setStorageAdapter(const std::optional<std::shared_ptr<HybridAuthStorageAdapterSpec>>& adapter) {
243
+ std::lock_guard<std::mutex> lock(_mutex);
244
+ _storageAdapter = adapter.value_or(nullptr);
245
+ if (_storageAdapter) {
246
+ loadFromCache();
247
+ notifyAuthStateChanged();
248
+ }
249
+ }
250
+
251
+ void HybridAuth::notifyTokensRefreshed(const AuthTokens& tokens) {
252
+ std::vector<std::function<void(const AuthTokens&)>> listeners;
253
+ {
254
+ std::lock_guard<std::mutex> lock(_mutex);
255
+ for (auto const& [id, listener] : _tokenListeners) {
256
+ listeners.push_back(listener);
257
+ }
258
+ }
259
+ for (const auto& listener : listeners) {
260
+ listener(tokens);
261
+ }
262
+ }
212
263
 
213
264
  } // namespace margelo::nitro::NitroAuth
@@ -28,20 +28,30 @@ public:
28
28
 
29
29
  void logout() override;
30
30
  std::function<void()> onAuthStateChanged(const std::function<void(const std::optional<AuthUser>&)>& callback) override;
31
+ std::function<void()> onTokensRefreshed(const std::function<void(const AuthTokens&)>& callback) override;
32
+ void setLoggingEnabled(bool enabled) override;
33
+ void setStorageAdapter(const std::optional<std::shared_ptr<HybridAuthStorageAdapterSpec>>& adapter) override;
31
34
 
32
35
  private:
33
36
  void loadFromCache();
34
37
  void saveToCache(const std::optional<AuthUser>& user);
35
38
  void notifyAuthStateChanged();
39
+ void notifyTokensRefreshed(const AuthTokens& tokens);
36
40
 
37
41
  private:
38
42
  std::optional<AuthUser> _currentUser;
39
43
  std::vector<std::string> _grantedScopes;
40
44
  std::map<int, std::function<void(const std::optional<AuthUser>&)>> _listeners;
41
45
  int _nextListenerId = 0;
46
+
47
+ std::shared_ptr<HybridAuthStorageAdapterSpec> _storageAdapter;
48
+ std::map<int, std::function<void(const AuthTokens&)>> _tokenListeners;
49
+ int _nextTokenListenerId = 0;
50
+
42
51
  std::mutex _mutex;
43
52
 
44
53
  static constexpr auto TAG = "Auth";
54
+ static bool sLoggingEnabled;
45
55
  };
46
56
 
47
57
  } // namespace margelo::nitro::NitroAuth
@@ -3,6 +3,7 @@
3
3
  #include "AuthProvider.hpp"
4
4
  #include "AuthUser.hpp"
5
5
  #include "AuthTokens.hpp"
6
+ #include "LoginOptions.hpp"
6
7
  #include <NitroModules/Promise.hpp>
7
8
  #include <memory>
8
9
  #include <vector>
@@ -14,7 +15,7 @@ using namespace margelo::nitro;
14
15
 
15
16
  class PlatformAuth {
16
17
  public:
17
- static std::shared_ptr<Promise<AuthUser>> login(AuthProvider provider, const std::vector<std::string>& scopes = {}, const std::optional<std::string>& loginHint = std::nullopt);
18
+ static std::shared_ptr<Promise<AuthUser>> login(AuthProvider provider, const std::optional<LoginOptions>& options = std::nullopt);
18
19
  static std::shared_ptr<Promise<AuthUser>> requestScopes(const std::vector<std::string>& scopes);
19
20
  static std::shared_ptr<Promise<AuthTokens>> refreshToken();
20
21
  static std::shared_ptr<Promise<std::optional<AuthUser>>> silentRestore();
@@ -11,14 +11,32 @@
11
11
  #import "react_native_nitro_auth-Swift.h"
12
12
  #endif
13
13
 
14
+ #include "LoginOptions.hpp"
15
+
14
16
  namespace margelo::nitro::NitroAuth {
15
17
 
16
- std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::vector<std::string>& scopes, const std::optional<std::string>& loginHint) {
18
+ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
17
19
  auto promise = Promise<AuthUser>::create();
18
20
  NSString* providerStr = provider == AuthProvider::GOOGLE ? @"google" : @"apple";
19
- NSMutableArray* scopesArray = [NSMutableArray arrayWithCapacity:scopes.size()];
20
- for (const auto& scope : scopes) [scopesArray addObject:[NSString stringWithUTF8String:scope.c_str()]];
21
- NSString* hintStr = loginHint.has_value() ? [NSString stringWithUTF8String:loginHint->c_str()] : nil;
21
+
22
+ NSMutableArray* scopesArray = [NSMutableArray array];
23
+ NSString* hintStr = nil;
24
+
25
+ if (options.has_value()) {
26
+ if (options->scopes.has_value()) {
27
+ for (const auto& scope : *options->scopes) {
28
+ [scopesArray addObject:[NSString stringWithUTF8String:scope.c_str()]];
29
+ }
30
+ }
31
+ if (options->loginHint.has_value()) {
32
+ hintStr = [NSString stringWithUTF8String:options->loginHint->c_str()];
33
+ }
34
+ }
35
+
36
+ // Default scopes if none provided
37
+ if (scopesArray.count == 0) {
38
+ [scopesArray addObjectsFromArray:@[@"openid", @"email", @"profile"]];
39
+ }
22
40
 
23
41
  [AuthAdapter loginWithProvider:providerStr scopes:scopesArray loginHint:hintStr completion:^(NSDictionary* _Nullable data, NSString* _Nullable error) {
24
42
  if (error != nil) {