react-native-nitro-auth 0.1.0
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/LICENSE +21 -0
- package/README.md +254 -0
- package/android/CMakeLists.txt +37 -0
- package/android/build.gradle +99 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/cpp/JniOnLoad.cpp +7 -0
- package/android/src/main/cpp/PlatformAuth+Android.cpp +293 -0
- package/android/src/main/java/com/auth/AuthAdapter.kt +286 -0
- package/android/src/main/java/com/auth/GoogleSignInActivity.kt +73 -0
- package/android/src/main/java/com/auth/NitroAuthModule.kt +19 -0
- package/android/src/main/java/com/auth/NitroAuthPackage.kt +16 -0
- package/app.plugin.js +64 -0
- package/cpp/AuthCache.cpp +105 -0
- package/cpp/AuthCache.hpp +20 -0
- package/cpp/HybridAuth.cpp +213 -0
- package/cpp/HybridAuth.hpp +47 -0
- package/cpp/JSONSerializer.hpp +57 -0
- package/cpp/PlatformAuth.hpp +25 -0
- package/ios/AuthAdapter.swift +200 -0
- package/ios/PlatformAuth+iOS.mm +119 -0
- package/lib/commonjs/Auth.nitro.js +6 -0
- package/lib/commonjs/Auth.nitro.js.map +1 -0
- package/lib/commonjs/Auth.web.js +256 -0
- package/lib/commonjs/Auth.web.js.map +1 -0
- package/lib/commonjs/global.d.js +6 -0
- package/lib/commonjs/global.d.js.map +1 -0
- package/lib/commonjs/index.js +52 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/index.web.js +52 -0
- package/lib/commonjs/index.web.js.map +1 -0
- package/lib/commonjs/service.js +9 -0
- package/lib/commonjs/service.js.map +1 -0
- package/lib/commonjs/service.web.js +13 -0
- package/lib/commonjs/service.web.js.map +1 -0
- package/lib/commonjs/ui/social-button.js +103 -0
- package/lib/commonjs/ui/social-button.js.map +1 -0
- package/lib/commonjs/ui/social-button.web.js +102 -0
- package/lib/commonjs/ui/social-button.web.js.map +1 -0
- package/lib/commonjs/use-auth.js +144 -0
- package/lib/commonjs/use-auth.js.map +1 -0
- package/lib/module/Auth.nitro.js +4 -0
- package/lib/module/Auth.nitro.js.map +1 -0
- package/lib/module/Auth.web.js +252 -0
- package/lib/module/Auth.web.js.map +1 -0
- package/lib/module/global.d.js +4 -0
- package/lib/module/global.d.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.web.js +7 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/service.js +5 -0
- package/lib/module/service.js.map +1 -0
- package/lib/module/service.web.js +4 -0
- package/lib/module/service.web.js.map +1 -0
- package/lib/module/ui/social-button.js +97 -0
- package/lib/module/ui/social-button.js.map +1 -0
- package/lib/module/ui/social-button.web.js +96 -0
- package/lib/module/ui/social-button.web.js.map +1 -0
- package/lib/module/use-auth.js +140 -0
- package/lib/module/use-auth.js.map +1 -0
- package/lib/typescript/Auth.nitro.d.ts +38 -0
- package/lib/typescript/Auth.nitro.d.ts.map +1 -0
- package/lib/typescript/Auth.web.d.ts +32 -0
- package/lib/typescript/Auth.web.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/index.web.d.ts +5 -0
- package/lib/typescript/index.web.d.ts.map +1 -0
- package/lib/typescript/service.d.ts +3 -0
- package/lib/typescript/service.d.ts.map +1 -0
- package/lib/typescript/service.web.d.ts +2 -0
- package/lib/typescript/service.web.d.ts.map +1 -0
- package/lib/typescript/ui/social-button.d.ts +17 -0
- package/lib/typescript/ui/social-button.d.ts.map +1 -0
- package/lib/typescript/ui/social-button.web.d.ts +17 -0
- package/lib/typescript/ui/social-button.web.d.ts.map +1 -0
- package/lib/typescript/use-auth.d.ts +15 -0
- package/lib/typescript/use-auth.d.ts.map +1 -0
- package/nitro.json +15 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroAuth+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroAuth+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroAuthOnLoad.cpp +44 -0
- package/nitrogen/generated/android/NitroAuthOnLoad.hpp +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/auth/NitroAuthOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroAuth+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroAuth-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroAuth-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroAuth-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroAuthAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroAuthAutolinking.swift +12 -0
- package/nitrogen/generated/shared/c++/AuthProvider.hpp +76 -0
- package/nitrogen/generated/shared/c++/AuthTokens.hpp +84 -0
- package/nitrogen/generated/shared/c++/AuthUser.hpp +107 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +30 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +85 -0
- package/nitrogen/generated/shared/c++/LoginOptions.hpp +81 -0
- package/package.json +113 -0
- package/react-native-nitro-auth.podspec +40 -0
- package/src/Auth.nitro.ts +50 -0
- package/src/Auth.web.ts +310 -0
- package/src/global.d.ts +38 -0
- package/src/index.ts +4 -0
- package/src/index.web.ts +4 -0
- package/src/service.ts +4 -0
- package/src/service.web.ts +1 -0
- package/src/ui/social-button.tsx +129 -0
- package/src/ui/social-button.web.tsx +128 -0
- package/src/use-auth.ts +157 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#include "PlatformAuth.hpp"
|
|
2
|
+
#include "AuthUser.hpp"
|
|
3
|
+
#include "AuthTokens.hpp"
|
|
4
|
+
#include "AuthCache.hpp"
|
|
5
|
+
#include <fbjni/fbjni.h>
|
|
6
|
+
#include <NitroModules/NitroLogger.hpp>
|
|
7
|
+
#include <NitroModules/Promise.hpp>
|
|
8
|
+
|
|
9
|
+
namespace margelo::nitro::NitroAuth {
|
|
10
|
+
|
|
11
|
+
using namespace facebook::jni;
|
|
12
|
+
|
|
13
|
+
struct JContext : JavaClass<JContext> {
|
|
14
|
+
static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
struct JAuthAdapter : JavaClass<JAuthAdapter> {
|
|
18
|
+
static constexpr auto kJavaDescriptor = "Lcom/auth/AuthAdapter;";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static std::shared_ptr<Promise<AuthUser>> gLoginPromise;
|
|
22
|
+
static std::shared_ptr<Promise<AuthUser>> gScopesPromise;
|
|
23
|
+
static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
|
|
24
|
+
static std::shared_ptr<Promise<std::optional<AuthUser>>> gSilentPromise;
|
|
25
|
+
static std::mutex gMutex;
|
|
26
|
+
|
|
27
|
+
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::vector<std::string>& scopes, const std::optional<std::string>& loginHint) {
|
|
28
|
+
auto promise = Promise<AuthUser>::create();
|
|
29
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
30
|
+
if (!contextPtr) {
|
|
31
|
+
promise->reject(std::make_exception_ptr(std::runtime_error("Android Context not initialized")));
|
|
32
|
+
return promise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
37
|
+
gLoginPromise = promise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
std::string providerStr = (provider == AuthProvider::GOOGLE) ? "google" : "apple";
|
|
41
|
+
JNIEnv* env = Environment::current();
|
|
42
|
+
jclass stringClass = env->FindClass("java/lang/String");
|
|
43
|
+
jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
|
|
44
|
+
for (size_t i = 0; i < scopes.size(); i++) {
|
|
45
|
+
env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
|
|
49
|
+
jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
|
|
50
|
+
jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
|
|
51
|
+
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V");
|
|
52
|
+
env->CallStaticVoidMethod(adapterClass, loginMethod,
|
|
53
|
+
contextPtr,
|
|
54
|
+
make_jstring(providerStr).get(),
|
|
55
|
+
nullptr,
|
|
56
|
+
jScopes,
|
|
57
|
+
jLoginHint);
|
|
58
|
+
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector<std::string>& scopes) {
|
|
63
|
+
auto promise = Promise<AuthUser>::create();
|
|
64
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
65
|
+
if (!contextPtr) {
|
|
66
|
+
promise->reject(std::make_exception_ptr(std::runtime_error("Android Context not initialized")));
|
|
67
|
+
return promise;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
72
|
+
gScopesPromise = promise;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
JNIEnv* env = Environment::current();
|
|
76
|
+
jclass stringClass = env->FindClass("java/lang/String");
|
|
77
|
+
jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
|
|
78
|
+
for (size_t i = 0; i < scopes.size(); i++) {
|
|
79
|
+
env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
|
|
83
|
+
jmethodID requestMethod = env->GetStaticMethodID(adapterClass, "requestScopesSync",
|
|
84
|
+
"(Landroid/content/Context;[Ljava/lang/String;)V");
|
|
85
|
+
env->CallStaticVoidMethod(adapterClass, requestMethod, contextPtr, jScopes);
|
|
86
|
+
return promise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
std::shared_ptr<Promise<AuthTokens>> PlatformAuth::refreshToken() {
|
|
90
|
+
auto promise = Promise<AuthTokens>::create();
|
|
91
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
92
|
+
if (!contextPtr) {
|
|
93
|
+
promise->reject(std::make_exception_ptr(std::runtime_error("Android Context not initialized")));
|
|
94
|
+
return promise;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
99
|
+
gRefreshPromise = promise;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
auto jContext = wrap_alias(contextPtr);
|
|
103
|
+
static auto refreshMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("refreshTokenSync");
|
|
104
|
+
refreshMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
|
|
105
|
+
return promise;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::shared_ptr<Promise<std::optional<AuthUser>>> PlatformAuth::silentRestore() {
|
|
109
|
+
auto promise = Promise<std::optional<AuthUser>>::create();
|
|
110
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
111
|
+
if (!contextPtr) {
|
|
112
|
+
promise->resolve(std::nullopt);
|
|
113
|
+
return promise;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
118
|
+
gSilentPromise = promise;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
auto jContext = wrap_alias(contextPtr);
|
|
122
|
+
static auto restoreMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("restoreSession");
|
|
123
|
+
restoreMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
|
|
124
|
+
return promise;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
bool PlatformAuth::hasPlayServices() {
|
|
128
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
129
|
+
if (!contextPtr) return false;
|
|
130
|
+
|
|
131
|
+
auto jContext = wrap_alias(contextPtr);
|
|
132
|
+
static auto hasPlayMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<jboolean(alias_ref<JContext>)>("hasPlayServices");
|
|
133
|
+
return hasPlayMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void PlatformAuth::logout() {
|
|
137
|
+
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
138
|
+
if (!contextPtr) return;
|
|
139
|
+
|
|
140
|
+
auto jContext = wrap_alias(contextPtr);
|
|
141
|
+
static auto logoutMethod = JAuthAdapter::javaClassStatic()->getStaticMethod<void(alias_ref<JContext>)>("logoutSync");
|
|
142
|
+
logoutMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv* env, jclass, jobject context) {
|
|
146
|
+
AuthCache::setAndroidContext(env->NewGlobalRef(context));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess(
|
|
150
|
+
JNIEnv* env, jclass,
|
|
151
|
+
jstring provider, jstring email, jstring name, jstring photo, jstring idToken, jstring accessToken, jobjectArray scopes, jobject expirationTime) {
|
|
152
|
+
|
|
153
|
+
std::shared_ptr<Promise<AuthUser>> loginPromise;
|
|
154
|
+
std::shared_ptr<Promise<AuthUser>> scopesPromise;
|
|
155
|
+
std::shared_ptr<Promise<std::optional<AuthUser>>> silentPromise;
|
|
156
|
+
{
|
|
157
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
158
|
+
loginPromise = gLoginPromise;
|
|
159
|
+
gLoginPromise = nullptr;
|
|
160
|
+
scopesPromise = gScopesPromise;
|
|
161
|
+
gScopesPromise = nullptr;
|
|
162
|
+
silentPromise = gSilentPromise;
|
|
163
|
+
gSilentPromise = nullptr;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
AuthUser user;
|
|
167
|
+
const char* providerCStr = env->GetStringUTFChars(provider, nullptr);
|
|
168
|
+
user.provider = (std::string(providerCStr) == "google") ? AuthProvider::GOOGLE : AuthProvider::APPLE;
|
|
169
|
+
env->ReleaseStringUTFChars(provider, providerCStr);
|
|
170
|
+
|
|
171
|
+
if (email) {
|
|
172
|
+
const char* s = env->GetStringUTFChars(email, nullptr);
|
|
173
|
+
user.email = std::string(s);
|
|
174
|
+
env->ReleaseStringUTFChars(email, s);
|
|
175
|
+
}
|
|
176
|
+
if (name) {
|
|
177
|
+
const char* s = env->GetStringUTFChars(name, nullptr);
|
|
178
|
+
user.name = std::string(s);
|
|
179
|
+
env->ReleaseStringUTFChars(name, s);
|
|
180
|
+
}
|
|
181
|
+
if (photo) {
|
|
182
|
+
const char* s = env->GetStringUTFChars(photo, nullptr);
|
|
183
|
+
user.photo = std::string(s);
|
|
184
|
+
env->ReleaseStringUTFChars(photo, s);
|
|
185
|
+
}
|
|
186
|
+
if (idToken) {
|
|
187
|
+
const char* s = env->GetStringUTFChars(idToken, nullptr);
|
|
188
|
+
user.idToken = std::string(s);
|
|
189
|
+
env->ReleaseStringUTFChars(idToken, s);
|
|
190
|
+
}
|
|
191
|
+
if (accessToken) {
|
|
192
|
+
const char* s = env->GetStringUTFChars(accessToken, nullptr);
|
|
193
|
+
user.accessToken = std::string(s);
|
|
194
|
+
env->ReleaseStringUTFChars(accessToken, s);
|
|
195
|
+
}
|
|
196
|
+
if (scopes) {
|
|
197
|
+
int len = env->GetArrayLength(scopes);
|
|
198
|
+
std::vector<std::string> scopeVec;
|
|
199
|
+
for (int i = 0; i < len; i++) {
|
|
200
|
+
jstring jstr = (jstring)env->GetObjectArrayElement(scopes, i);
|
|
201
|
+
const char* s = env->GetStringUTFChars(jstr, nullptr);
|
|
202
|
+
scopeVec.push_back(std::string(s));
|
|
203
|
+
env->ReleaseStringUTFChars(jstr, s);
|
|
204
|
+
}
|
|
205
|
+
user.scopes = scopeVec;
|
|
206
|
+
}
|
|
207
|
+
if (expirationTime) {
|
|
208
|
+
jclass longClass = env->FindClass("java/lang/Long");
|
|
209
|
+
jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
|
|
210
|
+
user.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
|
|
211
|
+
}
|
|
212
|
+
if (loginPromise) loginPromise->resolve(user);
|
|
213
|
+
if (scopesPromise) scopesPromise->resolve(user);
|
|
214
|
+
if (silentPromise) silentPromise->resolve(user);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginError(
|
|
218
|
+
JNIEnv* env, jclass, jstring error) {
|
|
219
|
+
|
|
220
|
+
std::shared_ptr<Promise<AuthUser>> loginPromise;
|
|
221
|
+
std::shared_ptr<Promise<AuthUser>> scopesPromise;
|
|
222
|
+
std::shared_ptr<Promise<std::optional<AuthUser>>> silentPromise;
|
|
223
|
+
{
|
|
224
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
225
|
+
loginPromise = gLoginPromise;
|
|
226
|
+
gLoginPromise = nullptr;
|
|
227
|
+
scopesPromise = gScopesPromise;
|
|
228
|
+
gScopesPromise = nullptr;
|
|
229
|
+
silentPromise = gSilentPromise;
|
|
230
|
+
gSilentPromise = nullptr;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const char* errorCStr = env->GetStringUTFChars(error, nullptr);
|
|
234
|
+
std::string errorStr(errorCStr);
|
|
235
|
+
env->ReleaseStringUTFChars(error, errorCStr);
|
|
236
|
+
|
|
237
|
+
if (loginPromise) loginPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
|
|
238
|
+
if (scopesPromise) scopesPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
|
|
239
|
+
if (silentPromise) {
|
|
240
|
+
if (errorStr == "No session") silentPromise->resolve(std::nullopt);
|
|
241
|
+
else silentPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshSuccess(
|
|
246
|
+
JNIEnv* env, jclass, jstring idToken, jstring accessToken, jobject expirationTime) {
|
|
247
|
+
|
|
248
|
+
std::shared_ptr<Promise<AuthTokens>> refreshPromise;
|
|
249
|
+
{
|
|
250
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
251
|
+
refreshPromise = gRefreshPromise;
|
|
252
|
+
gRefreshPromise = nullptr;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (refreshPromise) {
|
|
256
|
+
AuthTokens tokens;
|
|
257
|
+
if (idToken) {
|
|
258
|
+
const char* s = env->GetStringUTFChars(idToken, nullptr);
|
|
259
|
+
tokens.idToken = std::string(s);
|
|
260
|
+
env->ReleaseStringUTFChars(idToken, s);
|
|
261
|
+
}
|
|
262
|
+
if (accessToken) {
|
|
263
|
+
const char* s = env->GetStringUTFChars(accessToken, nullptr);
|
|
264
|
+
tokens.accessToken = std::string(s);
|
|
265
|
+
env->ReleaseStringUTFChars(accessToken, s);
|
|
266
|
+
}
|
|
267
|
+
if (expirationTime) {
|
|
268
|
+
jclass longClass = env->FindClass("java/lang/Long");
|
|
269
|
+
jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
|
|
270
|
+
tokens.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
|
|
271
|
+
}
|
|
272
|
+
refreshPromise->resolve(tokens);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshError(
|
|
277
|
+
JNIEnv* env, jclass, jstring error) {
|
|
278
|
+
|
|
279
|
+
std::shared_ptr<Promise<AuthTokens>> refreshPromise;
|
|
280
|
+
{
|
|
281
|
+
std::lock_guard<std::mutex> lock(gMutex);
|
|
282
|
+
refreshPromise = gRefreshPromise;
|
|
283
|
+
gRefreshPromise = nullptr;
|
|
284
|
+
}
|
|
285
|
+
if (refreshPromise) {
|
|
286
|
+
const char* errorCStr = env->GetStringUTFChars(error, nullptr);
|
|
287
|
+
std::string errorStr(errorCStr);
|
|
288
|
+
env->ReleaseStringUTFChars(error, errorCStr);
|
|
289
|
+
refreshPromise->reject(std::make_exception_ptr(std::runtime_error(errorStr)));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
} // namespace margelo::nitro::NitroAuth
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
package com.auth
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
|
9
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
|
10
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
|
11
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
|
12
|
+
import com.google.android.gms.common.GoogleApiAvailability
|
|
13
|
+
import com.google.android.gms.common.ConnectionResult
|
|
14
|
+
import com.google.android.gms.common.api.Scope
|
|
15
|
+
import java.lang.ref.WeakReference
|
|
16
|
+
|
|
17
|
+
object AuthAdapter : Application.ActivityLifecycleCallbacks {
|
|
18
|
+
private const val TAG = "AuthAdapter"
|
|
19
|
+
private const val PREF_NAME = "nitro_auth"
|
|
20
|
+
|
|
21
|
+
private var appContext: Context? = null
|
|
22
|
+
private var currentActivityRef: WeakReference<Activity>? = null
|
|
23
|
+
private var googleSignInClient: GoogleSignInClient? = null
|
|
24
|
+
private var pendingScopes: List<String> = emptyList()
|
|
25
|
+
|
|
26
|
+
@JvmStatic
|
|
27
|
+
private external fun nativeInitialize(context: Context)
|
|
28
|
+
|
|
29
|
+
@JvmStatic
|
|
30
|
+
private external fun nativeOnLoginSuccess(
|
|
31
|
+
provider: String,
|
|
32
|
+
email: String?,
|
|
33
|
+
name: String?,
|
|
34
|
+
photo: String?,
|
|
35
|
+
idToken: String?,
|
|
36
|
+
accessToken: String?,
|
|
37
|
+
scopes: Array<String>?,
|
|
38
|
+
expirationTime: Long?
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@JvmStatic
|
|
42
|
+
private external fun nativeOnLoginError(error: String)
|
|
43
|
+
|
|
44
|
+
@JvmStatic
|
|
45
|
+
private external fun nativeOnRefreshSuccess(idToken: String?, accessToken: String?, expirationTime: Long?)
|
|
46
|
+
|
|
47
|
+
@JvmStatic
|
|
48
|
+
private external fun nativeOnRefreshError(error: String)
|
|
49
|
+
|
|
50
|
+
fun initialize(context: Context) {
|
|
51
|
+
appContext = context.applicationContext
|
|
52
|
+
(appContext as? Application)?.registerActivityLifecycleCallbacks(this)
|
|
53
|
+
try {
|
|
54
|
+
System.loadLibrary("NitroAuth")
|
|
55
|
+
nativeInitialize(appContext!!)
|
|
56
|
+
} catch (e: Exception) {
|
|
57
|
+
Log.e(TAG, "Failed to load NitroAuth library", e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
|
62
|
+
currentActivityRef = WeakReference(activity)
|
|
63
|
+
}
|
|
64
|
+
override fun onActivityStarted(activity: Activity) {
|
|
65
|
+
currentActivityRef = WeakReference(activity)
|
|
66
|
+
}
|
|
67
|
+
override fun onActivityResumed(activity: Activity) {
|
|
68
|
+
currentActivityRef = WeakReference(activity)
|
|
69
|
+
}
|
|
70
|
+
override fun onActivityPaused(activity: Activity) {}
|
|
71
|
+
override fun onActivityStopped(activity: Activity) {}
|
|
72
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
73
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
74
|
+
if (currentActivityRef?.get() == activity) {
|
|
75
|
+
currentActivityRef = null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fun onSignInSuccess(account: GoogleSignInAccount, scopes: List<String>) {
|
|
80
|
+
val ctx = appContext ?: return
|
|
81
|
+
saveUser(ctx, "google", account.email, account.displayName,
|
|
82
|
+
account.photoUrl?.toString(), account.idToken, scopes)
|
|
83
|
+
nativeOnLoginSuccess("google", account.email, account.displayName,
|
|
84
|
+
account.photoUrl?.toString(), account.idToken, null, scopes.toTypedArray(), null)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fun onSignInError(errorCode: Int, message: String?) {
|
|
88
|
+
val mappedError = when (errorCode) {
|
|
89
|
+
12501 -> "cancelled"
|
|
90
|
+
7 -> "network_error"
|
|
91
|
+
8, 10 -> "configuration_error"
|
|
92
|
+
else -> message ?: "unknown"
|
|
93
|
+
}
|
|
94
|
+
nativeOnLoginError(mappedError)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@JvmStatic
|
|
98
|
+
fun loginSync(context: Context, provider: String, googleClientId: String?, scopes: Array<String>?, loginHint: String?) {
|
|
99
|
+
if (provider == "apple") {
|
|
100
|
+
nativeOnLoginError("Apple Sign-In is not supported on Android.")
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (provider != "google") {
|
|
105
|
+
nativeOnLoginError("Unsupported provider: $provider")
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
val ctx = appContext ?: context.applicationContext
|
|
110
|
+
val clientId = googleClientId ?: getClientIdFromResources(ctx)
|
|
111
|
+
if (clientId == null) {
|
|
112
|
+
nativeOnLoginError("Google Client ID is required. Set it in app.json plugins.")
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
val requestedScopes = scopes?.toList() ?: listOf("email", "profile")
|
|
117
|
+
pendingScopes = requestedScopes
|
|
118
|
+
|
|
119
|
+
val intent = GoogleSignInActivity.createIntent(ctx, clientId, requestedScopes.toTypedArray(), loginHint)
|
|
120
|
+
ctx.startActivity(intent)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@JvmStatic
|
|
124
|
+
fun requestScopesSync(context: Context, scopes: Array<String>) {
|
|
125
|
+
val ctx = appContext ?: context.applicationContext
|
|
126
|
+
val account = GoogleSignIn.getLastSignedInAccount(ctx)
|
|
127
|
+
if (account == null) {
|
|
128
|
+
nativeOnLoginError("No user logged in")
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
val newScopes = scopes.map { Scope(it) }
|
|
133
|
+
if (GoogleSignIn.hasPermissions(account, *newScopes.toTypedArray())) {
|
|
134
|
+
onSignInSuccess(account, (pendingScopes + scopes.toList()).distinct())
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
val clientId = getClientIdFromResources(ctx)
|
|
139
|
+
if (clientId == null) {
|
|
140
|
+
nativeOnLoginError("Google Client ID not configured")
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
val allScopes = (pendingScopes + scopes.toList()).distinct()
|
|
145
|
+
val intent = GoogleSignInActivity.createIntent(ctx, clientId, allScopes.toTypedArray(), account.email)
|
|
146
|
+
ctx.startActivity(intent)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@JvmStatic
|
|
150
|
+
fun refreshTokenSync(context: Context) {
|
|
151
|
+
val ctx = appContext ?: context.applicationContext
|
|
152
|
+
if (googleSignInClient == null) {
|
|
153
|
+
val account = GoogleSignIn.getLastSignedInAccount(ctx)
|
|
154
|
+
if (account == null) {
|
|
155
|
+
nativeOnRefreshError("No user logged in")
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
val clientId = getClientIdFromResources(ctx)
|
|
159
|
+
if (clientId == null) {
|
|
160
|
+
nativeOnRefreshError("Google Client ID not configured")
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
164
|
+
.requestIdToken(clientId)
|
|
165
|
+
.requestEmail()
|
|
166
|
+
.build()
|
|
167
|
+
googleSignInClient = GoogleSignIn.getClient(ctx, gso)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
googleSignInClient!!.silentSignIn().addOnCompleteListener { task ->
|
|
171
|
+
if (task.isSuccessful) {
|
|
172
|
+
val account = task.result
|
|
173
|
+
nativeOnRefreshSuccess(account?.idToken, null, null)
|
|
174
|
+
} else {
|
|
175
|
+
nativeOnRefreshError(task.exception?.message ?: "Silent sign-in failed")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@JvmStatic
|
|
181
|
+
fun hasPlayServices(context: Context): Boolean {
|
|
182
|
+
val ctx = context.applicationContext ?: appContext ?: return false
|
|
183
|
+
val availability = GoogleApiAvailability.getInstance()
|
|
184
|
+
val result = availability.isGooglePlayServicesAvailable(ctx)
|
|
185
|
+
return result == ConnectionResult.SUCCESS
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@JvmStatic
|
|
189
|
+
fun logoutSync(context: Context) {
|
|
190
|
+
val ctx = appContext ?: context.applicationContext
|
|
191
|
+
val clientId = getClientIdFromResources(ctx)
|
|
192
|
+
if (clientId != null) {
|
|
193
|
+
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
194
|
+
.requestIdToken(clientId)
|
|
195
|
+
.requestEmail()
|
|
196
|
+
.build()
|
|
197
|
+
GoogleSignIn.getClient(ctx, gso).signOut()
|
|
198
|
+
}
|
|
199
|
+
clearUser(ctx)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@JvmStatic
|
|
203
|
+
fun revokeAccessSync(context: Context) {
|
|
204
|
+
val ctx = appContext ?: context.applicationContext
|
|
205
|
+
val clientId = getClientIdFromResources(ctx)
|
|
206
|
+
if (clientId != null) {
|
|
207
|
+
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
208
|
+
.requestIdToken(clientId)
|
|
209
|
+
.requestEmail()
|
|
210
|
+
.build()
|
|
211
|
+
GoogleSignIn.getClient(ctx, gso).revokeAccess()
|
|
212
|
+
}
|
|
213
|
+
clearUser(ctx)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private fun getClientIdFromResources(context: Context): String? {
|
|
217
|
+
val resId = context.resources.getIdentifier("nitro_auth_google_client_id", "string", context.packageName)
|
|
218
|
+
return if (resId != 0) context.getString(resId) else null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@JvmStatic
|
|
222
|
+
fun getUserJson(context: Context): String? {
|
|
223
|
+
val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
224
|
+
return pref.getString("user_json", null)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@JvmStatic
|
|
228
|
+
fun setUserJson(context: Context, json: String) {
|
|
229
|
+
val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
230
|
+
pref.edit().putString("user_json", json).apply()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@JvmStatic
|
|
234
|
+
fun clearUser(context: Context) {
|
|
235
|
+
val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
236
|
+
pref.edit().clear().apply()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@JvmStatic
|
|
240
|
+
fun restoreSession(context: Context) {
|
|
241
|
+
val ctx = context.applicationContext ?: appContext ?: context
|
|
242
|
+
val account = GoogleSignIn.getLastSignedInAccount(ctx)
|
|
243
|
+
if (account != null) {
|
|
244
|
+
nativeOnLoginSuccess("google", account.email, account.displayName,
|
|
245
|
+
account.photoUrl?.toString(), account.idToken, null,
|
|
246
|
+
account.grantedScopes?.map { it.scopeUri }?.toTypedArray(), null)
|
|
247
|
+
} else {
|
|
248
|
+
val json = getUserJson(ctx)
|
|
249
|
+
if (json != null) {
|
|
250
|
+
val provider = if (json.contains("\"provider\":\"google\"")) "google" else "apple"
|
|
251
|
+
val email = extractJsonValue(json, "email")
|
|
252
|
+
val name = extractJsonValue(json, "name")
|
|
253
|
+
val photo = extractJsonValue(json, "photo")
|
|
254
|
+
val idToken = extractJsonValue(json, "idToken")
|
|
255
|
+
nativeOnLoginSuccess(provider, email, name, photo, idToken, null, null, null)
|
|
256
|
+
} else {
|
|
257
|
+
nativeOnLoginError("No session")
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private fun extractJsonValue(json: String, key: String): String? {
|
|
263
|
+
val pattern = "\"$key\":\"([^\"]*)\""
|
|
264
|
+
val regex = Regex(pattern)
|
|
265
|
+
return regex.find(json)?.groupValues?.get(1)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun saveUser(context: Context, provider: String, email: String?, name: String?,
|
|
269
|
+
photo: String?, idToken: String?, scopes: List<String>?) {
|
|
270
|
+
val pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
271
|
+
val json = StringBuilder()
|
|
272
|
+
json.append("{")
|
|
273
|
+
json.append("\"provider\":\"$provider\"")
|
|
274
|
+
if (email != null) json.append(",\"email\":\"$email\"")
|
|
275
|
+
if (name != null) json.append(",\"name\":\"$name\"")
|
|
276
|
+
if (photo != null) json.append(",\"photo\":\"$photo\"")
|
|
277
|
+
if (idToken != null) json.append(",\"idToken\":\"$idToken\"")
|
|
278
|
+
if (scopes != null) {
|
|
279
|
+
json.append(",\"scopes\":[")
|
|
280
|
+
json.append(scopes.joinToString(",") { "\"$it\"" })
|
|
281
|
+
json.append("]")
|
|
282
|
+
}
|
|
283
|
+
json.append("}")
|
|
284
|
+
pref.edit().putString("user_json", json.toString()).apply()
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.auth
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import androidx.activity.ComponentActivity
|
|
9
|
+
import androidx.activity.result.contract.ActivityResultContracts
|
|
10
|
+
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
|
11
|
+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
|
12
|
+
import com.google.android.gms.common.api.ApiException
|
|
13
|
+
import com.google.android.gms.common.api.Scope
|
|
14
|
+
|
|
15
|
+
class GoogleSignInActivity : ComponentActivity() {
|
|
16
|
+
companion object {
|
|
17
|
+
private const val TAG = "GoogleSignInActivity"
|
|
18
|
+
private const val EXTRA_CLIENT_ID = "client_id"
|
|
19
|
+
private const val EXTRA_SCOPES = "scopes"
|
|
20
|
+
private const val EXTRA_LOGIN_HINT = "login_hint"
|
|
21
|
+
|
|
22
|
+
fun createIntent(context: Context, clientId: String, scopes: Array<String>, loginHint: String?): Intent {
|
|
23
|
+
return Intent(context, GoogleSignInActivity::class.java).apply {
|
|
24
|
+
putExtra(EXTRA_CLIENT_ID, clientId)
|
|
25
|
+
putExtra(EXTRA_SCOPES, scopes)
|
|
26
|
+
putExtra(EXTRA_LOGIN_HINT, loginHint)
|
|
27
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private val signInLauncher = registerForActivityResult(
|
|
33
|
+
ActivityResultContracts.StartActivityForResult()
|
|
34
|
+
) { result ->
|
|
35
|
+
try {
|
|
36
|
+
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
|
|
37
|
+
val account = task.getResult(ApiException::class.java)
|
|
38
|
+
val scopes = intent.getStringArrayExtra(EXTRA_SCOPES)?.toList() ?: emptyList()
|
|
39
|
+
AuthAdapter.onSignInSuccess(account, scopes)
|
|
40
|
+
} catch (e: ApiException) {
|
|
41
|
+
AuthAdapter.onSignInError(e.statusCode, e.message)
|
|
42
|
+
}
|
|
43
|
+
finish()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
47
|
+
super.onCreate(savedInstanceState)
|
|
48
|
+
val clientId = intent.getStringExtra(EXTRA_CLIENT_ID)
|
|
49
|
+
val scopes = intent.getStringArrayExtra(EXTRA_SCOPES) ?: arrayOf("email", "profile")
|
|
50
|
+
val loginHint = intent.getStringExtra(EXTRA_LOGIN_HINT)
|
|
51
|
+
|
|
52
|
+
if (clientId == null) {
|
|
53
|
+
AuthAdapter.onSignInError(8, "Missing client ID")
|
|
54
|
+
finish()
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val gsoBuilder = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
59
|
+
.requestIdToken(clientId)
|
|
60
|
+
.requestEmail()
|
|
61
|
+
|
|
62
|
+
scopes.forEach { scopeStr ->
|
|
63
|
+
if (scopeStr != "email" && scopeStr != "profile" && scopeStr != "openid") {
|
|
64
|
+
gsoBuilder.requestScopes(Scope(scopeStr))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (loginHint != null) gsoBuilder.setAccountName(loginHint)
|
|
69
|
+
|
|
70
|
+
val client = GoogleSignIn.getClient(this, gsoBuilder.build())
|
|
71
|
+
signInLauncher.launch(client.signInIntent)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.auth
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
|
|
7
|
+
class NitroAuthModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
8
|
+
override fun getName(): String = "NitroAuthModule"
|
|
9
|
+
|
|
10
|
+
init {
|
|
11
|
+
try {
|
|
12
|
+
AuthAdapter.initialize(reactContext)
|
|
13
|
+
com.margelo.nitro.com.auth.NitroAuthOnLoad.initializeNative()
|
|
14
|
+
Log.d("NitroAuthModule", "NitroAuth initialized")
|
|
15
|
+
} catch (e: Exception) {
|
|
16
|
+
Log.e("NitroAuthModule", "Failed to initialize NitroAuth", e)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.auth
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class NitroAuthPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(NitroAuthModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|