fix(deepseek-account): rename login source parameter

This commit is contained in:
lsdsjy committed 2026-09-20 16:28:21 +08:00
1 parent 04debcbd65
commit 82ae763772
17 files changed
+51 -41

No files matched your search

+1 -1
View File
@@ -78,7 +78,7 @@ export function desktopAccountBackend(origin: string, invoke: AccountInvoke, coo
const call = async (method: string, args: Record<string, unknown> = {}): Promise<AccountView> =>
accountView(await invoke({ namespace: 'account', method, args }))
return {
state: () => call('getState'), start: locale => call('startSignIn', { locale, callbackOrigin: new URL(origin).origin, client: 'desktop' }),
state: () => call('getState'), start: locale => call('startSignIn', { locale, callbackOrigin: new URL(origin).origin, loginSource: 'desktop' }),
cancel: attemptId => call('cancelSignIn', { attemptId }), signOut: () => call('signOut'),
watch(listener, failed) {
let closed = false
+1 -1
View File
@@ -22,5 +22,5 @@ it('uses account Remote commands without returning additional wire fields', asyn
return Promise.resolve({ links: { usageUrl: 'http://localhost/usage', topUpUrl: 'http://localhost/top_up' }, status: 'signed-out', attempt: null, token: 'private' })
}, () => Promise.resolve(''))
expect(await backend.start('en')).toEqual({ links: { usageUrl: 'http://localhost/usage', topUpUrl: 'http://localhost/top_up' }, status: 'signed-out', attempt: null })
expect(requests).toEqual([{ namespace: 'account', method: 'startSignIn', args: { locale: 'en', callbackOrigin: 'http://127.0.0.1:1234', client: 'desktop' } }])
expect(requests).toEqual([{ namespace: 'account', method: 'startSignIn', args: { locale: 'en', callbackOrigin: 'http://127.0.0.1:1234', loginSource: 'desktop' } }])
})
+1 -1
View File
@@ -153,7 +153,7 @@ describe.skipIf(!existsSync(builtHost))('built Desktop welcome flow', () => {
await expect.poll(async () => (await backend!.account.state()).attempt?.phase).toBe('waiting-browser')
const response = await fetch(platform.callback(), { redirect: 'manual' })
expect(response.status).toBe(302)
expect(response.headers.get('location')).toBe(`${platform.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=desktop`)
expect(response.headers.get('location')).toBe(`${platform.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=desktop`)
expect(await status()).toMatchObject({ hasApiKey: true, loggedIn: true })
await restart()
expect(await status()).toMatchObject({ hasApiKey: true, loggedIn: true })
+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 docs/sign-in-readiness.md
sign-in-readiness.md: 25244b1a5939c7b0f1337b23d2e2d14bfabe3125
sign-in-readiness.zh.md: b19db05620af8cf733b12c49cc459834827f7e70
sign-in-readiness.md: 0d13f38060a7ed7feeef48b3c56e6e5a6ffc3ec0
sign-in-readiness.zh.md: 38672b53dec5a4deeb2b8f9830162347f343b508
+2 -2
View File
@@ -15,7 +15,7 @@ Keep completed items and their evidence; do not delete the checklist. Before com
## Pending work and completion criteria
- [x] **Loopback address policy (DSH / Platform / backend)**: User confirmed on 2026-09-15 that the backend supports localhost. Preserve the hostname and browser-visible port without conversion to an IP. DSH, Platform, and mock validation now accept exact localhost and reject lookalike domains. Evidence on 2026-09-15: 40 DSH account tests and lint passed; 72 Platform tests, two type checks, and lint passed, including successful exchange with the original URI and rejection after conversion to an IP. The callback validator also accepts [::1] and requires an explicit port under the updated backend protocol; real IPv6 listener/forwarding acceptance remains pending.
- [ ] **Real authorization API integration (backend / both applications)**: Verify auth_init client_type binding, authorize business codes, exchange token/authorized_url, auth_cancel verifier validation and idempotency, and invalidation of canceled requests and unexchanged codes against the real backend. Verify exchange cannot override desktop/web origin type. Mock implementation is not evidence of backend completion.
- [ ] **Real authorization API integration (backend / both applications)**: Verify auth_init login_source binding, authorize business codes, exchange token/authorized_url, auth_cancel verifier validation and idempotency, and invalidation of canceled requests and unexchanged codes against the real backend. Verify exchange cannot override desktop/web origin type. Mock implementation is not evidence of backend completion.
- [ ] **Real DSH token usage and logout (backend / DSH)**: Verify current, balance, inference, and file endpoints with a real DSH token; account credentials must still accompany inference/file requests only at allowed api.deepseek.com destinations. Verify restart restoration, explicit Platform logout, local credential deletion, and remote revocation outcomes. Use a dedicated test account to avoid revoking a shared daily-use dev token.
- [ ] **Latest Web tab integration (both applications)**: At localhost:8081, verify new-tab authorization, automatic closure on success and exchange failure, original-tab failure feedback, and explicit retry. Cover timeout, cancellation, blocked automatic closure, manually copied links, and localhost access through SSH with different local/remote ports and IPv4/IPv6 listener configurations. Confirm no second Web UI opens and Web completion has no desktop button.
- [ ] **Native client and installer acceptance (DSH)**: With current artifacts, verify Host readiness before login, workspace loading after login, failure/timeout recovery, and window focus. Verify installed dsh://open registration, cold launch, and activation of an existing app on supported release platforms. Earlier manual checks do not replace acceptance of the latest changes.
@@ -40,4 +40,4 @@ When completing an item, append the date, implementation location, and test comm
2026-09-16: macOS development `Harness Dev.app` was registered and verified through the real Platform completion-page button in Chrome. Both opening an already running window and cold-starting after quitting succeeded, preserving the account display. This does not replace installed release-platform acceptance.
- [ ] **Completion-page context (backend / both applications)**: The supplied backend auth_init schema does not store client_type (desktop/web) or locale. Confirm how authorized_url selects desktop open-app versus Web close-tab behavior and language. DSH continues sending both fields until a replacement is agreed; sending them alone does not prove backend support.
- [ ] **Completion-page context (backend / both applications)**: The supplied backend auth_init schema does not store login_source (desktop/web) or locale. Confirm how authorized_url selects desktop open-app versus Web close-tab behavior and language. DSH continues sending both fields until a replacement is agreed; sending them alone does not prove backend support.
+2 -2
View File
@@ -15,7 +15,7 @@
## 待办与完成条件
- [x] **Loopback 地址策略(DSH/Platform/后端)**:用户于 2026-09-15 明确后端支持 localhost,保留主机名及浏览器实际访问端口,不转换 IP。DSH、Platform 与 mock 已对齐精确 localhost 校验并拒绝仿冒域名。验收:2026-09-15,DSH 40 项账号测试及 lint 通过;Platform 72 项测试、两个类型检查及 lint 通过,覆盖原 URI 兑换成功、转换 IP 后兑换失败。新版后端协议下,回调校验也接受 [::1] 并要求显式端口;真实 IPv6 监听和转发仍待验收。
- [ ] **真实授权接口联调(后端/两端)**:在真实后端验证 auth_init 的 client_type 绑定、authorize 业务码、exchange 的 token/authorized_url、auth_cancel 的 verifier 校验和幂等、取消后申请及未兑换 code 失效。验证 desktop 与 web 来源不能在兑换时被覆盖;mock 实现不算后端完成证据。
- [ ] **真实授权接口联调(后端/两端)**:在真实后端验证 auth_init 的 login_source 绑定、authorize 业务码、exchange 的 token/authorized_url、auth_cancel 的 verifier 校验和幂等、取消后申请及未兑换 code 失效。验证 desktop 与 web 来源不能在兑换时被覆盖;mock 实现不算后端完成证据。
- [ ] **真实 DSH token 使用与退登(后端/DSH)**:验证 current、余额、推理和文件接口均可使用真实 DSH token;验证推理/文件请求仍仅在允许的 api.deepseek.com 地址附带账号凭证。验证重启恢复、主动退登调用 Platform logout、本地凭证删除与远端撤销结果。使用独立测试账号,避免撤销日常账号的共享 dev token。
- [ ] **最新 Web 标签页流程联调(两端)**:使用 localhost:8081 跑通新标签页授权、成功自动关闭、exchange 失败关闭、原页失败弹窗和手动重试;覆盖超时、取消、自动关页受限、手动复制链接,以及 SSH 本地/远端端口不同和 IPv4/IPv6 监听配置下的 localhost 访问。确认不再打开第二个 Web UI,Web 完成页不显示桌面按钮。
- [ ] **原生客户端与安装包验收(DSH)**:在最新产物验证登录前 Host ready、登录后主界面加载、失败/超时恢复和窗口聚焦;在支持的发布平台验证安装包 dsh://open 注册、冷启动及已运行时唤起。既有旧版本手测不能替代最新改动验收。
@@ -40,4 +40,4 @@
2026-09-16:已注册 macOS 开发用 `Harness Dev.app`,并通过 Chrome 中真实 Platform 完成页按钮验证运行中唤起和完全退出后的冷启动,账号展示保持正常。这不替代正式安装包及其他发布平台验收。
- [ ] **完成页上下文(后端/两端)**:提供的后端 auth_init schema 未保存 client_type(desktop/web)或 locale。确认 authorized_url 如何区分桌面打开应用与 Web 关闭标签页及语言。替代方案确定前 DSH 继续发送两字段;发送字段本身不代表后端支持。
- [ ] **完成页上下文(后端/两端)**:提供的后端 auth_init schema 未保存 login_source(desktop/web)或 locale。确认 authorized_url 如何区分桌面打开应用与 Web 关闭标签页及语言。替代方案确定前 DSH 继续发送两字段;发送字段本身不代表后端支持。
+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 docs/subsystems/credentials.md
credentials.md: 75d592bc15d4de8da7225253e2a310e74b9f15fd
credentials.zh.md: 23df660253af3dc035e78cac4856ddb872b2e574
credentials.md: f0d64c47cbe886941ed8ad8d3d41304dcc76a493
credentials.zh.md: c216cc58505f9f300626e5943e479df19537a9fa
+2 -2
View File
@@ -288,10 +288,10 @@ abstract getBalance(): Promise<AccountDetails['balance'] | null>
* Join an active attempt or start browser authorization.
* @param locale - active UI language for a new attempt; joining retains its original language.
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
* @param client - initiating UI, used to return from a failed exchange.
* @param loginSource - initiating UI, used to return from a failed exchange.
* @returns the initial snapshot without waiting for browser approval.
*/
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
/**
* Cancel only the named attempt; committing attempts settle before returning.
+2 -2
View File
@@ -288,10 +288,10 @@ abstract getBalance(): Promise<AccountDetails['balance'] | null>
* Join an active attempt or start browser authorization.
* @param locale - active UI language for a new attempt; joining retains its original language.
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
* @param client - initiating UI, used to return from a failed exchange.
* @param loginSource - initiating UI, used to return from a failed exchange.
* @returns the initial snapshot without waiting for browser approval.
*/
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
/**
* Cancel only the named attempt; committing attempts settle before returning.
+2 -2
View File
@@ -32,11 +32,11 @@ export class AccountController extends TypertRemoteService {
* Begin browser sign-in.
* @param locale - active UI language for a new attempt.
* @param callbackOrigin - browser-accessible loopback HTTP origin.
* @param client - initiating UI, used to return from a failed exchange.
* @param loginSource - initiating UI, used to return from a failed exchange.
* @returns a new or already-active login attempt.
*/
@Remote
startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView> { return this.ctx.deepseekAccount.startSignIn(locale, callbackOrigin, client) }
startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView> { return this.ctx.deepseekAccount.startSignIn(locale, callbackOrigin, loginSource) }
/**
* Cancel the named local attempt.
* @param attemptId - attempt to cancel.
@@ -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 packages/credentials/deepseek-account-platform/README.md
README.md: db337c7f999c1ce8055e3a427813189e84d53194
README.zh.md: 510ee10be0d58dc8c4e6b4d78a8ce1f7a837e80a
README.md: 248f679dd7b27af35eafa59579df7538db213b23
README.zh.md: 36c3f861d60f2c02cca76feabed85862630ead98
@@ -30,7 +30,7 @@ The profile projection maps `id_profile.picture` to `avatarUrl`, using null when
getProfile / getBalance sends the stored grant in the x-dsh-auth-token header to GET /auth-api/v0/users/current and GET /api/v0/users/get_user_summary on platformOrigin. The grant issuer must match that origin. Host projects only the account UID, profile name, avatar URL, phone or email exactly as masked by Platform, and normal_wallets / bonus_wallets currency/balance strings; response tokens and other fields are discarded. Bonus wallets remain separate from recharge balances. Credential changes and disposal invalidate in-flight queries.
Configure platformOrigin, allowLoopbackHttp, requestTimeoutMs, and attemptTimeoutMs in the plugin row. HTTP is accepted only for explicitly enabled loopback development. The provider registers /oauth/callback on the existing Host webServer before auth_init, validates state, exchanges an S256 PKCE code once, and commits a grant before redirecting to auth_exchange.biz_data.authorized_url. Browser destinations default to the configured platform origin and always require the fixed /dsh/authorize or /dsh/authorized path. The completion URL sets `client_type` to the initiating client (`web` or `desktop`) and preserves other Platform-supplied query parameters. Device identity is a separate random UUID record shared by processes using the same credential store; device_model reports OS and architecture.
Configure platformOrigin, allowLoopbackHttp, requestTimeoutMs, and attemptTimeoutMs in the plugin row. HTTP is accepted only for explicitly enabled loopback development. The provider registers /oauth/callback on the existing Host webServer before auth_init, validates state, exchanges an S256 PKCE code once, and commits a grant before redirecting to auth_exchange.biz_data.authorized_url. Browser destinations default to the configured platform origin and always require the fixed /dsh/authorize or /dsh/authorized path. The completion URL sets `login_source` to the initiating client (`web` or `desktop`) and preserves other Platform-supplied query parameters. Device identity is a separate random UUID record shared by processes using the same credential store; device_model reports OS and architecture.
Platform URLs have one configuration owner: `platformOrigin`. Authorization requests, browser validation, completion redirects, usage, and top-up destinations all use that origin. Set it in a private `$DSH_HOME/cordis.patch.yml`; deployment addresses do not belong in source control. HTTPS is required except for loopback HTTP with `allowLoopbackHttp: true`. Model and file token destinations use the separate `inferenceOrigin` configuration.
@@ -30,7 +30,7 @@ getPlatformSession 仅在已存授权的 issuer 与 platformOrigin 一致时导
getProfile / getBalance 将保存的授权 token 通过 x-dsh-auth-token 请求头,向 platformOrigin 上的 GET /auth-api/v0/users/current 和 GET /api/v0/users/get_user_summary 发起请求。授权签发来源必须与该来源一致。Host 只投影账号 UID、资料名称、头像 URL、由 Platform 脱敏的手机号或邮箱(原样保留),以及 normal_wallets / bonus_wallets 的币种和余额字符串,丢弃响应 token 与其他字段。赠送钱包不计入充值余额。凭证变化和销毁会使进行中的查询失效。
在插件行配置 platformOrigin、allowLoopbackHttp、requestTimeoutMs 和 attemptTimeoutMs。HTTP 仅用于显式启用的本机开发。提供者先在现有 Host webServer 注册 /oauth/callback,再调用 auth_init;校验 state、使用 S256 PKCE 授权码兑换一次,并在跳转 auth_exchange.biz_data.authorized_url 前提交授权记录。浏览器地址默认要求匹配配置的平台来源,并始终要求固定的 /dsh/authorize 或 /dsh/authorized 路径。完成页地址将 `client_type` 设为发起登录的客户端类型(`web` 或 `desktop`),并保留平台返回的其他查询参数。设备标识是独立的随机 UUID 记录,由使用同一凭证存储的进程共享;device_model 报告操作系统和架构。
在插件行配置 platformOrigin、allowLoopbackHttp、requestTimeoutMs 和 attemptTimeoutMs。HTTP 仅用于显式启用的本机开发。提供者先在现有 Host webServer 注册 /oauth/callback,再调用 auth_init;校验 state、使用 S256 PKCE 授权码兑换一次,并在跳转 auth_exchange.biz_data.authorized_url 前提交授权记录。浏览器地址默认要求匹配配置的平台来源,并始终要求固定的 /dsh/authorize 或 /dsh/authorized 路径。完成页地址将 `login_source` 设为发起登录的客户端类型(`web` 或 `desktop`),并保留平台返回的其他查询参数。设备标识是独立的随机 UUID 记录,由使用同一凭证存储的进程共享;device_model 报告操作系统和架构。
开放平台 URL 统一由 `platformOrigin` 配置管理。授权请求、浏览器地址校验、完成跳转、用量和充值入口都使用该来源。将配置放在私有的 `$DSH_HOME/cordis.patch.yml`,部署地址不进入源码仓库。默认要求 HTTPS;只有本机 HTTP 可通过 `allowLoopbackHttp: true` 显式启用。模型及文件 token 的目标由独立的 `inferenceOrigin` 配置管理。
@@ -69,7 +69,7 @@ interface Attempt {
done: Promise<void>
running: Promise<void>
origin: string
client: 'web' | 'desktop'
loginSource: 'web' | 'desktop'
disposeCallback?: () => Promise<void>
callback?: ServerResponse
initialProfile?: { token: string; value: AccountDetails['profile'] }
@@ -238,7 +238,7 @@ export class PlatformAccount extends DeepSeekAccount {
return result.data.token
}
override async startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView> {
override async startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView> {
if (this.removing !== undefined) await this.removing
const origin = loginOrigin(callbackOrigin)
if (this.closed) throw new PlatformAuthError('protocol')
@@ -254,7 +254,7 @@ export class PlatformAccount extends DeepSeekAccount {
if (this.attempt !== previous) return this.getState()
}
const attempt: Attempt = {
origin, client,
origin, loginSource,
locale: locale.toLowerCase().split(/[-_]/)[0] === 'zh' ? 'zh_CN' : 'en_US',
view: { id: randomUUID() as SignInAttemptId, phase: 'initializing' },
controller: new AbortController(), done: Promise.resolve(), running: Promise.resolve(),
@@ -398,7 +398,7 @@ export class PlatformAccount extends DeepSeekAccount {
const redirectUri = `${attempt.origin}/oauth/callback`
const init = initialization.safeParse(await this.request('auth_init', {
code_challenge: challenge, code_challenge_method: 'S256', state, redirect_uri: redirectUri, locale: attempt.locale,
client_type: attempt.client,
login_source: attempt.loginSource,
}, signal), { reportInput: true })
if (!init.success) {
console.info('[deepseek-account] payload rejected', {
@@ -437,7 +437,7 @@ export class PlatformAccount extends DeepSeekAccount {
throw new PlatformAuthError('protocol')
}
const completionUrl = new URL(browserUrl(result.data.authorized_url, this.origin, '/dsh/authorized', this.rewriteBrowserOrigin))
completionUrl.searchParams.set('client_type', attempt.client)
completionUrl.searchParams.set('login_source', attempt.loginSource)
attempt.completionUrl = completionUrl.href
signal.throwIfAborted()
if (result.data.user != null) {
@@ -476,7 +476,7 @@ export class PlatformAccount extends DeepSeekAccount {
}
private finishFailedCallback(attempt: Attempt): void {
if (attempt.client === 'web') {
if (attempt.loginSource === 'web') {
const nonce = randomBytes(16).toString('base64url')
const message = attempt.locale === 'zh_CN' ? '登录失败,请关闭此标签页并在原页面重试。'
: 'Sign-in failed. Close this tab and try again in the original tab.'
@@ -171,8 +171,9 @@ it('stores a grant before redirecting, restores account presence, and signs out
expect((await fetch(f.callback('wrong'))).status).toBe(400)
const response = await fetch(f.callback(), { redirect: 'manual' })
expect(response.status).toBe(302)
expect(f.init().client_type).toBe('desktop')
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=desktop`)
expect(f.init().login_source).toBe('desktop')
expect(f.init()).not.toHaveProperty('client_type')
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=desktop`)
expect((await f.account.getState()).status).toBe('credential-stored')
expect(await readFile(join(f.home, 'credentials.yaml'), 'utf8')).toContain('dsh_mock_test')
expect(await f.account.getPlatformSession()).toEqual({ origin: f.origin, token: 'dsh_mock_test' })
@@ -230,23 +231,32 @@ it('maps both returned browser pages to the development origin across the login
expect((await f.wait('waiting-browser')).attempt?.authorizeUrl).toBe(`${f.origin}/dsh/authorize?authorize_id=test`)
const response = await fetch(f.callback(), { redirect: 'manual' })
expect(response.status).toBe(302)
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&client_type=desktop`)
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&login_source=desktop`)
expect((await f.account.getState()).status).toBe('credential-stored')
})
it.each([
['web', ''],
['desktop', ''],
['web', '&client_type=desktop&client_type=desktop'],
['desktop', '&client_type=web&client_type=web'],
] as const)('redirects the %s login completion with its client type when Platform returns %s', async (client, query) => {
['web', '&login_source=desktop&login_source=desktop'],
['desktop', '&login_source=web&login_source=web'],
] as const)('redirects the %s login completion with its login source when Platform returns %s', async (client, query) => {
const f = await fixture()
f.exchangeResponse({ authorized_url: `${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN${query}` })
await f.account.startSignIn('en', f.callbackOrigin, client)
await f.wait('waiting-browser')
const response = await fetch(f.callback(), { redirect: 'manual' })
expect(response.status).toBe(302)
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&client_type=${client}`)
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&login_source=${client}`)
})
it('preserves Platform business client_type in the completion URL', async () => {
const f = await fixture()
f.exchangeResponse({ authorized_url: `${f.origin}/dsh/authorized?client_type=DSH` })
await f.account.startSignIn('en', f.callbackOrigin, 'web')
await f.wait('waiting-browser')
const response = await fetch(f.callback(), { redirect: 'manual' })
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?client_type=DSH&login_source=web`)
})
it.each([undefined, 'https://other.example/dsh/authorized', '/dsh/authorized'])('rejects an invalid exchange completion URL %s before storing a token', async (authorizedUrl) => {
@@ -454,7 +464,7 @@ it('closes the failed Web authorization tab without redirecting and keeps the sh
expect(response.headers.get('content-security-policy')).toContain(`script-src 'nonce-${nonce}'`)
await expect(`${page.match(/<p>(.*?)<\/p>/)![1]}\n`)
.toMatchFileSnapshot('./expected/web-exchange-failure.txt')
expect(f.init().client_type).toBe('web')
expect(f.init().login_source).toBe('web')
expect(await f.wait('failed')).toMatchObject({ status: 'signed-out', attempt: { errorCode: 'protocol' } })
expect(f.count()).toBe(1)
expect(await (await fetch(`${f.callbackOrigin}/health`)).text()).toBe('alive')
@@ -462,7 +472,7 @@ it('closes the failed Web authorization tab without redirecting and keeps the sh
await f.account.startSignIn('en', f.callbackOrigin, 'web')
await f.wait('waiting-browser')
const success = await fetch(f.callback(), { redirect: 'manual' })
expect(success.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=web`)
expect(success.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=web`)
expect(f.count()).toBe(2)
})
@@ -491,7 +501,7 @@ it('completes login through a local TCP forward using the browser port rather th
await f.wait('waiting-browser')
expect(f.init().redirect_uri).toBe(`${forwardedOrigin}/oauth/callback`)
const response = await fetch(f.callback(), { redirect: 'manual' })
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=web`)
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=web`)
expect((await f.account.getState()).status).toBe('credential-stored')
})
@@ -42,10 +42,10 @@ export abstract class DeepSeekAccount extends Service {
* Join an active attempt or start browser authorization.
* @param locale - active UI language for a new attempt; joining retains its original language.
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
* @param client - initiating UI, used to return from a failed exchange.
* @param loginSource - initiating UI, used to return from a failed exchange.
* @returns the initial snapshot without waiting for browser approval.
*/
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
/**
* Cancel only the named attempt; committing attempts settle before returning.
* @param id - attempt identity from this Host.
@@ -882,9 +882,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'balance outcome, or null if signed out or the grant changed during the query.',
},
{
signature: 'abstract startSignIn(locale: string, callbackOrigin: string, client: \'web\' | \'desktop\'): Promise<AccountView>',
signature: 'abstract startSignIn(locale: string, callbackOrigin: string, loginSource: \'web\' | \'desktop\'): Promise<AccountView>',
description: 'Join an active attempt or start browser authorization.',
parameters: [{ name: 'locale', description: 'active UI language for a new attempt; joining retains its original language.' }, { name: 'callbackOrigin', description: 'browser-accessible loopback HTTP origin, including any SSH local port.' }, { name: 'client', description: 'initiating UI, used to return from a failed exchange.' }],
parameters: [{ name: 'locale', description: 'active UI language for a new attempt; joining retains its original language.' }, { name: 'callbackOrigin', description: 'browser-accessible loopback HTTP origin, including any SSH local port.' }, { name: 'loginSource', description: 'initiating UI, used to return from a failed exchange.' }],
returns: 'the initial snapshot without waiting for browser approval.',
},
{