fix(qwen): redirect HavanaOne login bridge to qianwen home on success
Detect the passport.qianwen.com bridge page after a successful SSO login and navigate the webContents back to the original returnUrl so account binding can proceed instead of stalling on the bridge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ import type { GenericAIPageState } from '../../generic-ai-auth'
|
||||
export const QWEN_AUTH_PROBE_URL = 'https://www.qianwen.com/'
|
||||
|
||||
const QWEN_LOGGED_IN_COOKIE_NAMES = new Set(['tongyi_sso_ticket', 'tongyi_sso_ticket_hash'])
|
||||
const QWEN_LOGIN_BRIDGE_HOST = 'passport.qianwen.com'
|
||||
const QWEN_LOGIN_BRIDGE_PATH = '/havanaone/window_page_end_bridge.htm'
|
||||
|
||||
const QWEN_ACCOUNT_TEXT_STOP_WORDS =
|
||||
/^(千问|通义千问|Qwen|Qwen3\.5-千问|新建对话|我的空间|智能体|对话分组|新分组|最近对话|更多|设置|登录|注册|下载电脑端|API 服务|你好!初次对话)$/i
|
||||
@@ -65,6 +67,80 @@ function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function decodeQwenBridgeParams(value: string): Record<string, unknown> | null {
|
||||
const normalized = value.trim()
|
||||
if (!normalized) {
|
||||
return null
|
||||
}
|
||||
|
||||
const decodeAs = (encoding: BufferEncoding) => {
|
||||
try {
|
||||
return JSON.parse(Buffer.from(normalized, encoding).toString('utf8')) as Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return decodeAs('base64url') ?? decodeAs('base64')
|
||||
}
|
||||
|
||||
function isQwenWebURL(value: unknown): value is string {
|
||||
if (typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(value)
|
||||
return parsed.protocol === 'https:' && parsed.hostname === 'www.qianwen.com'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveQwenLoginBridgeReturnURL(currentURL: string): string | null {
|
||||
try {
|
||||
const parsed = new URL(currentURL)
|
||||
if (
|
||||
parsed.protocol !== 'https:' ||
|
||||
parsed.hostname !== QWEN_LOGIN_BRIDGE_HOST ||
|
||||
parsed.pathname !== QWEN_LOGIN_BRIDGE_PATH
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const params = decodeQwenBridgeParams(parsed.searchParams.get('params') ?? '')
|
||||
const loginResult = typeof params?.loginResult === 'string' ? params.loginResult : null
|
||||
const status = typeof params?.st === 'string' ? params.st : null
|
||||
if (loginResult !== 'success' && status !== 'success') {
|
||||
return null
|
||||
}
|
||||
|
||||
return isQwenWebURL(params?.returnUrl) ? params.returnUrl : QWEN_AUTH_PROBE_URL
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function redirectQwenSuccessfulLoginBridge(
|
||||
webContents: WebContents | null | undefined,
|
||||
): Promise<{ currentURL: string; returnURL: string } | null> {
|
||||
if (!webContents || webContents.isDestroyed()) {
|
||||
return null
|
||||
}
|
||||
|
||||
const currentURL = webContents.getURL()
|
||||
const returnURL = resolveQwenLoginBridgeReturnURL(currentURL)
|
||||
if (!returnURL) {
|
||||
return null
|
||||
}
|
||||
|
||||
await webContents.loadURL(returnURL)
|
||||
return { currentURL, returnURL }
|
||||
}
|
||||
|
||||
function normalizeQwenPageStateSnapshot(state: unknown): GenericAIPageState | null {
|
||||
if (!state || typeof state !== 'object') {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user