todofordevs
Version:
CLI for TodoForDevs - A simple task management tool for developers
260 lines (259 loc) • 11.4 kB
JavaScript
;
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;
}