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.
- package/README.md +62 -13
- package/android/build.gradle +8 -3
- package/android/src/main/cpp/PlatformAuth+Android.cpp +14 -3
- package/android/src/main/java/com/auth/AuthAdapter.kt +100 -3
- package/cpp/HybridAuth.cpp +64 -13
- package/cpp/HybridAuth.hpp +10 -0
- package/cpp/PlatformAuth.hpp +2 -1
- package/ios/PlatformAuth+iOS.mm +22 -4
- package/lib/commonjs/Auth.web.js +91 -23
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/AuthStorage.nitro.js +6 -0
- package/lib/commonjs/AuthStorage.nitro.js.map +1 -0
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +12 -0
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/ui/social-button.js +2 -2
- package/lib/commonjs/ui/social-button.js.map +1 -1
- package/lib/commonjs/ui/social-button.web.js +2 -2
- package/lib/commonjs/ui/social-button.web.js.map +1 -1
- package/lib/commonjs/utils/logger.js +17 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/module/Auth.web.js +91 -23
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/AuthStorage.nitro.js +4 -0
- package/lib/module/AuthStorage.nitro.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/ui/social-button.js +2 -2
- package/lib/module/ui/social-button.js.map +1 -1
- package/lib/module/ui/social-button.web.js +2 -2
- package/lib/module/ui/social-button.web.js.map +1 -1
- package/lib/module/utils/logger.js +13 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/typescript/Auth.nitro.d.ts +5 -0
- package/lib/typescript/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/Auth.web.d.ts +9 -1
- package/lib/typescript/Auth.web.d.ts.map +1 -1
- package/lib/typescript/AuthStorage.nitro.d.ts +19 -0
- package/lib/typescript/AuthStorage.nitro.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +1 -0
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/utils/logger.d.ts +8 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroAuth+autolinking.cmake +1 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +3 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +7 -0
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/LoginOptions.hpp +6 -2
- package/package.json +3 -4
- package/react-native-nitro-auth.podspec +1 -1
- package/src/Auth.nitro.ts +6 -0
- package/src/Auth.web.ts +114 -24
- package/src/AuthStorage.nitro.ts +17 -0
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/ui/social-button.tsx +3 -3
- package/src/ui/social-button.web.tsx +3 -3
- package/src/utils/logger.ts +11 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// HybridAuthStorageAdapterSpec.cpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#include "HybridAuthStorageAdapterSpec.hpp"
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro::NitroAuth {
|
|
11
|
+
|
|
12
|
+
void HybridAuthStorageAdapterSpec::loadHybridMethods() {
|
|
13
|
+
// load base methods/properties
|
|
14
|
+
HybridObject::loadHybridMethods();
|
|
15
|
+
// load custom methods/properties
|
|
16
|
+
registerHybrids(this, [](Prototype& prototype) {
|
|
17
|
+
prototype.registerHybridMethod("save", &HybridAuthStorageAdapterSpec::save);
|
|
18
|
+
prototype.registerHybridMethod("load", &HybridAuthStorageAdapterSpec::load);
|
|
19
|
+
prototype.registerHybridMethod("remove", &HybridAuthStorageAdapterSpec::remove);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
} // namespace margelo::nitro::NitroAuth
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// HybridAuthStorageAdapterSpec.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/HybridObject.hpp>)
|
|
11
|
+
#include <NitroModules/HybridObject.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#include <string>
|
|
19
|
+
#include <optional>
|
|
20
|
+
|
|
21
|
+
namespace margelo::nitro::NitroAuth {
|
|
22
|
+
|
|
23
|
+
using namespace margelo::nitro;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An abstract base class for `AuthStorageAdapter`
|
|
27
|
+
* Inherit this class to create instances of `HybridAuthStorageAdapterSpec` in C++.
|
|
28
|
+
* You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
|
|
29
|
+
* @example
|
|
30
|
+
* ```cpp
|
|
31
|
+
* class HybridAuthStorageAdapter: public HybridAuthStorageAdapterSpec {
|
|
32
|
+
* public:
|
|
33
|
+
* HybridAuthStorageAdapter(...): HybridObject(TAG) { ... }
|
|
34
|
+
* // ...
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
class HybridAuthStorageAdapterSpec: public virtual HybridObject {
|
|
39
|
+
public:
|
|
40
|
+
// Constructor
|
|
41
|
+
explicit HybridAuthStorageAdapterSpec(): HybridObject(TAG) { }
|
|
42
|
+
|
|
43
|
+
// Destructor
|
|
44
|
+
~HybridAuthStorageAdapterSpec() override = default;
|
|
45
|
+
|
|
46
|
+
public:
|
|
47
|
+
// Properties
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
public:
|
|
51
|
+
// Methods
|
|
52
|
+
virtual void save(const std::string& key, const std::string& value) = 0;
|
|
53
|
+
virtual std::optional<std::string> load(const std::string& key) = 0;
|
|
54
|
+
virtual void remove(const std::string& key) = 0;
|
|
55
|
+
|
|
56
|
+
protected:
|
|
57
|
+
// Hybrid Setup
|
|
58
|
+
void loadHybridMethods() override;
|
|
59
|
+
|
|
60
|
+
protected:
|
|
61
|
+
// Tag for logging
|
|
62
|
+
static constexpr auto TAG = "AuthStorageAdapter";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
} // namespace margelo::nitro::NitroAuth
|
|
@@ -38,10 +38,11 @@ namespace margelo::nitro::NitroAuth {
|
|
|
38
38
|
public:
|
|
39
39
|
std::optional<std::vector<std::string>> scopes SWIFT_PRIVATE;
|
|
40
40
|
std::optional<std::string> loginHint SWIFT_PRIVATE;
|
|
41
|
+
std::optional<bool> useOneTap SWIFT_PRIVATE;
|
|
41
42
|
|
|
42
43
|
public:
|
|
43
44
|
LoginOptions() = default;
|
|
44
|
-
explicit LoginOptions(std::optional<std::vector<std::string>> scopes, std::optional<std::string> loginHint): scopes(scopes), loginHint(loginHint) {}
|
|
45
|
+
explicit LoginOptions(std::optional<std::vector<std::string>> scopes, std::optional<std::string> loginHint, std::optional<bool> useOneTap): scopes(scopes), loginHint(loginHint), useOneTap(useOneTap) {}
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
} // namespace margelo::nitro::NitroAuth
|
|
@@ -55,13 +56,15 @@ namespace margelo::nitro {
|
|
|
55
56
|
jsi::Object obj = arg.asObject(runtime);
|
|
56
57
|
return margelo::nitro::NitroAuth::LoginOptions(
|
|
57
58
|
JSIConverter<std::optional<std::vector<std::string>>>::fromJSI(runtime, obj.getProperty(runtime, "scopes")),
|
|
58
|
-
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "loginHint"))
|
|
59
|
+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "loginHint")),
|
|
60
|
+
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "useOneTap"))
|
|
59
61
|
);
|
|
60
62
|
}
|
|
61
63
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::NitroAuth::LoginOptions& arg) {
|
|
62
64
|
jsi::Object obj(runtime);
|
|
63
65
|
obj.setProperty(runtime, "scopes", JSIConverter<std::optional<std::vector<std::string>>>::toJSI(runtime, arg.scopes));
|
|
64
66
|
obj.setProperty(runtime, "loginHint", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.loginHint));
|
|
67
|
+
obj.setProperty(runtime, "useOneTap", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.useOneTap));
|
|
65
68
|
return obj;
|
|
66
69
|
}
|
|
67
70
|
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
@@ -74,6 +77,7 @@ namespace margelo::nitro {
|
|
|
74
77
|
}
|
|
75
78
|
if (!JSIConverter<std::optional<std::vector<std::string>>>::canConvert(runtime, obj.getProperty(runtime, "scopes"))) return false;
|
|
76
79
|
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "loginHint"))) return false;
|
|
80
|
+
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "useOneTap"))) return false;
|
|
77
81
|
return true;
|
|
78
82
|
}
|
|
79
83
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "High-performance authentication library for React Native with Google Sign-In and Apple Sign-In support, powered by Nitro Modules (JSI)",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -78,9 +78,8 @@
|
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@expo/config-plugins": "^54.0.4",
|
|
80
80
|
"@react-native/babel-preset": "^0.81.0",
|
|
81
|
-
"@testing-library/react": "^16.
|
|
82
|
-
"
|
|
83
|
-
"@testing-library/react-native": "^13.3.3",
|
|
81
|
+
"@testing-library/react": "^16.1.0",
|
|
82
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
84
83
|
"react": "19.1.0",
|
|
85
84
|
"react-native": "0.81.5",
|
|
86
85
|
"react-native-nitro-modules": "^0.32.0",
|
package/src/Auth.nitro.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { HybridObject } from "react-native-nitro-modules";
|
|
2
2
|
|
|
3
|
+
import type { AuthStorageAdapter } from "./AuthStorage.nitro";
|
|
4
|
+
|
|
3
5
|
export type AuthProvider = "google" | "apple";
|
|
4
6
|
|
|
5
7
|
export type AuthErrorCode =
|
|
@@ -12,6 +14,7 @@ export type AuthErrorCode =
|
|
|
12
14
|
export interface LoginOptions {
|
|
13
15
|
scopes?: string[];
|
|
14
16
|
loginHint?: string;
|
|
17
|
+
useOneTap?: boolean;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export interface AuthTokens {
|
|
@@ -48,4 +51,7 @@ export interface Auth extends HybridObject<{ ios: "c++"; android: "c++" }> {
|
|
|
48
51
|
onAuthStateChanged(
|
|
49
52
|
callback: (user: AuthUser | undefined) => void
|
|
50
53
|
): () => void;
|
|
54
|
+
onTokensRefreshed(callback: (tokens: AuthTokens) => void): () => void;
|
|
55
|
+
setLoggingEnabled(enabled: boolean): void;
|
|
56
|
+
setStorageAdapter(adapter: AuthStorageAdapter | undefined): void;
|
|
51
57
|
}
|
package/src/Auth.web.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Auth,
|
|
3
|
+
AuthUser,
|
|
4
|
+
AuthProvider,
|
|
5
|
+
LoginOptions,
|
|
6
|
+
AuthTokens,
|
|
7
|
+
} from "./Auth.nitro";
|
|
8
|
+
import type { AuthStorageAdapter } from "./AuthStorage.nitro";
|
|
9
|
+
import { logger } from "./utils/logger";
|
|
2
10
|
|
|
3
11
|
const CACHE_KEY = "nitro_auth_user";
|
|
4
12
|
const SCOPES_KEY = "nitro_auth_scopes";
|
|
@@ -17,26 +25,47 @@ class AuthWeb implements Auth {
|
|
|
17
25
|
private _currentUser: AuthUser | undefined;
|
|
18
26
|
private _grantedScopes: string[] = [];
|
|
19
27
|
private _listeners: ((user: AuthUser | undefined) => void)[] = [];
|
|
28
|
+
private _tokenListeners: ((tokens: AuthTokens) => void)[] = [];
|
|
29
|
+
private _storageAdapter: AuthStorageAdapter | undefined;
|
|
20
30
|
|
|
21
31
|
constructor() {
|
|
22
|
-
|
|
32
|
+
this.loadFromCache();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private loadFromCache() {
|
|
36
|
+
const cached = this._storageAdapter
|
|
37
|
+
? this._storageAdapter.load(CACHE_KEY)
|
|
38
|
+
: localStorage.getItem(CACHE_KEY);
|
|
39
|
+
|
|
23
40
|
if (cached) {
|
|
24
41
|
try {
|
|
25
42
|
this._currentUser = JSON.parse(cached);
|
|
26
43
|
} catch {
|
|
27
|
-
|
|
44
|
+
this.removeFromCache(CACHE_KEY);
|
|
28
45
|
}
|
|
29
46
|
}
|
|
30
|
-
|
|
47
|
+
|
|
48
|
+
const scopes = this._storageAdapter
|
|
49
|
+
? this._storageAdapter.load(SCOPES_KEY)
|
|
50
|
+
: localStorage.getItem(SCOPES_KEY);
|
|
51
|
+
|
|
31
52
|
if (scopes) {
|
|
32
53
|
try {
|
|
33
54
|
this._grantedScopes = JSON.parse(scopes);
|
|
34
55
|
} catch {
|
|
35
|
-
|
|
56
|
+
this.removeFromCache(SCOPES_KEY);
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
}
|
|
39
60
|
|
|
61
|
+
private removeFromCache(key: string) {
|
|
62
|
+
if (this._storageAdapter) {
|
|
63
|
+
this._storageAdapter.remove(key);
|
|
64
|
+
} else {
|
|
65
|
+
localStorage.removeItem(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
40
69
|
get currentUser(): AuthUser | undefined {
|
|
41
70
|
return this._currentUser;
|
|
42
71
|
}
|
|
@@ -59,6 +88,13 @@ class AuthWeb implements Auth {
|
|
|
59
88
|
};
|
|
60
89
|
}
|
|
61
90
|
|
|
91
|
+
onTokensRefreshed(callback: (tokens: AuthTokens) => void): () => void {
|
|
92
|
+
this._tokenListeners.push(callback);
|
|
93
|
+
return () => {
|
|
94
|
+
this._tokenListeners = this._tokenListeners.filter((l) => l !== callback);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
62
98
|
private notify() {
|
|
63
99
|
this._listeners.forEach((l) => l(this._currentUser));
|
|
64
100
|
}
|
|
@@ -66,13 +102,18 @@ class AuthWeb implements Auth {
|
|
|
66
102
|
async login(provider: AuthProvider, options?: LoginOptions): Promise<void> {
|
|
67
103
|
const scopes = options?.scopes ?? DEFAULT_SCOPES;
|
|
68
104
|
const loginHint = options?.loginHint;
|
|
105
|
+
logger.log(`Starting login with ${provider}`, { scopes });
|
|
69
106
|
try {
|
|
70
107
|
if (provider === "google") {
|
|
71
|
-
|
|
108
|
+
await this.loginGoogle(scopes, loginHint);
|
|
109
|
+
} else {
|
|
110
|
+
await this.loginApple();
|
|
72
111
|
}
|
|
73
|
-
|
|
112
|
+
logger.log(`Login successful with ${provider}`);
|
|
74
113
|
} catch (e: unknown) {
|
|
75
|
-
|
|
114
|
+
const error = this.mapError(e);
|
|
115
|
+
logger.error(`Login failed for ${provider}:`, error.message);
|
|
116
|
+
throw error;
|
|
76
117
|
}
|
|
77
118
|
}
|
|
78
119
|
|
|
@@ -83,17 +124,28 @@ class AuthWeb implements Auth {
|
|
|
83
124
|
if (this._currentUser.provider !== "google") {
|
|
84
125
|
throw new Error("Scope management only supported for Google");
|
|
85
126
|
}
|
|
127
|
+
logger.log("Requesting additional scopes:", scopes);
|
|
86
128
|
const newScopes = [...new Set([...this._grantedScopes, ...scopes])];
|
|
87
129
|
return this.loginGoogle(newScopes).catch((e) => {
|
|
88
|
-
|
|
130
|
+
const error = this.mapError(e);
|
|
131
|
+
logger.error("Requesting scopes failed:", error.message);
|
|
132
|
+
throw error;
|
|
89
133
|
});
|
|
90
134
|
}
|
|
91
135
|
|
|
92
136
|
async revokeScopes(scopes: string[]): Promise<void> {
|
|
137
|
+
logger.log("Revoking scopes:", scopes);
|
|
93
138
|
this._grantedScopes = this._grantedScopes.filter(
|
|
94
139
|
(s) => !scopes.includes(s)
|
|
95
140
|
);
|
|
96
|
-
|
|
141
|
+
if (this._storageAdapter) {
|
|
142
|
+
this._storageAdapter.save(
|
|
143
|
+
SCOPES_KEY,
|
|
144
|
+
JSON.stringify(this._grantedScopes)
|
|
145
|
+
);
|
|
146
|
+
} else {
|
|
147
|
+
localStorage.setItem(SCOPES_KEY, JSON.stringify(this._grantedScopes));
|
|
148
|
+
}
|
|
97
149
|
if (this._currentUser) {
|
|
98
150
|
this._currentUser.scopes = this._grantedScopes;
|
|
99
151
|
this.updateUser(this._currentUser);
|
|
@@ -104,6 +156,7 @@ class AuthWeb implements Auth {
|
|
|
104
156
|
if (this._currentUser?.expirationTime) {
|
|
105
157
|
const now = Date.now();
|
|
106
158
|
if (now + 300000 > this._currentUser.expirationTime) {
|
|
159
|
+
logger.log("Token about to expire, refreshing...");
|
|
107
160
|
await this.refreshToken();
|
|
108
161
|
}
|
|
109
162
|
}
|
|
@@ -117,27 +170,38 @@ class AuthWeb implements Auth {
|
|
|
117
170
|
if (this._currentUser.provider !== "google") {
|
|
118
171
|
throw new Error("Token refresh only supported for Google");
|
|
119
172
|
}
|
|
173
|
+
logger.log("Refreshing tokens...");
|
|
120
174
|
await this.loginGoogle(
|
|
121
175
|
this._grantedScopes.length > 0 ? this._grantedScopes : DEFAULT_SCOPES
|
|
122
176
|
);
|
|
123
|
-
|
|
177
|
+
const tokens = {
|
|
124
178
|
accessToken: this._currentUser.accessToken,
|
|
125
179
|
idToken: this._currentUser.idToken,
|
|
126
180
|
};
|
|
181
|
+
this._tokenListeners.forEach((l) => l(tokens));
|
|
182
|
+
return tokens;
|
|
127
183
|
}
|
|
128
184
|
|
|
129
185
|
private mapError(error: unknown): Error {
|
|
130
|
-
|
|
186
|
+
if (error instanceof Error) {
|
|
187
|
+
const msg = error.message.toLowerCase();
|
|
188
|
+
if (msg.includes("cancel") || msg.includes("popup_closed")) {
|
|
189
|
+
return new Error("cancelled");
|
|
190
|
+
}
|
|
191
|
+
if (msg.includes("network")) {
|
|
192
|
+
return new Error("network_error");
|
|
193
|
+
}
|
|
194
|
+
if (msg.includes("client id") || msg.includes("config")) {
|
|
195
|
+
return new Error("configuration_error");
|
|
196
|
+
}
|
|
197
|
+
return error;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const msg = String(error).toLowerCase();
|
|
131
201
|
if (msg.includes("cancel") || msg.includes("popup_closed")) {
|
|
132
202
|
return new Error("cancelled");
|
|
133
203
|
}
|
|
134
|
-
|
|
135
|
-
return new Error("network_error");
|
|
136
|
-
}
|
|
137
|
-
if (msg.includes("client id") || msg.includes("config")) {
|
|
138
|
-
return new Error("configuration_error");
|
|
139
|
-
}
|
|
140
|
-
return error as Error;
|
|
204
|
+
return new Error(String(error));
|
|
141
205
|
}
|
|
142
206
|
|
|
143
207
|
private async loginGoogle(
|
|
@@ -158,9 +222,13 @@ class AuthWeb implements Auth {
|
|
|
158
222
|
const authUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth");
|
|
159
223
|
authUrl.searchParams.set("client_id", clientId);
|
|
160
224
|
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
161
|
-
|
|
225
|
+
// Requesting code alongside tokens for server-side verification if needed
|
|
226
|
+
authUrl.searchParams.set("response_type", "id_token token code");
|
|
162
227
|
authUrl.searchParams.set("scope", scopes.join(" "));
|
|
163
228
|
authUrl.searchParams.set("nonce", Math.random().toString(36).slice(2));
|
|
229
|
+
authUrl.searchParams.set("access_type", "offline"); // Needed for server auth code flow
|
|
230
|
+
authUrl.searchParams.set("prompt", "consent"); // Force consent to get refresh token/code sometimes
|
|
231
|
+
|
|
164
232
|
if (loginHint) {
|
|
165
233
|
authUrl.searchParams.set("login_hint", loginHint);
|
|
166
234
|
}
|
|
@@ -199,15 +267,21 @@ class AuthWeb implements Auth {
|
|
|
199
267
|
const idToken = params.get("id_token");
|
|
200
268
|
const accessToken = params.get("access_token");
|
|
201
269
|
const expiresIn = params.get("expires_in");
|
|
270
|
+
const code = params.get("code");
|
|
202
271
|
|
|
203
272
|
if (idToken) {
|
|
204
273
|
this._grantedScopes = scopes;
|
|
205
|
-
|
|
274
|
+
if (this._storageAdapter) {
|
|
275
|
+
this._storageAdapter.save(SCOPES_KEY, JSON.stringify(scopes));
|
|
276
|
+
} else {
|
|
277
|
+
localStorage.setItem(SCOPES_KEY, JSON.stringify(scopes));
|
|
278
|
+
}
|
|
206
279
|
|
|
207
280
|
const user: AuthUser = {
|
|
208
281
|
provider: "google",
|
|
209
282
|
idToken,
|
|
210
283
|
accessToken: accessToken ?? undefined,
|
|
284
|
+
serverAuthCode: code ?? undefined,
|
|
211
285
|
scopes,
|
|
212
286
|
expirationTime: expiresIn
|
|
213
287
|
? Date.now() + parseInt(expiresIn) * 1000
|
|
@@ -289,17 +363,33 @@ class AuthWeb implements Auth {
|
|
|
289
363
|
logout(): void {
|
|
290
364
|
this._currentUser = undefined;
|
|
291
365
|
this._grantedScopes = [];
|
|
292
|
-
|
|
293
|
-
|
|
366
|
+
this.removeFromCache(CACHE_KEY);
|
|
367
|
+
this.removeFromCache(SCOPES_KEY);
|
|
294
368
|
this.notify();
|
|
295
369
|
}
|
|
296
370
|
|
|
297
371
|
private updateUser(user: AuthUser) {
|
|
298
372
|
this._currentUser = user;
|
|
299
|
-
|
|
373
|
+
if (this._storageAdapter) {
|
|
374
|
+
this._storageAdapter.save(CACHE_KEY, JSON.stringify(user));
|
|
375
|
+
} else {
|
|
376
|
+
localStorage.setItem(CACHE_KEY, JSON.stringify(user));
|
|
377
|
+
}
|
|
300
378
|
this.notify();
|
|
301
379
|
}
|
|
302
380
|
|
|
381
|
+
setLoggingEnabled(enabled: boolean): void {
|
|
382
|
+
logger.setEnabled(enabled);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
setStorageAdapter(adapter: AuthStorageAdapter | undefined): void {
|
|
386
|
+
this._storageAdapter = adapter;
|
|
387
|
+
if (adapter) {
|
|
388
|
+
this.loadFromCache();
|
|
389
|
+
this.notify();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
303
393
|
name = "Auth";
|
|
304
394
|
dispose() {}
|
|
305
395
|
equals(other: any) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HybridObject } from "react-native-nitro-modules";
|
|
2
|
+
|
|
3
|
+
export interface AuthStorageAdapter
|
|
4
|
+
extends HybridObject<{ ios: "c++"; android: "c++" }> {
|
|
5
|
+
/**
|
|
6
|
+
* Called to save a value to the custom storage.
|
|
7
|
+
*/
|
|
8
|
+
save(key: string, value: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Called to load a value from the custom storage.
|
|
11
|
+
*/
|
|
12
|
+
load(key: string): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Called to remove a value from the custom storage.
|
|
15
|
+
*/
|
|
16
|
+
remove(key: string): void;
|
|
17
|
+
}
|
package/src/index.ts
CHANGED
package/src/index.web.ts
CHANGED
package/src/ui/social-button.tsx
CHANGED
|
@@ -23,10 +23,9 @@ interface SocialButtonProps {
|
|
|
23
23
|
onPress?: () => void;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async function performLogin(provider: AuthProvider): Promise<
|
|
26
|
+
async function performLogin(provider: AuthProvider): Promise<void> {
|
|
27
27
|
const auth = NitroModules.createHybridObject<Auth>("Auth");
|
|
28
28
|
await auth.login(provider);
|
|
29
|
-
return auth.currentUser ?? null;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export const SocialButton: React.FC<SocialButtonProps> = ({
|
|
@@ -50,8 +49,9 @@ export const SocialButton: React.FC<SocialButtonProps> = ({
|
|
|
50
49
|
}
|
|
51
50
|
setLoading(true);
|
|
52
51
|
performLogin(provider)
|
|
53
|
-
.then((
|
|
52
|
+
.then(() => {
|
|
54
53
|
setLoading(false);
|
|
54
|
+
const user = NitroModules.createHybridObject<Auth>("Auth").currentUser;
|
|
55
55
|
if (user) onSuccess?.(user);
|
|
56
56
|
})
|
|
57
57
|
.catch((e) => {
|
|
@@ -23,9 +23,8 @@ interface SocialButtonProps {
|
|
|
23
23
|
onPress?: () => void;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async function performLogin(provider: AuthProvider): Promise<
|
|
26
|
+
async function performLogin(provider: AuthProvider): Promise<void> {
|
|
27
27
|
await AuthModule.login(provider);
|
|
28
|
-
return AuthModule.currentUser ?? null;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
export const SocialButton: React.FC<SocialButtonProps> = ({
|
|
@@ -49,8 +48,9 @@ export const SocialButton: React.FC<SocialButtonProps> = ({
|
|
|
49
48
|
}
|
|
50
49
|
setLoading(true);
|
|
51
50
|
performLogin(provider)
|
|
52
|
-
.then((
|
|
51
|
+
.then(() => {
|
|
53
52
|
setLoading(false);
|
|
53
|
+
const user = AuthModule.currentUser;
|
|
54
54
|
if (user) onSuccess?.(user);
|
|
55
55
|
})
|
|
56
56
|
.catch((e) => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let enabled = false;
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
setEnabled: (value: boolean) => {
|
|
5
|
+
enabled = value;
|
|
6
|
+
},
|
|
7
|
+
log: (...args: any[]) => enabled && console.log("[NitroAuth]", ...args),
|
|
8
|
+
warn: (...args: any[]) => enabled && console.warn("[NitroAuth]", ...args),
|
|
9
|
+
error: (...args: any[]) => enabled && console.error("[NitroAuth]", ...args),
|
|
10
|
+
debug: (...args: any[]) => enabled && console.debug("[NitroAuth]", ...args),
|
|
11
|
+
};
|