{"record":{"id":"3fda3dc3ffe98f0d","repo":"lbjlaq/Antigravity-Manager","slug":"accounts-add-oauth-error-no-url","errorCode":null,"errorMessage":"accounts.add.oauth.error_no_url","messagePattern":"accounts\\.add\\.oauth\\.error_no_url","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/components/accounts/AddAccountDialog.tsx","lineNumber":287,"sourceCode":"            // 不自动关闭,让用户看到结果\n        } else {\n            // 全部失败\n            setStatus('error');\n            setMessage(t('accounts.add.token.batch_fail'));\n        }\n    };\n\n    const handleOAuthWeb = async () => {\n        try {\n            setStatus('loading');\n            setMessage(t('accounts.add.oauth.btn_start') + '...');\n\n            // 1. 获取 URL (指向 /auth/callback)\n            const res = await invoke<any>('prepare_oauth_url');\n            const url = typeof res === 'string' ? res : res.url;\n\n            if (!url) {\n                throw new Error(t('accounts.add.oauth.error_no_url', 'OAuth URLを取得できませんでした'));\n            }\n\n            setOauthUrl(url); // 确保链接在 UI 中可见，方便用户手动复制\n\n            // 2. 打开新标签页 (响应用户反馈：Web 端直接使用新标签体验更好)\n            const popup = window.open(url, '_blank');\n\n            if (!popup) {\n                setStatus('error');\n                setMessage(t('accounts.add.oauth.popup_blocked', 'ポップアップがブロックされました'));\n                return;\n            }\n\n            // 3. 监听消息\n            const handleMessage = async (event: MessageEvent) => {\n                // 安全检查: 如果定义了 ORIGIN 校验更好，这里暂时检查 data type\n                if (event.data?.type === 'oauth-success') {\n                    popup.close();","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/lbjlaq/Antigravity-Manager/blob/90a2b6b20797e2e593a83d0c54ab86037863fe70/src/components/accounts/AddAccountDialog.tsx#L269-L305","documentation":"handleOAuthWeb throws this when prepare_oauth_url returns no usable URL (neither a plain string nor an object with a truthy url property). It signals that the backend could not build the OAuth authorization URL.","triggerScenarios":"invoke('prepare_oauth_url') resolves to null/undefined/empty string, or to an object without a url field — typically because the backend OAuth client config is missing or get_auth_url panicked/failed.","commonSituations":"OAuth client credentials not configured on the backend; backend returned an error object the frontend misreads; the active OAuth client config is invalid.","solutions":["Check backend OAuth client configuration (client id/secret, auth endpoint) is set before opening the dialog","Inspect what prepare_oauth_url actually returns (log res) and handle both string and {url} shapes","Surface the backend error message instead of the generic no-url error"],"exampleFix":"// before\nconst url = typeof res === 'string' ? res : res.url;\nif (!url) {\n    throw new Error(t('accounts.add.oauth.error_no_url', 'OAuth URLを取得できませんでした'));\n}\n// after\nconst url = typeof res === 'string' ? res : res?.url;\nif (!url) {\n    throw new Error(res?.error ?? t('accounts.add.oauth.error_no_url', 'OAuth URLを取得できませんでした'));\n}","handlingStrategy":"try-catch","validationCode":"const res = await invoke('prepare_oauth_url'); const url = typeof res === 'string' ? res : res?.url; if (!url) return showError('no OAuth URL');","typeGuard":"const hasUrl = (r: unknown): r is { url: string } => typeof r === 'object' && r !== null && typeof (r as any).url === 'string' && (r as any).url.length > 0;","tryCatchPattern":"try { await handleOAuthWeb(); } catch (e) { setDialogError(e?.message || t('accounts.add.oauth.error_no_url')); }","preventionTips":["Validate OAuth client config before showing the OAuth button","Log the raw prepare_oauth_url response to catch shape changes","Handle both string and object returns defensively everywhere invoke is used"],"tags":["oauth","frontend","empty-response"],"backgroundTag":"empty-response-body","analyzedSha":"90a2b6b20797e2e593a83d0c54ab86037863fe70","analyzedAt":"2026-09-19T08:34:01.322Z","contentChangedAt":"2026-09-19T08:34:01.322Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}