()
| 39 | ) {} |
| 40 | |
| 41 | async startDeviceFlow(): Promise< |
| 42 | Result<{ flowId: string; verificationUri: string; userCode: string }, string> |
| 43 | > { |
| 44 | const flowId = crypto.randomUUID(); |
| 45 | |
| 46 | try { |
| 47 | const res = await fetch(GITHUB_DEVICE_CODE_URL, { |
| 48 | method: "POST", |
| 49 | headers: { |
| 50 | Accept: "application/json", |
| 51 | "Content-Type": "application/x-www-form-urlencoded", |
| 52 | }, |
| 53 | body: new URLSearchParams({ |
| 54 | client_id: GITHUB_COPILOT_CLIENT_ID, |
| 55 | scope: SCOPE, |
| 56 | }), |
| 57 | }); |
| 58 | |
| 59 | if (!res.ok) { |
| 60 | const text = await res.text().catch(() => ""); |
| 61 | return Err(`GitHub device code request failed (${res.status}): ${text}`); |
| 62 | } |
| 63 | |
| 64 | const data = (await res.json()) as { |
| 65 | verification_uri?: string; |
| 66 | user_code?: string; |
| 67 | device_code?: string; |
| 68 | interval?: number; |
| 69 | }; |
| 70 | |
| 71 | if (!data.verification_uri || !data.user_code || !data.device_code) { |
| 72 | return Err("Invalid response from GitHub device code endpoint"); |
| 73 | } |
| 74 | |
| 75 | const { promise: resultPromise, resolve: resolveResult } = |
| 76 | createDeferred<Result<void, string>>(); |
| 77 | |
| 78 | const timeout = setTimeout(() => { |
| 79 | void this.finishFlow(flowId, Err("Timed out waiting for GitHub authorization")); |
| 80 | }, DEFAULT_TIMEOUT_MS); |
| 81 | |
| 82 | this.flows.set(flowId, { |
| 83 | flowId, |
| 84 | deviceCode: data.device_code, |
| 85 | interval: data.interval ?? 5, |
| 86 | cancelled: false, |
| 87 | pollingStarted: false, |
| 88 | timeout, |
| 89 | cleanupTimeout: null, |
| 90 | resultPromise, |
| 91 | resolveResult, |
| 92 | }); |
| 93 | |
| 94 | log.debug(`Copilot OAuth device flow started (flowId=${flowId})`); |
| 95 | |
| 96 | return Ok({ |
| 97 | flowId, |
| 98 | verificationUri: data.verification_uri, |
nothing calls this directly
no test coverage detected