{"record":{"id":"ac34411e40b5e217","repo":"jlcodes99/cockpit-tools","slug":"qoder-oauth-start","errorCode":null,"errorMessage":"Qoder OAuth start 响应缺少关键字段","messagePattern":"Qoder OAuth start 响应缺少关键字段","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/qoderService.ts","lineNumber":28,"sourceCode":"}\n\ntype QoderOAuthStartResponseRaw = Partial<QoderOAuthStartResponse> & {\n  login_id?: string;\n  verification_uri?: string;\n  expires_in?: number;\n  interval_seconds?: number;\n  callback_url?: string | null;\n};\n\nfunction normalizeQoderOAuthStartResponse(raw: QoderOAuthStartResponseRaw): QoderOAuthStartResponse {\n  const loginId = raw.loginId ?? raw.login_id ?? '';\n  const verificationUri = raw.verificationUri ?? raw.verification_uri ?? '';\n  const expiresIn = Number(raw.expiresIn ?? raw.expires_in ?? 0);\n  const intervalSeconds = Number(raw.intervalSeconds ?? raw.interval_seconds ?? 0);\n  const callbackUrl = raw.callbackUrl ?? raw.callback_url ?? null;\n\n  if (!loginId || !verificationUri) {\n    throw new Error('Qoder OAuth start 响应缺少关键字段');\n  }\n\n  return {\n    loginId,\n    verificationUri,\n    expiresIn: Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 600,\n    intervalSeconds: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds : 1,\n    callbackUrl,\n  };\n}\n\nexport async function listQoderAccounts(): Promise<QoderAccount[]> {\n  return await invoke('list_qoder_accounts');\n}\n\nexport async function deleteQoderAccount(accountId: string): Promise<void> {\n  return await invoke('delete_qoder_account', { accountId });\n}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/services/qoderService.ts#L10-L46","documentation":"normalizeQoderOAuthStartResponse validates the device-authorization start response from the Qoder backend. loginId and verificationUri are the minimum fields the polling/device-link flow needs; if the raw response (after camelCase/snake_case fallbacks) lacks either, the library cannot start an OAuth device login and throws this Chinese-language error ('OAuth start response missing key fields').","triggerScenarios":"Calling qoderOauthLoginStart or qoderOauthLoginPeek when the backend returns a 200 body missing loginId or verificationUri/verification_uri — e.g. an error payload shaped as success, a proxy stripping fields, or an API contract change.","commonSituations":"Server-side outage returning an error JSON with 200 status, corporate proxy/API gateway mangling the response, or running against an outdated backend whose field names changed.","solutions":["Log the raw response body and confirm whether loginId and verificationUri are present","Check that requests reach the official Qoder endpoint and are not intercepted by a proxy returning an error payload","Update to a backend/client version pair where the OAuth start contract matches (verificationUri vs verification_uri are both handled)","Retry the login start; if it persists, report the backend response shape"],"exampleFix":"// before: assuming success\nconst res = await fetch(startUrl);\nconst session = normalizeQoderOAuthStartResponse(await res.json());\n// after: guard on status and shape first\nconst res = await fetch(startUrl);\nif (!res.ok) throw new Error(`OAuth start failed: ${res.status}`);\nconst raw = await res.json();\nif (!raw?.loginId && !raw?.login_id) throw new Error(`Unexpected OAuth start body: ${JSON.stringify(raw)}`);\nconst session = normalizeQoderOAuthStartResponse(raw);","handlingStrategy":"try-catch","validationCode":"const raw = await res.clone().json();\nif (!(raw?.loginId ?? raw?.login_id) || !(raw?.verificationUri ?? raw?.verification_uri)) {\n  console.error('Qoder OAuth start body malformed:', raw);\n}","typeGuard":"function hasOAuthStartFields(v: unknown): v is { loginId: string; verificationUri: string } {\n  const r = v as any;\n  return !!(r?.loginId ?? r?.login_id) && !!(r?.verificationUri ?? r?.verification_uri);\n}","tryCatchPattern":"try {\n  const session = await qoderOauthLoginStart();\n} catch (e) {\n  if (String(e.message).includes('缺少关键字段')) {\n    showRetryDialog('Qoder login could not start — check network/proxy and retry');\n  } else throw e;\n}","preventionTips":["Bypass or allowlist the provider endpoints in corporate proxies","Verify HTTP status before parsing the body","Keep the app updated with backend OAuth contract changes","Log raw payloads for diagnosis"],"tags":["oauth","device-flow","qoder","api-response"],"backgroundTag":"oauth-start-response-invalid","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}