otpless-headless-rn 2.2.0 → 3.0.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/README.md +30 -1
- package/android/build.gradle +3 -3
- package/android/gradle.properties +2 -2
- package/android/src/main/java/com/otplessheadlessrn/OtplessReactNativeModule.kt +111 -133
- package/android/src/main/java/com/otplessheadlessrn/utils.kt +96 -0
- package/ios/OtplessHeadlessRN.m +13 -4
- package/ios/OtplessHeadlessRN.swift +128 -30
- package/lib/commonjs/index.js +53 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/version.js +24 -0
- package/lib/commonjs/version.js.map +1 -0
- package/lib/module/index.js +53 -11
- package/lib/module/index.js.map +1 -1
- package/lib/module/version.js +18 -0
- package/lib/module/version.js.map +1 -0
- package/lib/typescript/index.d.ts +9 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models.d.ts +35 -1
- package/lib/typescript/models.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +18 -0
- package/lib/typescript/version.d.ts.map +1 -0
- package/otpless-headless-rn.podspec +1 -1
- package/package.json +5 -2
- package/src/index.tsx +79 -15
- package/src/models.tsx +127 -65
- package/src/version.ts +17 -0
package/README.md
CHANGED
|
@@ -44,6 +44,24 @@ useEffect(() => {
|
|
|
44
44
|
}, []);
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### SSL pinning (optional)
|
|
48
|
+
|
|
49
|
+
SSL pinning is **off by default**; the two-argument `initialize` call above behaves exactly as before. To opt in, pass a third `options` argument:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
headlessModule.initialize("YOUR_APPID", null, { sslPinning: 'enabled' });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
type OtplessSslPinning = 'enabled' | 'disabled';
|
|
57
|
+
interface OtplessInitOptions { sslPinning?: OtplessSslPinning }
|
|
58
|
+
initialize(appId: string, loginUri?: string | null, options?: OtplessInitOptions): void
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
When enabled, the native SDK pins the TLS certificate of `sigma.otpless.app` on both Android and iOS. If the pin does not match (for example behind an intercepting proxy such as Charles or Proxyman), the SDK **fails closed**: no request leaves the device and you receive `FAILED` with `statusCode` `5004` (see [Response Handling](#response-handling)). If your organisation runs a corporate proxy or a network allowlist, make sure `sigma.otpless.app` is reachable directly with its original certificate.
|
|
62
|
+
|
|
63
|
+
Requires `io.github.otpless-tech:otpless-headless-sdk` >= 2.0.1 on Android and `OtplessBM/Core` >= 3.0.1 on iOS (both pulled in automatically by this package).
|
|
64
|
+
|
|
47
65
|
# Initiate Authentication
|
|
48
66
|
|
|
49
67
|
## Phone Auth
|
|
@@ -86,8 +104,19 @@ const onHeadlessResult = (result: any) => {
|
|
|
86
104
|
break;
|
|
87
105
|
}
|
|
88
106
|
case "FAILED": {
|
|
107
|
+
if (result.statusCode == 5003) {
|
|
108
|
+
// SDK initialization failed (e.g. invalid appId or network unavailable)
|
|
89
109
|
console.log("SDK initialization failed");
|
|
90
|
-
|
|
110
|
+
} else if (result.statusCode == 5004) {
|
|
111
|
+
// SSL pin validation failed. Only emitted when `initialize` was called
|
|
112
|
+
// with `{ sslPinning: 'enabled' }`. The SDK sent nothing to the server;
|
|
113
|
+
// the connection to sigma.otpless.app is being intercepted or its
|
|
114
|
+
// certificate does not match the expected pin.
|
|
115
|
+
// result.response = { errorCode: "5004", errorMessage: "SSL pin validation failed" }
|
|
116
|
+
console.log("SSL pin validation failed");
|
|
117
|
+
} else {
|
|
118
|
+
console.log("SDK failed", result.statusCode);
|
|
119
|
+
}
|
|
91
120
|
break;
|
|
92
121
|
}
|
|
93
122
|
case "INITIATE": {
|
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
dependencies {
|
|
11
|
-
classpath "com.android.tools.build:gradle:8.
|
|
11
|
+
classpath "com.android.tools.build:gradle:8.10.0"
|
|
12
12
|
// noinspection DifferentKotlinGradleVersion
|
|
13
13
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
14
|
}
|
|
@@ -37,7 +37,7 @@ def getExtOrIntegerDefault(name) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
android {
|
|
40
|
-
compileSdk
|
|
40
|
+
compileSdk 36
|
|
41
41
|
namespace "com.otplessheadlessrn"
|
|
42
42
|
defaultConfig {
|
|
43
43
|
minSdkVersion 21
|
|
@@ -74,7 +74,7 @@ dependencies {
|
|
|
74
74
|
//noinspection GradleDynamicVersion
|
|
75
75
|
implementation "com.facebook.react:react-native:+"
|
|
76
76
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
77
|
-
implementation ("io.github.otpless-tech:otpless-headless-sdk:0.
|
|
77
|
+
implementation ("io.github.otpless-tech:otpless-headless-sdk:2.0.1")
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
if (isNewArchitectureEnabled()) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
OtplessHeadlessRN_kotlinVersion=
|
|
1
|
+
OtplessHeadlessRN_kotlinVersion=2.0.21
|
|
2
2
|
OtplessHeadlessRN_minSdkVersion=24
|
|
3
3
|
OtplessHeadlessRN_targetSdkVersion=35
|
|
4
|
-
OtplessHeadlessRN_compileSdkVersion=
|
|
4
|
+
OtplessHeadlessRN_compileSdkVersion=36
|
|
5
5
|
OtplessHeadlessRN_ndkversion=21.4.7075529
|
|
@@ -2,30 +2,29 @@ package com.otplessheadlessrn
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.content.Intent
|
|
5
|
-
import
|
|
5
|
+
import android.util.Log
|
|
6
6
|
import androidx.fragment.app.FragmentActivity
|
|
7
|
-
import androidx.lifecycle.lifecycleScope
|
|
8
7
|
import com.facebook.react.bridge.ActivityEventListener
|
|
9
8
|
import com.facebook.react.bridge.Promise
|
|
10
9
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
10
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
12
11
|
import com.facebook.react.bridge.ReactMethod
|
|
13
12
|
import com.facebook.react.bridge.ReadableMap
|
|
14
|
-
import com.facebook.react.bridge.ReadableType
|
|
15
13
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
16
14
|
import com.otpless.longclaw.tc.OTScopeRequest
|
|
17
|
-
import com.otpless.v2.android.sdk.dto.AuthEvent
|
|
18
|
-
import com.otpless.v2.android.sdk.dto.OtplessChannelType
|
|
19
|
-
import com.otpless.v2.android.sdk.dto.ProviderType
|
|
20
|
-
import com.otpless.v2.android.sdk.dto.OtplessRequest
|
|
21
15
|
import com.otpless.v2.android.sdk.dto.OtplessResponse
|
|
22
|
-
import com.otpless.v2.android.sdk.dto.
|
|
16
|
+
import com.otpless.v2.android.sdk.dto.OtplessSslKind
|
|
23
17
|
import com.otpless.v2.android.sdk.main.OtplessSDK
|
|
18
|
+
import com.otpless.v2.android.sdk.utils.BuildPlatform
|
|
24
19
|
import com.otpless.v2.android.sdk.utils.OtplessUtils
|
|
20
|
+
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
25
21
|
import kotlinx.coroutines.CoroutineScope
|
|
26
22
|
import kotlinx.coroutines.Dispatchers
|
|
27
23
|
import kotlinx.coroutines.Job
|
|
24
|
+
import kotlinx.coroutines.SupervisorJob
|
|
28
25
|
import kotlinx.coroutines.launch
|
|
26
|
+
import kotlinx.coroutines.sync.Mutex
|
|
27
|
+
import kotlinx.coroutines.sync.withLock
|
|
29
28
|
import org.json.JSONException
|
|
30
29
|
import org.json.JSONObject
|
|
31
30
|
|
|
@@ -33,6 +32,10 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
33
32
|
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
34
33
|
|
|
35
34
|
private var otplessJob: Job? = null
|
|
35
|
+
private val lifecycleMutex = Mutex()
|
|
36
|
+
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { context, throwable ->
|
|
37
|
+
Log.d("OTPLESS", "Error in coroutine", throwable)
|
|
38
|
+
})
|
|
36
39
|
|
|
37
40
|
init {
|
|
38
41
|
reactContext.addActivityEventListener(this)
|
|
@@ -71,24 +74,28 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
@ReactMethod
|
|
74
|
-
fun initialize(appId: String, loginUri: String
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
fun initialize(appId: String, loginUri: String?, sslPinning: String?, buildPlatform: String?) {
|
|
78
|
+
val activity = currentActivity ?: return
|
|
79
|
+
val sslKind: OtplessSslKind =
|
|
80
|
+
if (sslPinning == "enabled") OtplessSslKind.SslEnabled else OtplessSslKind.SslDisabled
|
|
81
|
+
// Wrapper attribution, computed in JS (which owns the single source of
|
|
82
|
+
// truth for the package version) and arriving as
|
|
83
|
+
// "react-native-android-<packageVersion>". This bridge is a dumb forwarder;
|
|
84
|
+
// the literal fallback covers an older JS layer that does not send the
|
|
85
|
+
// argument, so the token can never be blank.
|
|
86
|
+
val buildPlatformToken = buildPlatform?.trim()?.takeIf { it.isNotEmpty() } ?: "react-native-android"
|
|
87
|
+
ioScope.launch {
|
|
88
|
+
lifecycleMutex.withLock {
|
|
89
|
+
// Tells the native SDK this session came through the React Native
|
|
90
|
+
// wrapper, so device telemetry reports
|
|
91
|
+
// platform = "otpless-headless-sdk(react-native-android-<version>)"
|
|
92
|
+
// instead of the default "otpless-headless-sdk(android)". Must be set
|
|
93
|
+
// before initialize, which is what emits the event.
|
|
94
|
+
OtplessSDK.buildPlatform = BuildPlatform(buildPlatformToken)
|
|
78
95
|
OtplessSDK.initialize(
|
|
79
|
-
appId = appId,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
callback = this@OtplessHeadlessRNModule::sendHeadlessEventCallback
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
} ?: run {
|
|
86
|
-
CoroutineScope(Dispatchers.IO).launch {
|
|
87
|
-
OtplessSDK.initialize(
|
|
88
|
-
appId = appId,
|
|
89
|
-
activity = currentActivity!!,
|
|
90
|
-
loginUri = loginUri,
|
|
91
|
-
callback = this@OtplessHeadlessRNModule::sendHeadlessEventCallback
|
|
96
|
+
appId = appId, activity = activity,
|
|
97
|
+
loginUri = loginUri, callback = this@OtplessHeadlessRNModule::sendHeadlessEventCallback,
|
|
98
|
+
sslKind = sslKind
|
|
92
99
|
)
|
|
93
100
|
}
|
|
94
101
|
}
|
|
@@ -96,11 +103,11 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
96
103
|
|
|
97
104
|
@ReactMethod
|
|
98
105
|
fun initTrueCaller(requestMap: ReadableMap, promise: Promise) {
|
|
99
|
-
|
|
106
|
+
val activity = currentActivity ?: return
|
|
100
107
|
val request = parseTrueCallerRequest(requestMap)
|
|
101
108
|
val scopes = parseTrueCallerScope(requestMap)
|
|
102
|
-
val result = OtplessSDK.initTrueCaller(
|
|
103
|
-
OTScopeRequest.ActivityRequest(
|
|
109
|
+
val result = OtplessSDK.initTrueCaller(activity, request) {
|
|
110
|
+
OTScopeRequest.ActivityRequest(activity as FragmentActivity, scopes)
|
|
104
111
|
}
|
|
105
112
|
debugLog("init truecaller result: $result")
|
|
106
113
|
promise.resolve(result)
|
|
@@ -108,100 +115,32 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
108
115
|
|
|
109
116
|
@ReactMethod
|
|
110
117
|
fun userAuthEvent(event: String, fallback: Boolean, providerType: String, providerInfo: ReadableMap?) {
|
|
111
|
-
|
|
112
|
-
val authEvent = runCatching { AuthEvent.valueOf(event) }.getOrElse {
|
|
118
|
+
val authEvent = parseAuthEvent(event) ?: run {
|
|
113
119
|
debugLog("userAuthEvent: unknown AuthEvent '$event', ignoring call")
|
|
114
120
|
return
|
|
115
121
|
}
|
|
116
|
-
val provider =
|
|
122
|
+
val provider = parseProviderType(providerType) ?: run {
|
|
117
123
|
debugLog("userAuthEvent: unknown ProviderType '$providerType', ignoring call")
|
|
118
124
|
return
|
|
119
125
|
}
|
|
120
|
-
|
|
121
|
-
// primitives via toString(), Maps/Arrays via JSON serialisation, Null skipped.
|
|
122
|
-
val infoMap = mutableMapOf<String, String>()
|
|
123
|
-
if (providerInfo != null) {
|
|
124
|
-
try {
|
|
125
|
-
val iterator = providerInfo.keySetIterator()
|
|
126
|
-
while (iterator.hasNextKey()) {
|
|
127
|
-
val key = iterator.nextKey() ?: continue
|
|
128
|
-
val value: String? = when (providerInfo.getType(key)) {
|
|
129
|
-
ReadableType.String -> providerInfo.getString(key)
|
|
130
|
-
ReadableType.Number -> providerInfo.getDouble(key).toString()
|
|
131
|
-
ReadableType.Boolean -> providerInfo.getBoolean(key).toString()
|
|
132
|
-
ReadableType.Map -> convertMapToJson(providerInfo.getMap(key))?.toString()
|
|
133
|
-
ReadableType.Array -> convertArrayToJson(providerInfo.getArray(key))?.toString()
|
|
134
|
-
else -> null // Null — skip
|
|
135
|
-
}
|
|
136
|
-
if (value != null) infoMap[key] = value
|
|
137
|
-
}
|
|
138
|
-
} catch (_: Exception) {
|
|
139
|
-
// swallow any unexpected bridge parsing errors
|
|
140
|
-
}
|
|
141
|
-
}
|
|
126
|
+
val infoMap = parseProviderInfo(providerInfo)
|
|
142
127
|
debugLog("pushing the user auth event\nauthEvent: $authEvent, providerType: $providerType")
|
|
143
128
|
OtplessSDK.userAuthEvent(authEvent, fallback, provider, infoMap)
|
|
144
129
|
}
|
|
145
130
|
|
|
146
131
|
@ReactMethod
|
|
147
132
|
fun start(data: ReadableMap) {
|
|
148
|
-
val otplessRequest =
|
|
149
|
-
val
|
|
150
|
-
var isOtpPresent = false
|
|
151
|
-
|
|
152
|
-
// phone number authentication
|
|
153
|
-
if (phone.isNotEmpty()) {
|
|
154
|
-
val countryCode = data.getString("countryCode") ?: ""
|
|
155
|
-
otplessRequest.setPhoneNumber(number = phone, countryCode = countryCode)
|
|
156
|
-
data.getString("otp")?.let {
|
|
157
|
-
otplessRequest.setOtp(it)
|
|
158
|
-
isOtpPresent = true
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
// email authentication
|
|
162
|
-
val email = data.getString("email") ?: ""
|
|
163
|
-
if (email.isNotEmpty()) {
|
|
164
|
-
otplessRequest.setEmail(email)
|
|
165
|
-
data.getString("otp")?.let {
|
|
166
|
-
otplessRequest.setOtp(it)
|
|
167
|
-
isOtpPresent = true
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
// oauth case
|
|
171
|
-
otplessRequest.setChannelType(
|
|
172
|
-
OtplessChannelType.fromString(
|
|
173
|
-
data.getString("channelType") ?: ""
|
|
174
|
-
)
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
data.getString("expiry")?.takeIf { it.isNotBlank() }?.let {
|
|
179
|
-
otplessRequest.setExpiry(it)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
data.getString("otpLength")?.takeIf { it.isNotBlank() }?.let {
|
|
183
|
-
otplessRequest.setOtpLength(it)
|
|
184
|
-
}
|
|
133
|
+
val otplessRequest = parseOtplessRequest(data)
|
|
134
|
+
val isOtpVerification = !data.getString("otp").isNullOrEmpty()
|
|
185
135
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
data.getString("tid")?.takeIf { it.isNotBlank() }?.let { templateId ->
|
|
191
|
-
otplessRequest.setTemplateId(templateId)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
otplessJob?.cancel()
|
|
195
|
-
currentActivity?.let {
|
|
196
|
-
if (isOtpPresent) (it as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {
|
|
136
|
+
if (isOtpVerification) {
|
|
137
|
+
// OTP submit — slot into the current auth flow; don't cancel, don't track
|
|
138
|
+
ioScope.launch {
|
|
197
139
|
OtplessSDK.start(request = otplessRequest, this@OtplessHeadlessRNModule::sendHeadlessEventCallback)
|
|
198
|
-
} else {
|
|
199
|
-
otplessJob = (it as AppCompatActivity).lifecycleScope.launch {
|
|
200
|
-
OtplessSDK.start(request = otplessRequest, this@OtplessHeadlessRNModule::sendHeadlessEventCallback)
|
|
201
|
-
}
|
|
202
140
|
}
|
|
203
|
-
}
|
|
204
|
-
|
|
141
|
+
} else {
|
|
142
|
+
otplessJob?.cancel()
|
|
143
|
+
otplessJob = ioScope.launch {
|
|
205
144
|
OtplessSDK.start(request = otplessRequest, this@OtplessHeadlessRNModule::sendHeadlessEventCallback)
|
|
206
145
|
}
|
|
207
146
|
}
|
|
@@ -215,7 +154,11 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
215
154
|
@ReactMethod
|
|
216
155
|
fun cleanup() {
|
|
217
156
|
otplessJob?.cancel()
|
|
218
|
-
|
|
157
|
+
ioScope.launch {
|
|
158
|
+
lifecycleMutex.withLock {
|
|
159
|
+
OtplessSDK.cleanup()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
219
162
|
}
|
|
220
163
|
|
|
221
164
|
@ReactMethod
|
|
@@ -224,47 +167,82 @@ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext)
|
|
|
224
167
|
OtplessSDK.devLogging = devLogging
|
|
225
168
|
}
|
|
226
169
|
|
|
227
|
-
|
|
228
|
-
|
|
170
|
+
@ReactMethod
|
|
171
|
+
fun setMfaEnabled(enabled: Boolean) {
|
|
172
|
+
OtplessSDK.isMfaEnabled = enabled
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@ReactMethod
|
|
176
|
+
fun setSimBindingEnabled(enabled: Boolean) {
|
|
177
|
+
OtplessSDK.isSimBindingEnabled = enabled
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@ReactMethod
|
|
181
|
+
fun checkSimBindingStatus(promise: Promise) {
|
|
182
|
+
ioScope.launch {
|
|
183
|
+
try {
|
|
184
|
+
val bound = OtplessSDK.checkSimBindingStatus(reactContext.applicationContext)
|
|
185
|
+
promise.resolve(bound)
|
|
186
|
+
} catch (_: Throwable) {
|
|
187
|
+
promise.resolve(false)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@ReactMethod
|
|
193
|
+
fun clearSimBinding(promise: Promise) {
|
|
194
|
+
ioScope.launch {
|
|
195
|
+
try {
|
|
196
|
+
OtplessSDK.clearSimBinding(reactContext.applicationContext)
|
|
197
|
+
promise.resolve(null)
|
|
198
|
+
} catch (_: Throwable) {
|
|
199
|
+
promise.resolve(null)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@ReactMethod
|
|
205
|
+
fun startInBackground(data: ReadableMap) {
|
|
206
|
+
val otplessRequest = parseOtplessRequest(data)
|
|
207
|
+
otplessJob?.cancel()
|
|
208
|
+
otplessJob = ioScope.launch(Dispatchers.IO) {
|
|
209
|
+
OtplessSDK.startInBackground(otplessRequest, this@OtplessHeadlessRNModule::sendHeadlessEventCallback)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@ReactMethod
|
|
214
|
+
fun startBackgroundAuth(config: ReadableMap, promise: Promise) {
|
|
215
|
+
val activity = currentActivity
|
|
216
|
+
if (activity == null) {
|
|
217
|
+
promise.resolve(false)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
val authConfig = parseOtplessAuthConfig(config)
|
|
221
|
+
ioScope.launch {
|
|
222
|
+
val result = OtplessSDK.start(authConfig)
|
|
223
|
+
promise.resolve(result)
|
|
224
|
+
}
|
|
229
225
|
}
|
|
230
226
|
|
|
231
227
|
override fun onActivityResult(
|
|
232
|
-
activity: Activity?,
|
|
233
|
-
requestCode: Int,
|
|
234
|
-
resultCode: Int,
|
|
235
|
-
data: Intent?
|
|
228
|
+
activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?
|
|
236
229
|
) {
|
|
237
230
|
OtplessSDK.onActivityResult(requestCode, resultCode, data)
|
|
238
231
|
}
|
|
239
232
|
|
|
240
233
|
override fun onNewIntent(intent: Intent?) {
|
|
241
234
|
intent ?: return
|
|
242
|
-
|
|
243
|
-
ac.lifecycleScope.launch(Dispatchers.IO) {
|
|
244
|
-
OtplessSDK.onNewIntent(intent)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
235
|
+
ioScope.launch { OtplessSDK.onNewIntent(intent) }
|
|
247
236
|
}
|
|
248
237
|
|
|
249
238
|
@ReactMethod
|
|
250
239
|
fun commitResponse(data: ReadableMap?) {
|
|
251
|
-
val
|
|
252
|
-
val jsonResponse = JSONObject(dataMap as Map<*, *>)
|
|
253
|
-
val otplessResponse = OtplessResponse(
|
|
254
|
-
responseType = getResponseType(
|
|
255
|
-
responseTypeString = jsonResponse.optString(
|
|
256
|
-
"responseType",
|
|
257
|
-
""
|
|
258
|
-
)
|
|
259
|
-
),
|
|
260
|
-
jsonResponse.optJSONObject("response"),
|
|
261
|
-
jsonResponse.optInt("statusCode", 0),
|
|
262
|
-
)
|
|
240
|
+
val otplessResponse = parseOtplessResponse(data) ?: return
|
|
263
241
|
OtplessSDK.commit(otplessResponse)
|
|
264
242
|
}
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
|
|
244
|
+
companion object {
|
|
245
|
+
const val NAME = "OtplessHeadlessRN"
|
|
268
246
|
}
|
|
269
247
|
}
|
|
270
248
|
|
|
@@ -17,6 +17,14 @@ import com.otpless.longclaw.tc.OTLoginPrefixText
|
|
|
17
17
|
import com.otpless.longclaw.tc.OTScope
|
|
18
18
|
import com.otpless.longclaw.tc.OTVerifyOption
|
|
19
19
|
import com.otpless.longclaw.tc.OtplessTruecallerRequest
|
|
20
|
+
import com.otpless.v2.android.sdk.dto.AuthEvent
|
|
21
|
+
import com.otpless.v2.android.sdk.dto.DeviceFingerprintMode
|
|
22
|
+
import com.otpless.v2.android.sdk.dto.OtplessChannelType
|
|
23
|
+
import com.otpless.v2.android.sdk.dto.OtplessRequest
|
|
24
|
+
import com.otpless.v2.android.sdk.dto.OtplessResponse
|
|
25
|
+
import com.otpless.v2.android.sdk.dto.ProviderType
|
|
26
|
+
import com.otpless.v2.android.sdk.dto.ResponseTypes
|
|
27
|
+
import com.otpless.v2.android.sdk.view.models.OtplessAuthConfig
|
|
20
28
|
import org.json.JSONArray
|
|
21
29
|
import org.json.JSONException
|
|
22
30
|
import org.json.JSONObject
|
|
@@ -131,6 +139,94 @@ internal fun convertJsonToArray(jsonArray: JSONArray): WritableArray {
|
|
|
131
139
|
return array
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
internal fun parseOtplessRequest(data: ReadableMap): OtplessRequest {
|
|
143
|
+
val otplessRequest = OtplessRequest()
|
|
144
|
+
data.getString("phone")?.takeIf { it.isNotBlank() }?.let { phone ->
|
|
145
|
+
val countryCode = data.getString("countryCode") ?: ""
|
|
146
|
+
otplessRequest.setPhoneNumber(number = phone, countryCode = countryCode)
|
|
147
|
+
}
|
|
148
|
+
data.getString("email")?.takeIf { it.isNotBlank() }?.let { otplessRequest.setEmail(it) }
|
|
149
|
+
data.getString("channelType")?.takeIf { it.isNotBlank() }?.let {
|
|
150
|
+
otplessRequest.setChannelType(OtplessChannelType.fromString(it))
|
|
151
|
+
}
|
|
152
|
+
data.getString("otp")?.takeIf { it.isNotBlank() }?.let { otplessRequest.setOtp(it) }
|
|
153
|
+
data.getString("requestId")?.takeIf { it.isNotBlank() }?.let { otplessRequest.requestId = it }
|
|
154
|
+
data.getString("expiry")?.takeIf { it.isNotBlank() }?.let { otplessRequest.setExpiry(it) }
|
|
155
|
+
data.getString("otpLength")?.takeIf { it.isNotBlank() }?.let { otplessRequest.setOtpLength(it) }
|
|
156
|
+
data.getString("deliveryChannel")?.takeIf { it.isNotBlank() }?.let {
|
|
157
|
+
otplessRequest.setDeliveryChannel(it.uppercase())
|
|
158
|
+
}
|
|
159
|
+
data.getString("tid")?.takeIf { it.isNotBlank() }?.let { otplessRequest.setTemplateId(it) }
|
|
160
|
+
data.getString("deviceFingerprintMode")?.takeIf { it.isNotBlank() }?.let { mode ->
|
|
161
|
+
runCatching { DeviceFingerprintMode.valueOf(mode.uppercase()) }.getOrNull()?.let {
|
|
162
|
+
otplessRequest.deviceFingerprintMode = it
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return otplessRequest
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
internal fun parseOtplessAuthConfig(config: ReadableMap): OtplessAuthConfig {
|
|
169
|
+
val isForeground = if (config.hasKey("isForeground")) config.getBoolean("isForeground") else false
|
|
170
|
+
val otp = config.getString("otp") ?: ""
|
|
171
|
+
val tid = config.getString("tid")
|
|
172
|
+
val fingerprintMode = config.getString("deviceFingerprintMode")
|
|
173
|
+
?.takeIf { it.isNotBlank() }
|
|
174
|
+
?.let { runCatching { DeviceFingerprintMode.valueOf(it.uppercase()) }.getOrNull() }
|
|
175
|
+
?: DeviceFingerprintMode.NONE
|
|
176
|
+
return OtplessAuthConfig(
|
|
177
|
+
isForeground = isForeground,
|
|
178
|
+
otp = otp,
|
|
179
|
+
tid = tid,
|
|
180
|
+
deviceFingerprintMode = fingerprintMode
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
internal fun parseOtplessResponse(data: ReadableMap?): OtplessResponse? {
|
|
185
|
+
val dataMap = data?.toHashMap() ?: return null
|
|
186
|
+
val jsonResponse = JSONObject(dataMap as Map<*, *>)
|
|
187
|
+
val responseTypeString = jsonResponse.optString("responseType", "")
|
|
188
|
+
val responseType = runCatching { ResponseTypes.valueOf(responseTypeString) }.getOrElse {
|
|
189
|
+
debugLog("commitResponse: unknown ResponseType '$responseTypeString', ignoring call")
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
return OtplessResponse(
|
|
193
|
+
responseType = responseType,
|
|
194
|
+
jsonResponse.optJSONObject("response"),
|
|
195
|
+
jsonResponse.optInt("statusCode", 0),
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
internal fun parseAuthEvent(event: String): AuthEvent? =
|
|
200
|
+
runCatching { AuthEvent.valueOf(event) }.getOrNull()
|
|
201
|
+
|
|
202
|
+
internal fun parseProviderType(providerType: String): ProviderType? =
|
|
203
|
+
runCatching { ProviderType.valueOf(providerType) }.getOrNull()
|
|
204
|
+
|
|
205
|
+
// Defensive providerInfo parsing — JS passes `any`, so coerce each value to String:
|
|
206
|
+
// primitives via toString(), Maps/Arrays via JSON serialisation, Null skipped.
|
|
207
|
+
internal fun parseProviderInfo(providerInfo: ReadableMap?): Map<String, String> {
|
|
208
|
+
val infoMap = mutableMapOf<String, String>()
|
|
209
|
+
if (providerInfo == null) return infoMap
|
|
210
|
+
try {
|
|
211
|
+
val iterator = providerInfo.keySetIterator()
|
|
212
|
+
while (iterator.hasNextKey()) {
|
|
213
|
+
val key = iterator.nextKey() ?: continue
|
|
214
|
+
val value: String? = when (providerInfo.getType(key)) {
|
|
215
|
+
ReadableType.String -> providerInfo.getString(key)
|
|
216
|
+
ReadableType.Number -> providerInfo.getDouble(key).toString()
|
|
217
|
+
ReadableType.Boolean -> providerInfo.getBoolean(key).toString()
|
|
218
|
+
ReadableType.Map -> convertMapToJson(providerInfo.getMap(key))?.toString()
|
|
219
|
+
ReadableType.Array -> convertArrayToJson(providerInfo.getArray(key))?.toString()
|
|
220
|
+
else -> null
|
|
221
|
+
}
|
|
222
|
+
if (value != null) infoMap[key] = value
|
|
223
|
+
}
|
|
224
|
+
} catch (_: Exception) {
|
|
225
|
+
// swallow any unexpected bridge parsing errors
|
|
226
|
+
}
|
|
227
|
+
return infoMap
|
|
228
|
+
}
|
|
229
|
+
|
|
134
230
|
internal fun parseTrueCallerRequest(requestMap: ReadableMap): OtplessTruecallerRequest {
|
|
135
231
|
val trueCallerRequestMap = requestMap.getMap("trueCallerRequest") ?: return OtplessTruecallerRequest()
|
|
136
232
|
// parsing footer type
|
package/ios/OtplessHeadlessRN.m
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
RCT_EXTERN_METHOD(initialize:(NSString *)appId
|
|
7
7
|
loginUri: (nullable NSString *) loginUri
|
|
8
|
+
sslPinning: (nullable NSString *) sslPinning
|
|
9
|
+
buildPlatform: (nullable NSString *) buildPlatform
|
|
8
10
|
)
|
|
9
11
|
|
|
10
12
|
RCT_EXTERN_METHOD(start:(NSDictionary *)request)
|
|
@@ -15,15 +17,22 @@ RCT_EXTERN_METHOD(cleanup)
|
|
|
15
17
|
|
|
16
18
|
RCT_EXTERN_METHOD(decimateAll)
|
|
17
19
|
|
|
18
|
-
RCT_EXTERN_METHOD(setOneTapDataCallback)
|
|
19
|
-
|
|
20
|
-
RCT_EXTERN_METHOD(performOneTap: (NSDictionary *)request)
|
|
21
|
-
|
|
22
20
|
RCT_EXTERN_METHOD(authorizeViaPasskey: (NSDictionary *)request)
|
|
23
21
|
|
|
24
22
|
RCT_EXTERN_METHOD(setDevLogging:(BOOL)enable)
|
|
25
23
|
|
|
24
|
+
RCT_EXTERN_METHOD(userAuthEvent:(NSString *)event
|
|
25
|
+
fallback:(BOOL)fallback
|
|
26
|
+
providerType:(NSString *)providerType
|
|
27
|
+
providerInfo:(nullable NSDictionary *)providerInfo)
|
|
28
|
+
|
|
26
29
|
RCT_EXTERN_METHOD(isSdkReady: (RCTPromiseResolveBlock*)resolve reject: (RCTPromiseRejectBlock*)reject)
|
|
27
30
|
|
|
31
|
+
RCT_EXTERN_METHOD(setMfaEnabled:(BOOL)enabled)
|
|
32
|
+
|
|
33
|
+
RCT_EXTERN_METHOD(startBackgroundAuth:(NSDictionary *)config
|
|
34
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
35
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
36
|
+
|
|
28
37
|
@end
|
|
29
38
|
|