fix(desktop): leave sign-in activation to the completion page

This commit is contained in:
lsdsjy committed 2026-09-23 11:11:44 +08:00
1 parent 10b6ffec3f
commit 4283f62d01
5 files changed
+27 -10

No files matched your search

+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/desktop/README.md
README.md: 372ea4a0ac1ccb6ec7aeedd99899111f9b5801c7
README.zh.md: b155745f68e9a3f706650f81309c8d1e7d179586
README.md: f00608919c1a44441bffa57d2b601c12b47724a8
README.zh.md: 76862075461d651a4d25235d57eb1855602ea90e
+1 -1
View File
@@ -411,7 +411,7 @@ An unpackaged Electron process uses `.desktop-build/development/project` under i
- Dependency lifecycle scripts follow pnpm’s build permissions; Desktop provides no separate approval dialog.
- The desktop shell shares sessions, settings, credentials, workspaces, and storage under `$DSH_HOME` with CLI dsh, while executable packages, plugin activation, and lockfiles remain separate.
Sign in opens the configured platform page in the system browser. The Host owns PKCE and a temporary loopback callback, saves the credential before entering the workspace, and redirects the browser to the platform completion page. Opened and copied authorization links carry the effective Desktop theme as `theme=light` or `theme=dark`; `system` resolves at the time of the action. Cancel withdraws the local attempt even if the platform page later approves it. Settings offers Account sign-out; without a separate API key, sign-out returns to the welcome window. Packaged applications register dsh://open to show the window without passing credentials. On macOS, the development launcher prepares an ad-hoc-signed `Harness Dev.app` under `.desktop-build/development`, declares `dsh` in its Info.plist, and registers it with Launch Services. It loads the current workspace and records the selected development home, browser-data path, and debug settings for cold starts. Starting this bundle makes it the default `dsh://` handler; starting the packaged application registers the packaged handler again. The generated bundle does not contain account tokens and requires the workspace and prepared runtime to remain available.
Sign in opens the configured platform page in the system browser. The Host owns PKCE and a temporary loopback callback, saves the credential before entering the workspace, and redirects the browser to the platform completion page. Opened and copied authorization links carry the effective Desktop theme as `theme=light` or `theme=dark`; `system` resolves at the time of the action. Cancel withdraws the local attempt even if the platform page later approves it. Settings offers Account sign-out; without a separate API key, sign-out returns to the welcome window. Successful browser sign-in switches Welcome to the workspace without activating the application; the completion page’s dsh://open link brings it to the foreground. Packaged applications register dsh://open to show the window without passing credentials. On macOS, the development launcher prepares an ad-hoc-signed `Harness Dev.app` under `.desktop-build/development`, declares `dsh` in its Info.plist, and registers it with Launch Services. It loads the current workspace and records the selected development home, browser-data path, and debug settings for cold starts. Starting this bundle makes it the default `dsh://` handler; starting the packaged application registers the packaged handler again. The generated bundle does not contain account tokens and requires the workspace and prepared runtime to remain available.
When an expired account returns to Welcome, the main process retains a one-time notification until the renderer requests it over window-owned IPC. Reloading Welcome does not repeat the notice; manual sign-out and cold startup do not create it.
+1 -1
View File
@@ -411,7 +411,7 @@ node apps/desktop/node_modules/pnpm/bin/pnpm.mjs --dir apps/desktop run test:upd
- 依赖的生命周期脚本遵循 pnpm 的构建权限;Desktop 不提供单独的审批对话框。
- 桌面壳与 CLI dsh 共享 `$DSH_HOME` 下的会话、设置、凭据、工作区和存储,但可执行包、插件激活和锁文件彼此隔离。
登录会在系统浏览器中打开配置的平台页面。Host 负责 PKCE 和临时本机回调,在进入工作区前保存凭证,再将浏览器跳转到平台完成页。打开和复制的授权链接通过 `theme=light` 或 `theme=dark` 携带当前生效的 Desktop 主题;`system` 在执行操作时解析。即使平台页面随后批准,取消仍会撤销本地尝试。设置中的账号页面提供退出;没有独立 API Key 时,退出后返回欢迎窗。打包应用注册 dsh://open,只显示窗口而不传递凭证。macOS 开发启动器在 `.desktop-build/development` 下准备经临时签名的 `Harness Dev.app`,在 Info.plist 中声明 `dsh` 并注册到 Launch Services。它加载当前工作区,并记录选定的开发 home、浏览器数据路径和调试设置,以供冷启动使用。启动此应用会将其设为 `dsh://` 默认处理程序;启动打包应用会重新注册打包版处理程序。生成的应用包不包含账号 token,依赖工作区和已准备的运行环境继续存在。
登录会在系统浏览器中打开配置的平台页面。Host 负责 PKCE 和临时本机回调,在进入工作区前保存凭证,再将浏览器跳转到平台完成页。打开和复制的授权链接通过 `theme=light` 或 `theme=dark` 携带当前生效的 Desktop 主题;`system` 在执行操作时解析。即使平台页面随后批准,取消仍会撤销本地尝试。设置中的账号页面提供退出;没有独立 API Key 时,退出后返回欢迎窗。浏览器登录成功后,Welcome 切换到工作区但不激活应用;完成页的 dsh://open 链接负责将客户端置前。打包应用注册 dsh://open,只显示窗口而不传递凭证。macOS 开发启动器在 `.desktop-build/development` 下准备经临时签名的 `Harness Dev.app`,在 Info.plist 中声明 `dsh` 并注册到 Launch Services。它加载当前工作区,并记录选定的开发 home、浏览器数据路径和调试设置,以供冷启动使用。启动此应用会将其设为 `dsh://` 默认处理程序;启动打包应用会重新注册打包版处理程序。生成的应用包不包含账号 token,依赖工作区和已准备的运行环境继续存在。
账号失效并返回 Welcome 时,主进程保留一次性通知,直到渲染器通过所属窗口的 IPC 领取。重新加载 Welcome 不会重复提示;主动退登和冷启动不会生成该通知。
+5 -4
View File
@@ -395,7 +395,7 @@ async function main(): Promise<void> {
returnedAttempt = attempt.id
focusPrimaryWindow()
}
if (attempt?.phase === 'succeeded' && welcomeWindow !== undefined) void enterWorkspace().catch(() => undefined)
if (attempt?.phase === 'succeeded' && welcomeWindow !== undefined) void enterWorkspace({ activate: false }).catch(() => undefined)
if (previousAccountStatus === 'credential-stored' && state.status === 'signed-out') {
void readWelcomeState().then(async (value) => {
if (needsWelcome(value) && !quitting) {
@@ -924,19 +924,20 @@ async function main(): Promise<void> {
})
return window
}
const enterWorkspace = async (): Promise<void> => {
const enterWorkspace = async ({ activate = true }: { activate?: boolean } = {}): Promise<void> => {
if (quitting) return
const window = mainWindow ?? createMainWindow()
await navigateMain(applicationUrl)
if (isQuitting() || recovery.active || window.isDestroyed()) return
window.show()
if (activate) window.show()
else window.showInactive()
enteredWorkspace = true
if (welcomeWindow !== undefined) {
welcomeWindow.close()
window.webContents.send(DESKTOP_IPC.enterWorkspace)
}
welcomeWindow = undefined
if (development && process.env.DSH_DESKTOP_OPEN_DEVTOOLS !== '0') {
if (activate && development && process.env.DSH_DESKTOP_OPEN_DEVTOOLS !== '0') {
window.webContents.openDevTools({ mode: 'detach' })
}
}
+18 -2
View File
@@ -23,6 +23,9 @@ const state = vi.hoisted(() => ({
stopHost: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
loadWorkspace: vi.fn<(url: string) => Promise<void>>().mockResolvedValue(undefined),
showWorkspace: vi.fn(),
showInactiveWorkspace: vi.fn(),
focusWorkspace: vi.fn(),
openDevTools: vi.fn(),
closeWelcome: vi.fn(),
welcomeLocale: undefined as DesktopLocale | undefined,
preference: 'zh',
@@ -64,16 +67,18 @@ vi.mock('electron', () => ({
BrowserWindow: class {
constructor(options: BrowserWindowConstructorOptions) { state.windowOptions = options }
private ready: (() => void) | undefined
webContents = { mainFrame: { url: 'dsh-app://app/' }, setWindowOpenHandler: vi.fn(), on: vi.fn(), once: vi.fn(), send: vi.fn(), openDevTools: vi.fn() }
webContents = { mainFrame: { url: 'dsh-app://app/' }, setWindowOpenHandler: vi.fn(),
on: vi.fn(), once: vi.fn(), send: vi.fn(), openDevTools: state.openDevTools }
static getAllWindows() { return [] }
once(name: string, callback: () => void) { if (name === 'ready-to-show') this.ready = callback; return this }
on() { return this }
isDestroyed() { return false }
isMinimized() { return false }
restore = vi.fn()
focus = vi.fn()
focus = state.focusWorkspace
hide = vi.fn()
show = state.showWorkspace
showInactive = state.showInactiveWorkspace
async loadURL(url: string) { state.contents = this.webContents; await state.loadWorkspace(url); this.ready?.() }
},
net: { fetch: vi.fn() },
@@ -251,5 +256,16 @@ it('starts the Host for welcome onboarding and opens the workspace on skip witho
state.accountListener!({ ...account, status: 'signed-out', attempt: null })
await vi.advanceTimersByTimeAsync(0)
expect(await state.operations!.takeNotice()).toBeUndefined()
state.showWorkspace.mockClear()
state.focusWorkspace.mockClear()
vi.stubEnv('DSH_DESKTOP_OPEN_DEVTOOLS', '1')
state.accountListener!({ ...account, status: 'credential-stored', attempt: { id: attemptId, phase: 'succeeded' } })
await vi.waitFor(() => { expect(state.showInactiveWorkspace).toHaveBeenCalledOnce() })
expect(state.showWorkspace).not.toHaveBeenCalled()
expect(state.focusWorkspace).not.toHaveBeenCalled()
expect(state.openDevTools).not.toHaveBeenCalled()
state.appListeners.get('open-url')!({ preventDefault: vi.fn() }, 'dsh://open')
expect(state.showWorkspace).toHaveBeenCalledOnce()
expect(state.focusWorkspace).toHaveBeenCalledOnce()
})