| import { OAuthService } from "./oauth";
|
| import { GITHUB_CONFIG } from "../constants/oauth";
|
| import { spinner as createSpinner } from "../utils/ui";
|
|
|
| |
| |
| |
|
|
| export class GitHubService extends OAuthService {
|
| constructor() {
|
| super(GITHUB_CONFIG);
|
| }
|
|
|
| |
| |
|
|
| async getDeviceCode() {
|
| const response = await fetch(`${GITHUB_CONFIG.deviceCodeUrl}`, {
|
| method: "POST",
|
| headers: {
|
| "Content-Type": "application/x-www-form-urlencoded",
|
| Accept: "application/json",
|
| },
|
| body: new URLSearchParams({
|
| client_id: GITHUB_CONFIG.clientId,
|
| scope: GITHUB_CONFIG.scopes,
|
| }),
|
| });
|
|
|
| if (!response.ok) {
|
| const error = await response.text();
|
| throw new Error(`Failed to get device code: ${error}`);
|
| }
|
|
|
| return await response.json();
|
| }
|
|
|
| |
| |
|
|
| async pollAccessToken(deviceCode, verificationUri, userCode, interval = 5000) {
|
| const spinner = createSpinner("Waiting for GitHub authentication...").start();
|
|
|
|
|
| console.log(`\nPlease visit: ${verificationUri}`);
|
| console.log(`Enter code: ${userCode}\n`);
|
|
|
|
|
| try {
|
| const open = (await import("open")).default;
|
| await open(verificationUri);
|
| } catch (error) {
|
| console.log("Could not open browser automatically. Please visit the URL above manually.");
|
| }
|
|
|
|
|
| while (true) {
|
| await new Promise((resolve) => setTimeout(resolve, interval));
|
|
|
| const response = await fetch(`${GITHUB_CONFIG.tokenUrl}`, {
|
| method: "POST",
|
| headers: {
|
| "Content-Type": "application/x-www-form-urlencoded",
|
| Accept: "application/json",
|
| },
|
| body: new URLSearchParams({
|
| client_id: GITHUB_CONFIG.clientId,
|
| device_code: deviceCode,
|
| grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
| }),
|
| });
|
|
|
| const data = await response.json();
|
|
|
| if (data.access_token) {
|
| spinner.succeed("GitHub authentication successful!");
|
| return {
|
| access_token: data.access_token,
|
| token_type: data.token_type,
|
| scope: data.scope,
|
| };
|
| } else if (data.error === "authorization_pending") {
|
|
|
| continue;
|
| } else if (data.error === "slow_down") {
|
|
|
| interval += 5000;
|
| continue;
|
| } else if (data.error === "expired_token") {
|
| spinner.fail("Device code expired. Please try again.");
|
| throw new Error("Device code expired");
|
| } else if (data.error === "access_denied") {
|
| spinner.fail("Access denied by user.");
|
| throw new Error("Access denied");
|
| } else {
|
| spinner.fail("Failed to get access token.");
|
| throw new Error(data.error_description || data.error);
|
| }
|
| }
|
| }
|
|
|
| |
| |
|
|
| async getCopilotToken(accessToken) {
|
| const response = await fetch(`${GITHUB_CONFIG.copilotTokenUrl}`, {
|
| headers: {
|
| Authorization: `Bearer ${accessToken}`,
|
| Accept: "application/json",
|
| "X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
|
| "User-Agent": GITHUB_CONFIG.userAgent,
|
| },
|
| });
|
|
|
| if (!response.ok) {
|
| const error = await response.text();
|
| throw new Error(`Failed to get Copilot token: ${error}`);
|
| }
|
|
|
| return await response.json();
|
| }
|
|
|
| |
| |
|
|
| async getUserInfo(accessToken) {
|
| const response = await fetch(`${GITHUB_CONFIG.userInfoUrl}`, {
|
| headers: {
|
| Authorization: `Bearer ${accessToken}`,
|
| Accept: "application/json",
|
| "X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
|
| "User-Agent": GITHUB_CONFIG.userAgent,
|
| },
|
| });
|
|
|
| if (!response.ok) {
|
| const error = await response.text();
|
| throw new Error(`Failed to get user info: ${error}`);
|
| }
|
|
|
| return await response.json();
|
| }
|
|
|
| |
| |
|
|
| async authenticate(): Promise<any> {
|
| try {
|
|
|
| const deviceResponse = await this.getDeviceCode();
|
|
|
|
|
| const tokenResponse = await this.pollAccessToken(
|
| deviceResponse.device_code,
|
| deviceResponse.verification_uri,
|
| deviceResponse.user_code
|
| );
|
|
|
|
|
| const copilotToken = await this.getCopilotToken(tokenResponse.access_token);
|
|
|
|
|
| const userInfo = await this.getUserInfo(tokenResponse.access_token);
|
|
|
| console.log(`\n✅ Successfully authenticated as ${userInfo.login}`);
|
|
|
| return {
|
| accessToken: tokenResponse.access_token,
|
| copilotToken: copilotToken.token,
|
| refreshToken: null,
|
| expiresIn: copilotToken.expires_at,
|
| userInfo: {
|
| id: userInfo.id,
|
| login: userInfo.login,
|
| name: userInfo.name,
|
| email: userInfo.email,
|
| },
|
| copilotTokenInfo: copilotToken,
|
| };
|
| } catch (error) {
|
| throw new Error(`GitHub authentication failed: ${error.message}`);
|
| }
|
| }
|
|
|
| |
| |
|
|
| async connect() {
|
| try {
|
|
|
| const authResult = await this.authenticate();
|
|
|
|
|
| const { server, token, userId } = await import("../config/index").then((m) =>
|
| m.getServerCredentials()
|
| );
|
| const spinner = (await import("../utils/ui")).spinner("Connecting to server...").start();
|
|
|
| const response = await fetch(`${server}/api/cli/providers/github`, {
|
| method: "POST",
|
| headers: {
|
| "Content-Type": "application/json",
|
| Authorization: `Bearer ${token}`,
|
| "X-User-Id": userId,
|
| },
|
| body: JSON.stringify({
|
| accessToken: authResult.accessToken,
|
| copilotToken: authResult.copilotToken,
|
| userInfo: authResult.userInfo,
|
| copilotTokenInfo: authResult.copilotTokenInfo,
|
| }),
|
| });
|
|
|
| if (!response.ok) {
|
| const errorData = await response.json();
|
| throw new Error(errorData.error || "Failed to connect to server");
|
| }
|
|
|
| spinner.succeed("GitHub Copilot connected successfully!");
|
| console.log(`\nConnected as: ${authResult.userInfo.login}`);
|
| } catch (error) {
|
| const { error: showError } = await import("../utils/ui");
|
| showError(`GitHub connection failed: ${error.message}`);
|
| throw error;
|
| }
|
| }
|
| }
|
|
|