react-native-otp-auto-verify 0.1.7 → 0.1.8
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 +167 -1
- package/android/src/main/java/com/otpautoverify/AppSignatureHelper.kt +80 -80
- package/android/src/main/java/com/otpautoverify/OtpAutoVerifyModule.kt +146 -146
- package/android/src/main/java/com/otpautoverify/SmsRetrieverBroadcastReceiver.kt +61 -61
- package/ios/OtpAutoVerify.h +5 -5
- package/ios/OtpAutoVerify.mm +36 -36
- package/lib/commonjs/NativeOtpAutoVerify.ts +11 -0
- package/lib/commonjs/core/extractOtp.js +24 -0
- package/lib/commonjs/core/extractOtp.js.map +1 -0
- package/lib/commonjs/hooks/useOtpVerification.js +70 -0
- package/lib/commonjs/hooks/useOtpVerification.js.map +1 -0
- package/lib/commonjs/index.js +57 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native/bridge.js +71 -0
- package/lib/commonjs/native/bridge.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/package.json +6 -6
- package/src/index.tsx +172 -172
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/react-native-otp-auto-verify) [](https://www.npmjs.com/package/react-native-otp-auto-verify) [](https://github.com/kailas-rathod/react-native-otp-auto-verify/blob/main/LICENSE) [](https://www.typescriptlang.org/)
|
|
5
5
|
|
|
6
6
|
**react-native-otp-auto-verify** is a lightweight and secure React Native OTP auto-verification library for Android, built on the official Google SMS Retriever API. It enables automatic OTP detection without requiring READ_SMS or RECEIVE_SMS permissions, ensuring full Google Play Store compliance and enhanced user trust. Designed for modern authentication flows, this library is ideal for fintech apps, banking applications, e-commerce platforms, and secure login systems.
|
|
7
|
+
No Permissions: It requires zero SMS permissions from the user, making it compliant with strict Google Play Store policies
|
|
7
8
|
|
|
8
9
|
With minimal dependencies and clean architecture, it integrates seamlessly into both React Native Old Architecture and the New Architecture (TurboModule) environments. The solution improves user experience by eliminating manual OTP entry on Android while maintaining strong server-side validation standards.
|
|
9
10
|
|
|
10
11
|
Works best for onboarding/login flows in **banking**, **fintech**, and authentication-heavy apps.
|
|
11
12
|
Supports both RN Old Architecture and React Native **New Architecture** (TurboModule).
|
|
12
13
|
**Android**: automatic OTP detection.
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
**iOS**: Uses iOS Security Code AutoFill (manual tap required).
|
|
14
16
|
|
|
15
17
|
**Connect:** [GitHub](https://github.com/kailas-rathod/react-native-otp-auto-verify) · [npm](https://www.npmjs.com/package/react-native-otp-auto-verify) · [Issues](https://github.com/kailas-rathod/react-native-otp-auto-verify/issues) · [License](https://github.com/kailas-rathod/react-native-otp-auto-verify/blob/main/LICENSE)
|
|
16
18
|
|
|
@@ -44,9 +46,13 @@ Supports both RN Old Architecture and React Native **New Architecture** (TurboMo
|
|
|
44
46
|
|
|
45
47
|
```sh
|
|
46
48
|
npm install react-native-otp-auto-verify
|
|
49
|
+
```
|
|
47
50
|
# or
|
|
51
|
+
```sh
|
|
48
52
|
yarn add react-native-otp-auto-verify
|
|
53
|
+
```
|
|
49
54
|
# or
|
|
55
|
+
```sh
|
|
50
56
|
pnpm add react-native-otp-auto-verify
|
|
51
57
|
```
|
|
52
58
|
|
|
@@ -133,6 +139,150 @@ sub.remove();
|
|
|
133
139
|
// or
|
|
134
140
|
removeListener();
|
|
135
141
|
```
|
|
142
|
+
🔹 Step 1 – Start OTP Listener
|
|
143
|
+
```ts
|
|
144
|
+
import { useOtpVerification } from 'react-native-otp-auto-verify';
|
|
145
|
+
|
|
146
|
+
const { startOtpListener, stopListener, otp } = useOtpVerification();
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
startOtpListener();
|
|
150
|
+
|
|
151
|
+
return () => stopListener();
|
|
152
|
+
}, []);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Create OTP Screen (Recommended Hook Method)
|
|
156
|
+
```ts
|
|
157
|
+
import React, { useEffect, useState } from 'react';
|
|
158
|
+
import {
|
|
159
|
+
View,
|
|
160
|
+
Text,
|
|
161
|
+
TextInput,
|
|
162
|
+
Button,
|
|
163
|
+
Platform,
|
|
164
|
+
} from 'react-native';
|
|
165
|
+
import { useOtpVerification } from 'react-native-otp-auto-verify';
|
|
166
|
+
|
|
167
|
+
const OtpScreen = () => {
|
|
168
|
+
const [otpValue, setOtpValue] = useState('');
|
|
169
|
+
|
|
170
|
+
const {
|
|
171
|
+
otp,
|
|
172
|
+
hashCode,
|
|
173
|
+
timeoutError,
|
|
174
|
+
error,
|
|
175
|
+
startListening,
|
|
176
|
+
stopListening,
|
|
177
|
+
} = useOtpVerification({ numberOfDigits: 6 });
|
|
178
|
+
|
|
179
|
+
// Start listener when screen opens
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (Platform.OS === 'android') {
|
|
182
|
+
startListening();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
stopListening();
|
|
187
|
+
};
|
|
188
|
+
}, []);
|
|
189
|
+
|
|
190
|
+
// Auto verify when OTP received
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (otp) {
|
|
193
|
+
setOtpValue(otp);
|
|
194
|
+
verifyOtp(otp);
|
|
195
|
+
}
|
|
196
|
+
}, [otp]);
|
|
197
|
+
|
|
198
|
+
const verifyOtp = async (code: string) => {
|
|
199
|
+
console.log('Verifying OTP:', code);
|
|
200
|
+
|
|
201
|
+
// Call your backend API here
|
|
202
|
+
// await api.post('/verify-otp', { otp: code })
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<View style={{ padding: 20 }}>
|
|
207
|
+
<Text>Enter OTP</Text>
|
|
208
|
+
|
|
209
|
+
<TextInput
|
|
210
|
+
value={otpValue}
|
|
211
|
+
onChangeText={setOtpValue}
|
|
212
|
+
keyboardType="number-pad"
|
|
213
|
+
maxLength={6}
|
|
214
|
+
textContentType="oneTimeCode"
|
|
215
|
+
autoComplete="sms-otp"
|
|
216
|
+
style={{
|
|
217
|
+
borderWidth: 1,
|
|
218
|
+
padding: 12,
|
|
219
|
+
marginVertical: 12,
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
<Button title="Verify" onPress={() => verifyOtp(otpValue)} />
|
|
224
|
+
|
|
225
|
+
{timeoutError && (
|
|
226
|
+
<Text style={{ color: 'red' }}>
|
|
227
|
+
Timeout. Please resend OTP.
|
|
228
|
+
</Text>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{error && (
|
|
232
|
+
<Text style={{ color: 'red' }}>
|
|
233
|
+
Error: {error.message}
|
|
234
|
+
</Text>
|
|
235
|
+
)}
|
|
236
|
+
</View>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export default OtpScreen;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
# Start OTP Listener in Screen
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import React, { useEffect, useState } from 'react';
|
|
247
|
+
import { View, TextInput, Text } from 'react-native';
|
|
248
|
+
import { useOtpVerification } from 'react-native-otp-auto-verify';
|
|
249
|
+
|
|
250
|
+
export default function OtpScreen() {
|
|
251
|
+
const [otpValue, setOtpValue] = useState('');
|
|
252
|
+
|
|
253
|
+
const {
|
|
254
|
+
otp,
|
|
255
|
+
startListening,
|
|
256
|
+
stopListening,
|
|
257
|
+
} = useOtpVerification({ numberOfDigits: 6 });
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
startListening(); // Start listening
|
|
261
|
+
|
|
262
|
+
return () => {
|
|
263
|
+
stopListening(); // Cleanup
|
|
264
|
+
};
|
|
265
|
+
}, []);
|
|
266
|
+
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
if (otp) {
|
|
269
|
+
setOtpValue(otp); // OTP automatically retrieved here
|
|
270
|
+
console.log('Retrieved OTP:', otp);
|
|
271
|
+
}
|
|
272
|
+
}, [otp]);
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<View>
|
|
276
|
+
<TextInput
|
|
277
|
+
value={otpValue}
|
|
278
|
+
onChangeText={setOtpValue}
|
|
279
|
+
keyboardType="number-pad"
|
|
280
|
+
maxLength={6}
|
|
281
|
+
/>
|
|
282
|
+
</View>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
```
|
|
136
286
|
|
|
137
287
|
# iOS OTP AutoFill (Native)
|
|
138
288
|
|
|
@@ -233,6 +383,22 @@ The New Architecture (also known as Fabric + TurboModules) is React Native's new
|
|
|
233
383
|
- Synchronous native module calls
|
|
234
384
|
- Improved interoperability with native code
|
|
235
385
|
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
## ✨ Feature Comparison
|
|
390
|
+
|
|
391
|
+
| Feature | react-native-otp-auto-verify | Other Packages/Libraries (react-native-otp-auto-verify)|
|
|
392
|
+
|---------------------------|------------------------------|---------------------------|
|
|
393
|
+
| SMS Retriever API | ✅ Yes | ✅ Yes |
|
|
394
|
+
| Requires SMS Permission | ❌ No | ❌ No |
|
|
395
|
+
| TurboModule Support | ✅ Yes | ❌ Usually No |
|
|
396
|
+
| TypeScript Support | ✅ Full | ⚠️ Partial |
|
|
397
|
+
| Hook API | ✅ `useOtpVerification` | ❌ Not Available |
|
|
398
|
+
| App Hash Utility | ✅ Built-in | ⚠️ Basic |
|
|
399
|
+
| Architecture Ready | ✅ Old + New | ⚠️ Mostly Old Only |
|
|
400
|
+
| Maintenance | ✅ Actively Maintained | ⚠️ Varies |
|
|
401
|
+
|
|
236
402
|
### Enabling New Architecture
|
|
237
403
|
|
|
238
404
|
The library works automatically with both **Old Architecture** and **New Architecture**. No code changes needed.
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
package com.otpautoverify
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.content.ContextWrapper
|
|
5
|
-
import android.content.pm.PackageManager
|
|
6
|
-
import android.os.Build
|
|
7
|
-
import android.util.Base64
|
|
8
|
-
import android.util.Log
|
|
9
|
-
import java.nio.charset.StandardCharsets
|
|
10
|
-
import java.security.MessageDigest
|
|
11
|
-
import java.security.NoSuchAlgorithmException
|
|
12
|
-
import java.util.ArrayList
|
|
13
|
-
import java.util.Arrays
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Computes the 11-character app hash required by the SMS Retriever API.
|
|
17
|
-
* Uses GET_SIGNING_CERTIFICATES on API 28+ and GET_SIGNATURES on older versions.
|
|
18
|
-
*/
|
|
19
|
-
class AppSignatureHelper(context: Context) : ContextWrapper(context) {
|
|
20
|
-
|
|
21
|
-
companion object {
|
|
22
|
-
private const val TAG = "AppSignatureHelper"
|
|
23
|
-
private const val HASH_TYPE = "SHA-256"
|
|
24
|
-
private const val NUM_HASHED_BYTES = 9
|
|
25
|
-
private const val NUM_BASE64_CHAR = 11
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns app hash strings for the current package.
|
|
30
|
-
* Include one of these in your SMS message (e.g. at the end) for the Retriever to match.
|
|
31
|
-
*/
|
|
32
|
-
fun getAppSignatures(): ArrayList<String> {
|
|
33
|
-
val appCodes = ArrayList<String>()
|
|
34
|
-
try {
|
|
35
|
-
val packageName = packageName
|
|
36
|
-
val packageManager = packageManager
|
|
37
|
-
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
38
|
-
val packageInfo = packageManager.getPackageInfo(
|
|
39
|
-
packageName,
|
|
40
|
-
PackageManager.GET_SIGNING_CERTIFICATES
|
|
41
|
-
)
|
|
42
|
-
packageInfo.signingInfo?.apkContentsSigners
|
|
43
|
-
} else {
|
|
44
|
-
@Suppress("DEPRECATION")
|
|
45
|
-
val packageInfo = packageManager.getPackageInfo(
|
|
46
|
-
packageName,
|
|
47
|
-
PackageManager.GET_SIGNATURES
|
|
48
|
-
)
|
|
49
|
-
packageInfo.signatures
|
|
50
|
-
} ?: return appCodes
|
|
51
|
-
|
|
52
|
-
for (signature in signatures) {
|
|
53
|
-
val hash = hash(packageName, signature.toCharsString())
|
|
54
|
-
if (hash != null) {
|
|
55
|
-
appCodes.add(hash)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch (e: PackageManager.NameNotFoundException) {
|
|
59
|
-
Log.e(TAG, "Unable to find package to obtain hash.", e)
|
|
60
|
-
}
|
|
61
|
-
return appCodes
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private fun hash(packageName: String, signature: String): String? {
|
|
65
|
-
return try {
|
|
66
|
-
val appInfo = "$packageName $signature"
|
|
67
|
-
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
|
|
68
|
-
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
|
|
69
|
-
var hashSignature = messageDigest.digest()
|
|
70
|
-
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
|
|
71
|
-
var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
|
|
72
|
-
base64Hash = base64Hash.substring(0, minOf(NUM_BASE64_CHAR, base64Hash.length))
|
|
73
|
-
Log.d(TAG, "pkg: $packageName -- hash: $base64Hash")
|
|
74
|
-
base64Hash
|
|
75
|
-
} catch (e: NoSuchAlgorithmException) {
|
|
76
|
-
Log.e(TAG, "hash: NoSuchAlgorithm", e)
|
|
77
|
-
null
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
1
|
+
package com.otpautoverify
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.ContextWrapper
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.util.Base64
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import java.nio.charset.StandardCharsets
|
|
10
|
+
import java.security.MessageDigest
|
|
11
|
+
import java.security.NoSuchAlgorithmException
|
|
12
|
+
import java.util.ArrayList
|
|
13
|
+
import java.util.Arrays
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Computes the 11-character app hash required by the SMS Retriever API.
|
|
17
|
+
* Uses GET_SIGNING_CERTIFICATES on API 28+ and GET_SIGNATURES on older versions.
|
|
18
|
+
*/
|
|
19
|
+
class AppSignatureHelper(context: Context) : ContextWrapper(context) {
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
private const val TAG = "AppSignatureHelper"
|
|
23
|
+
private const val HASH_TYPE = "SHA-256"
|
|
24
|
+
private const val NUM_HASHED_BYTES = 9
|
|
25
|
+
private const val NUM_BASE64_CHAR = 11
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns app hash strings for the current package.
|
|
30
|
+
* Include one of these in your SMS message (e.g. at the end) for the Retriever to match.
|
|
31
|
+
*/
|
|
32
|
+
fun getAppSignatures(): ArrayList<String> {
|
|
33
|
+
val appCodes = ArrayList<String>()
|
|
34
|
+
try {
|
|
35
|
+
val packageName = packageName
|
|
36
|
+
val packageManager = packageManager
|
|
37
|
+
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
38
|
+
val packageInfo = packageManager.getPackageInfo(
|
|
39
|
+
packageName,
|
|
40
|
+
PackageManager.GET_SIGNING_CERTIFICATES
|
|
41
|
+
)
|
|
42
|
+
packageInfo.signingInfo?.apkContentsSigners
|
|
43
|
+
} else {
|
|
44
|
+
@Suppress("DEPRECATION")
|
|
45
|
+
val packageInfo = packageManager.getPackageInfo(
|
|
46
|
+
packageName,
|
|
47
|
+
PackageManager.GET_SIGNATURES
|
|
48
|
+
)
|
|
49
|
+
packageInfo.signatures
|
|
50
|
+
} ?: return appCodes
|
|
51
|
+
|
|
52
|
+
for (signature in signatures) {
|
|
53
|
+
val hash = hash(packageName, signature.toCharsString())
|
|
54
|
+
if (hash != null) {
|
|
55
|
+
appCodes.add(hash)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
59
|
+
Log.e(TAG, "Unable to find package to obtain hash.", e)
|
|
60
|
+
}
|
|
61
|
+
return appCodes
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun hash(packageName: String, signature: String): String? {
|
|
65
|
+
return try {
|
|
66
|
+
val appInfo = "$packageName $signature"
|
|
67
|
+
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
|
|
68
|
+
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
|
|
69
|
+
var hashSignature = messageDigest.digest()
|
|
70
|
+
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
|
|
71
|
+
var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
|
|
72
|
+
base64Hash = base64Hash.substring(0, minOf(NUM_BASE64_CHAR, base64Hash.length))
|
|
73
|
+
Log.d(TAG, "pkg: $packageName -- hash: $base64Hash")
|
|
74
|
+
base64Hash
|
|
75
|
+
} catch (e: NoSuchAlgorithmException) {
|
|
76
|
+
Log.e(TAG, "hash: NoSuchAlgorithm", e)
|
|
77
|
+
null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,146 +1,146 @@
|
|
|
1
|
-
package com.otpautoverify
|
|
2
|
-
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
|
-
import android.content.IntentFilter
|
|
5
|
-
import android.os.Build
|
|
6
|
-
import android.util.Log
|
|
7
|
-
import com.facebook.react.bridge.Arguments
|
|
8
|
-
import com.facebook.react.bridge.LifecycleEventListener
|
|
9
|
-
import com.facebook.react.bridge.Promise
|
|
10
|
-
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
-
import com.google.android.gms.auth.api.phone.SmsRetriever
|
|
12
|
-
import com.google.android.gms.tasks.OnFailureListener
|
|
13
|
-
import com.google.android.gms.tasks.OnSuccessListener
|
|
14
|
-
class OtpAutoVerifyModule(reactContext: ReactApplicationContext) :
|
|
15
|
-
NativeOtpAutoVerifySpec(reactContext), LifecycleEventListener {
|
|
16
|
-
|
|
17
|
-
companion object {
|
|
18
|
-
const val NAME = NativeOtpAutoVerifySpec.NAME
|
|
19
|
-
private const val TAG = "OtpAutoVerifyModule"
|
|
20
|
-
const val OTP_RECEIVED_EVENT = "otpReceived"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private var smsReceiver: SmsRetrieverBroadcastReceiver? = null
|
|
24
|
-
private var isReceiverRegistered = false
|
|
25
|
-
private var isListening = false
|
|
26
|
-
|
|
27
|
-
init {
|
|
28
|
-
reactContext.addLifecycleEventListener(this)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
override fun getTypedExportedConstants(): MutableMap<String, Any> {
|
|
32
|
-
return mutableMapOf("OTP_RECEIVED_EVENT" to OTP_RECEIVED_EVENT)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
override fun getHash(promise: Promise) {
|
|
36
|
-
try {
|
|
37
|
-
val helper = AppSignatureHelper(reactApplicationContext)
|
|
38
|
-
val signatures = helper.getAppSignatures()
|
|
39
|
-
val arr = Arguments.createArray()
|
|
40
|
-
for (s in signatures) {
|
|
41
|
-
arr.pushString(s)
|
|
42
|
-
}
|
|
43
|
-
promise.resolve(arr)
|
|
44
|
-
} catch (e: Exception) {
|
|
45
|
-
Log.e(TAG, "getHash failed", e)
|
|
46
|
-
promise.reject("GET_HASH_ERROR", e.message, e)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
override fun startSmsRetriever(promise: Promise) {
|
|
51
|
-
val activity = currentActivity
|
|
52
|
-
if (activity == null) {
|
|
53
|
-
promise.reject("NO_ACTIVITY", "No current activity. Ensure the app is in the foreground.")
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
registerReceiverIfNecessary(activity)
|
|
58
|
-
|
|
59
|
-
val client = SmsRetriever.getClient(reactApplicationContext)
|
|
60
|
-
client.startSmsRetriever()
|
|
61
|
-
.addOnSuccessListener(OnSuccessListener {
|
|
62
|
-
Log.d(TAG, "SMS retriever started")
|
|
63
|
-
isListening = true
|
|
64
|
-
promise.resolve(true)
|
|
65
|
-
})
|
|
66
|
-
.addOnFailureListener(OnFailureListener { e ->
|
|
67
|
-
Log.e(TAG, "Failed to start SMS retriever", e)
|
|
68
|
-
promise.reject("START_SMS_RETRIEVER_ERROR", e.message, e)
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
73
|
-
private fun registerReceiverIfNecessary(activity: android.app.Activity) {
|
|
74
|
-
if (isReceiverRegistered) return
|
|
75
|
-
try {
|
|
76
|
-
smsReceiver = SmsRetrieverBroadcastReceiver(reactApplicationContext, OTP_RECEIVED_EVENT)
|
|
77
|
-
val filter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
|
|
78
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
79
|
-
activity.registerReceiver(
|
|
80
|
-
smsReceiver,
|
|
81
|
-
filter,
|
|
82
|
-
SmsRetriever.SEND_PERMISSION,
|
|
83
|
-
null,
|
|
84
|
-
android.content.Context.RECEIVER_EXPORTED
|
|
85
|
-
)
|
|
86
|
-
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
87
|
-
activity.registerReceiver(
|
|
88
|
-
smsReceiver,
|
|
89
|
-
filter,
|
|
90
|
-
android.content.Context.RECEIVER_EXPORTED
|
|
91
|
-
)
|
|
92
|
-
} else {
|
|
93
|
-
activity.registerReceiver(smsReceiver, filter)
|
|
94
|
-
}
|
|
95
|
-
isReceiverRegistered = true
|
|
96
|
-
Log.d(TAG, "SMS receiver registered")
|
|
97
|
-
} catch (e: Exception) {
|
|
98
|
-
Log.e(TAG, "Failed to register SMS receiver", e)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private fun unregisterReceiver() {
|
|
103
|
-
val activity = currentActivity
|
|
104
|
-
if (isReceiverRegistered && activity != null && smsReceiver != null) {
|
|
105
|
-
try {
|
|
106
|
-
activity.unregisterReceiver(smsReceiver)
|
|
107
|
-
Log.d(TAG, "SMS receiver unregistered")
|
|
108
|
-
} catch (e: Exception) {
|
|
109
|
-
Log.e(TAG, "Failed to unregister SMS receiver", e)
|
|
110
|
-
}
|
|
111
|
-
isReceiverRegistered = false
|
|
112
|
-
smsReceiver = null
|
|
113
|
-
}
|
|
114
|
-
isListening = false
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
override fun addListener(eventName: String) {
|
|
118
|
-
// Required for NativeEventEmitter; no-op.
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
override fun removeListeners(count: Double) {
|
|
122
|
-
unregisterReceiver()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
override fun onHostResume() {
|
|
126
|
-
// Optionally re-register if we were listening and activity was recreated.
|
|
127
|
-
if (isListening && currentActivity != null && !isReceiverRegistered) {
|
|
128
|
-
currentActivity?.let { registerReceiverIfNecessary(it) }
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
override fun onHostPause() {
|
|
133
|
-
unregisterReceiver()
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
override fun onHostDestroy() {
|
|
137
|
-
unregisterReceiver()
|
|
138
|
-
reactApplicationContext.removeLifecycleEventListener(this)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
override fun invalidate() {
|
|
142
|
-
unregisterReceiver()
|
|
143
|
-
reactApplicationContext.removeLifecycleEventListener(this)
|
|
144
|
-
super.invalidate()
|
|
145
|
-
}
|
|
146
|
-
}
|
|
1
|
+
package com.otpautoverify
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.IntentFilter
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.google.android.gms.auth.api.phone.SmsRetriever
|
|
12
|
+
import com.google.android.gms.tasks.OnFailureListener
|
|
13
|
+
import com.google.android.gms.tasks.OnSuccessListener
|
|
14
|
+
class OtpAutoVerifyModule(reactContext: ReactApplicationContext) :
|
|
15
|
+
NativeOtpAutoVerifySpec(reactContext), LifecycleEventListener {
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val NAME = NativeOtpAutoVerifySpec.NAME
|
|
19
|
+
private const val TAG = "OtpAutoVerifyModule"
|
|
20
|
+
const val OTP_RECEIVED_EVENT = "otpReceived"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private var smsReceiver: SmsRetrieverBroadcastReceiver? = null
|
|
24
|
+
private var isReceiverRegistered = false
|
|
25
|
+
private var isListening = false
|
|
26
|
+
|
|
27
|
+
init {
|
|
28
|
+
reactContext.addLifecycleEventListener(this)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override fun getTypedExportedConstants(): MutableMap<String, Any> {
|
|
32
|
+
return mutableMapOf("OTP_RECEIVED_EVENT" to OTP_RECEIVED_EVENT)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun getHash(promise: Promise) {
|
|
36
|
+
try {
|
|
37
|
+
val helper = AppSignatureHelper(reactApplicationContext)
|
|
38
|
+
val signatures = helper.getAppSignatures()
|
|
39
|
+
val arr = Arguments.createArray()
|
|
40
|
+
for (s in signatures) {
|
|
41
|
+
arr.pushString(s)
|
|
42
|
+
}
|
|
43
|
+
promise.resolve(arr)
|
|
44
|
+
} catch (e: Exception) {
|
|
45
|
+
Log.e(TAG, "getHash failed", e)
|
|
46
|
+
promise.reject("GET_HASH_ERROR", e.message, e)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun startSmsRetriever(promise: Promise) {
|
|
51
|
+
val activity = currentActivity
|
|
52
|
+
if (activity == null) {
|
|
53
|
+
promise.reject("NO_ACTIVITY", "No current activity. Ensure the app is in the foreground.")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
registerReceiverIfNecessary(activity)
|
|
58
|
+
|
|
59
|
+
val client = SmsRetriever.getClient(reactApplicationContext)
|
|
60
|
+
client.startSmsRetriever()
|
|
61
|
+
.addOnSuccessListener(OnSuccessListener {
|
|
62
|
+
Log.d(TAG, "SMS retriever started")
|
|
63
|
+
isListening = true
|
|
64
|
+
promise.resolve(true)
|
|
65
|
+
})
|
|
66
|
+
.addOnFailureListener(OnFailureListener { e ->
|
|
67
|
+
Log.e(TAG, "Failed to start SMS retriever", e)
|
|
68
|
+
promise.reject("START_SMS_RETRIEVER_ERROR", e.message, e)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
73
|
+
private fun registerReceiverIfNecessary(activity: android.app.Activity) {
|
|
74
|
+
if (isReceiverRegistered) return
|
|
75
|
+
try {
|
|
76
|
+
smsReceiver = SmsRetrieverBroadcastReceiver(reactApplicationContext, OTP_RECEIVED_EVENT)
|
|
77
|
+
val filter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
|
|
78
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
79
|
+
activity.registerReceiver(
|
|
80
|
+
smsReceiver,
|
|
81
|
+
filter,
|
|
82
|
+
SmsRetriever.SEND_PERMISSION,
|
|
83
|
+
null,
|
|
84
|
+
android.content.Context.RECEIVER_EXPORTED
|
|
85
|
+
)
|
|
86
|
+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
87
|
+
activity.registerReceiver(
|
|
88
|
+
smsReceiver,
|
|
89
|
+
filter,
|
|
90
|
+
android.content.Context.RECEIVER_EXPORTED
|
|
91
|
+
)
|
|
92
|
+
} else {
|
|
93
|
+
activity.registerReceiver(smsReceiver, filter)
|
|
94
|
+
}
|
|
95
|
+
isReceiverRegistered = true
|
|
96
|
+
Log.d(TAG, "SMS receiver registered")
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
Log.e(TAG, "Failed to register SMS receiver", e)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun unregisterReceiver() {
|
|
103
|
+
val activity = currentActivity
|
|
104
|
+
if (isReceiverRegistered && activity != null && smsReceiver != null) {
|
|
105
|
+
try {
|
|
106
|
+
activity.unregisterReceiver(smsReceiver)
|
|
107
|
+
Log.d(TAG, "SMS receiver unregistered")
|
|
108
|
+
} catch (e: Exception) {
|
|
109
|
+
Log.e(TAG, "Failed to unregister SMS receiver", e)
|
|
110
|
+
}
|
|
111
|
+
isReceiverRegistered = false
|
|
112
|
+
smsReceiver = null
|
|
113
|
+
}
|
|
114
|
+
isListening = false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun addListener(eventName: String) {
|
|
118
|
+
// Required for NativeEventEmitter; no-op.
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun removeListeners(count: Double) {
|
|
122
|
+
unregisterReceiver()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun onHostResume() {
|
|
126
|
+
// Optionally re-register if we were listening and activity was recreated.
|
|
127
|
+
if (isListening && currentActivity != null && !isReceiverRegistered) {
|
|
128
|
+
currentActivity?.let { registerReceiverIfNecessary(it) }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun onHostPause() {
|
|
133
|
+
unregisterReceiver()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun onHostDestroy() {
|
|
137
|
+
unregisterReceiver()
|
|
138
|
+
reactApplicationContext.removeLifecycleEventListener(this)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override fun invalidate() {
|
|
142
|
+
unregisterReceiver()
|
|
143
|
+
reactApplicationContext.removeLifecycleEventListener(this)
|
|
144
|
+
super.invalidate()
|
|
145
|
+
}
|
|
146
|
+
}
|