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
144 lines (122 loc) · 4.09 KB
/
token.ts
File metadata and controls
144 lines (122 loc) · 4.09 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
132
133
134
135
136
137
138
139
140
141
142
143
144
import consola from "consola"
import fs from "node:fs/promises"
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"
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")
const writeGithubToken = (token: string) =>
fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)
// Retry logic with exponential backoff
const sleep = (ms: number) =>
new Promise((resolve) => {
setTimeout(resolve, ms)
})
const getCopilotTokenWithRetry = async (
maxRetries = 3,
initialDelayMs = 1000,
) => {
let lastError: Error | null = null
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await getCopilotToken()
} catch (error) {
lastError = error as Error
const delayMs = initialDelayMs * Math.pow(2, attempt)
consola.warn(
`Token refresh attempt ${attempt + 1}/${maxRetries} failed, retrying in ${delayMs}ms:`,
lastError.message,
)
if (attempt < maxRetries - 1) {
await sleep(delayMs)
}
}
}
throw lastError || new Error("Failed to refresh Copilot token")
}
// Force refresh token (called when 401 is detected)
export const forceRefreshCopilotToken = async () => {
try {
consola.debug("Force refreshing Copilot token due to 401 response")
const { token } = await getCopilotTokenWithRetry()
state.copilotToken = token
consola.debug("Copilot token force refreshed successfully")
} catch (error) {
consola.error("Failed to force refresh Copilot token:", error)
// Don't throw - let the caller handle the error
}
}
export const setupCopilotToken = async () => {
const { token, refresh_in } = await getCopilotToken()
state.copilotToken = token
// Display the Copilot token to the screen
consola.debug("GitHub Copilot Token fetched successfully!")
if (state.showToken) {
consola.info("Copilot token:", token)
}
const refreshInterval = (refresh_in - 60) * 1000
setInterval(async () => {
try {
consola.debug("Refreshing Copilot token")
const { token } = await getCopilotTokenWithRetry()
state.copilotToken = token
consola.debug("Copilot token refreshed successfully")
if (state.showToken) {
consola.info("Refreshed Copilot token:", token)
}
} catch (error) {
// Don't throw - just log the error and continue with old token
// If token is actually expired, 401 response will trigger forceRefreshCopilotToken
consola.error(
"Failed to refresh Copilot token, will retry on next interval:",
error,
)
}
}, refreshInterval)
}
interface SetupGitHubTokenOptions {
force?: boolean
}
export async function setupGitHubToken(
options?: SetupGitHubTokenOptions,
): Promise<void> {
try {
const githubToken = await readGithubToken()
if (githubToken && !options?.force) {
state.githubToken = githubToken
if (state.showToken) {
consola.info("GitHub token:", githubToken)
}
await logUser()
return
}
consola.info("Not logged in, getting new access token")
const response = await getDeviceCode()
consola.debug("Device code response:", response)
consola.info(
`Please enter the code "${response.user_code}" in ${response.verification_uri}`,
)
const token = await pollAccessToken(response)
await writeGithubToken(token)
state.githubToken = token
if (state.showToken) {
consola.info("GitHub token:", token)
}
await logUser()
} 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() {
const user = await getGitHubUser()
state.githubLogin = user.login
consola.info(`Logged in as ${user.login}`)
}