fix bug of WebOTP
This commit is contained in:
+93
-16
@@ -1,39 +1,116 @@
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
/** استخراج کد عددی از نتیجه WebOTP / Credential */
|
||||
type OtpHandler = (code: string) => void
|
||||
|
||||
let activeController: AbortController | null = null
|
||||
let listenGeneration = 0
|
||||
let pendingCode: string | null = null
|
||||
const handlers = new Set<OtpHandler>()
|
||||
|
||||
/** استخراج کد عددی از نتیجه WebOTP / Credential / رشته خام */
|
||||
export function extractOtpCode(
|
||||
credential: Credential | null | undefined,
|
||||
length = 4
|
||||
credential: Credential | null | undefined | string,
|
||||
length = 4,
|
||||
): string {
|
||||
const raw = (credential as { code?: string } | null | undefined)?.code ?? ''
|
||||
let raw = ''
|
||||
if (typeof credential === 'string') {
|
||||
raw = credential
|
||||
} else if (credential && typeof credential === 'object') {
|
||||
const c = credential as { code?: string; id?: string }
|
||||
raw = c.code ?? c.id ?? ''
|
||||
}
|
||||
return persianToEnglishDigits(raw).replace(/\D/g, '').slice(0, length)
|
||||
}
|
||||
|
||||
/**
|
||||
* گوش دادن به SMS از طریق WebOTP API (Chrome Android).
|
||||
* با کلیک Allow روی دیالوگ مرورگر، onCode با کد ۴ رقمی صدا زده میشود.
|
||||
*/
|
||||
export function listenForSmsOtp(onCode: (code: string) => void): AbortController {
|
||||
const ac = new AbortController()
|
||||
function emitCode(code: string) {
|
||||
pendingCode = code
|
||||
handlers.forEach((handler) => {
|
||||
try {
|
||||
handler(code)
|
||||
} catch {
|
||||
/* ignore subscriber errors */
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!('OTPCredential' in window) || !navigator.credentials?.get) {
|
||||
return ac
|
||||
/**
|
||||
* شروع زودهنگام WebOTP — باید قبل از ارسال SMS / همزمان با درخواست کد صدا زده شود.
|
||||
* اگر از قبل در حال listen باشد، دوباره abort نمیکند (دیالوگ Allow را خراب نکند).
|
||||
*/
|
||||
export function beginSmsOtpCapture(length = 4): void {
|
||||
if (typeof window === 'undefined' || !navigator.credentials?.get) {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeController && !activeController.signal.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
const ac = new AbortController()
|
||||
activeController = ac
|
||||
const generation = ++listenGeneration
|
||||
|
||||
void navigator.credentials
|
||||
.get({
|
||||
otp: { transport: ['sms'] },
|
||||
signal: ac.signal,
|
||||
} as CredentialRequestOptions)
|
||||
.then((credential) => {
|
||||
const code = extractOtpCode(credential, 4)
|
||||
if (code.length === 4) {
|
||||
onCode(code)
|
||||
if (generation !== listenGeneration) return
|
||||
const code = extractOtpCode(credential, length)
|
||||
if (code.length === length) {
|
||||
emitCode(code)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* aborted or denied — ignore */
|
||||
/* Deny / Abort / unsupported */
|
||||
})
|
||||
.finally(() => {
|
||||
if (activeController === ac) {
|
||||
activeController = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** عضویت برای دریافت کد — اگر کد از قبل آمده باشد همان لحظه میدهد */
|
||||
export function subscribeSmsOtp(handler: OtpHandler): () => void {
|
||||
handlers.add(handler)
|
||||
if (pendingCode) {
|
||||
try {
|
||||
handler(pendingCode)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
handlers.delete(handler)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearPendingSmsOtp(): void {
|
||||
pendingCode = null
|
||||
}
|
||||
|
||||
export function stopSmsOtpCapture(): void {
|
||||
listenGeneration += 1
|
||||
activeController?.abort()
|
||||
activeController = null
|
||||
}
|
||||
|
||||
/**
|
||||
* سازگاری با کد قبلی — subscribe + begin
|
||||
* توجه: abort کردن این controller دیگر listener سراسری را قطع نمیکند.
|
||||
*/
|
||||
export function listenForSmsOtp(onCode: (code: string) => void): AbortController {
|
||||
const ac = new AbortController()
|
||||
const unsubscribe = subscribeSmsOtp((code) => {
|
||||
if (!ac.signal.aborted) {
|
||||
onCode(code)
|
||||
}
|
||||
})
|
||||
beginSmsOtpCapture()
|
||||
ac.signal.addEventListener('abort', () => {
|
||||
unsubscribe()
|
||||
})
|
||||
return ac
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user