Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/lib/dweb-keyboard-overlay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, expect, it, vi } from 'vitest'
import { applyDwebKeyboardOverlay, type DwebPluginsModule } from './dweb-keyboard-overlay'

describe('dweb keyboard overlay', () => {
it('skips when current environment is not dweb', async () => {
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>()

const result = await applyDwebKeyboardOverlay({
isDweb: () => false,
loadPlugins,
})

expect(result).toBe(false)
expect(loadPlugins).not.toHaveBeenCalled()
})

it('applies overlay in dweb environment', async () => {
const setOverlay = vi.fn<(overlay: boolean) => Promise<void>>().mockResolvedValue()
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockResolvedValue({
virtualKeyboardPlugin: { setOverlay },
})

const result = await applyDwebKeyboardOverlay({
isDweb: () => true,
loadPlugins,
})

expect(result).toBe(true)
expect(loadPlugins).toHaveBeenCalledTimes(1)
expect(setOverlay).toHaveBeenCalledWith(true)
})

it('returns false when virtual keyboard plugin is unavailable', async () => {
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockResolvedValue({})

const result = await applyDwebKeyboardOverlay({
isDweb: () => true,
loadPlugins,
})

expect(result).toBe(false)
expect(loadPlugins).toHaveBeenCalledTimes(1)
})

it('returns false when plugin loading fails', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockRejectedValue(new Error('load-failed'))

const result = await applyDwebKeyboardOverlay({
isDweb: () => true,
loadPlugins,
})

expect(result).toBe(false)
expect(warnSpy).toHaveBeenCalledTimes(1)

warnSpy.mockRestore()
})
})
48 changes: 48 additions & 0 deletions src/lib/dweb-keyboard-overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { isDwebEnvironment } from './crypto/secure-storage'

export interface DwebVirtualKeyboardPlugin {
setOverlay(overlay: boolean): Promise<unknown>
}

export interface DwebPluginsModule {
virtualKeyboardPlugin?: DwebVirtualKeyboardPlugin
}

export interface ApplyDwebKeyboardOverlayOptions {
isDweb?: () => boolean
loadPlugins?: () => Promise<DwebPluginsModule>
}

async function defaultLoadPlugins(): Promise<DwebPluginsModule> {
const moduleName = '@plaoc/plugins'
const module = await import(/* @vite-ignore */ moduleName)
return module as DwebPluginsModule
}

/**
* 在 DWEB 环境启用键盘 overlay,避免输入法弹出导致 document 发生 resize。
*/
export async function applyDwebKeyboardOverlay(
options: ApplyDwebKeyboardOverlayOptions = {},
): Promise<boolean> {
const isDweb = options.isDweb ?? isDwebEnvironment
const loadPlugins = options.loadPlugins ?? defaultLoadPlugins

if (!isDweb()) {
return false
}

try {
const plugins = await loadPlugins()
const virtualKeyboardPlugin = plugins.virtualKeyboardPlugin
if (!virtualKeyboardPlugin || typeof virtualKeyboardPlugin.setOverlay !== 'function') {
return false
}

await virtualKeyboardPlugin.setOverlay(true)
return true
} catch (error) {
console.warn('[dweb-keyboard-overlay] apply failed', error)
return false
}
}
4 changes: 4 additions & 0 deletions src/service-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
installLegacyAuthorizeHashRewriter,
rewriteLegacyAuthorizeHashInPlace,
} from '@/services/authorize/deep-link'
import { applyDwebKeyboardOverlay } from '@/lib/dweb-keyboard-overlay'

export type ServiceMainCleanup = () => void

Expand All @@ -17,6 +18,9 @@ export function startServiceMain(): ServiceMainCleanup {
// Normalize legacy mpay-style authorize deep links before Stackflow reads URL.
rewriteLegacyAuthorizeHashInPlace()

// DWEB: keep viewport stable when soft keyboard appears.
void applyDwebKeyboardOverlay()

// Initialize preference side effects (i18n + RTL) as early as possible.
preferencesActions.initialize()

Expand Down