diff --git a/components/OTPBindUI.tsx b/components/OTPBindUI.tsx index 07a648a..7aaa42b 100644 --- a/components/OTPBindUI.tsx +++ b/components/OTPBindUI.tsx @@ -6,9 +6,7 @@ import { Text, StyleSheet, ActivityIndicator, - Platform, } from 'react-native'; -import { normalizeHintPhone, requestPhoneNumberHint } from '../utils/smsRetriever'; import { WalletType } from 'rnwalletman'; import { useOTPBind } from '../hooks/useOTPBind'; @@ -59,29 +57,6 @@ export const OTPBindUI: React.FC = ({ const isLoading = state.loading || state.step === 'processing'; - const pickPhoneFromSystem = async () => { - if (Platform.OS !== 'android' || isLoading) return; - try { - const raw = await requestPhoneNumberHint(); - const digits = normalizeHintPhone(raw, mobileLength); - if (digits.length === mobileLength) { - actions.setMobile(digits); - if (state.errorMessage) actions.clearError(); - } - } catch { - /* 用户取消 */ - } - }; - - const renderPickPhone = () => { - if (Platform.OS !== 'android') return null; - return ( - - 从系统选择号码 - - ); - }; - const renderResendOtp = () => { if (!state.otpSent) return null; const disabled = isLoading || state.resendCountdown > 0; @@ -123,7 +98,6 @@ export const OTPBindUI: React.FC = ({ }} editable={!state.formStarted && !isLoading} /> - {!state.formStarted && renderPickPhone()} {state.formStarted && state.passwordRequired && ( <> {passwordLabel} @@ -160,6 +134,7 @@ export const OTPBindUI: React.FC = ({ {state.otpSent && ( <> OTP sent to {state.mobile} + 收到短信后点「允许」可填入验证码 Verification code = ({ }} editable={!isLoading} /> - {renderPickPhone()} = ({ {showOtp && ( <> OTP sent to {state.mobile} + 收到短信后点「允许」可填入验证码 {!!state.errorMessage && ( {state.errorMessage} )} @@ -338,6 +313,13 @@ const styles = StyleSheet.create({ textAlign: 'center', marginBottom: 14, }, + consentHint: { + fontSize: 11, + color: '#888', + textAlign: 'center', + marginTop: -8, + marginBottom: 12, + }, input: { borderWidth: 1.5, borderColor: '#e0e0e0', diff --git a/hooks/useOTPBind.ts b/hooks/useOTPBind.ts index 89cb6e2..f5abdaa 100644 --- a/hooks/useOTPBind.ts +++ b/hooks/useOTPBind.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { WalletType } from 'rnwalletman'; -import { startOtpSmsListener } from '../utils/smsRetriever'; +import { startSmsUserConsentListener } from '../utils/smsUserConsent'; export interface OTPBindCallbacks { onRequestOTP: (walletType: WalletType, params: any) => Promise; @@ -104,22 +104,16 @@ export function useOTPBind( return; } let cancelled = false; - (async () => { - smsStopRef.current?.(); - const stop = await startOtpSmsListener(otpLength, (code) => { - if (cancelled) return; - setOtp(code); - log('OTP from SMS Retriever:', code); - }); - if (cancelled) { - stop(); - return; - } - smsStopRef.current = stop; - })(); + smsStopRef.current?.(); + const stop = startSmsUserConsentListener(otpLength, (digits) => { + if (cancelled) return; + setOtp(digits); + log('SMS User Consent:', digits); + }); + smsStopRef.current = stop; return () => { cancelled = true; - smsStopRef.current?.(); + stop(); smsStopRef.current = null; }; }, [shouldListenSms, smsListenKey, otpLength]); diff --git a/package.json b/package.json index e07d274..d0d3959 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "postinstall": "patch-package" }, "dependencies": { + "@eabdullazyanov/react-native-sms-user-consent": "1.3.0", "@react-native-async-storage/async-storage": "^1.23.1", "@react-native-cookies/cookies": "^6.2.1", "@react-native-ml-kit/barcode-scanning": "^2.0.0", @@ -26,7 +27,6 @@ "react-native-gesture-handler": "~2.9.0", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "~3.36.0", - "react-native-sms-retriever": "1.1.1", "react-native-svg": "^14.1.0", "react-native-svg-transformer": "^1.5.3", "react-native-tcp-socket": "^6.4.1", diff --git a/patches/@eabdullazyanov+react-native-sms-user-consent+1.3.0.patch b/patches/@eabdullazyanov+react-native-sms-user-consent+1.3.0.patch new file mode 100644 index 0000000..b1710ba --- /dev/null +++ b/patches/@eabdullazyanov+react-native-sms-user-consent+1.3.0.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@eabdullazyanov/react-native-sms-user-consent/android/src/main/java/com/akvelon/reactnativesmsuserconsent/ReactNativeSmsUserConsentModule.java b/node_modules/@eabdullazyanov/react-native-sms-user-consent/android/src/main/java/com/akvelon/reactnativesmsuserconsent/ReactNativeSmsUserConsentModule.java +index 0000000..1111111 100644 +--- a/node_modules/@eabdullazyanov/react-native-sms-user-consent/android/src/main/java/com/akvelon/reactnativesmsuserconsent/ReactNativeSmsUserConsentModule.java ++++ b/node_modules/@eabdullazyanov/react-native-sms-user-consent/android/src/main/java/com/akvelon/reactnativesmsuserconsent/ReactNativeSmsUserConsentModule.java +@@ -49,7 +49,7 @@ public class ReactNativeSmsUserConsentModule extends ReactContextBaseJavaModule + SmsRetriever.getClient(getCurrentActivity()).startSmsUserConsent(null); + + broadcastReceiver = new SmsBroadcastReceiver(getCurrentActivity(), this); +- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { ++ if (Build.VERSION.SDK_INT >= 34) { + getCurrentActivity().registerReceiver( + broadcastReceiver, + new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION), diff --git a/utils/smsRetriever.ts b/utils/smsRetriever.ts deleted file mode 100644 index 0621a0a..0000000 --- a/utils/smsRetriever.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Platform } from 'react-native'; - -type SmsRetrieverModule = { - requestPhoneNumber: () => Promise; - startSmsRetriever: () => Promise; - addSmsListener: (callback: (event: { message?: string }) => void) => Promise; - removeSmsListener: () => void; -}; - -function getModule(): SmsRetrieverModule | null { - if (Platform.OS !== 'android') return null; - return require('react-native-sms-retriever').default as SmsRetrieverModule; -} - -export function extractOtpFromMessage(message: string, length: number): string | null { - const exact = new RegExp(`(?:^|[^\\d])(\\d{${length}})(?:[^\\d]|$)`); - const m = message.match(exact); - if (m) return m[1]; - const any = message.match(/(\d{4,8})/); - if (!any) return null; - const digits = any[1]; - if (digits.length >= length) return digits.slice(0, length); - if (digits.length === length) return digits; - return null; -} - -/** SMS Retriever:无需 READ_SMS,短信需含 app hash(见 Google 文档) */ -export async function startOtpSmsListener( - otpLength: number, - onOtp: (otp: string) => void, -): Promise<() => void> { - const mod = getModule(); - if (!mod) return () => {}; - - const stop = () => { - try { - mod.removeSmsListener(); - } catch { - /* ignore */ - } - }; - - try { - const registered = await mod.startSmsRetriever(); - if (!registered) return stop; - await mod.addSmsListener((event) => { - const msg = event.message ?? ''; - const otp = extractOtpFromMessage(msg, otpLength); - if (otp) { - onOtp(otp); - stop(); - } - }); - } catch { - stop(); - } - return stop; -} - -/** 系统号码选择器,一次性授权,无需 READ_SMS */ -export async function requestPhoneNumberHint(): Promise { - const mod = getModule(); - if (!mod) throw new Error('仅支持 Android'); - return mod.requestPhoneNumber(); -} - -export function normalizeHintPhone(raw: string, mobileLength = 10): string { - return raw.replace(/\D/g, '').slice(-mobileLength); -} diff --git a/utils/smsUserConsent.ts b/utils/smsUserConsent.ts new file mode 100644 index 0000000..0413c32 --- /dev/null +++ b/utils/smsUserConsent.ts @@ -0,0 +1,32 @@ +import { Platform } from 'react-native'; +import { + retrieveVerificationCode, + startSmsHandling, +} from '@eabdullazyanov/react-native-sms-user-consent'; + +/** 用户点允许后,从短信正文取数字(优先 preferLength 位,否则取第一段数字) */ +export function digitsFromSms(message: string, preferLength?: number): string { + if (preferLength) { + const exact = retrieveVerificationCode(message, preferLength); + if (exact) return exact; + } + const m = message.match(/\d+/); + return m ? m[0] : ''; +} + +/** + * SMS User Consent:系统弹窗,用户同意后才回调;无需 READ_SMS / app hash。 + */ +export function startSmsUserConsentListener( + preferLength: number, + onDigits: (digits: string) => void, +): () => void { + if (Platform.OS !== 'android') return () => {}; + + return startSmsHandling((event) => { + const sms = event?.sms ?? ''; + if (!sms) return; + const digits = digitsFromSms(sms, preferLength); + if (digits) onDigits(digits); + }); +}