react-native-otp-auto-verify 0.1.8 → 0.2.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 +452 -466
- package/android/src/main/java/com/otpautoverify/OtpAutoVerifyModule.kt +12 -5
- package/lib/module/NativeOtpAutoVerify.ts +17 -3
- package/lib/module/index.js +41 -22
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeOtpAutoVerify.d.ts +2 -2
- package/lib/typescript/src/NativeOtpAutoVerify.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -5
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +175 -183
- package/src/NativeOtpAutoVerify.ts +17 -3
- package/src/index.tsx +197 -172
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
1
|
+
import { NativeModules, TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface OtpAutoVerifySpec extends TurboModule {
|
|
4
4
|
getConstants(): { OTP_RECEIVED_EVENT: string };
|
|
5
5
|
getHash(): Promise<ReadonlyArray<string>>;
|
|
6
6
|
startSmsRetriever(): Promise<boolean>;
|
|
@@ -8,4 +8,18 @@ export interface Spec extends TurboModule {
|
|
|
8
8
|
removeListeners(count: number): void;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function getNativeModule(): OtpAutoVerifySpec {
|
|
12
|
+
try {
|
|
13
|
+
return TurboModuleRegistry.getEnforcing<OtpAutoVerifySpec>('OtpAutoVerify');
|
|
14
|
+
} catch {
|
|
15
|
+
const legacy = NativeModules.OtpAutoVerify;
|
|
16
|
+
if (!legacy) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'OtpAutoVerify native module is not available. Ensure the library is properly linked.'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return legacy as OtpAutoVerifySpec;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default getNativeModule();
|
package/src/index.tsx
CHANGED
|
@@ -1,172 +1,197 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { NativeEventEmitter,
|
|
3
|
-
import NativeOtpAutoVerify from './NativeOtpAutoVerify';
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { NativeEventEmitter, Platform } from 'react-native';
|
|
3
|
+
import NativeOtpAutoVerify from './NativeOtpAutoVerify';
|
|
4
|
+
|
|
5
|
+
const eventEmitter =
|
|
6
|
+
Platform.OS === 'android' && NativeOtpAutoVerify
|
|
7
|
+
? new NativeEventEmitter(NativeOtpAutoVerify)
|
|
8
|
+
: null;
|
|
9
|
+
|
|
10
|
+
const OTP_RECEIVED_EVENT =
|
|
11
|
+
(NativeOtpAutoVerify.getConstants?.()?.OTP_RECEIVED_EVENT as string) ??
|
|
12
|
+
'otpReceived';
|
|
13
|
+
|
|
14
|
+
export const TIMEOUT_MESSAGE = 'Timeout Error.';
|
|
15
|
+
const DEFAULT_DIGITS = 6;
|
|
16
|
+
|
|
17
|
+
const MIN_OTP_DIGITS = 4;
|
|
18
|
+
const MAX_OTP_DIGITS = 8;
|
|
19
|
+
|
|
20
|
+
export type OtpDigits = 4 | 5 | 6 | 7 | 8;
|
|
21
|
+
|
|
22
|
+
function getOtpRegex(digits: number): RegExp {
|
|
23
|
+
if (digits < MIN_OTP_DIGITS || digits > MAX_OTP_DIGITS) {
|
|
24
|
+
return new RegExp(`\\b(\\d{${DEFAULT_DIGITS}})\\b`);
|
|
25
|
+
}
|
|
26
|
+
return new RegExp(`\\b(\\d{${digits}})\\b`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UseOtpVerificationOptions {
|
|
30
|
+
/** Extract OTP with this many digits (4–8). OTP is set when SMS is received. */
|
|
31
|
+
numberOfDigits?: OtpDigits;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UseOtpVerificationResult {
|
|
35
|
+
/** App hash string for SMS (e.g. "uW87Uq6teXc"). Use at end of your OTP message. */
|
|
36
|
+
hashCode: string;
|
|
37
|
+
/** Extracted OTP when numberOfDigits is set and an SMS was received. */
|
|
38
|
+
otp: string | null;
|
|
39
|
+
/** Full SMS text when received. */
|
|
40
|
+
sms: string | null;
|
|
41
|
+
/** True when the 5-minute SMS Retriever timeout occurred. */
|
|
42
|
+
timeoutError: boolean;
|
|
43
|
+
/** Set when getHash fails (non-fatal) or when startListening fails. Cleared when startListening is called again. */
|
|
44
|
+
error: Error | null;
|
|
45
|
+
/** Start listening for OTP again (e.g. after timeout or error). */
|
|
46
|
+
startListening: () => Promise<void>;
|
|
47
|
+
/** Stop listening and clean up. Call on unmount. */
|
|
48
|
+
stopListening: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OtpListenerSubscription {
|
|
52
|
+
remove: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extracts a numeric OTP of 4–8 digits from SMS text.
|
|
57
|
+
*/
|
|
58
|
+
export function extractOtp(
|
|
59
|
+
sms: string,
|
|
60
|
+
numberOfDigits: OtpDigits = DEFAULT_DIGITS
|
|
61
|
+
): string | null {
|
|
62
|
+
if (!sms || typeof sms !== 'string') return null;
|
|
63
|
+
const trimmed = sms.trim();
|
|
64
|
+
if (!trimmed) return null;
|
|
65
|
+
const regex = getOtpRegex(numberOfDigits);
|
|
66
|
+
const match = trimmed.match(regex);
|
|
67
|
+
return match ? match[1]! : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Returns app hash strings for the current app. Android only; iOS returns []. */
|
|
71
|
+
export async function getHash(): Promise<string[]> {
|
|
72
|
+
if (Platform.OS !== 'android') return [];
|
|
73
|
+
const arr = await NativeOtpAutoVerify.getHash();
|
|
74
|
+
return Array.from(arr);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** No-op subscription for platforms where SMS Retriever is not supported (e.g. iOS). */
|
|
78
|
+
const NOOP_SUBSCRIPTION: OtpListenerSubscription = {
|
|
79
|
+
remove: () => {},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Starts SMS Retriever and subscribes to OTP events. Returns subscription with remove(). On iOS, returns no-op (call remove() safely). */
|
|
83
|
+
export async function activateOtpListener(
|
|
84
|
+
handler: (sms: string, extractedOtp?: string | null) => void,
|
|
85
|
+
options?: { numberOfDigits?: OtpDigits }
|
|
86
|
+
): Promise<OtpListenerSubscription> {
|
|
87
|
+
if (Platform.OS !== 'android' || !eventEmitter) {
|
|
88
|
+
return NOOP_SUBSCRIPTION;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const numberOfDigits = options?.numberOfDigits ?? DEFAULT_DIGITS;
|
|
92
|
+
const subscription = eventEmitter.addListener(
|
|
93
|
+
OTP_RECEIVED_EVENT,
|
|
94
|
+
(...args: unknown[]) => {
|
|
95
|
+
const smsText = String(args[0] ?? '');
|
|
96
|
+
handler(smsText, extractOtp(smsText, numberOfDigits));
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
await NativeOtpAutoVerify.startSmsRetriever();
|
|
101
|
+
return {
|
|
102
|
+
remove: () => {
|
|
103
|
+
subscription.remove();
|
|
104
|
+
NativeOtpAutoVerify.removeListeners(0);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Stops SMS listening and removes all listeners.
|
|
111
|
+
* The native module ignores the count parameter and always unregisters the SMS receiver.
|
|
112
|
+
*/
|
|
113
|
+
export function removeListener(): void {
|
|
114
|
+
if (Platform.OS === 'android') {
|
|
115
|
+
NativeOtpAutoVerify.removeListeners(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Hook for OTP verification. Call startListening() to begin; listener is stopped on unmount. */
|
|
120
|
+
export function useOtpVerification(
|
|
121
|
+
options: UseOtpVerificationOptions = {}
|
|
122
|
+
): UseOtpVerificationResult {
|
|
123
|
+
const numberOfDigits = options.numberOfDigits ?? DEFAULT_DIGITS;
|
|
124
|
+
const [hashCode, setHashCode] = React.useState('');
|
|
125
|
+
const [otp, setOtp] = React.useState<string | null>(null);
|
|
126
|
+
const [sms, setSms] = React.useState<string | null>(null);
|
|
127
|
+
const [timeoutError, setTimeoutError] = React.useState(false);
|
|
128
|
+
const [error, setError] = React.useState<Error | null>(null);
|
|
129
|
+
const subscriptionRef = React.useRef<OtpListenerSubscription | null>(null);
|
|
130
|
+
const isStartingRef = React.useRef(false);
|
|
131
|
+
|
|
132
|
+
const stopListening = React.useCallback(() => {
|
|
133
|
+
subscriptionRef.current?.remove();
|
|
134
|
+
subscriptionRef.current = null;
|
|
135
|
+
isStartingRef.current = false;
|
|
136
|
+
removeListener();
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
139
|
+
const startListening = React.useCallback(async () => {
|
|
140
|
+
if (Platform.OS !== 'android') return;
|
|
141
|
+
if (isStartingRef.current) return;
|
|
142
|
+
|
|
143
|
+
isStartingRef.current = true;
|
|
144
|
+
subscriptionRef.current?.remove();
|
|
145
|
+
subscriptionRef.current = null;
|
|
146
|
+
setOtp(null);
|
|
147
|
+
setSms(null);
|
|
148
|
+
setTimeoutError(false);
|
|
149
|
+
setError(null);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
try {
|
|
153
|
+
const hashes = await getHash();
|
|
154
|
+
setHashCode(hashes[0] ?? '');
|
|
155
|
+
} catch (err) {
|
|
156
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const sub = await activateOtpListener(
|
|
160
|
+
(smsText, extractedOtp) => {
|
|
161
|
+
setSms(smsText);
|
|
162
|
+
if (smsText === TIMEOUT_MESSAGE) {
|
|
163
|
+
setTimeoutError(true);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (extractedOtp) setOtp(extractedOtp);
|
|
167
|
+
},
|
|
168
|
+
{ numberOfDigits }
|
|
169
|
+
);
|
|
170
|
+
subscriptionRef.current = sub;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
subscriptionRef.current = null;
|
|
173
|
+
const wrapped = new Error('Failed to start OTP listener', { cause: err });
|
|
174
|
+
setError(wrapped);
|
|
175
|
+
throw wrapped;
|
|
176
|
+
} finally {
|
|
177
|
+
isStartingRef.current = false;
|
|
178
|
+
}
|
|
179
|
+
}, [numberOfDigits]);
|
|
180
|
+
|
|
181
|
+
React.useEffect(() => () => stopListening(), [stopListening]);
|
|
182
|
+
|
|
183
|
+
return React.useMemo(
|
|
184
|
+
() => ({
|
|
185
|
+
hashCode,
|
|
186
|
+
otp,
|
|
187
|
+
sms,
|
|
188
|
+
timeoutError,
|
|
189
|
+
error,
|
|
190
|
+
startListening,
|
|
191
|
+
stopListening,
|
|
192
|
+
}),
|
|
193
|
+
[hashCode, otp, sms, timeoutError, error, startListening, stopListening]
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { OTP_RECEIVED_EVENT };
|