UNPKG

@google/clasp

Version:

Develop Apps Script Projects locally

107 lines (106 loc) 5.5 kB
// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file implements the `AuthorizationCodeFlow` for local development // environments. It starts a local HTTP server to receive the authorization // code after the user grants permission. import { createServer } from 'http'; import open from 'open'; import enableDestroy from 'server-destroy'; import { intl } from '../intl.js'; import { AuthorizationCodeFlow, parseAuthResponseUrl } from './auth_code_flow.js'; /** * Implements the Authorization Code Flow by starting a local HTTP server * to act as the redirect URI. This is suitable for CLI environments * where a browser can be opened and a local server can receive the * authorization code. */ export class LocalServerAuthorizationCodeFlow extends AuthorizationCodeFlow { constructor(oauth2client, port) { super(oauth2client); this.port = 0; this.port = port; } /** * Starts a local HTTP server and returns its address as the redirect URI. * The server will listen on the configured port (or a random available port if 0). * @returns {Promise<string>} The local redirect URI (e.g., "http://localhost:1234"). * @throws {Error} If the server cannot be started (e.g., port in use). */ async getRedirectUri() { this.server = await new Promise((resolve, reject) => { const s = createServer(); enableDestroy(s); // Allows the server to be destroyed gracefully. // Try to listen on the specified port (or a random one if port is 0). s.listen(this.port, () => resolve(s)).on('error', (err) => { // Handle common server errors like port already in use. if (err.code === 'EADDRINUSE') { const msg = intl.formatMessage({ id: "smVcjx", defaultMessage: [{ type: 0, value: "Error: Port " }, { type: 1, value: "port" }, { type: 0, value: " is already in use. Please specify a different port with --redirect-port" }] }, { port: this.port, }); console.error(msg); } else { const msg = intl.formatMessage({ id: "3X2J9l", defaultMessage: [{ type: 0, value: "Error: Unable to start the server on port " }, { type: 1, value: "port" }] }, { port: this.port, errorMessage: err.message, }); console.error(msg, err.message); } reject(err); }); }); const { port } = this.server.address(); return `http://localhost:${port}`; } /** * Prompts the user to authorize by opening the provided authorization URL * in their default web browser. It then waits for the local server (started by * `getRedirectUri`) to receive the callback containing the authorization code. * @param {string} authorizationUrl - The URL to open for user authorization. * @returns {Promise<string>} The authorization code extracted from the redirect. * @throws {Error} If the server is not started, the request URL is missing, or an error * parameter is present in the redirect URL. */ async promptAndReturnCode(authorizationUrl) { return await new Promise((resolve, reject) => { if (!this.server) { reject(new Error('Server not started')); return; } this.server.on('request', (request, response) => { if (!request.url) { reject(new Error('Missing URL in request')); return; } const { code, error } = parseAuthResponseUrl(request.url); // Extract code or error from the redirect URL. if (code) { resolve(code); // Successfully obtained the authorization code. } else { reject(error); // An error occurred during authorization. } // Send a simple response to the browser. const msg = intl.formatMessage({ id: "ZT8LeG", defaultMessage: [{ type: 0, value: "Logged in! You may close this page." }] }); response.end(msg); }); // Open the authorization URL in the user's default browser. void open(authorizationUrl); // Log the authorization URL to the console as a fallback or for visibility. const msg = intl.formatMessage({ id: "NbCrKh", defaultMessage: [{ type: 0, value: "`\uD83D\uDD11 Authorize clasp by visiting this url: " }, { type: 1, value: "url" }] }, { url: authorizationUrl, }); console.log(msg); }).finally(() => { var _a; return (_a = this.server) === null || _a === void 0 ? void 0 : _a.destroy(); }); // Ensure the server is destroyed after completion or error. } }