UNPKG

todofordevs

Version:

CLI for TodoForDevs - A simple task management tool for developers

260 lines (259 loc) 11.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.initiateLogin = initiateLogin; exports.logout = logout; exports.checkStatus = checkStatus; exports.requireAuth = requireAuth; const open_1 = __importDefault(require("open")); const clipboardy_1 = __importDefault(require("clipboardy")); const api_1 = require("./api"); const config_1 = require("../config"); const output = __importStar(require("./output")); const chalk_1 = __importDefault(require("chalk")); /** * Initiate the browser-based login flow * @param silent If true, don't show browser window or output messages (for token refresh) */ async function initiateLogin(silent = false) { try { // Step 1: Request a device code if (!silent) { output.info('Initiating login process...'); } const deviceCodeResponse = await (0, api_1.post)(api_1.endpoints.auth.login()); // Step 2: Display the verification URI and user code (if not silent) if (!silent) { output.heading('Authentication Required'); output.info('To authenticate, please follow these steps:'); output.info(`1. Open this URL in your browser: ${output.truncate(deviceCodeResponse.verification_uri, 50)}`); // Copy the code to clipboard try { await clipboardy_1.default.write(deviceCodeResponse.user_code); // Create a visually appealing banner for the code const code = deviceCodeResponse.user_code; const boxWidth = code.length + 8; // Add padding console.log('\n' + chalk_1.default.cyan('┌' + '─'.repeat(boxWidth) + '┐')); console.log(chalk_1.default.cyan('│') + ' '.repeat(boxWidth) + chalk_1.default.cyan('│')); console.log(chalk_1.default.cyan('│') + ' '.repeat(4) + chalk_1.default.bold(code) + ' '.repeat(4) + chalk_1.default.cyan('│') + chalk_1.default.green(' (copied to clipboard)')); console.log(chalk_1.default.cyan('│') + ' '.repeat(boxWidth) + chalk_1.default.cyan('│')); console.log(chalk_1.default.cyan('└' + '─'.repeat(boxWidth) + '┘') + '\n'); output.info('2. Enter the code shown above when prompted'); } catch (clipboardError) { // Fallback if clipboard access fails // Create a visually appealing banner for the code without clipboard message const code = deviceCodeResponse.user_code; const boxWidth = code.length + 8; // Add padding console.log('\n' + chalk_1.default.cyan('┌' + '─'.repeat(boxWidth) + '┐')); console.log(chalk_1.default.cyan('│') + ' '.repeat(boxWidth) + chalk_1.default.cyan('│')); console.log(chalk_1.default.cyan('│') + ' '.repeat(4) + chalk_1.default.bold(code) + ' '.repeat(4) + chalk_1.default.cyan('│')); console.log(chalk_1.default.cyan('│') + ' '.repeat(boxWidth) + chalk_1.default.cyan('│')); console.log(chalk_1.default.cyan('└' + '─'.repeat(boxWidth) + '┘') + '\n'); output.info('2. Enter the code shown above when prompted'); } // Step 3: Open the browser to the verification URI (if not silent) await (0, open_1.default)(deviceCodeResponse.verification_uri); // Step 4: Poll for the token output.info('Waiting for authentication to complete...'); } const token = await pollForToken(deviceCodeResponse.device_code, deviceCodeResponse.interval, deviceCodeResponse.expires_in, silent); // Step 5: Store the token if (token) { if (!silent) { output.success('Authentication successful!'); const user = (0, config_1.getAuthUser)(); if (user) { output.info(`Logged in as ${user.email}${user.name ? ` (${user.name})` : ''}`); } } } else { if (!silent) { output.error('Authentication failed or timed out.'); } } } catch (error) { if (!silent) { output.error('Failed to initiate login process.'); console.error(error); } throw error; // Re-throw for silent mode to handle } } /** * Poll for the authentication token with exponential backoff * @param deviceCode The device code to poll for * @param initialInterval The initial polling interval in seconds * @param expiresIn The expiration time in seconds * @param silent If true, don't show progress indicator or output messages */ async function pollForToken(deviceCode, initialInterval, expiresIn, silent = false) { const startTime = Date.now(); const expiresAt = startTime + expiresIn * 1000; // Initialize polling parameters let interval = initialInterval; const maxInterval = 30; // Maximum polling interval in seconds const backoffFactor = 1.5; // Exponential backoff factor // Initialize progress indicator (if not silent) let dots = 0; const maxDots = 3; let progressInterval = null; if (!silent) { progressInterval = setInterval(() => { process.stdout.write('\r'); process.stdout.write(`Waiting for authentication${'.'.repeat(dots)}${' '.repeat(maxDots - dots)}`); dots = (dots + 1) % (maxDots + 1); }, 1000); } try { // Poll until we get a token or timeout let attempts = 0; while (Date.now() < expiresAt) { attempts++; try { // Wait for the current interval await new Promise((resolve) => setTimeout(resolve, interval * 1000)); // Check if the user has completed the authentication const response = await (0, api_1.get)(api_1.endpoints.auth.token(deviceCode)); if (response && response.token) { // Clear the progress indicator (if not silent) if (progressInterval) { clearInterval(progressInterval); process.stdout.write('\r' + ' '.repeat(50) + '\r'); } // Store the token and user info (0, config_1.setAuthToken)(response.token, response.user); return true; } } catch (error) { // If the error is "authorization_pending", continue polling with backoff silently if (error.response && error.response.status === 400 && error.response.data && error.response.data.error === 'authorization_pending') { // Apply exponential backoff, but don't exceed the maximum interval interval = Math.min(interval * backoffFactor, maxInterval); continue; } // For other errors, log and continue polling (if not silent) if (!silent) { if (progressInterval) { clearInterval(progressInterval); process.stdout.write('\r' + ' '.repeat(50) + '\r'); } // Only show error message for non-authorization_pending errors output.warning(`Error polling for token (attempt ${attempts}): ${error.message}`); output.info('Retrying...'); // Restart the progress indicator dots = 0; progressInterval = setInterval(() => { process.stdout.write('\r'); process.stdout.write(`Waiting for authentication${'.'.repeat(dots)}${' '.repeat(maxDots - dots)}`); dots = (dots + 1) % (maxDots + 1); }, 1000); } } } // Clear the progress indicator if we time out (if not silent) if (progressInterval) { clearInterval(progressInterval); process.stdout.write('\r' + ' '.repeat(50) + '\r'); } if (!silent) { output.warning('Authentication timed out. Please try again.'); } return false; } catch (error) { // Clear the progress indicator if there's an unexpected error (if not silent) if (progressInterval) { clearInterval(progressInterval); process.stdout.write('\r' + ' '.repeat(50) + '\r'); } throw error; } } /** * Log out the current user */ function logout() { (0, config_1.clearAuthToken)(); output.success('Successfully logged out.'); } /** * Check and display the current authentication status */ function checkStatus() { if ((0, config_1.isAuthenticated)()) { const user = (0, config_1.getAuthUser)(); if (user) { output.success(`Logged in as ${user.email}${user.name ? ` (${user.name})` : ''}`); } else { output.success('Logged in'); } } else { output.info("Not logged in. Run 'todo auth login' to authenticate."); } } /** * Verify that the user is authenticated * Returns true if authenticated, false otherwise * If not authenticated, displays an error message */ function requireAuth() { if (!(0, config_1.isAuthenticated)()) { output.error('Authentication required.'); output.info("Please run 'todo auth login' to authenticate."); return false; } return true; }