| | |
| | | bindTicketLoading: false, |
| | | openAppUrl: '', |
| | | autoOpenTimer: 0, |
| | | resumeWatchTimer: 0, |
| | | openConfirmTimer: 0, |
| | | openingApp: false, |
| | | pendingResumeOpen: false |
| | | } |
| | |
| | | const PENDING_OPEN_STORAGE_KEY = 'appInvitePendingOpen' |
| | | const AUTO_DOWNLOAD_DELAY_MS = 1600 |
| | | const RESUME_AUTO_OPEN_DELAY_MS = 500 |
| | | const OPEN_CONFIRM_DELAY_MS = 1200 |
| | | const RESUME_WATCH_INTERVAL_MS = 1200 |
| | | const RESUME_WATCH_DURATION_MS = 15000 |
| | | |
| | | /** |
| | | * 页面初始化。 |
| | |
| | | if (!openAppUrl) { |
| | | return |
| | | } |
| | | const pendingOpen = readPendingOpen() |
| | | const shouldOnlyOpenApp = !fromResume && pendingOpen && pendingOpen.inviteCode === state.inviteCode && pendingOpen.resumeAttempted |
| | | persistPendingOpen() |
| | | tryOpenApp(openAppUrl) |
| | | if (fromResume) { |
| | | clearPendingOpen() |
| | | if (fromResume || shouldOnlyOpenApp) { |
| | | showStatus('正在尝试打开 APP,如未打开请点击按钮重试') |
| | | scheduleOpenFallbackConfirm() |
| | | return |
| | | } |
| | | scheduleDownloadFallback() |
| | |
| | | document.getElementById('flowTip').textContent = '安装完成后返回本页,页面会自动打开 APP 继续注册。' |
| | | document.getElementById('primaryActionBtn').textContent = '安装完成后自动打开中...' |
| | | showStatus('未检测到 APP,正在为您下载对应安装包') |
| | | persistPendingOpen({ |
| | | downloadStartedAt: Date.now(), |
| | | resumeAttempted: false |
| | | }) |
| | | window.location.href = downloadUrl |
| | | }, AUTO_DOWNLOAD_DELAY_MS) |
| | | } |
| | |
| | | /** |
| | | * 记录安装后返回页面需要继续打开 APP。 |
| | | */ |
| | | function persistPendingOpen() { |
| | | function persistPendingOpen(extraData) { |
| | | try { |
| | | window.localStorage.setItem(PENDING_OPEN_STORAGE_KEY, JSON.stringify({ |
| | | const currentValue = readPendingOpen() || {} |
| | | window.localStorage.setItem(PENDING_OPEN_STORAGE_KEY, JSON.stringify(Object.assign({}, currentValue, { |
| | | inviteCode: state.inviteCode, |
| | | openAppUrl: state.openAppUrl, |
| | | createdAt: Date.now() |
| | | })) |
| | | }, extraData || {}))) |
| | | } catch (error) { |
| | | } |
| | | } |
| | |
| | | if (!pendingOpen || pendingOpen.inviteCode !== state.inviteCode || state.pendingResumeOpen) { |
| | | return |
| | | } |
| | | if (pendingOpen.resumeAttempted) { |
| | | renderInstalledOpenFallback() |
| | | return |
| | | } |
| | | state.pendingResumeOpen = true |
| | | if (pendingOpen.openAppUrl) { |
| | | state.openAppUrl = pendingOpen.openAppUrl |
| | | } |
| | | window.setTimeout(function () { |
| | | if (!document.hidden) { |
| | | persistPendingOpen({ |
| | | resumeAttempted: true |
| | | }) |
| | | void handlePrimaryAction(true) |
| | | } |
| | | state.pendingResumeOpen = false |
| | |
| | | function bindResumeAutoOpenListeners() { |
| | | window.addEventListener('pageshow', function () { |
| | | scheduleResumeAutoOpen() |
| | | startResumeWatch() |
| | | }) |
| | | window.addEventListener('focus', function () { |
| | | scheduleResumeAutoOpen() |
| | | startResumeWatch() |
| | | }) |
| | | document.addEventListener('visibilitychange', function () { |
| | | if (!document.hidden && !state.openingApp) { |
| | | scheduleResumeAutoOpen() |
| | | startResumeWatch() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 在安卓安装器返回浏览器后短时间轮询补偿事件丢失。 |
| | | */ |
| | | function startResumeWatch() { |
| | | const pendingOpen = readPendingOpen() |
| | | if (!pendingOpen || pendingOpen.inviteCode !== state.inviteCode || state.resumeWatchTimer) { |
| | | return |
| | | } |
| | | const watchStartedAt = Date.now() |
| | | state.resumeWatchTimer = window.setInterval(function () { |
| | | if (Date.now() - watchStartedAt > RESUME_WATCH_DURATION_MS) { |
| | | clearResumeWatch() |
| | | return |
| | | } |
| | | if (!document.hidden && !state.openingApp) { |
| | | scheduleResumeAutoOpen() |
| | | } |
| | | }, RESUME_WATCH_INTERVAL_MS) |
| | | } |
| | | |
| | | /** |
| | | * 清理安卓返回检测轮询。 |
| | | */ |
| | | function clearResumeWatch() { |
| | | if (state.resumeWatchTimer) { |
| | | window.clearInterval(state.resumeWatchTimer) |
| | | state.resumeWatchTimer = 0 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 自动拉起失败后保留用户点击兜底入口。 |
| | | */ |
| | | function scheduleOpenFallbackConfirm() { |
| | | clearOpenConfirmTimer() |
| | | state.openConfirmTimer = window.setTimeout(function () { |
| | | if (document.hidden) { |
| | | clearPendingOpen() |
| | | clearResumeWatch() |
| | | return |
| | | } |
| | | document.getElementById('flowTip').textContent = '若未自动打开,请点击下方按钮继续注册。' |
| | | document.getElementById('primaryActionBtn').textContent = '已安装,点此打开 APP' |
| | | showStatus('若未自动打开 APP,请点击按钮继续注册') |
| | | }, OPEN_CONFIRM_DELAY_MS) |
| | | } |
| | | |
| | | /** |
| | | * 展示已安装后的手动打开兜底按钮态。 |
| | | */ |
| | | function renderInstalledOpenFallback() { |
| | | document.getElementById('flowTip').textContent = '安装完成后,请点击下方按钮打开 APP 继续注册。' |
| | | document.getElementById('primaryActionBtn').textContent = '已安装,点此打开 APP' |
| | | showStatus('若未自动打开 APP,请点击按钮继续注册') |
| | | } |
| | | |
| | | /** |
| | | * 清理自动打开确认计时器。 |
| | | */ |
| | | function clearOpenConfirmTimer() { |
| | | if (state.openConfirmTimer) { |
| | | window.clearTimeout(state.openConfirmTimer) |
| | | state.openConfirmTimer = 0 |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | window.localStorage.removeItem(PENDING_OPEN_STORAGE_KEY) |
| | | } catch (error) { |
| | | } |
| | | clearResumeWatch() |
| | | } |
| | | |
| | | /** |
| | |
| | | function updateButtonState() { |
| | | const primaryActionBtn = document.getElementById('primaryActionBtn') |
| | | primaryActionBtn.disabled = state.bindTicketLoading || (!state.androidDownloadUrl && !state.iosDownloadUrl) |
| | | const pendingOpen = readPendingOpen() |
| | | if (pendingOpen && pendingOpen.inviteCode === state.inviteCode && pendingOpen.resumeAttempted) { |
| | | primaryActionBtn.textContent = '已安装,点此打开 APP' |
| | | return |
| | | } |
| | | primaryActionBtn.textContent = state.bindTicketLoading ? '正在准备注册信息...' : '打开 APP 继续注册' |
| | | } |
| | | |