From 0f37e5645d49ee722cba069d0dda10343bb2863b Mon Sep 17 00:00:00 2001 From: Gaubee Date: Sun, 15 Feb 2026 23:13:59 +0800 Subject: [PATCH] fix(dweb): retry virtual keyboard overlay initialization --- src/lib/dweb-keyboard-overlay.test.ts | 55 ++++++++++++++++++++++++++- src/lib/dweb-keyboard-overlay.ts | 45 ++++++++++++++++++++++ src/service-main.ts | 5 ++- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/lib/dweb-keyboard-overlay.test.ts b/src/lib/dweb-keyboard-overlay.test.ts index b3748d3b6..419cdc73d 100644 --- a/src/lib/dweb-keyboard-overlay.test.ts +++ b/src/lib/dweb-keyboard-overlay.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it, vi } from 'vitest' -import { applyDwebKeyboardOverlay, type DwebPluginsModule } from './dweb-keyboard-overlay' +import { + applyDwebKeyboardOverlay, + startDwebKeyboardOverlay, + type DwebPluginsModule, +} from './dweb-keyboard-overlay' describe('dweb keyboard overlay', () => { it('skips when current environment is not dweb', async () => { @@ -56,4 +60,53 @@ describe('dweb keyboard overlay', () => { warnSpy.mockRestore() }) + + it('retries until dweb environment is ready', async () => { + vi.useFakeTimers() + + const setOverlay = vi.fn<(overlay: boolean) => Promise>().mockResolvedValue() + const loadPlugins = vi.fn<() => Promise>().mockResolvedValue({ + virtualKeyboardPlugin: { setOverlay }, + }) + + let ready = false + const stop = startDwebKeyboardOverlay({ + isDweb: () => ready, + loadPlugins, + maxAttempts: 4, + retryDelayMs: 100, + }) + + expect(loadPlugins).not.toHaveBeenCalled() + + ready = true + await vi.advanceTimersByTimeAsync(120) + + expect(loadPlugins).toHaveBeenCalledTimes(1) + expect(setOverlay).toHaveBeenCalledWith(true) + + stop() + vi.useRealTimers() + }) + + it('stops retrying after cleanup', async () => { + vi.useFakeTimers() + + const loadPlugins = vi.fn<() => Promise>().mockResolvedValue({}) + const stop = startDwebKeyboardOverlay({ + isDweb: () => true, + loadPlugins, + maxAttempts: 10, + retryDelayMs: 100, + }) + + await vi.advanceTimersByTimeAsync(120) + expect(loadPlugins).toHaveBeenCalledTimes(2) + + stop() + await vi.advanceTimersByTimeAsync(500) + expect(loadPlugins).toHaveBeenCalledTimes(2) + + vi.useRealTimers() + }) }) diff --git a/src/lib/dweb-keyboard-overlay.ts b/src/lib/dweb-keyboard-overlay.ts index 96b52c484..83fd20a83 100644 --- a/src/lib/dweb-keyboard-overlay.ts +++ b/src/lib/dweb-keyboard-overlay.ts @@ -13,6 +13,11 @@ export interface ApplyDwebKeyboardOverlayOptions { loadPlugins?: () => Promise } +export interface StartDwebKeyboardOverlayOptions extends ApplyDwebKeyboardOverlayOptions { + maxAttempts?: number + retryDelayMs?: number +} + async function defaultLoadPlugins(): Promise { const moduleName = '@plaoc/plugins' const module = await import(/* @vite-ignore */ moduleName) @@ -46,3 +51,43 @@ export async function applyDwebKeyboardOverlay( return false } } + +/** + * 启动键盘 overlay 应用流程(包含重试),用于规避运行时初始化时机过早导致的失效。 + */ +export function startDwebKeyboardOverlay( + options: StartDwebKeyboardOverlayOptions = {}, +): () => void { + const maxAttempts = Math.max(1, options.maxAttempts ?? 10) + const retryDelayMs = Math.max(50, options.retryDelayMs ?? 300) + let stopped = false + let timer: ReturnType | null = null + let attempts = 0 + + const run = async (): Promise => { + if (stopped) { + return + } + + const applied = await applyDwebKeyboardOverlay(options) + attempts += 1 + + if (applied || stopped || attempts >= maxAttempts) { + return + } + + timer = setTimeout(() => { + void run() + }, retryDelayMs) + } + + void run() + + return () => { + stopped = true + if (timer !== null) { + clearTimeout(timer) + timer = null + } + } +} diff --git a/src/service-main.ts b/src/service-main.ts index ab7c46c88..6dd03b6b6 100644 --- a/src/service-main.ts +++ b/src/service-main.ts @@ -3,7 +3,7 @@ import { installLegacyAuthorizeHashRewriter, rewriteLegacyAuthorizeHashInPlace, } from '@/services/authorize/deep-link' -import { applyDwebKeyboardOverlay } from '@/lib/dweb-keyboard-overlay' +import { startDwebKeyboardOverlay } from '@/lib/dweb-keyboard-overlay' export type ServiceMainCleanup = () => void @@ -19,7 +19,7 @@ export function startServiceMain(): ServiceMainCleanup { rewriteLegacyAuthorizeHashInPlace() // DWEB: keep viewport stable when soft keyboard appears. - void applyDwebKeyboardOverlay() + const cleanupKeyboardOverlay = startDwebKeyboardOverlay() // Initialize preference side effects (i18n + RTL) as early as possible. preferencesActions.initialize() @@ -37,5 +37,6 @@ export function startServiceMain(): ServiceMainCleanup { return () => { cleanupDeepLink() + cleanupKeyboardOverlay() } }