Merge pull request #4898 from deepseek-harness/fix/contact-form-url
fix(client): update Feedback form and omit account UID
This commit is contained in:
commit
32ca127565
16 files changed
+38
-33
No files matched your search
@@ -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 .agents/notes/implemented/architecture/2026-09-14-deepseek-account-login.md
|
||||
2026-09-14-deepseek-account-login.md: 9d9853617a3b7e288084c93eac4e150f3c180999
|
||||
2026-09-14-deepseek-account-login.zh.md: d9cdbed25b0ae79caa4c61b310383cc52d8b586a
|
||||
2026-09-14-deepseek-account-login.md: 458e020c2000fd3db5ee9806e99d4b653710cb1e
|
||||
2026-09-14-deepseek-account-login.zh.md: 2fb59009302309a67e2c3ec99050685ba39b4212
|
||||
@@ -36,7 +36,7 @@ The account plugin supplies choice, waiting, failure and timeout dialogs through
|
||||
|
||||
Platform embedding transfers a grant from Host to Electron over private Node IPC and then once to the trusted Platform main frame through a sandboxed preload. Preload performs one synchronous IPC before page scripts execute; the main process only validates the sender and returns prepared memory. Platform reads the token synchronously thereafter without a readiness API. A failed initialization retains embedded mode and a throwing getter. Account UI projections remain credential-free. The Platform document can read this credential, so its script security is part of account protection; context isolation protects native capabilities, not a token intentionally returned to the document. Replacing or removing the grant destroys the document and its temporary session.
|
||||
|
||||
Support questionnaires receive only the Platform UID and available environment metadata through explicit prefill fields. Account tokens and masked contact details stay out of questionnaire URLs. The account provider projects the current-user UID for this purpose; the UI uses the latest account snapshot so sign-out cannot reuse a previous UID.
|
||||
Support questionnaires receive only available environment metadata through explicit prefill fields. Account UIDs, tokens and masked contact details stay out of questionnaire URLs.
|
||||
|
||||
Private proxy development can explicitly map authorization and completion URLs to platformOrigin with rewriteBrowserOrigin. Both pages retain their fixed paths and full query strings; allowing a remote origin without mapping it would send the browser outside the configured environment. Shipped configuration requires same-origin URLs.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ API Key 与账号记录独立保存。账号 token 没有过期或刷新流程
|
||||
|
||||
Platform 内嵌通过私有 Node IPC 将授权从 Host 传给 Electron,再经沙箱 preload 一次性传给受信任的 Platform 主 frame。preload 在页面脚本执行前执行一次同步 IPC;主进程仅校验调用来源并返回已准备的内存数据。Platform 此后同步读取 token,无需就绪接口。初始化失败时保留内嵌模式,由 getter 抛错。账号 UI 投影仍不包含凭证。Platform 文档能够读取此凭证,因此其脚本安全也是账号保护的一部分;上下文隔离保护原生能力,不能隐藏有意返回给文档的 token。替换或移除授权会销毁文档及其临时会话。
|
||||
|
||||
支持问卷仅通过显式预填字段接收 Platform UID 和已有的环境信息,URL 不包含账号 token 或脱敏联系方式。账号提供者为此投影 current-user 的 UID;UI 使用最新账号快照,避免退出登录后复用旧 UID。
|
||||
支持问卷仅通过显式预填字段接收已有的环境信息,URL 不包含账号 UID、token 或脱敏联系方式。
|
||||
|
||||
私有代理开发可通过 rewriteBrowserOrigin 显式将授权页和完成页映射到 platformOrigin。两者保留固定路径和完整查询字符串;仅放宽来源而不做映射会让浏览器离开配置的环境。发布配置要求浏览器地址同源。
|
||||
|
||||
|
||||
@@ -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/client/ui-settings-account/README.md
|
||||
README.md: 27682d9fa145723019573d492bbc1627ed43d1be
|
||||
README.zh.md: 0fd64077f980482a31774d91fa62d349b0a6616f
|
||||
README.md: be38bcd68ab7affe4aa840499ef79009ba25b779
|
||||
README.zh.md: 9429d21cbd988a538c18320acbbfcb65dd6acfc0
|
||||
@@ -30,7 +30,7 @@ Balances use Platform Web currency formatting: two decimal places and digit grou
|
||||
|
||||
Desktop usage and top-up actions open an isolated native Platform view below a 48px return bar. Back destroys the view and preserves the Account settings page. Loading failures retain the return action and show a centered localized error with Retry. Retry reloads the same destination; renderer commands never receive the account token.
|
||||
|
||||
Contact us opens the Feishu questionnaire in the system browser. It supplies the available Platform UID, build version, UI locale and physical screen resolution as prefill_* parameters, with hide_*=1 for every context field; tokens and contact details are excluded. Configure contactFormUrl on the ui-settings-account plugin to select another HTTPS form. contactSource defaults to empty until the questionnaire supports a Harness source option; OS and device fields remain unfilled, matching the Web implementation.
|
||||
The account menu's Feedback entry opens the Feishu questionnaire in the system browser. It supplies the available build version, UI locale and physical screen resolution as prefill_* parameters, with hide_*=1 for every context field; the account UID, tokens and contact details are excluded. Configure contactFormUrl on the ui-settings-account plugin to select another HTTPS form. contactSource defaults to empty until the questionnaire supports a Harness source option; OS and device fields remain unfilled, matching the Web implementation.
|
||||
|
||||
The sidebar account menu uses the shared Menu surface, backdrop blur, spacing, and row typography; feature styles only size its launcher.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Desktop 设置中的账号页面显示 DeepSeek 登录状态,并提供浏览
|
||||
|
||||
Desktop 的用量和充值操作在 48px 返回栏下方打开隔离的原生 Platform 视图。返回操作销毁视图并保留 Account 设置页。加载失败时保留返回操作,居中显示本地化错误提示和“重试”按钮。重试重新加载同一目标页面;渲染进程命令不接收账号 token。
|
||||
|
||||
“联系我们”在系统浏览器中打开飞书问卷。链接通过 prefill_* 参数填写已有的 Platform UID、构建版本、界面语言和屏幕物理分辨率,并为所有上下文字段设置 hide_*=1;不传 token 或联系方式。可在 ui-settings-account 插件配置 contactFormUrl,切换到另一个 HTTPS 问卷。问卷支持 Harness 来源选项前,contactSource 默认为空;OS 和设备字段沿用 Web 实现,保持未填写。
|
||||
账号菜单的“意见反馈”入口在系统浏览器中打开飞书问卷。链接通过 prefill_* 参数填写已有的构建版本、界面语言和屏幕物理分辨率,并为所有上下文字段设置 hide_*=1;不传账号 UID、token 或联系方式。可在 ui-settings-account 插件配置 contactFormUrl,切换到另一个 HTTPS 问卷。问卷支持 Harness 来源选项前,contactSource 默认为空;OS 和设备字段沿用 Web 实现,保持未填写。
|
||||
|
||||
侧边栏账号菜单使用公共 Menu 的表面、背景模糊、间距和菜单项排版;功能样式仅设置入口布局。
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface AccountSectionInjected {
|
||||
}
|
||||
/** @returns after account details are refreshed; concurrent refreshes share a request. */
|
||||
refresh: () => Promise<void>
|
||||
/** Open the external support questionnaire with the current account and environment. */
|
||||
/** Open the external support questionnaire with the current build and browser environment. */
|
||||
contactUs: () => void
|
||||
/** Open or dismiss the login dialog. */
|
||||
showLogin: (visible: boolean) => void
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/** Feishu questionnaire context follows the Platform Web ticket fields. */
|
||||
import type { Config } from '../contact-config.ts'
|
||||
import type { AccountProfile } from '@deepseek-ai/dsh-deepseek-account/types'
|
||||
|
||||
/**
|
||||
* Build an external questionnaire URL without authentication credentials.
|
||||
* @param config - questionnaire destination and supported source option.
|
||||
* @param context - currently available account and browser environment.
|
||||
* @param context - currently available build and browser environment.
|
||||
* @returns questionnaire URL with hidden, optionally prefilled context fields.
|
||||
*/
|
||||
export function contactUrl(config: Config, context: {
|
||||
uid: AccountProfile['id']
|
||||
version: string | undefined
|
||||
locale: string
|
||||
width: number
|
||||
@@ -21,7 +19,7 @@ export function contactUrl(config: Config, context: {
|
||||
const width = Math.round(context.width * ratio)
|
||||
const height = Math.round(context.height * ratio)
|
||||
const fields = {
|
||||
uid: context.uid, source: config.contactSource, app_version: context.version,
|
||||
source: config.contactSource, app_version: context.version,
|
||||
os_version: undefined, device_brand: undefined, device_model: undefined,
|
||||
app_locale: context.locale, screen_resolution: width > 0 && height > 0 ? `${width}x${height}` : undefined,
|
||||
}
|
||||
@@ -30,5 +28,7 @@ export function contactUrl(config: Config, context: {
|
||||
url.searchParams.delete(`prefill_${name}`)
|
||||
if (value) url.searchParams.set(`prefill_${name}`, value)
|
||||
}
|
||||
url.searchParams.delete('prefill_uid')
|
||||
url.searchParams.delete('hide_uid')
|
||||
return url.href
|
||||
}
|
||||
@@ -82,9 +82,7 @@ export function apply(ctx: Context): void {
|
||||
...nativePlatform === undefined ? {} : { platform: nativePlatform },
|
||||
refresh,
|
||||
contactUs() {
|
||||
const profile = snapshot.details?.profile
|
||||
const url = contactUrl(config, {
|
||||
uid: profile?.status === 'ready' ? profile.value.id : null,
|
||||
version: process.env.DSH_CLIENT_VERSION,
|
||||
locale: ctx.locale.getSnapshot().active === 'zh' ? 'zh-CN' : 'en',
|
||||
width: window.screen.width, height: window.screen.height, pixelRatio: window.devicePixelRatio,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const en = {
|
||||
failureTitle: 'Could not sign in',
|
||||
platformFailed: 'Could not complete the operation. Try again.', platformRetry: 'Retry',
|
||||
loading: 'Loading…', backToHarness: 'Back to DeepSeek Harness',
|
||||
settings: 'Settings', contactUs: 'Contact us', menu: 'Account menu',
|
||||
settings: 'Settings', contactUs: 'Feedback', menu: 'Account menu',
|
||||
nav: 'Account', signedIn: 'Signed in to DeepSeek', signedOut: 'Not signed in',
|
||||
signIn: 'Sign in', signOut: 'Sign out', cancel: 'Cancel', open: 'Open browser',
|
||||
initializing: 'Starting sign in…', waiting: 'Continue in your browser',
|
||||
@@ -33,7 +33,7 @@ export const zh: Record<AccountKey, string> = {
|
||||
failureTitle: '登录失败',
|
||||
platformFailed: '操作未完成,请重试', platformRetry: '重试',
|
||||
loading: '加载中…', backToHarness: '返回 DeepSeek Harness',
|
||||
settings: '设置', contactUs: '联系我们', menu: '账号菜单',
|
||||
settings: '设置', contactUs: '意见反馈', menu: '账号菜单',
|
||||
nav: '账号与余额', signedIn: '已登录 DeepSeek', signedOut: '尚未登录',
|
||||
signIn: '登录', signOut: '退出登录', cancel: '取消', open: '打开浏览器',
|
||||
initializing: '正在发起登录…', waiting: '请在浏览器中继续',
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface Config {
|
||||
}
|
||||
/** Validate public questionnaire options. */
|
||||
export const Config: z<Partial<Config>, Config> = z.object({
|
||||
contactFormUrl: z.string().pattern(/^https:\/\/[^/\s]+\//).default('https://trtgsjkv6r.feishu.cn/share/base/form/shrcnhcHE4A6lQaQ3v0raCXmBAg'),
|
||||
contactFormUrl: z.string().pattern(/^https:\/\/[^/\s]+\//).default('https://trtgsjkv6r.feishu.cn/share/base/form/shrcnlCoGElW7MQznGy9r3YYXcg'),
|
||||
contactSource: z.string().default(''),
|
||||
})
|
||||
/** Bootstrap key containing no account credentials. */
|
||||
|
||||
@@ -63,7 +63,9 @@ it('shares account actions across seats, publishes dialog ownership, and opens c
|
||||
actions.showLogin(false)
|
||||
expect(listener).toHaveBeenCalledTimes(2)
|
||||
actions.contactUs()
|
||||
expect(new URL(String(open.mock.calls.at(-1)![0])).searchParams.has('prefill_uid')).toBe(false)
|
||||
const signedOut = new URL(String(open.mock.calls.at(-1)![0]))
|
||||
expect(signedOut.searchParams.has('prefill_uid')).toBe(false)
|
||||
expect(signedOut.searchParams.has('hide_uid')).toBe(false)
|
||||
c.mock.remote.account.getProfile.mockResolvedValue(ok(profile))
|
||||
c.mock.streams.push('account/watch', stored)
|
||||
await vi.waitFor(() => { expect(actions.hooks.account.getSnapshot().details?.profile).toEqual(profile) })
|
||||
@@ -73,7 +75,8 @@ it('shares account actions across seats, publishes dialog ownership, and opens c
|
||||
vi.spyOn(c.ctx.locale, 'getSnapshot').mockReturnValue({ ...c.ctx.locale.getSnapshot(), active: 'zh' })
|
||||
actions.contactUs()
|
||||
const support = new URL(String(open.mock.calls.at(-1)![0]))
|
||||
expect(support.searchParams.get('prefill_uid')).toBe('account-user')
|
||||
expect(support.searchParams.has('prefill_uid')).toBe(false)
|
||||
expect(support.searchParams.has('hide_uid')).toBe(false)
|
||||
expect(support.searchParams.get('prefill_app_locale')).toBe('zh-CN')
|
||||
await c.unload(SELF)
|
||||
expect(c.ctx.slots.entries('settings.launcher')).toHaveLength(0)
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
import { expect, it } from 'vitest'
|
||||
import type { AccountUserId } from '@deepseek-ai/dsh-deepseek-account/types'
|
||||
import { Config } from '../src/contact-config.ts'
|
||||
import { contactUrl } from '../src/client/contact-url.ts'
|
||||
|
||||
it('prefills available support context and hides all context fields', () => {
|
||||
const config = Config({ contactSource: 'app_harness' })
|
||||
const url = new URL(contactUrl(config, {
|
||||
uid: 'user&123' as AccountUserId, version: '1.2.3', locale: 'zh-CN',
|
||||
width: 1512, height: 982, pixelRatio: 2,
|
||||
version: '1.2.3', locale: 'zh-CN', width: 1512, height: 982, pixelRatio: 2,
|
||||
}))
|
||||
expect(url.origin).toBe('https://trtgsjkv6r.feishu.cn')
|
||||
expect(url.pathname).toBe('/share/base/form/shrcnlCoGElW7MQznGy9r3YYXcg')
|
||||
expect(Object.fromEntries(url.searchParams)).toEqual({
|
||||
hide_uid: '1', prefill_uid: 'user&123', hide_source: '1', prefill_source: 'app_harness',
|
||||
hide_app_version: '1', prefill_app_version: '1.2.3', hide_os_version: '1',
|
||||
hide_source: '1', prefill_source: 'app_harness', hide_app_version: '1', prefill_app_version: '1.2.3',
|
||||
hide_os_version: '1',
|
||||
hide_device_brand: '1', hide_device_model: '1', hide_app_locale: '1', prefill_app_locale: 'zh-CN',
|
||||
hide_screen_resolution: '1', prefill_screen_resolution: '3024x1964',
|
||||
})
|
||||
expect(url.searchParams.has('prefill_uid')).toBe(false)
|
||||
expect(url.searchParams.has('hide_uid')).toBe(false)
|
||||
})
|
||||
|
||||
it('opens a configured form without stale account context when signed out', () => {
|
||||
const url = new URL(contactUrl(Config({ contactFormUrl: 'https://example.test/form/?prefill_uid=old&prefill_source=old' }), {
|
||||
uid: null, version: undefined, locale: 'en', width: 0, height: 0, pixelRatio: 1,
|
||||
it('opens a configured form without stale context when unavailable', () => {
|
||||
const url = new URL(contactUrl(Config({ contactFormUrl: 'https://example.test/form/?prefill_uid=old&hide_uid=1&prefill_app_version=old&prefill_source=old' }), {
|
||||
version: undefined, locale: 'en', width: 0, height: 0, pixelRatio: 1,
|
||||
}))
|
||||
expect(url.origin).toBe('https://example.test')
|
||||
expect(url.searchParams.has('prefill_uid')).toBe(false)
|
||||
expect(url.searchParams.has('hide_uid')).toBe(false)
|
||||
expect(url.searchParams.has('prefill_app_version')).toBe(false)
|
||||
expect(url.searchParams.has('prefill_source')).toBe(false)
|
||||
expect(url.searchParams.has('prefill_screen_resolution')).toBe(false)
|
||||
expect(url.searchParams.get('hide_uid')).toBe('1')
|
||||
expect(url.searchParams.get('hide_source')).toBe('1')
|
||||
expect(url.searchParams.get('hide_app_version')).toBe('1')
|
||||
expect(() => Config({ contactFormUrl: 'javascript:alert(1)' })).toThrow()
|
||||
})
|
||||
|
||||
it.each([NaN, Infinity])('uses CSS pixel dimensions when device pixel ratio is %s', (pixelRatio) => {
|
||||
const url = new URL(contactUrl(Config({}), {
|
||||
uid: null, version: undefined, locale: 'en', width: 800, height: 600, pixelRatio,
|
||||
version: undefined, locale: 'en', width: 800, height: 600, pixelRatio,
|
||||
}))
|
||||
expect(url.searchParams.get('prefill_screen_resolution')).toBe('800x600')
|
||||
})
|
||||
@@ -1 +1 @@
|
||||
SettingsContact usSign out
|
||||
SettingsFeedbackSign out
|
||||
@@ -1 +1 @@
|
||||
设置联系我们退出登录
|
||||
设置意见反馈退出登录
|
||||
@@ -27,7 +27,7 @@ export interface AccountView {
|
||||
|
||||
/** Platform account identifier supplied by the current-user endpoint. */
|
||||
export type AccountUserId = Branded<'AccountUserId'>
|
||||
/** Display identity and support UID; contacts retain Platform masking. */
|
||||
/** Display identity; contacts retain Platform masking. */
|
||||
export interface AccountProfile {
|
||||
readonly id: AccountUserId | null
|
||||
readonly name: string | null
|
||||
|
||||
Reference in new issue
Block a user