otpless-headless-rn 1.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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Digvijay Singh
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ <p align="center">
2
+ <img src="https://github.com/otpless-tech/Otpless-iOS-SDK/blob/main/otpless.svg" height="80"/>
3
+ </p>
4
+
5
+ # OTPLESS react native SDK
6
+ [![npm version](https://badge.fury.io/js/otpless-headless-rn.svg)](https://badge.fury.io/js/otpless-headless-rn)
7
+ [![npm downloads](https://img.shields.io/npm/dm/otpless-headless-rn.svg)](https://www.npmjs.com/package/otpless-headless-rn)
8
+
9
+
10
+ ## Installation
11
+
12
+ You can checkout the complete [installation guide here](https://otpless.com/platforms/react-native)
13
+
14
+ ## Author
15
+
16
+ [OTPLESS](https://otpless.com), developer@otpless.com
@@ -0,0 +1,86 @@
1
+ buildscript {
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["OtplessHeadlessRN_kotlinVersion"]
4
+
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+
10
+ dependencies {
11
+ classpath "com.android.tools.build:gradle:8.3.0"
12
+ // noinspection DifferentKotlinGradleVersion
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
+ }
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: "kotlin-android"
23
+
24
+
25
+ def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
26
+
27
+ if (isNewArchitectureEnabled()) {
28
+ apply plugin: "com.facebook.react"
29
+ }
30
+
31
+ def getExtOrDefault(name) {
32
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["OtplessHeadlessRN_" + name]
33
+ }
34
+
35
+ def getExtOrIntegerDefault(name) {
36
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["OtplessHeadlessRN_" + name]).toInteger()
37
+ }
38
+
39
+ android {
40
+ compileSdk 35
41
+ namespace "com.otplessheadlessrn"
42
+ defaultConfig {
43
+ minSdkVersion 21
44
+ targetSdkVersion 35
45
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
46
+ }
47
+ buildTypes {
48
+ release {
49
+ minifyEnabled false
50
+ }
51
+ }
52
+
53
+ lintOptions {
54
+ disable "GradleCompatible"
55
+ }
56
+
57
+ compileOptions {
58
+ sourceCompatibility JavaVersion.VERSION_1_8
59
+ targetCompatibility JavaVersion.VERSION_1_8
60
+ }
61
+
62
+ }
63
+
64
+ repositories {
65
+ mavenCentral()
66
+ google()
67
+ }
68
+
69
+ def kotlin_version = getExtOrDefault("kotlinVersion")
70
+
71
+ dependencies {
72
+ // For < 0.71, this will be from the local maven repo
73
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
74
+ //noinspection GradleDynamicVersion
75
+ implementation "com.facebook.react:react-native:+"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
+ implementation ("io.github.otpless-tech:otpless-headless-sdk:0.1.1")
78
+ }
79
+
80
+ if (isNewArchitectureEnabled()) {
81
+ react {
82
+ jsRootDir = file("../src/")
83
+ libraryName = "OtplessHeadlessRN"
84
+ codegenJavaPackageName = "com.otplessheadlessrn"
85
+ }
86
+ }
@@ -0,0 +1,5 @@
1
+ OtplessHeadlessRN_kotlinVersion=1.8.22
2
+ OtplessHeadlessRN_minSdkVersion=24
3
+ OtplessHeadlessRN_targetSdkVersion=35
4
+ OtplessHeadlessRN_compileSdkVersion=35
5
+ OtplessHeadlessRN_ndkversion=21.4.7075529
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.otplessheadlessrn">
3
+ </manifest>
@@ -0,0 +1,12 @@
1
+ package com.otplessheadlessrn
2
+
3
+ import java.lang.ref.WeakReference
4
+
5
+ object OtplessHeadlessRNManager {
6
+
7
+ private var wModule: WeakReference<OtplessHeadlessRNModule>? = null
8
+
9
+ internal fun registerOtplessModule(otplessModule: OtplessHeadlessRNModule) {
10
+ wModule = WeakReference(otplessModule)
11
+ }
12
+ }
@@ -0,0 +1,170 @@
1
+ package com.otplessheadlessrn
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import com.facebook.react.bridge.ActivityEventListener
6
+ import com.facebook.react.bridge.Callback
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+ import com.facebook.react.bridge.ReadableMap
11
+ import com.facebook.react.modules.core.DeviceEventManagerModule
12
+ import com.otpless.v2.android.sdk.dto.OtplessChannelType
13
+ import com.otpless.v2.android.sdk.dto.OtplessRequest
14
+ import com.otpless.v2.android.sdk.dto.OtplessResponse
15
+ import com.otpless.v2.android.sdk.dto.ResponseTypes
16
+ import com.otpless.v2.android.sdk.main.OtplessPhoneHint
17
+ import com.otpless.v2.android.sdk.main.OtplessSDK
18
+ import com.otpless.v2.android.sdk.utils.OtplessUtils
19
+ import org.json.JSONException
20
+ import org.json.JSONObject
21
+
22
+ class OtplessHeadlessRNModule(private val reactContext: ReactApplicationContext) :
23
+ ReactContextBaseJavaModule(reactContext), ActivityEventListener {
24
+
25
+ init {
26
+ OtplessHeadlessRNManager.registerOtplessModule(this)
27
+ reactContext.addActivityEventListener(this)
28
+ }
29
+
30
+ private val phoneHintApi by lazy { OtplessPhoneHint() }
31
+
32
+ override fun getName(): String {
33
+ return NAME
34
+ }
35
+
36
+ private fun sendHeadlessEventCallback(result: OtplessResponse) {
37
+ fun sendResultEvent(result: JSONObject) {
38
+ try {
39
+ val map = convertJsonToMap(result)
40
+ this.reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
41
+ .emit("OTPlessEventResult", map)
42
+ } catch (_: JSONException) {
43
+
44
+ }
45
+ }
46
+
47
+ val jsonObject = JSONObject()
48
+ try {
49
+ jsonObject.put("responseType", result.responseType)
50
+ jsonObject.put("response", result.response)
51
+ jsonObject.put("statusCode", result.statusCode)
52
+ } catch (_: JSONException) {
53
+
54
+ }
55
+ sendResultEvent(jsonObject)
56
+ }
57
+
58
+ @ReactMethod
59
+ fun isWhatsappInstalled(callback: Callback) {
60
+ val hasWhatsapp = OtplessUtils.isWhatsAppInstalled(reactContext)
61
+ val json = JSONObject().also {
62
+ it.put("hasWhatsapp", hasWhatsapp)
63
+ }
64
+ callback.invoke(convertJsonToMap(json))
65
+ }
66
+
67
+ @ReactMethod
68
+ fun initialize(appId: String, loginUri: String? = null) {
69
+ currentActivity?.let {
70
+ OtplessSDK.initialize(
71
+ appId = appId,
72
+ activity = it,
73
+ loginUri = loginUri
74
+ )
75
+ }
76
+ }
77
+
78
+ @ReactMethod
79
+ fun start(data: ReadableMap) {
80
+ val otplessRequest = OtplessRequest()
81
+ val phone = data.getString("phone") ?: ""
82
+ // phone number authentication
83
+ if (phone.isNotEmpty()) {
84
+ val countryCode = data.getString("countryCode") ?: ""
85
+ otplessRequest.setPhoneNumber(number = phone, countryCode = countryCode)
86
+ data.getString("otp")?.let {
87
+ otplessRequest.setOtp(it)
88
+ }
89
+ } else {
90
+ // email authentication
91
+ val email = data.getString("email") ?: ""
92
+ if (email.isNotEmpty()) {
93
+ otplessRequest.setEmail(email)
94
+ data.getString("otp")?.let {
95
+ otplessRequest.setOtp(it)
96
+ }
97
+ } else {
98
+ // oauth case
99
+ otplessRequest.setChannelType(OtplessChannelType.fromString(data.getString("channelType") ?: ""))
100
+ }
101
+ }
102
+ data.getString("expiry")?.takeIf { it.isNotBlank() }?.let {
103
+ otplessRequest.setExpiry(it)
104
+ }
105
+
106
+ data.getString("otpLength")?.takeIf { it.isNotBlank() }?.let {
107
+ otplessRequest.setOtpLength(it)
108
+ }
109
+
110
+ data.getString("deliveryChannel")?.takeIf { it.isNotBlank() }?.let { deliveryChannel ->
111
+ otplessRequest.setDeliveryChannel(deliveryChannel.uppercase())
112
+ }
113
+
114
+ data.getString("tid")?.takeIf { it.isNotBlank() }?.let { templateId ->
115
+ otplessRequest.setTemplateId(templateId)
116
+ }
117
+
118
+ OtplessSDK.startAsync(request = otplessRequest, this::sendHeadlessEventCallback)
119
+ }
120
+
121
+ @ReactMethod
122
+ fun setResponseCallback() {
123
+ OtplessSDK.setResponseCallback(this::sendHeadlessEventCallback)
124
+ }
125
+
126
+ @ReactMethod
127
+ fun cleanup() {
128
+ OtplessSDK.cleanup()
129
+ }
130
+
131
+ companion object {
132
+ const val NAME = "OtplessHeadlessRN"
133
+ }
134
+
135
+ override fun onActivityResult(
136
+ activity: Activity?,
137
+ requestCode: Int,
138
+ resultCode: Int,
139
+ data: Intent?
140
+ ) {
141
+ OtplessSDK.onActivityResult(requestCode, resultCode, data)
142
+ }
143
+
144
+ override fun onNewIntent(intent: Intent?) {
145
+ intent ?: return
146
+ OtplessSDK.onNewIntentAsync(intent)
147
+ }
148
+
149
+ @ReactMethod
150
+ fun commitResponse(data: ReadableMap?) {
151
+ val dataMap = data?.toHashMap() ?: return
152
+ val jsonResponse = JSONObject(dataMap)
153
+ val otplessResponse = OtplessResponse(
154
+ responseType = getResponseType(responseTypeString = jsonResponse.optString("responseType", "")),
155
+ jsonResponse.optJSONObject("response"),
156
+ jsonResponse.optInt("statusCode", 0),
157
+ )
158
+ OtplessSDK.commit(otplessResponse)
159
+ }
160
+
161
+ private fun getResponseType(responseTypeString: String) : ResponseTypes {
162
+ return ResponseTypes.valueOf(responseTypeString)
163
+ }
164
+ }
165
+
166
+
167
+
168
+
169
+
170
+
@@ -0,0 +1,17 @@
1
+ package com.otplessheadlessrn
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
+
9
+ class OtplessHeadlessRNPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(OtplessHeadlessRNModule(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return emptyList()
16
+ }
17
+ }
@@ -0,0 +1,121 @@
1
+ package com.otplessheadlessrn
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.bridge.ReadableType
6
+ import com.facebook.react.bridge.WritableArray
7
+ import com.facebook.react.bridge.WritableMap
8
+ import com.facebook.react.bridge.WritableNativeArray
9
+ import com.facebook.react.bridge.WritableNativeMap
10
+ import org.json.JSONArray
11
+ import org.json.JSONException
12
+ import org.json.JSONObject
13
+
14
+
15
+ internal fun convertMapToJson(map: ReadableMap?): JSONObject? {
16
+ map ?: return null
17
+ try {
18
+ val resultJson = JSONObject()
19
+ val iterator = map.keySetIterator()
20
+ while (iterator.hasNextKey()) {
21
+ val key = iterator.nextKey() ?: continue
22
+ when (map.getType(key)) {
23
+ ReadableType.Null -> continue
24
+ ReadableType.Boolean -> resultJson.put(key, map.getBoolean(key))
25
+ ReadableType.String -> resultJson.put(key, map.getString(key))
26
+ ReadableType.Number -> resultJson.put(key, map.getDouble(key))
27
+ ReadableType.Map -> {
28
+ val innerMap = convertMapToJson(map.getMap(key)) ?: continue
29
+ resultJson.put(key, innerMap)
30
+ }
31
+
32
+ ReadableType.Array -> {
33
+ val array = map.getArray(key) ?: continue
34
+ resultJson.put(key, convertArrayToJson(array))
35
+ }
36
+ }
37
+ }
38
+ return resultJson
39
+ } catch (ex: Exception) {
40
+ return null
41
+ }
42
+ }
43
+
44
+ internal fun convertArrayToJson(array: ReadableArray?): JSONArray? {
45
+ array ?: return null
46
+ try {
47
+ val resultArray = JSONArray()
48
+ var index = 0
49
+ while (index < array.size()) {
50
+ when (array.getType(index)) {
51
+ ReadableType.Null -> continue
52
+ ReadableType.Boolean -> resultArray.put(array.getBoolean(index))
53
+ ReadableType.String -> resultArray.put(array.getString(index))
54
+ ReadableType.Number -> resultArray.put(array.getDouble(index))
55
+ ReadableType.Map -> {
56
+ val innerMap = convertMapToJson(array.getMap(index)) ?: continue
57
+ resultArray.put(innerMap)
58
+ }
59
+
60
+ ReadableType.Array -> {
61
+ val innerArray = convertArrayToJson(array.getArray(index)) ?: continue
62
+ resultArray.put(innerArray)
63
+ }
64
+ }
65
+ index++
66
+ }
67
+ return resultArray
68
+ } catch (ex: Exception) {
69
+ return null
70
+ }
71
+ }
72
+
73
+ @Throws(JSONException::class)
74
+ internal fun convertJsonToMap(jsonObject: JSONObject): WritableMap {
75
+ val map: WritableMap = WritableNativeMap()
76
+ val iterator = jsonObject.keys()
77
+ while (iterator.hasNext()) {
78
+ val key = iterator.next()
79
+ val value = jsonObject[key]
80
+ if (value is JSONObject) {
81
+ map.putMap(key, convertJsonToMap(value))
82
+ } else if (value is JSONArray) {
83
+ map.putArray(key, convertJsonToArray(value))
84
+ } else if (value is Boolean) {
85
+ map.putBoolean(key, value)
86
+ } else if (value is Int) {
87
+ map.putInt(key, value)
88
+ } else if (value is Double) {
89
+ map.putDouble(key, value)
90
+ } else if (value is String) {
91
+ map.putString(key, value)
92
+ } else {
93
+ map.putString(key, value.toString())
94
+ }
95
+ }
96
+ return map
97
+ }
98
+
99
+ @Throws(JSONException::class)
100
+ internal fun convertJsonToArray(jsonArray: JSONArray): WritableArray {
101
+ val array: WritableArray = WritableNativeArray()
102
+ for (i in 0 until jsonArray.length()) {
103
+ val value = jsonArray[i]
104
+ if (value is JSONObject) {
105
+ array.pushMap(convertJsonToMap(value))
106
+ } else if (value is JSONArray) {
107
+ array.pushArray(convertJsonToArray(value))
108
+ } else if (value is Boolean) {
109
+ array.pushBoolean(value)
110
+ } else if (value is Int) {
111
+ array.pushInt(value)
112
+ } else if (value is Double) {
113
+ array.pushDouble(value)
114
+ } else if (value is String) {
115
+ array.pushString(value)
116
+ } else {
117
+ array.pushString(value.toString())
118
+ }
119
+ }
120
+ return array
121
+ }
@@ -0,0 +1,3 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
3
+ #import <React/RCTEventEmitter.h>
@@ -0,0 +1,27 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(OtplessHeadlessRN, RCTEventEmitter<RCTBridgeModule>)
5
+
6
+ RCT_EXTERN_METHOD(initialize:(NSString *)appId
7
+ loginUri: (nullable NSString *) loginUri
8
+ )
9
+
10
+ RCT_EXTERN_METHOD(setResponseCallback)
11
+
12
+ RCT_EXTERN_METHOD(start:(NSDictionary *)request)
13
+
14
+ RCT_EXTERN_METHOD(enableDebugLogging:(BOOL)enable)
15
+
16
+ RCT_EXTERN_METHOD(commitResponse: (nullable NSDictionary *) response)
17
+
18
+ RCT_EXTERN_METHOD(cleanup)
19
+
20
+ RCT_EXTERN_METHOD(setOneTapDataCallback)
21
+
22
+ RCT_EXTERN_METHOD(performOneTap: (NSDictionary *)request)
23
+
24
+ RCT_EXTERN_METHOD(authorizeViaPasskey: (NSDictionary *)request)
25
+
26
+ @end
27
+
@@ -0,0 +1,187 @@
1
+ import OtplessBM
2
+
3
+
4
+ @objc(OtplessHeadlessRN)
5
+ class OtplessHeadlessRN: RCTEventEmitter, OtplessResponseDelegate {
6
+ private var currentTask: Task<Void, Never>?
7
+
8
+ func onResponse(_ response: OtplessBM.OtplessResponse) {
9
+ onOtplessResponse(response: response)
10
+ }
11
+
12
+ @objc(commitResponse:)
13
+ func commitResponse(response: [String: Any]?) {
14
+ guard let response = response else {
15
+ return
16
+ }
17
+ let responseType = response["responseType"] as? String ?? "FAILED"
18
+ let statusCode = response["statusCode"] as? Int ?? -25000
19
+ let responseDict = response["response"] as? [String: Any]
20
+ let otplessResponse = OtplessResponse(responseType: ResponseTypes(rawValue: responseType) ?? .FAILED, response: responseDict, statusCode: statusCode)
21
+ Otpless.shared.commitOtplessResponse(otplessResponse)
22
+ }
23
+
24
+ func onOtplessResponse(response: OtplessResponse?) {
25
+ if response == nil {
26
+ return
27
+ }
28
+ var params = [String: Any]()
29
+ params["response"] = response!.response
30
+ params["statusCode"] = response!.statusCode
31
+ params["responseType"] = response!.responseType.rawValue
32
+ sendEvent(withName: "OTPlessEventResult", body: params)
33
+ }
34
+
35
+ private func createOtplessRequest(args: [String: Any]) -> OtplessRequest {
36
+ let otplessRequest = OtplessRequest()
37
+ if let phone = args["phone"] as? String,
38
+ let countryCode = args["countryCode"] as? String {
39
+ otplessRequest.set(phoneNumber: phone, withCountryCode: countryCode)
40
+ } else if let email = args["email"] as? String {
41
+ otplessRequest.set(email: email)
42
+ } else if let channelType = args["channelType"] as? String {
43
+ otplessRequest.set(channelType: OtplessChannelType.fromString(channelType))
44
+ }
45
+ if let otp = args["otp"] as? String {
46
+ otplessRequest.set(otp: otp)
47
+ }
48
+ if let deliveryChannel = args["deliveryChannel"] as? String,
49
+ !deliveryChannel.isEmpty {
50
+ otplessRequest.set(deliveryChannelForTransaction: deliveryChannel)
51
+ }
52
+ if let otpExpiry = args["expiry"] as? String,
53
+ !otpExpiry.isEmpty {
54
+ otplessRequest.set(otpExpiry: otpExpiry)
55
+ }
56
+ if let otpLength = args["otpLength"] as? String,
57
+ !otpLength.isEmpty {
58
+ otplessRequest.set(otpLength: otpLength)
59
+ }
60
+
61
+ if let tid = args["tid"] as? String,
62
+ !tid.isEmpty {
63
+ otplessRequest.set(tid: tid)
64
+ }
65
+
66
+ return otplessRequest
67
+ }
68
+
69
+ @objc(authorizeViaPasskey:)
70
+ func authorizeViaPasskey(request: [String: Any]) {
71
+ let requestId = request["requestId"] as? String ?? ""
72
+ let request = OtplessRequest()
73
+ request.set(requestIdForWebAuthn: requestId)
74
+ if let windowScene = getWindowScene() {
75
+ Task(priority: .userInitiated) {
76
+ await Otpless.shared.authorizeViaPasskey(withRequest: request, windowScene: windowScene)
77
+ }
78
+ }
79
+ }
80
+
81
+ @objc(enableDebugLogging:)
82
+ func enableDebugLogging(enable: Bool) {
83
+ if enable {
84
+ Otpless.shared.setLoggerDelegate(self)
85
+ }
86
+ }
87
+
88
+ override func supportedEvents() -> [String]! {
89
+ return ["OTPlessEventResult"]
90
+ }
91
+
92
+ @MainActor
93
+ @objc(initialize:loginUri:)
94
+ func initialize(appId: String, loginUri: String?) {
95
+ DispatchQueue.main.async {
96
+ let rootViewController = UIApplication.shared.delegate?.window??.rootViewController
97
+ if rootViewController != nil {
98
+ Otpless.shared.initialise(withAppId: appId, vc: rootViewController!)
99
+ return
100
+ }
101
+
102
+ // Could not get an instance of RootViewController. Try to get RootViewController from `windowScene`.
103
+ if #available(iOS 13.0, *) {
104
+ let windowSceneVC = self.getRootViewControllerFromWindowScene()
105
+ if windowSceneVC != nil {
106
+ Otpless.shared.initialise(withAppId: appId, vc: rootViewController!)
107
+ return
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ @objc(setResponseCallback)
114
+ func setResponseCallback() {
115
+ Otpless.shared.setResponseDelegate(self)
116
+ }
117
+
118
+ @objc(start:)
119
+ func start(request: [String: Any]) {
120
+ let otplessRequest = createOtplessRequest(args: request)
121
+
122
+ let isOtpVerification = (request["otp"] as? String)?.isEmpty == false
123
+
124
+ if !isOtpVerification {
125
+ // Cancel the existing task if it's not an OTP verification request
126
+ currentTask?.cancel()
127
+ }
128
+
129
+ let newTask = Task(priority: .userInitiated) {
130
+ await Otpless.shared.start(withRequest: otplessRequest)
131
+ }
132
+
133
+ if !isOtpVerification {
134
+ currentTask = newTask
135
+ }
136
+ }
137
+
138
+ @objc(cleanup)
139
+ func cleanup() {
140
+ Otpless.shared.cleanup()
141
+ currentTask?.cancel()
142
+ currentTask = nil
143
+ }
144
+
145
+ @MainActor @available(iOS 13.0, *)
146
+ private func getRootViewControllerFromWindowScene() -> UIViewController? {
147
+ guard let windowScene = UIApplication.shared.connectedScenes
148
+ .filter({ $0.activationState == .foregroundActive })
149
+ .first as? UIWindowScene else {
150
+ return nil
151
+ }
152
+
153
+ if #available(iOS 15.0, *) {
154
+ let keyWindowVC = windowScene.windows.first?.windowScene?.keyWindow?.rootViewController
155
+ if keyWindowVC != nil {
156
+ return keyWindowVC
157
+ }
158
+ }
159
+
160
+ return windowScene.windows.first?.rootViewController
161
+ }
162
+
163
+ @MainActor @available(iOS 13.0, *)
164
+ private func getWindowScene() -> UIWindowScene? {
165
+ guard let windowScene = UIApplication.shared.connectedScenes
166
+ .filter({ $0.activationState == .foregroundActive })
167
+ .first as? UIWindowScene else {
168
+ return nil
169
+ }
170
+ return windowScene
171
+ }
172
+
173
+ @objc(performOneTap:)
174
+ func performOneTap(request: [String: Any]) {
175
+ let oneTapIdentity = OneTapIdentity(name: request["name"] as? String, identity: request["identity"] as? String ?? "", uiId: request["uiId"] as? String ?? "", logo: request["logo"] as? String)
176
+ Task(priority: .userInitiated) {
177
+ await Otpless.shared.performOneTap(forIdentity: oneTapIdentity)
178
+ }
179
+ }
180
+ }
181
+
182
+ extension OtplessHeadlessRN: OtplessLoggerDelegate {
183
+ func log(message: String, type: OtplessBM.LogType) {
184
+ print("OtplessHeadlessRN: \(type)\n \(message)")
185
+ }
186
+
187
+ }