Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/lib/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,28 @@ export class HTTPError extends Error {
response: Response

constructor(message: string, response: Response) {
super(message)
super(`${message} (${response.status} ${response.statusText})`)
this.response = response
}
}

export async function getHTTPErrorDetails(error: HTTPError): Promise<unknown> {
const errorText = await error.response.clone().text()

if (!errorText) {
return {
status: error.response.status,
statusText: error.response.statusText,
}
}

try {
return JSON.parse(errorText) as unknown
} catch {
return errorText
}
}

export async function forwardError(c: Context, error: unknown) {
consola.error("Error occurred:", error)

Expand Down
39 changes: 33 additions & 6 deletions src/lib/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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 { HTTPError, getHTTPErrorDetails } from "./error"
import { state } from "./state"

const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")
Expand All @@ -16,16 +16,32 @@ const writeGithubToken = (token: string) =>
fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)

export const setupCopilotToken = async () => {
const { token, refresh_in } = await getCopilotToken()
state.copilotToken = token
let refreshIn: number

try {
const { token, refresh_in } = await getCopilotToken()
state.copilotToken = token
refreshIn = refresh_in
} catch (error) {
if (error instanceof HTTPError) {
consola.error(
"Failed to get Copilot token:",
await getHTTPErrorDetails(error),
)
throw error
}

consola.error("Failed to get Copilot token:", error)
throw error
}

// Display the Copilot token to the screen
consola.debug("GitHub Copilot Token fetched successfully!")
if (state.showToken) {
consola.info("Copilot token:", token)
consola.info("Copilot token:", state.copilotToken)
}

const refreshInterval = (refresh_in - 60) * 1000
const refreshInterval = (refreshIn - 60) * 1000
setInterval(async () => {
consola.debug("Refreshing Copilot token")
try {
Expand All @@ -36,6 +52,14 @@ export const setupCopilotToken = async () => {
consola.info("Refreshed Copilot token:", token)
}
} catch (error) {
if (error instanceof HTTPError) {
consola.error(
"Failed to refresh Copilot token:",
await getHTTPErrorDetails(error),
)
throw error
}

consola.error("Failed to refresh Copilot token:", error)
throw error
}
Expand Down Expand Up @@ -80,7 +104,10 @@ export async function setupGitHubToken(
await logUser()
} catch (error) {
if (error instanceof HTTPError) {
consola.error("Failed to get GitHub token:", await error.response.json())
consola.error(
"Failed to get GitHub token:",
await getHTTPErrorDetails(error),
)
throw error
}

Expand Down
65 changes: 65 additions & 0 deletions tests/http-error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, test } from "bun:test"

import { HTTPError, getHTTPErrorDetails } from "~/lib/error"

describe("HTTPError", () => {
test("includes response status in the error message", () => {
const error = new HTTPError(
"Failed to get Copilot token",
new Response("Forbidden", { status: 403, statusText: "Forbidden" }),
)

expect(error.message).toBe("Failed to get Copilot token (403 Forbidden)")
})
})

describe("getHTTPErrorDetails", () => {
test("parses JSON response bodies", async () => {
const details = await getHTTPErrorDetails(
new HTTPError(
"Failed to get Copilot token",
Response.json(
{
error_details: {
message: "Your subscription has ended.",
notification_id: "subscription_ended",
},
},
{ status: 403, statusText: "Forbidden" },
),
),
)

expect(details).toEqual({
error_details: {
message: "Your subscription has ended.",
notification_id: "subscription_ended",
},
})
})

test("returns text response bodies", async () => {
const details = await getHTTPErrorDetails(
new HTTPError(
"Failed to get Copilot token",
new Response("Forbidden", { status: 403, statusText: "Forbidden" }),
),
)

expect(details).toBe("Forbidden")
})

test("returns status details for empty response bodies", async () => {
const details = await getHTTPErrorDetails(
new HTTPError(
"Failed to get Copilot token",
new Response(null, { status: 403, statusText: "Forbidden" }),
),
)

expect(details).toEqual({
status: 403,
statusText: "Forbidden",
})
})
})