react-native-oauth-essentials 1.0.2
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 +20 -0
- package/OauthEssentials.podspec +22 -0
- package/README.md +30 -0
- package/android/build.gradle +74 -0
- package/android/src/main/AndroidManifest.xml +22 -0
- package/android/src/main/java/com/oauthessentials/CredentialError.kt +7 -0
- package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +114 -0
- package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +133 -0
- package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +603 -0
- package/android/src/main/java/com/oauthessentials/OauthEssentialsPackage.kt +33 -0
- package/android/src/main/java/com/oauthessentials/RCTPromiseSettler.kt +60 -0
- package/ios/AppleIdRetriever.swift +61 -0
- package/ios/CredentialError.swift +6 -0
- package/ios/CredentialFactory.swift +133 -0
- package/ios/CredentialsType.swift +7 -0
- package/ios/GoogleIdRetriever.swift +83 -0
- package/ios/HybridRetriever.swift +64 -0
- package/ios/OauthEssentials.h +8 -0
- package/ios/OauthEssentials.mm +104 -0
- package/ios/OauthEssentialsModule.swift +210 -0
- package/ios/PasskeyRetriever.swift +226 -0
- package/ios/PasswordRetriever.swift +72 -0
- package/ios/RCTPromiseSettler.swift +28 -0
- package/lib/module/NativeOauthEssentials.js +5 -0
- package/lib/module/NativeOauthEssentials.js.map +1 -0
- package/lib/module/index.js +148 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +23 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeOauthEssentials.d.ts +60 -0
- package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +78 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +71 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +156 -0
- package/src/NativeOauthEssentials.ts +75 -0
- package/src/index.tsx +199 -0
- package/src/types.ts +79 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
package com.oauthessentials
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.BroadcastReceiver
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.IntentFilter
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import android.util.Patterns
|
|
12
|
+
import androidx.annotation.RequiresApi
|
|
13
|
+
import androidx.core.content.ContextCompat
|
|
14
|
+
import androidx.core.net.toUri
|
|
15
|
+
import androidx.credentials.CreatePasswordRequest
|
|
16
|
+
import androidx.credentials.CreatePublicKeyCredentialRequest
|
|
17
|
+
import androidx.credentials.CredentialManager
|
|
18
|
+
import androidx.credentials.GetCredentialRequest
|
|
19
|
+
import androidx.credentials.GetPasswordOption
|
|
20
|
+
import androidx.credentials.GetPublicKeyCredentialOption
|
|
21
|
+
import androidx.credentials.exceptions.CreateCredentialCancellationException
|
|
22
|
+
import androidx.credentials.exceptions.GetCredentialCancellationException
|
|
23
|
+
import com.facebook.react.bridge.Arguments
|
|
24
|
+
import com.facebook.react.bridge.Promise
|
|
25
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
26
|
+
import com.facebook.react.bridge.ReadableMap
|
|
27
|
+
import com.facebook.react.bridge.WritableMap
|
|
28
|
+
import com.google.android.gms.common.ConnectionResult
|
|
29
|
+
import com.google.android.gms.common.GoogleApiAvailability
|
|
30
|
+
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
|
31
|
+
import kotlinx.coroutines.CoroutineScope
|
|
32
|
+
import kotlinx.coroutines.Dispatchers
|
|
33
|
+
import kotlinx.coroutines.Job
|
|
34
|
+
import kotlinx.coroutines.SupervisorJob
|
|
35
|
+
import kotlinx.coroutines.cancel
|
|
36
|
+
import kotlinx.coroutines.launch
|
|
37
|
+
import kotlinx.coroutines.withContext
|
|
38
|
+
import java.security.SecureRandom
|
|
39
|
+
import java.util.Base64
|
|
40
|
+
import java.util.UUID
|
|
41
|
+
|
|
42
|
+
class OauthEssentialsModule(reactContext: ReactApplicationContext) :
|
|
43
|
+
NativeOauthEssentialsSpec(reactContext) {
|
|
44
|
+
|
|
45
|
+
private var appleSignInPromiseSettler: RCTPromiseSettler? = null
|
|
46
|
+
private val moduleScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
47
|
+
|
|
48
|
+
private val receiver = object : BroadcastReceiver() {
|
|
49
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
50
|
+
intent?.action?.let { action ->
|
|
51
|
+
Log.d(LOG_TAG, "Handling intent action ${action} received")
|
|
52
|
+
when (action) {
|
|
53
|
+
WEB_APPLE_DEEP_LINK_RECEIVED_INTENT -> {
|
|
54
|
+
val url = intent.getStringExtra("url")
|
|
55
|
+
Log.d(LOG_TAG, "Receiving deep link ${url}")
|
|
56
|
+
intent.getStringExtra("url")?.let { url ->
|
|
57
|
+
val uri = url.toUri()
|
|
58
|
+
appleSignInPromiseSettler?.let {
|
|
59
|
+
if (!it.settled()) {
|
|
60
|
+
Log.d(LOG_TAG, "Sending deep link callback")
|
|
61
|
+
val result = Arguments.createMap().apply {
|
|
62
|
+
putString("type", CredentialFactory.CredentialsType.WEB_APPLE_ID.code)
|
|
63
|
+
putMap("data", Arguments.createMap().apply {
|
|
64
|
+
putString("url", uri.toString())
|
|
65
|
+
putString("scheme", uri.scheme)
|
|
66
|
+
putString("host", uri.host)
|
|
67
|
+
putString("path", uri.path)
|
|
68
|
+
putString("query", uri.query)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
resolveAndEmit(result, it)
|
|
72
|
+
} else {
|
|
73
|
+
Log.d(LOG_TAG, "Cannot call deep link callback")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
WEB_APPLE_CANCELLED_INTENT -> {
|
|
80
|
+
Log.d(LOG_TAG, "Receiving cancellation from CustomTabsActivity")
|
|
81
|
+
appleSignInPromiseSettler?.let {
|
|
82
|
+
if (!it.settled()) {
|
|
83
|
+
Log.d(LOG_TAG, "Sending cancellation callback")
|
|
84
|
+
resolveAndEmit(CredentialFactory.fromCancelled(), it)
|
|
85
|
+
} else {
|
|
86
|
+
Log.d(LOG_TAG, "Cannot call cancellation callback, promise already settled")
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
else -> {
|
|
92
|
+
throw IllegalArgumentException("Intent action not supported")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@SuppressLint("ObsoleteSdkInt")
|
|
100
|
+
override fun getTypedExportedConstants(): MutableMap<String, Boolean> {
|
|
101
|
+
hasGooglePlayServices()
|
|
102
|
+
val hasCredentialManager = getCredentialManagerSupported()
|
|
103
|
+
val constants: MutableMap<String, Boolean> = mutableMapOf(
|
|
104
|
+
"GOOGLE_PLAY_SERVICES_SUPPORTED" to hasGooglePlayServices(),
|
|
105
|
+
"PASSWORD_SUPPORTED" to hasCredentialManager,
|
|
106
|
+
"GOOGLE_ID_SUPPORTED" to googleIdSupported(),
|
|
107
|
+
"APPLE_ID_SUPPORTED" to getAppleIdSupported(),
|
|
108
|
+
"HYBRID_SUPPORTED" to hasCredentialManager,
|
|
109
|
+
"PASSKEYS_SUPPORTED" to getPasskeysSupported()
|
|
110
|
+
)
|
|
111
|
+
return constants
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
115
|
+
override fun hybridSignIn(clientId: String, options: ReadableMap, promise: Promise) {
|
|
116
|
+
if (!hasGooglePlayServices()) {
|
|
117
|
+
rejectAndEmitEvent(
|
|
118
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
119
|
+
"Device does not have GooglePlay Services",
|
|
120
|
+
promise
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
if (!getCredentialManagerSupported()) {
|
|
125
|
+
rejectAndEmitEvent(
|
|
126
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
127
|
+
"Device does not support Credential Manager, only android >=12 allowed.",
|
|
128
|
+
promise
|
|
129
|
+
)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
val activity = reactApplicationContext.currentActivity
|
|
134
|
+
if (activity == null) {
|
|
135
|
+
rejectAndEmitEvent(
|
|
136
|
+
CredentialError.NO_ACTIVITY_ERROR.code,
|
|
137
|
+
"Activity not available",
|
|
138
|
+
promise
|
|
139
|
+
)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
moduleScope.launch {
|
|
144
|
+
try {
|
|
145
|
+
val manager = CredentialManager.create(activity)
|
|
146
|
+
val randomBytes = ByteArray(32)
|
|
147
|
+
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
|
|
148
|
+
val randomNonce = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
|
|
149
|
+
|
|
150
|
+
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
|
|
151
|
+
.setNonce(randomNonce)
|
|
152
|
+
.setServerClientId(clientId)
|
|
153
|
+
.setFilterByAuthorizedAccounts(options.getBoolean("authorizedAccounts"))
|
|
154
|
+
.setAutoSelectEnabled(options.getBoolean("autoSelectEnabled"))
|
|
155
|
+
.build()
|
|
156
|
+
|
|
157
|
+
val request: GetCredentialRequest = GetCredentialRequest.Builder()
|
|
158
|
+
.addCredentialOption(googleIdOption)
|
|
159
|
+
.addCredentialOption(GetPasswordOption())
|
|
160
|
+
.build()
|
|
161
|
+
|
|
162
|
+
resolveAndEmit(
|
|
163
|
+
CredentialFactory.fromCredentialResponse(
|
|
164
|
+
manager.getCredential(
|
|
165
|
+
request = request,
|
|
166
|
+
context = activity
|
|
167
|
+
)
|
|
168
|
+
), promise
|
|
169
|
+
)
|
|
170
|
+
} catch (e: GetCredentialCancellationException) {
|
|
171
|
+
Log.d(LOG_TAG, e.toString())
|
|
172
|
+
resolveAndEmit(CredentialFactory.fromCancelled(), promise)
|
|
173
|
+
} catch (e: Throwable) {
|
|
174
|
+
Log.d(LOG_TAG, e.toString())
|
|
175
|
+
rejectAndEmitEvent(
|
|
176
|
+
CredentialError.INVALID_RESULT_ERROR.code,
|
|
177
|
+
e,
|
|
178
|
+
promise
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Performs the apple sign in.
|
|
186
|
+
* The signing in is redirecting to the device default browser to handle it.
|
|
187
|
+
*/
|
|
188
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
189
|
+
override fun appleSignIn(androidWebUrl: String?, promise: Promise) {
|
|
190
|
+
appleSignInPromiseSettler?.let {
|
|
191
|
+
if (!it.settled()) {
|
|
192
|
+
it.resolve(CredentialFactory.fromCancelled())
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
appleSignInPromiseSettler = null
|
|
196
|
+
|
|
197
|
+
val activity = this.reactApplicationContext.currentActivity
|
|
198
|
+
if (activity === null) {
|
|
199
|
+
rejectAndEmitEvent(
|
|
200
|
+
CredentialError.NO_ACTIVITY_ERROR.code,
|
|
201
|
+
"Could not find app activity.",
|
|
202
|
+
promise
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
var uri: Uri
|
|
207
|
+
try {
|
|
208
|
+
uri = parseWebUrl(androidWebUrl)
|
|
209
|
+
} catch (e: Throwable) {
|
|
210
|
+
rejectAndEmitEvent(CredentialError.INVALID_RESULT_ERROR.code, e, promise)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
appleSignInPromiseSettler = RCTPromiseSettler(promise)
|
|
215
|
+
activity.runOnUiThread {
|
|
216
|
+
val intent = Intent(activity, CustomTabsActivity::class.java).apply {
|
|
217
|
+
data = uri
|
|
218
|
+
action = DISPLAY_CUSTOM_TABS_INTENT
|
|
219
|
+
}
|
|
220
|
+
activity.startActivity(intent)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Performs the google sign in.
|
|
226
|
+
*/
|
|
227
|
+
@Suppress("DEPRECATION")
|
|
228
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
229
|
+
override fun googleSignIn(clientId: String, options: ReadableMap, promise: Promise) {
|
|
230
|
+
Log.d(LOG_TAG, "Calling googleSignIn")
|
|
231
|
+
if (!googleIdSupported() || !getCredentialManagerSupported()) {
|
|
232
|
+
rejectAndEmitEvent(
|
|
233
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
234
|
+
"Device does not support googleSignIn method.",
|
|
235
|
+
promise
|
|
236
|
+
)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
val activity = reactApplicationContext.currentActivity
|
|
241
|
+
if (activity == null) {
|
|
242
|
+
rejectAndEmitEvent(CredentialError.NO_ACTIVITY_ERROR.code, "Activity not available", promise)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
moduleScope.launch {
|
|
247
|
+
try {
|
|
248
|
+
val manager = CredentialManager.create(activity)
|
|
249
|
+
|
|
250
|
+
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
|
|
251
|
+
.setNonce(UUID.randomUUID().toString())
|
|
252
|
+
.setServerClientId(clientId)
|
|
253
|
+
.setFilterByAuthorizedAccounts(options.getBoolean("authorizedAccounts"))
|
|
254
|
+
.setAutoSelectEnabled(options.getBoolean("autoSelectEnabled"))
|
|
255
|
+
.build()
|
|
256
|
+
|
|
257
|
+
val request: GetCredentialRequest = GetCredentialRequest.Builder()
|
|
258
|
+
.addCredentialOption(googleIdOption)
|
|
259
|
+
.build()
|
|
260
|
+
|
|
261
|
+
val result = withContext(Dispatchers.Main) {
|
|
262
|
+
manager.getCredential(activity, request)
|
|
263
|
+
}
|
|
264
|
+
resolveAndEmit(CredentialFactory.fromCredentialResponse(result), promise)
|
|
265
|
+
} catch (e: GetCredentialCancellationException) {
|
|
266
|
+
Log.d(LOG_TAG, e.toString())
|
|
267
|
+
resolveAndEmit(CredentialFactory.fromCancelled(), promise)
|
|
268
|
+
} catch (e: Throwable) {
|
|
269
|
+
rejectAndEmitEvent(CredentialError.INVALID_RESULT_ERROR.code, e.toString(), promise)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Gets the user current credentials.
|
|
276
|
+
*/
|
|
277
|
+
override fun getPassword(promise: Promise) {
|
|
278
|
+
if (!googleIdSupported()) {
|
|
279
|
+
rejectAndEmitEvent(
|
|
280
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
281
|
+
"Device does not support googleSignIn method.",
|
|
282
|
+
promise
|
|
283
|
+
)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
val activity = reactApplicationContext.currentActivity
|
|
288
|
+
if (activity == null) {
|
|
289
|
+
rejectAndEmitEvent(
|
|
290
|
+
CredentialError.NO_ACTIVITY_ERROR.code,
|
|
291
|
+
"Activity not available",
|
|
292
|
+
promise
|
|
293
|
+
)
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
moduleScope.launch {
|
|
298
|
+
try {
|
|
299
|
+
val manager = CredentialManager.create(activity)
|
|
300
|
+
val request = GetCredentialRequest(
|
|
301
|
+
listOf(GetPasswordOption())
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
resolveAndEmit(
|
|
305
|
+
CredentialFactory.fromCredentialResponse(
|
|
306
|
+
manager.getCredential(
|
|
307
|
+
request = request,
|
|
308
|
+
context = activity
|
|
309
|
+
)
|
|
310
|
+
), promise
|
|
311
|
+
)
|
|
312
|
+
} catch (e: Throwable) {
|
|
313
|
+
Log.d(LOG_TAG, e.toString())
|
|
314
|
+
promise.resolve(false)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Stores a brand new username and password into the OS.
|
|
321
|
+
*/
|
|
322
|
+
override fun passwordSignIn(username: String, password: String, promise: Promise) {
|
|
323
|
+
if (!googleIdSupported()) {
|
|
324
|
+
rejectAndEmitEvent(
|
|
325
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
326
|
+
"Device does not support googleSignIn method.",
|
|
327
|
+
promise
|
|
328
|
+
)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
val activity = reactApplicationContext.currentActivity
|
|
333
|
+
if (activity == null) {
|
|
334
|
+
rejectAndEmitEvent(CredentialError.NO_ACTIVITY_ERROR.code, "Activity not available", promise)
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
moduleScope.launch {
|
|
339
|
+
try {
|
|
340
|
+
val manager = CredentialManager.create(activity)
|
|
341
|
+
val request = CreatePasswordRequest(
|
|
342
|
+
id = username,
|
|
343
|
+
password = password
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
manager.createCredential(
|
|
347
|
+
request = request,
|
|
348
|
+
context = activity
|
|
349
|
+
)
|
|
350
|
+
promise.resolve(true)
|
|
351
|
+
} catch (e: Throwable) {
|
|
352
|
+
Log.e(LOG_TAG, e.toString())
|
|
353
|
+
promise.resolve(false)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Creates the passkey.
|
|
360
|
+
*/
|
|
361
|
+
@RequiresApi(Build.VERSION_CODES.P)
|
|
362
|
+
override fun createPassKey(options: ReadableMap, promise: Promise) {
|
|
363
|
+
if (!getPasskeysSupported()) {
|
|
364
|
+
rejectAndEmitEvent(
|
|
365
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
366
|
+
"Device does not support googleSignIn method.",
|
|
367
|
+
promise
|
|
368
|
+
)
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
val activity = reactApplicationContext.currentActivity
|
|
373
|
+
if (activity == null) {
|
|
374
|
+
rejectAndEmitEvent(CredentialError.NO_ACTIVITY_ERROR.code, "Activity not available", promise)
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
moduleScope.launch {
|
|
379
|
+
try {
|
|
380
|
+
val requestJson = options.getString("requestJson")
|
|
381
|
+
?: throw IllegalArgumentException("\"requestJson\" field should be set on android.")
|
|
382
|
+
var preferImmediatelyAvailableCredentials = false
|
|
383
|
+
if (options.hasKey("preferImmediatelyAvailableCredentials")) {
|
|
384
|
+
preferImmediatelyAvailableCredentials =
|
|
385
|
+
options.getBoolean("preferImmediatelyAvailableCredentials")
|
|
386
|
+
}
|
|
387
|
+
var isConditional = false
|
|
388
|
+
if (options.hasKey("isConditional")) {
|
|
389
|
+
isConditional = options.getBoolean("isConditional")
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
val manager = CredentialManager.create(activity)
|
|
393
|
+
val request = CreatePublicKeyCredentialRequest(
|
|
394
|
+
requestJson = requestJson,
|
|
395
|
+
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
|
|
396
|
+
isConditional = isConditional
|
|
397
|
+
)
|
|
398
|
+
val result = manager.createCredential(
|
|
399
|
+
request = request,
|
|
400
|
+
context = activity
|
|
401
|
+
)
|
|
402
|
+
resolveAndEmit(CredentialFactory.fromPublicKeyCredential(result), promise)
|
|
403
|
+
} catch (e: Throwable) {
|
|
404
|
+
when (e) {
|
|
405
|
+
is CreateCredentialCancellationException -> {
|
|
406
|
+
resolveAndEmit(CredentialFactory.fromCancelled(), promise)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
else -> {
|
|
410
|
+
Log.e(LOG_TAG, e.toString())
|
|
411
|
+
rejectAndEmitEvent(CredentialError.INVALID_RESULT_ERROR.code, e.toString(), promise)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Gets the passkey.
|
|
420
|
+
*/
|
|
421
|
+
@RequiresApi(Build.VERSION_CODES.P)
|
|
422
|
+
override fun getPassKey(options: ReadableMap, promise: Promise) {
|
|
423
|
+
if (!getPasskeysSupported()) {
|
|
424
|
+
rejectAndEmitEvent(
|
|
425
|
+
CredentialError.NOT_SUPPORTED_ERROR.code,
|
|
426
|
+
"Device does not support googleSignIn method.",
|
|
427
|
+
promise
|
|
428
|
+
)
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
val activity = reactApplicationContext.currentActivity
|
|
433
|
+
if (activity == null) {
|
|
434
|
+
rejectAndEmitEvent(CredentialError.NO_ACTIVITY_ERROR.code, "Activity not available", promise)
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
moduleScope.launch {
|
|
439
|
+
try {
|
|
440
|
+
val requestJson = options.getString("requestJson")
|
|
441
|
+
?: throw IllegalArgumentException("\"requestJson\" field should be set on android.")
|
|
442
|
+
var preferImmediatelyAvailableCredentials = false
|
|
443
|
+
if (options.hasKey("preferImmediatelyAvailableCredentials")) {
|
|
444
|
+
preferImmediatelyAvailableCredentials =
|
|
445
|
+
options.getBoolean("preferImmediatelyAvailableCredentials")
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
val manager = CredentialManager.create(activity)
|
|
449
|
+
val request = GetCredentialRequest(
|
|
450
|
+
listOf(
|
|
451
|
+
GetPublicKeyCredentialOption(
|
|
452
|
+
requestJson = requestJson,
|
|
453
|
+
),
|
|
454
|
+
),
|
|
455
|
+
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
|
|
456
|
+
)
|
|
457
|
+
val result = manager.getCredential(
|
|
458
|
+
request = request,
|
|
459
|
+
context = activity
|
|
460
|
+
)
|
|
461
|
+
resolveAndEmit(CredentialFactory.fromPublicKeyCredential(result), promise)
|
|
462
|
+
} catch (e: Throwable) {
|
|
463
|
+
when (e) {
|
|
464
|
+
is CreateCredentialCancellationException -> {
|
|
465
|
+
resolveAndEmit(CredentialFactory.fromCancelled(), promise)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
else -> {
|
|
469
|
+
Log.e(LOG_TAG, e.toString())
|
|
470
|
+
rejectAndEmitEvent(CredentialError.INVALID_RESULT_ERROR.code, e.toString(), promise)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Gets if has google play services.
|
|
479
|
+
*/
|
|
480
|
+
private fun hasGooglePlayServices(): Boolean {
|
|
481
|
+
val result = GoogleApiAvailability.getInstance()
|
|
482
|
+
.isGooglePlayServicesAvailable(reactApplicationContext)
|
|
483
|
+
return result == ConnectionResult.SUCCESS
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
487
|
+
override fun initialize() {
|
|
488
|
+
super.initialize()
|
|
489
|
+
|
|
490
|
+
Log.d(LOG_TAG, "Initialize has been called")
|
|
491
|
+
Log.d(LOG_TAG, "Registering deep link broadcast receiver")
|
|
492
|
+
ContextCompat.registerReceiver(
|
|
493
|
+
reactApplicationContext.applicationContext,
|
|
494
|
+
receiver,
|
|
495
|
+
IntentFilter().apply {
|
|
496
|
+
addAction(WEB_APPLE_DEEP_LINK_RECEIVED_INTENT)
|
|
497
|
+
addAction(WEB_APPLE_CANCELLED_INTENT)
|
|
498
|
+
},
|
|
499
|
+
ContextCompat.RECEIVER_NOT_EXPORTED
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
override fun invalidate() {
|
|
504
|
+
super.invalidate()
|
|
505
|
+
Log.d(LOG_TAG, "Invalidate has been called")
|
|
506
|
+
|
|
507
|
+
Log.d(LOG_TAG, "Cancelling all coroutines and credential operations")
|
|
508
|
+
moduleScope.cancel()
|
|
509
|
+
|
|
510
|
+
appleSignInPromiseSettler?.let {
|
|
511
|
+
if (!it.settled()) {
|
|
512
|
+
Log.d(LOG_TAG, "Rejecting pending Apple sign-in promise without event")
|
|
513
|
+
it.reject(
|
|
514
|
+
"CANCELLED",
|
|
515
|
+
"Module is being invalidated",
|
|
516
|
+
null,
|
|
517
|
+
null
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
appleSignInPromiseSettler = null
|
|
522
|
+
|
|
523
|
+
Log.d(LOG_TAG, "Unregistering deep link broadcast receiver")
|
|
524
|
+
reactApplicationContext.unregisterReceiver(receiver)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Parses the web url.
|
|
529
|
+
*/
|
|
530
|
+
private fun parseWebUrl(url: String?): Uri {
|
|
531
|
+
val url = url?.trim()
|
|
532
|
+
?: throw IllegalArgumentException("URL cannot be null")
|
|
533
|
+
|
|
534
|
+
if (!Patterns.WEB_URL.matcher(url).matches()) {
|
|
535
|
+
throw IllegalArgumentException("Invalid URL format: $url")
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return url.toUri()
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private fun googleIdSupported(): Boolean = hasGooglePlayServices()
|
|
542
|
+
|
|
543
|
+
private fun getAppleIdSupported(): Boolean = true
|
|
544
|
+
|
|
545
|
+
private fun getCredentialManagerSupported(): Boolean = true
|
|
546
|
+
|
|
547
|
+
private fun getPasskeysSupported(): Boolean {
|
|
548
|
+
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getCredentialManagerSupported()
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Reject and emit event.
|
|
553
|
+
*/
|
|
554
|
+
private fun rejectAndEmitEvent(code: String, e: Throwable, promise: Promise) {
|
|
555
|
+
promise.reject(code, e)
|
|
556
|
+
|
|
557
|
+
val error = Arguments.createMap()
|
|
558
|
+
error.putString("code", code)
|
|
559
|
+
error.putString("message", e.toString())
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Reject and emit event.
|
|
564
|
+
*/
|
|
565
|
+
private fun rejectAndEmitEvent(code: String, e: String, promise: Promise) {
|
|
566
|
+
promise.reject(code, e)
|
|
567
|
+
|
|
568
|
+
val error = Arguments.createMap()
|
|
569
|
+
error.putString("code", code)
|
|
570
|
+
error.putString("message", e)
|
|
571
|
+
emitOnCredentialFailure(error)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Resolves and emit event.
|
|
576
|
+
*/
|
|
577
|
+
private fun resolveAndEmit(data: WritableMap, promise: Promise) {
|
|
578
|
+
promise.resolve(data.copy())
|
|
579
|
+
emitOnCredentialSuccess(data)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Resolves and emit event.
|
|
584
|
+
*/
|
|
585
|
+
private fun resolveAndEmit(data: WritableMap, settler: RCTPromiseSettler) {
|
|
586
|
+
settler.resolve(data.copy())
|
|
587
|
+
emitOnCredentialSuccess(data)
|
|
588
|
+
Log.d(LOG_TAG, "Resolving event emitOnCredentialSuccess")
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
companion object {
|
|
592
|
+
const val NAME = NativeOauthEssentialsSpec.NAME
|
|
593
|
+
|
|
594
|
+
const val LOG_TAG = "NativeOauthEssentials"
|
|
595
|
+
|
|
596
|
+
// Apple OAuth intents
|
|
597
|
+
const val WEB_APPLE_DEEP_LINK_RECEIVED_INTENT =
|
|
598
|
+
"com.oauthessentials.WEB_APPLE_DEEP_LINK_RECEIVED_INTENT"
|
|
599
|
+
const val WEB_APPLE_CANCELLED_INTENT =
|
|
600
|
+
"com.oauthessentials.WEB_APPLE_CANCELLED_INTENT"
|
|
601
|
+
const val DISPLAY_CUSTOM_TABS_INTENT = "com.oauthessentials.DISPLAY_CUSTOM_TABS_INTENT"
|
|
602
|
+
}
|
|
603
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.oauthessentials
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class OauthEssentialsPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == OauthEssentialsModule.NAME) {
|
|
13
|
+
OauthEssentialsModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
22
|
+
moduleInfos[OauthEssentialsModule.NAME] = ReactModuleInfo(
|
|
23
|
+
OauthEssentialsModule.NAME,
|
|
24
|
+
OauthEssentialsModule.NAME,
|
|
25
|
+
false, // canOverrideExistingModule
|
|
26
|
+
false, // needsEagerInit
|
|
27
|
+
false, // isCxxModule
|
|
28
|
+
true // isTurboModule
|
|
29
|
+
)
|
|
30
|
+
moduleInfos
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.oauthessentials
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
|
|
6
|
+
class RCTPromiseSettler(private val promise: Promise) {
|
|
7
|
+
private var settled = false
|
|
8
|
+
|
|
9
|
+
fun settled(): Boolean {
|
|
10
|
+
return settled
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fun resolve(value: Any) {
|
|
14
|
+
if (settled) {
|
|
15
|
+
throw IllegalStateException("Cannot resolve an already settled promise.")
|
|
16
|
+
}
|
|
17
|
+
settled = true
|
|
18
|
+
promise.resolve(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun reject(throwable: Throwable, userInfo: WritableMap) {
|
|
22
|
+
if (settled) {
|
|
23
|
+
throw IllegalStateException("Cannot reject an already settled promise.")
|
|
24
|
+
}
|
|
25
|
+
settled = true
|
|
26
|
+
promise.reject(throwable, userInfo)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fun reject(code: String, userInfo: WritableMap) {
|
|
30
|
+
if (settled) {
|
|
31
|
+
throw IllegalStateException("Cannot reject an already settled promise.")
|
|
32
|
+
}
|
|
33
|
+
settled = true
|
|
34
|
+
promise.reject(code, userInfo)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun reject(code: String, throwable: Throwable?, userInfo: WritableMap) {
|
|
38
|
+
if (settled) {
|
|
39
|
+
throw IllegalStateException("Cannot reject an already settled promise.")
|
|
40
|
+
}
|
|
41
|
+
settled = true
|
|
42
|
+
promise.reject(code, throwable, userInfo)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fun reject(code: String, message: String?, userInfo: WritableMap) {
|
|
46
|
+
if (settled) {
|
|
47
|
+
throw IllegalStateException("Cannot reject an already settled promise.")
|
|
48
|
+
}
|
|
49
|
+
settled = true
|
|
50
|
+
promise.reject(code, message, userInfo)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fun reject(code: String?, message: String?, throwable: Throwable?, userInfo: WritableMap?) {
|
|
54
|
+
if (settled) {
|
|
55
|
+
throw IllegalStateException("Cannot reject an already settled promise.")
|
|
56
|
+
}
|
|
57
|
+
settled = true
|
|
58
|
+
promise.reject(code, message, throwable, userInfo)
|
|
59
|
+
}
|
|
60
|
+
}
|