forked from ericc-ch/copilot-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtoken.ts
More file actions
131 lines (115 loc) · 4.05 KB
/
token.ts
File metadata and controls
131 lines (115 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import consola from "consola"
import fs from "node:fs/promises"
import type { Account } from "~/lib/account-pool"
import { PATHS } from "~/lib/paths"
import { getCopilotToken } from "~/services/github/get-copilot-token"
import { getDeviceCode } from "~/services/github/get-device-code"
import { getGitHubUser } from "~/services/github/get-user"
import { pollAccessToken } from "~/services/github/poll-access-token"
import { HTTPError } from "./error"
import { state } from "./state"
import { makeApiContext } from "./utils"
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")
const writeGithubToken = (token: string) =>
fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)
/** Per-account Copilot token setup with auto-refresh. */
export const setupCopilotTokenFor = async (account: Account) => {
const ctx = makeApiContext(account)
const { token, refresh_in } = await getCopilotToken(ctx)
/* eslint-disable require-atomic-updates */
account.copilotToken = token
account.copilotTokenRefreshAt = Date.now() + refresh_in * 1000
/* eslint-enable require-atomic-updates */
consola.debug(`[${account.name}] Copilot token fetched successfully`)
if (state.showToken) {
consola.info(`[${account.name}] Copilot token:`, token)
}
const refreshInterval = (refresh_in - 60) * 1000
account.refreshTimer = setInterval(async () => {
consola.debug(`[${account.name}] Refreshing Copilot token`)
try {
const refreshed = await getCopilotToken(makeApiContext(account))
/* eslint-disable require-atomic-updates */
account.copilotToken = refreshed.token
account.copilotTokenRefreshAt = Date.now() + refreshed.refresh_in * 1000
/* eslint-enable require-atomic-updates */
consola.debug(`[${account.name}] Copilot token refreshed`)
if (state.showToken) {
consola.info(
`[${account.name}] Refreshed Copilot token:`,
refreshed.token,
)
}
} catch (error) {
consola.error(`[${account.name}] Failed to refresh Copilot token:`, error)
throw error
}
}, refreshInterval)
}
interface SetupGitHubTokenOptions {
force?: boolean
}
/**
* Run the GitHub Device Flow OAuth and return the raw access token.
* Does NOT write the token to disk — caller decides what to do with it.
*/
export async function runDeviceFlow(): Promise<string> {
const response = await getDeviceCode()
consola.debug("Device code response:", response)
consola.info(
`Please enter the code "${response.user_code}" in ${response.verification_uri}`,
)
return pollAccessToken(response)
}
/**
* Reads or fetches a single GitHub token file at PATHS.GITHUB_TOKEN_PATH.
* Returns the token; the caller is responsible for putting it into the
* account pool.
*/
export async function setupGitHubToken(
options?: SetupGitHubTokenOptions,
): Promise<string> {
try {
const githubToken = await readGithubToken()
if (githubToken && !options?.force) {
if (state.showToken) {
consola.info("GitHub token:", githubToken)
}
await logUser(githubToken)
return githubToken
}
consola.info("Not logged in, getting new access token")
const token = await runDeviceFlow()
await writeGithubToken(token)
if (state.showToken) {
consola.info("GitHub token:", token)
}
await logUser(token)
return token
} catch (error) {
if (error instanceof HTTPError) {
consola.error("Failed to get GitHub token:", await error.response.json())
throw error
}
consola.error("Failed to get GitHub token:", error)
throw error
}
}
async function logUser(githubToken: string) {
// Build a temporary "anonymous" account with just the GitHub token,
// so we can call /user without going through the pool.
const tempAccount: Account = {
name: "_setup",
accountType: state.accountType,
githubToken,
copilotTokenRefreshAt: 0,
inFlight: 0,
lastUsedAt: 0,
failureCount: 0,
}
const user = await getGitHubUser({
account: tempAccount,
vsCodeVersion: state.vsCodeVersion,
})
consola.info(`Logged in as ${user.login}`)
}