react-native-otp-auto-verify 0.2.0 → 0.2.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.
@@ -1,6 +1,10 @@
1
- import { NativeModules, TurboModuleRegistry, type TurboModule } from 'react-native';
1
+ import {
2
+ NativeModules,
3
+ TurboModuleRegistry,
4
+ type TurboModule,
5
+ } from 'react-native';
2
6
 
3
- export interface OtpAutoVerifySpec extends TurboModule {
7
+ export interface Spec extends TurboModule {
4
8
  getConstants(): { OTP_RECEIVED_EVENT: string };
5
9
  getHash(): Promise<ReadonlyArray<string>>;
6
10
  startSmsRetriever(): Promise<boolean>;
@@ -8,9 +12,9 @@ export interface OtpAutoVerifySpec extends TurboModule {
8
12
  removeListeners(count: number): void;
9
13
  }
10
14
 
11
- function getNativeModule(): OtpAutoVerifySpec {
15
+ function loadNativeModule(): Spec {
12
16
  try {
13
- return TurboModuleRegistry.getEnforcing<OtpAutoVerifySpec>('OtpAutoVerify');
17
+ return TurboModuleRegistry.getEnforcing<Spec>('OtpAutoVerify');
14
18
  } catch {
15
19
  const legacy = NativeModules.OtpAutoVerify;
16
20
  if (!legacy) {
@@ -18,8 +22,16 @@ function getNativeModule(): OtpAutoVerifySpec {
18
22
  'OtpAutoVerify native module is not available. Ensure the library is properly linked.'
19
23
  );
20
24
  }
21
- return legacy as OtpAutoVerifySpec;
25
+ return legacy as Spec;
22
26
  }
23
27
  }
24
28
 
25
- export default getNativeModule();
29
+ let cachedModule: Spec | null = null;
30
+
31
+ /** Resolves the native module on first use so importing this package does not throw before APIs run. */
32
+ export function getOtpNativeModule(): Spec {
33
+ if (cachedModule === null) {
34
+ cachedModule = loadNativeModule();
35
+ }
36
+ return cachedModule;
37
+ }
package/src/index.tsx CHANGED
@@ -1,15 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { NativeEventEmitter, Platform } from 'react-native';
3
- import NativeOtpAutoVerify from './NativeOtpAutoVerify';
3
+ import { getOtpNativeModule } from './NativeOtpAutoVerify';
4
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';
5
+ /** Must match native `OTP_RECEIVED_EVENT` (Android/iOS). */
6
+ export const OTP_RECEIVED_EVENT = 'otpReceived';
13
7
 
14
8
  export const TIMEOUT_MESSAGE = 'Timeout Error.';
15
9
  const DEFAULT_DIGITS = 6;
@@ -19,11 +13,29 @@ const MAX_OTP_DIGITS = 8;
19
13
 
20
14
  export type OtpDigits = 4 | 5 | 6 | 7 | 8;
21
15
 
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`);
16
+ let androidEventEmitter: NativeEventEmitter | null | undefined;
17
+
18
+ function getAndroidEventEmitter(): NativeEventEmitter | null {
19
+ if (Platform.OS !== 'android') return null;
20
+ if (androidEventEmitter !== undefined) {
21
+ return androidEventEmitter;
22
+ }
23
+ try {
24
+ androidEventEmitter = new NativeEventEmitter(getOtpNativeModule());
25
+ return androidEventEmitter;
26
+ } catch {
27
+ androidEventEmitter = null;
28
+ return null;
25
29
  }
26
- return new RegExp(`\\b(\\d{${digits}})\\b`);
30
+ }
31
+
32
+ function getOtpRegex(digits: number): RegExp {
33
+ const d =
34
+ digits >= MIN_OTP_DIGITS && digits <= MAX_OTP_DIGITS
35
+ ? digits
36
+ : DEFAULT_DIGITS;
37
+ // Prefer non-`\b` boundaries so OTPs still match after ":" and similar (e.g. "OTP:123456").
38
+ return new RegExp(`(^|[^0-9])(\\d{${d}})(?!\\d)`);
27
39
  }
28
40
 
29
41
  export interface UseOtpVerificationOptions {
@@ -64,13 +76,13 @@ export function extractOtp(
64
76
  if (!trimmed) return null;
65
77
  const regex = getOtpRegex(numberOfDigits);
66
78
  const match = trimmed.match(regex);
67
- return match ? match[1]! : null;
79
+ return match ? match[2]! : null;
68
80
  }
69
81
 
70
82
  /** Returns app hash strings for the current app. Android only; iOS returns []. */
71
83
  export async function getHash(): Promise<string[]> {
72
84
  if (Platform.OS !== 'android') return [];
73
- const arr = await NativeOtpAutoVerify.getHash();
85
+ const arr = await getOtpNativeModule().getHash();
74
86
  return Array.from(arr);
75
87
  }
76
88
 
@@ -84,12 +96,17 @@ export async function activateOtpListener(
84
96
  handler: (sms: string, extractedOtp?: string | null) => void,
85
97
  options?: { numberOfDigits?: OtpDigits }
86
98
  ): Promise<OtpListenerSubscription> {
87
- if (Platform.OS !== 'android' || !eventEmitter) {
99
+ if (Platform.OS !== 'android') {
100
+ return NOOP_SUBSCRIPTION;
101
+ }
102
+
103
+ const emitter = getAndroidEventEmitter();
104
+ if (!emitter) {
88
105
  return NOOP_SUBSCRIPTION;
89
106
  }
90
107
 
91
108
  const numberOfDigits = options?.numberOfDigits ?? DEFAULT_DIGITS;
92
- const subscription = eventEmitter.addListener(
109
+ const subscription = emitter.addListener(
93
110
  OTP_RECEIVED_EVENT,
94
111
  (...args: unknown[]) => {
95
112
  const smsText = String(args[0] ?? '');
@@ -97,11 +114,21 @@ export async function activateOtpListener(
97
114
  }
98
115
  );
99
116
 
100
- await NativeOtpAutoVerify.startSmsRetriever();
117
+ try {
118
+ await getOtpNativeModule().startSmsRetriever();
119
+ } catch (err) {
120
+ subscription.remove();
121
+ throw err;
122
+ }
123
+
101
124
  return {
102
125
  remove: () => {
103
126
  subscription.remove();
104
- NativeOtpAutoVerify.removeListeners(0);
127
+ try {
128
+ getOtpNativeModule().removeListeners(0);
129
+ } catch {
130
+ // Native may be unavailable during teardown
131
+ }
105
132
  },
106
133
  };
107
134
  }
@@ -111,8 +138,11 @@ export async function activateOtpListener(
111
138
  * The native module ignores the count parameter and always unregisters the SMS receiver.
112
139
  */
113
140
  export function removeListener(): void {
114
- if (Platform.OS === 'android') {
115
- NativeOtpAutoVerify.removeListeners(0);
141
+ if (Platform.OS !== 'android') return;
142
+ try {
143
+ getOtpNativeModule().removeListeners(0);
144
+ } catch {
145
+ // Native not linked or already torn down
116
146
  }
117
147
  }
118
148
 
@@ -153,7 +183,9 @@ export function useOtpVerification(
153
183
  const hashes = await getHash();
154
184
  setHashCode(hashes[0] ?? '');
155
185
  } catch (err) {
156
- setError(err instanceof Error ? err : new Error(String(err)));
186
+ const wrapped = err instanceof Error ? err : new Error(String(err));
187
+ setError(wrapped);
188
+ return;
157
189
  }
158
190
 
159
191
  const sub = await activateOtpListener(
@@ -193,5 +225,3 @@ export function useOtpVerification(
193
225
  [hashCode, otp, sms, timeoutError, error, startListening, stopListening]
194
226
  );
195
227
  }
196
-
197
- export { OTP_RECEIVED_EVENT };