@dongtran/google-calendar-mcp 2.4.1 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/build/auth-server.js +42 -5
- package/build/auth-server.js.map +2 -2
- package/build/index.js +62 -20
- package/build/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,20 @@ export GOOGLE_OAUTH_CREDENTIALS="/path/to/your/gcp-oauth.keys.json"
|
|
|
101
101
|
npx @cocal/google-calendar-mcp auth
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
**Alternative: Using JSON string (recommended for containers/CI):**
|
|
105
|
+
```bash
|
|
106
|
+
export GOOGLE_OAUTH_CREDENTIALS_JSON='{"installed":{"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","redirect_uris":["http://localhost:3000/oauth2callback"],"project_id":"YOUR_PROJECT_ID"}}'
|
|
107
|
+
npx @dongtran/google-calendar-mcp auth
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> [!TIP]
|
|
111
|
+
> Use `GOOGLE_OAUTH_CREDENTIALS_JSON` in containerized environments (Docker, Kubernetes) or CI/CD pipelines to avoid mounting credential files as volumes. The JSON string takes highest priority over file paths.
|
|
112
|
+
|
|
113
|
+
**Docker example:**
|
|
114
|
+
```dockerfile
|
|
115
|
+
ENV GOOGLE_OAUTH_CREDENTIALS_JSON='{"installed":{"client_id":"...","client_secret":"...","redirect_uris":["http://localhost:3000/oauth2callback"]}}'
|
|
116
|
+
```
|
|
117
|
+
|
|
104
118
|
**For local installation:**
|
|
105
119
|
```bash
|
|
106
120
|
npm run auth
|
|
@@ -230,7 +244,7 @@ npx @cocal/google-calendar-mcp start --enable-tools list-events,create-event,get
|
|
|
230
244
|
"mcpServers": {
|
|
231
245
|
"google-calendar": {
|
|
232
246
|
"command": "npx",
|
|
233
|
-
"args": ["@
|
|
247
|
+
"args": ["@dongtran/google-calendar-mcp"],
|
|
234
248
|
"env": {
|
|
235
249
|
"GOOGLE_OAUTH_CREDENTIALS": "/path/to/credentials.json",
|
|
236
250
|
"ENABLED_TOOLS": "list-events,create-event,get-current-time,update-event"
|
|
@@ -240,6 +254,9 @@ npx @cocal/google-calendar-mcp start --enable-tools list-events,create-event,get
|
|
|
240
254
|
}
|
|
241
255
|
```
|
|
242
256
|
|
|
257
|
+
> [!NOTE]
|
|
258
|
+
> You can also use `GOOGLE_OAUTH_CREDENTIALS_JSON` instead of `GOOGLE_OAUTH_CREDENTIALS` to pass credentials as a JSON string. This is particularly useful for containerized deployments.
|
|
259
|
+
|
|
243
260
|
**Available tool names:** `list-calendars`, `list-events`, `search-events`, `get-event`, `list-colors`, `create-event`, `update-event`, `delete-event`, `get-freebusy`, `get-current-time`, `respond-to-event`, `manage-accounts`
|
|
244
261
|
|
|
245
262
|
**Note:** The `manage-accounts` tool is always available regardless of filtering, as it's needed for authentication management.
|
package/build/auth-server.js
CHANGED
|
@@ -116,13 +116,17 @@ function getKeysFilePath() {
|
|
|
116
116
|
}
|
|
117
117
|
function generateCredentialsErrorMessage() {
|
|
118
118
|
return `
|
|
119
|
-
OAuth credentials not found. Please provide credentials using one of these methods:
|
|
119
|
+
OAuth credentials not found. Please provide credentials using one of these methods (in priority order):
|
|
120
120
|
|
|
121
|
-
1.
|
|
121
|
+
1. JSON string (recommended for containers/CI):
|
|
122
|
+
Set GOOGLE_OAUTH_CREDENTIALS_JSON with the full JSON content:
|
|
123
|
+
export GOOGLE_OAUTH_CREDENTIALS_JSON='{"installed":{"client_id":"...","client_secret":"...","redirect_uris":["http://localhost:3000/oauth2callback"]}}'
|
|
124
|
+
|
|
125
|
+
2. File path via environment variable:
|
|
122
126
|
Set GOOGLE_OAUTH_CREDENTIALS to the path of your credentials file:
|
|
123
127
|
export GOOGLE_OAUTH_CREDENTIALS="/path/to/gcp-oauth.keys.json"
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
3. Default file path:
|
|
126
130
|
Place your gcp-oauth.keys.json file in the package root directory.
|
|
127
131
|
|
|
128
132
|
Token storage:
|
|
@@ -139,7 +143,33 @@ To get OAuth credentials:
|
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
// src/auth/client.ts
|
|
142
|
-
async function
|
|
146
|
+
async function loadCredentialsFromJSON() {
|
|
147
|
+
const jsonString = process.env.GOOGLE_OAUTH_CREDENTIALS_JSON;
|
|
148
|
+
if (!jsonString) {
|
|
149
|
+
throw new Error("GOOGLE_OAUTH_CREDENTIALS_JSON environment variable is not set");
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const keys = JSON.parse(jsonString);
|
|
153
|
+
if (keys.installed) {
|
|
154
|
+
const { client_id, client_secret, redirect_uris } = keys.installed;
|
|
155
|
+
return { client_id, client_secret, redirect_uris };
|
|
156
|
+
} else if (keys.client_id && keys.client_secret) {
|
|
157
|
+
return {
|
|
158
|
+
client_id: keys.client_id,
|
|
159
|
+
client_secret: keys.client_secret,
|
|
160
|
+
redirect_uris: keys.redirect_uris || ["http://localhost:3000/oauth2callback"]
|
|
161
|
+
};
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error('Invalid credentials JSON format. Expected either "installed" object or direct client_id/client_secret fields.');
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error instanceof SyntaxError) {
|
|
167
|
+
throw new Error(`Failed to parse GOOGLE_OAUTH_CREDENTIALS_JSON: Invalid JSON syntax - ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function loadCredentialsFromPath() {
|
|
143
173
|
const keysContent = await fs.readFile(getKeysFilePath(), "utf-8");
|
|
144
174
|
const keys = JSON.parse(keysContent);
|
|
145
175
|
if (keys.installed) {
|
|
@@ -156,8 +186,15 @@ async function loadCredentialsFromFile() {
|
|
|
156
186
|
}
|
|
157
187
|
}
|
|
158
188
|
async function loadCredentialsWithFallback() {
|
|
189
|
+
if (process.env.GOOGLE_OAUTH_CREDENTIALS_JSON) {
|
|
190
|
+
try {
|
|
191
|
+
return await loadCredentialsFromJSON();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new Error(`Error loading from GOOGLE_OAUTH_CREDENTIALS_JSON: ${error instanceof Error ? error.message : error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
159
196
|
try {
|
|
160
|
-
return await
|
|
197
|
+
return await loadCredentialsFromPath();
|
|
161
198
|
} catch (fileError) {
|
|
162
199
|
const errorMessage = generateCredentialsErrorMessage();
|
|
163
200
|
throw new Error(`${errorMessage}
|
package/build/auth-server.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auth/paths.js", "../src/auth/client.ts", "../src/auth/utils.ts", "../src/auth/server.ts", "../src/auth/tokenManager.ts", "../src/web/templates.ts", "../src/auth-server.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Shared path utilities for token management\n * This module provides consistent token path resolution across all scripts\n */\n\nimport path from 'path';\nimport { homedir } from 'os';\n\n/**\n * Get the secure token storage path\n * Priority: GOOGLE_CALENDAR_MCP_TOKEN_PATH > XDG_CONFIG_HOME > ~/.config\n */\nexport function getSecureTokenPath() {\n // Priority 1: Custom token path from environment variable\n if (process.env.GOOGLE_CALENDAR_MCP_TOKEN_PATH) {\n return path.resolve(process.env.GOOGLE_CALENDAR_MCP_TOKEN_PATH);\n }\n // Priority 2: XDG Base Directory specification\n const configDir = process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config');\n return path.join(configDir, 'google-calendar-mcp', 'tokens.json');\n}\n\n/**\n * Get the legacy token path (for migration purposes)\n */\nexport function getLegacyTokenPath() {\n return path.join(process.cwd(), '.gcp-saved-tokens.json');\n}\n\n/**\n * Reserved account names that cannot be used\n */\nconst RESERVED_NAMES = ['.', '..', 'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4',\n 'lpt1', 'lpt2', 'lpt3'];\n\n/**\n * Validate account ID format\n * Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only\n * Cannot be reserved names\n */\nexport function validateAccountId(accountId) {\n if (!accountId || accountId.length === 0) {\n throw new Error('Invalid account ID. Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only.');\n }\n\n // Check reserved names first (before regex, since \".\" and \"..\" won't match regex)\n if (RESERVED_NAMES.includes(accountId)) {\n throw new Error(`Account ID \"${accountId}\" is reserved and cannot be used.`);\n }\n\n // Check format: lowercase alphanumeric, dashes, underscores, 1-64 chars\n if (!/^[a-z0-9_-]{1,64}$/.test(accountId)) {\n throw new Error('Invalid account ID. Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only.');\n }\n\n return accountId;\n}\n\n/**\n * Get current account mode from environment\n * Uses same logic as utils.ts but compatible with both JS and TS\n */\nexport function getAccountMode() {\n // If set explicitly via environment variable use that instead\n const explicitMode = process.env.GOOGLE_ACCOUNT_MODE;\n if (explicitMode !== undefined && explicitMode !== null) {\n // Validate the account ID (no lowercasing - must be lowercase already)\n return validateAccountId(explicitMode);\n }\n\n // Auto-detect test environment\n if (process.env.NODE_ENV === 'test') {\n return 'test';\n }\n\n // Default to normal for regular app usage\n return 'normal';\n}", "import { OAuth2Client } from 'google-auth-library';\nimport * as fs from 'fs/promises';\nimport { getKeysFilePath, generateCredentialsErrorMessage, OAuthCredentials } from './utils.js';\n\nasync function loadCredentialsFromFile(): Promise<OAuthCredentials> {\n const keysContent = await fs.readFile(getKeysFilePath(), \"utf-8\");\n const keys = JSON.parse(keysContent);\n\n if (keys.installed) {\n // Standard OAuth credentials file format\n const { client_id, client_secret, redirect_uris } = keys.installed;\n return { client_id, client_secret, redirect_uris };\n } else if (keys.client_id && keys.client_secret) {\n // Direct format\n return {\n client_id: keys.client_id,\n client_secret: keys.client_secret,\n redirect_uris: keys.redirect_uris || ['http://localhost:3000/oauth2callback']\n };\n } else {\n throw new Error('Invalid credentials file format. Expected either \"installed\" object or direct client_id/client_secret fields.');\n }\n}\n\nasync function loadCredentialsWithFallback(): Promise<OAuthCredentials> {\n // Load credentials from file (CLI param, env var, or default path)\n try {\n return await loadCredentialsFromFile();\n } catch (fileError) {\n // Generate helpful error message\n const errorMessage = generateCredentialsErrorMessage();\n throw new Error(`${errorMessage}\\n\\nOriginal error: ${fileError instanceof Error ? fileError.message : fileError}`);\n }\n}\n\nexport async function initializeOAuth2Client(): Promise<OAuth2Client> {\n // Always use real OAuth credentials - no mocking.\n // Unit tests should mock at the handler level, integration tests need real credentials.\n try {\n const credentials = await loadCredentialsWithFallback();\n \n // Use the first redirect URI as the default for the base client\n return new OAuth2Client({\n clientId: credentials.client_id,\n clientSecret: credentials.client_secret,\n redirectUri: credentials.redirect_uris[0],\n });\n } catch (error) {\n throw new Error(`Error loading OAuth keys: ${error instanceof Error ? error.message : error}`);\n }\n}\n\nexport async function loadCredentials(): Promise<{ client_id: string; client_secret: string }> {\n try {\n const credentials = await loadCredentialsWithFallback();\n \n if (!credentials.client_id || !credentials.client_secret) {\n throw new Error('Client ID or Client Secret missing in credentials.');\n }\n return {\n client_id: credentials.client_id,\n client_secret: credentials.client_secret\n };\n } catch (error) {\n throw new Error(`Error loading credentials: ${error instanceof Error ? error.message : error}`);\n }\n}", "import * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport { getSecureTokenPath as getSharedSecureTokenPath, getLegacyTokenPath as getSharedLegacyTokenPath, getAccountMode as getSharedAccountMode } from './paths.js';\n\n// Helper to get the project root directory reliably\nfunction getProjectRoot(): string {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n // In build output (e.g., build/bundle.js), __dirname is .../build\n // Go up ONE level to get the project root\n const projectRoot = path.join(__dirname, \"..\"); // Corrected: Go up ONE level\n return path.resolve(projectRoot); // Ensure absolute path\n}\n\n// Get the current account mode - delegates to shared implementation\n// Now supports arbitrary account IDs instead of just 'normal' and 'test'\nexport function getAccountMode(): string {\n return getSharedAccountMode();\n}\n\n// Helper to detect if we're running in a test environment\nfunction isRunningInTestEnvironment(): boolean {\n // Simple and reliable: just check NODE_ENV\n return process.env.NODE_ENV === 'test';\n}\n\n// Returns the absolute path for the saved token file - delegates to shared implementation\nexport function getSecureTokenPath(): string {\n return getSharedSecureTokenPath();\n}\n\n// Returns the legacy token path for backward compatibility - delegates to shared implementation \nexport function getLegacyTokenPath(): string {\n return getSharedLegacyTokenPath();\n}\n\n// Returns the absolute path for the GCP OAuth keys file with priority:\n// 1. Environment variable GOOGLE_OAUTH_CREDENTIALS (highest priority)\n// 2. Default file path (lowest priority)\nexport function getKeysFilePath(): string {\n // Priority 1: Environment variable\n const envCredentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;\n if (envCredentialsPath) {\n return path.resolve(envCredentialsPath);\n }\n \n // Priority 2: Default file path\n const projectRoot = getProjectRoot();\n const keysPath = path.join(projectRoot, \"gcp-oauth.keys.json\");\n return keysPath; // Already absolute from getProjectRoot\n}\n\n// Helper to determine if we're currently in test mode\nexport function isTestMode(): boolean {\n return getAccountMode() === 'test';\n}\n\n// Interface for OAuth credentials\nexport interface OAuthCredentials {\n client_id: string;\n client_secret: string;\n redirect_uris: string[];\n}\n\n// Interface for credentials file with project_id\nexport interface OAuthCredentialsWithProject {\n installed?: {\n project_id?: string;\n client_id?: string;\n client_secret?: string;\n redirect_uris?: string[];\n };\n project_id?: string;\n client_id?: string;\n client_secret?: string;\n redirect_uris?: string[];\n}\n\n// Get project ID from OAuth credentials file\n// Returns undefined if credentials file doesn't exist, is invalid, or missing project_id\nexport function getCredentialsProjectId(): string | undefined {\n try {\n // Use existing helper to get credentials file path\n const credentialsPath = getKeysFilePath();\n\n if (!fs.existsSync(credentialsPath)) {\n return undefined;\n }\n\n const credentialsContent = fs.readFileSync(credentialsPath, 'utf-8');\n const credentials: OAuthCredentialsWithProject = JSON.parse(credentialsContent);\n\n // Extract project_id from installed format or direct format\n if (credentials.installed?.project_id) {\n return credentials.installed.project_id;\n } else if (credentials.project_id) {\n return credentials.project_id;\n }\n\n return undefined;\n } catch (error) {\n // If we can't read project ID, return undefined (backward compatibility)\n return undefined;\n }\n}\n\n// Generate helpful error message for missing credentials\nexport function generateCredentialsErrorMessage(): string {\n return `\nOAuth credentials not found. Please provide credentials using one of these methods:\n\n1. Environment variable:\n Set GOOGLE_OAUTH_CREDENTIALS to the path of your credentials file:\n export GOOGLE_OAUTH_CREDENTIALS=\"/path/to/gcp-oauth.keys.json\"\n\n2. Default file path:\n Place your gcp-oauth.keys.json file in the package root directory.\n\nToken storage:\n- Tokens are saved to: ${getSecureTokenPath()}\n- To use a custom token location, set GOOGLE_CALENDAR_MCP_TOKEN_PATH environment variable\n\nTo get OAuth credentials:\n1. Go to the Google Cloud Console (https://console.cloud.google.com/)\n2. Create or select a project\n3. Enable the Google Calendar API\n4. Create OAuth 2.0 credentials\n5. Download the credentials file as gcp-oauth.keys.json\n`.trim();\n}\n", "import { OAuth2Client } from 'google-auth-library';\nimport { TokenManager } from './tokenManager.js';\nimport http from 'http';\nimport { URL } from 'url';\nimport open from 'open';\nimport { loadCredentials } from './client.js';\nimport { getAccountMode } from './utils.js';\nimport { renderAuthSuccess, renderAuthError, renderAuthLanding, loadWebFile } from '../web/templates.js';\n\nexport interface StartForMcpToolResult {\n success: boolean;\n authUrl?: string;\n callbackUrl?: string;\n error?: string;\n}\n\nexport class AuthServer {\n private baseOAuth2Client: OAuth2Client; // Used by TokenManager for validation/refresh\n private flowOAuth2Client: OAuth2Client | null = null; // Used specifically for the auth code flow\n private server: http.Server | null = null;\n private tokenManager: TokenManager;\n private portRange: { start: number; end: number };\n private activeConnections: Set<import('net').Socket> = new Set(); // Track active socket connections\n public authCompletedSuccessfully = false; // Flag for standalone script\n private mcpToolTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for MCP tool auth flow\n private autoShutdownOnSuccess = false; // Whether to auto-shutdown after successful auth\n\n constructor(oauth2Client: OAuth2Client) {\n this.baseOAuth2Client = oauth2Client;\n this.tokenManager = new TokenManager(oauth2Client);\n this.portRange = { start: 3500, end: 3505 };\n }\n\n /**\n * Creates the flow-specific OAuth2Client with the correct redirect URI.\n */\n private async createFlowOAuth2Client(port: number): Promise<OAuth2Client> {\n const { client_id, client_secret } = await loadCredentials();\n return new OAuth2Client(\n client_id,\n client_secret,\n `http://localhost:${port}/oauth2callback`\n );\n }\n\n /**\n * Generates an OAuth authorization URL with standard settings.\n */\n private generateOAuthUrl(client: OAuth2Client): string {\n return client.generateAuthUrl({\n access_type: 'offline',\n scope: ['https://www.googleapis.com/auth/calendar'],\n prompt: 'consent'\n });\n }\n\n private createServer(): http.Server {\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n \n if (url.pathname === '/styles.css') {\n // Serve shared CSS\n const css = await loadWebFile('styles.css');\n res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8' });\n res.end(css);\n\n } else if (url.pathname === '/') {\n // Root route - show auth link\n const clientForUrl = this.flowOAuth2Client || this.baseOAuth2Client;\n const authUrl = this.generateOAuthUrl(clientForUrl);\n const accountMode = getAccountMode();\n\n const landingHtml = await renderAuthLanding({\n accountId: accountMode,\n authUrl: authUrl\n });\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(landingHtml);\n\n } else if (url.pathname === '/oauth2callback') {\n // OAuth callback route\n const code = url.searchParams.get('code');\n if (!code) {\n const errorHtml = await renderAuthError({\n errorMessage: 'Authorization code missing'\n });\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n return;\n }\n\n if (!this.flowOAuth2Client) {\n const errorHtml = await renderAuthError({\n errorMessage: 'Authentication flow not properly initiated.'\n });\n res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n return;\n }\n \n try {\n const { tokens } = await this.flowOAuth2Client.getToken(code);\n await this.tokenManager.saveTokens(tokens);\n this.authCompletedSuccessfully = true;\n\n const tokenPath = this.tokenManager.getTokenPath();\n const accountMode = this.tokenManager.getAccountMode();\n\n // Auto-shutdown after successful auth if triggered by MCP tool\n if (this.autoShutdownOnSuccess) {\n // Clear the timeout since auth succeeded\n if (this.mcpToolTimeout) {\n clearTimeout(this.mcpToolTimeout);\n this.mcpToolTimeout = null;\n }\n // Give the browser time to render success page, then shutdown\n setTimeout(() => {\n this.stop().catch(() => {});\n }, 2000);\n }\n \n const successHtml = await renderAuthSuccess({\n accountId: accountMode,\n tokenPath: tokenPath\n });\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(successHtml);\n } catch (error: unknown) {\n this.authCompletedSuccessfully = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n process.stderr.write(`\u2717 Token save failed: ${message}\\n`);\n\n const errorHtml = await renderAuthError({\n errorMessage: message\n });\n res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n }\n } else {\n // 404 for other routes\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n }\n });\n\n // Track connections at server level\n server.on('connection', (socket) => {\n this.activeConnections.add(socket);\n socket.on('close', () => {\n this.activeConnections.delete(socket);\n });\n });\n \n return server;\n }\n\n async start(openBrowser = true): Promise<boolean> {\n // Add timeout wrapper to prevent hanging\n return Promise.race([\n this.startWithTimeout(openBrowser),\n new Promise<boolean>((_, reject) => {\n setTimeout(() => reject(new Error('Auth server start timed out after 10 seconds')), 10000);\n })\n ]).catch(() => false); // Return false on timeout instead of throwing\n }\n\n private async startWithTimeout(openBrowser = true): Promise<boolean> {\n if (await this.tokenManager.validateTokens()) {\n this.authCompletedSuccessfully = true;\n return true;\n }\n \n // Try to start the server and get the port\n const port = await this.startServerOnAvailablePort();\n if (port === null) {\n process.stderr.write(`Could not start auth server on available port. Please check port availability (${this.portRange.start}-${this.portRange.end}) and try again.\\n`);\n\n this.authCompletedSuccessfully = false;\n return false;\n }\n\n // Successfully started server on `port`. Now create the flow-specific OAuth client.\n try {\n this.flowOAuth2Client = await this.createFlowOAuth2Client(port);\n } catch (error) {\n // Could not load credentials, cannot proceed with auth flow\n this.authCompletedSuccessfully = false;\n await this.stop(); // Stop the server we just started\n return false;\n }\n\n // Generate Auth URL using the newly created flow client\n const authorizeUrl = this.generateOAuthUrl(this.flowOAuth2Client);\n \n // Always show the URL in console for easy access\n process.stderr.write(`\\n\uD83D\uDD17 Authentication URL: ${authorizeUrl}\\n\\n`);\n process.stderr.write(`Or visit: http://localhost:${port}\\n\\n`);\n \n if (openBrowser) {\n try {\n await open(authorizeUrl);\n process.stderr.write(`Browser opened automatically. If it didn't open, use the URL above.\\n`);\n } catch (error) {\n process.stderr.write(`Could not open browser automatically. Please use the URL above.\\n`);\n }\n } else {\n process.stderr.write(`Please visit the URL above to complete authentication.\\n`);\n }\n\n return true; // Auth flow initiated\n }\n\n private async startServerOnAvailablePort(): Promise<number | null> {\n for (let port = this.portRange.start; port <= this.portRange.end; port++) {\n try {\n await new Promise<void>((resolve, reject) => {\n const testServer = this.createServer();\n testServer.listen(port, () => {\n this.server = testServer; // Assign to class property *only* if successful\n resolve();\n });\n testServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n // Port is in use, close the test server and reject\n testServer.close(() => reject(err)); \n } else {\n // Other error, reject\n reject(err);\n }\n });\n });\n return port; // Port successfully bound\n } catch (error: unknown) {\n // Check if it's EADDRINUSE, otherwise rethrow or handle\n if (!(error instanceof Error && 'code' in error && error.code === 'EADDRINUSE')) {\n // An unexpected error occurred during server start\n return null;\n }\n // EADDRINUSE occurred, loop continues\n }\n }\n return null; // No port found\n }\n\n public getRunningPort(): number | null {\n if (this.server) {\n const address = this.server.address();\n if (typeof address === 'object' && address !== null) {\n return address.port;\n }\n }\n return null;\n }\n\n async stop(): Promise<void> {\n // Clear any pending MCP tool timeout\n if (this.mcpToolTimeout) {\n clearTimeout(this.mcpToolTimeout);\n this.mcpToolTimeout = null;\n }\n this.autoShutdownOnSuccess = false;\n\n return new Promise((resolve, reject) => {\n if (this.server) {\n // Force close all active connections\n for (const connection of this.activeConnections) {\n connection.destroy();\n }\n this.activeConnections.clear();\n\n // Add a timeout to force close if server doesn't close gracefully\n const timeout = setTimeout(() => {\n process.stderr.write('Server close timeout, forcing exit...\\n');\n this.server = null;\n resolve();\n }, 2000); // 2 second timeout\n\n this.server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n reject(err);\n } else {\n this.server = null;\n resolve();\n }\n });\n } else {\n resolve();\n }\n });\n }\n\n /**\n * Start the auth server for use by an MCP tool.\n *\n * Unlike the regular start() method:\n * - Does not open the browser automatically\n * - Returns the auth URL for the MCP tool to return to the user\n * - Auto-shutdowns after successful auth or timeout (5 minutes)\n * - Does not validate existing tokens (allows adding new accounts)\n *\n * @param accountId - The account ID to authenticate\n * @returns Result with auth URL on success, or error on failure\n */\n async startForMcpTool(accountId: string): Promise<StartForMcpToolResult> {\n // If server is already running, stop it first\n if (this.server) {\n await this.stop();\n }\n\n // Set the account mode\n this.tokenManager.setAccountMode(accountId);\n\n // Try to start the server and get the port\n const port = await this.startServerOnAvailablePort();\n if (port === null) {\n return {\n success: false,\n error: `Could not start auth server. Ports ${this.portRange.start}-${this.portRange.end} may be in use.`\n };\n }\n\n // Create the flow-specific OAuth client\n try {\n this.flowOAuth2Client = await this.createFlowOAuth2Client(port);\n } catch (error) {\n await this.stop();\n return {\n success: false,\n error: `Failed to load OAuth credentials: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n\n // Generate Auth URL\n const authUrl = this.generateOAuthUrl(this.flowOAuth2Client);\n\n // Enable auto-shutdown on success\n this.autoShutdownOnSuccess = true;\n this.authCompletedSuccessfully = false;\n\n // Set timeout to auto-shutdown if auth not completed (5 minutes)\n this.mcpToolTimeout = setTimeout(async () => {\n if (!this.authCompletedSuccessfully) {\n process.stderr.write(`Auth timeout for account \"${accountId}\" - shutting down auth server\\n`);\n await this.stop();\n }\n }, 5 * 60 * 1000);\n\n return {\n success: true,\n authUrl,\n callbackUrl: `http://localhost:${port}/oauth2callback`\n };\n }\n} ", "import { OAuth2Client, Credentials } from 'google-auth-library';\nimport fs from 'fs/promises';\nimport { getSecureTokenPath, getAccountMode, getLegacyTokenPath } from './utils.js';\nimport { GaxiosError } from 'gaxios';\nimport { mkdir } from 'fs/promises';\nimport { dirname } from 'path';\n\n// Cached calendar info\ninterface CachedCalendar {\n id: string;\n summary: string;\n summaryOverride?: string;\n accessRole: string;\n primary: boolean;\n backgroundColor?: string;\n}\n\n// Extended credentials with cached email and calendars\ninterface CachedCredentials extends Credentials {\n cached_email?: string;\n cached_calendars?: CachedCalendar[];\n calendars_cached_at?: number;\n}\n\n// Interface for multi-account token storage\n// Now supports arbitrary account IDs\ninterface MultiAccountTokens {\n [accountId: string]: CachedCredentials;\n}\n\nexport class TokenManager {\n private oauth2Client: OAuth2Client;\n private tokenPath: string;\n private accountMode: string;\n private accounts: Map<string, OAuth2Client> = new Map();\n private credentials: {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n };\n private writeQueue: Promise<void> = Promise.resolve();\n\n constructor(oauth2Client: OAuth2Client) {\n this.oauth2Client = oauth2Client;\n this.tokenPath = getSecureTokenPath();\n this.accountMode = getAccountMode();\n\n // Store credentials to avoid accessing private properties later\n this.credentials = {\n clientId: (oauth2Client as any)._clientId,\n clientSecret: (oauth2Client as any)._clientSecret,\n redirectUri: (oauth2Client as any)._redirectUri\n };\n\n this.setupTokenRefresh();\n }\n\n // Method to expose the token path\n public getTokenPath(): string {\n return this.tokenPath;\n }\n\n // Method to get current account mode\n public getAccountMode(): string {\n return this.accountMode;\n }\n\n // Method to switch account mode (supports arbitrary account IDs)\n public setAccountMode(mode: string): void {\n this.accountMode = mode;\n }\n\n private async ensureTokenDirectoryExists(): Promise<void> {\n try {\n await mkdir(dirname(this.tokenPath), { recursive: true });\n } catch (error) {\n process.stderr.write(`Failed to create token directory: ${error}\\n`);\n }\n }\n\n private isFileNotFoundError(error: unknown): boolean {\n return error instanceof Error && 'code' in error && (error as any).code === 'ENOENT';\n }\n\n private async writeTokenFile(tokens: MultiAccountTokens): Promise<void> {\n await this.ensureTokenDirectoryExists();\n await fs.writeFile(this.tokenPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });\n }\n\n private async loadMultiAccountTokens(): Promise<MultiAccountTokens> {\n try {\n const fileContent = await fs.readFile(this.tokenPath, \"utf-8\");\n const parsed = JSON.parse(fileContent);\n\n // Check if this is the old single-account format\n if (parsed.access_token || parsed.refresh_token) {\n // Convert old format to new multi-account format\n const multiAccountTokens: MultiAccountTokens = {\n normal: parsed\n };\n await this.saveMultiAccountTokens(multiAccountTokens);\n return multiAccountTokens;\n }\n\n // Already in multi-account format\n return parsed as MultiAccountTokens;\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n return {};\n }\n throw error;\n }\n }\n\n /**\n * Raw token file read without migration logic.\n * Used for atomic read-modify-write operations where we need to re-read current state.\n */\n private async loadMultiAccountTokensRaw(): Promise<MultiAccountTokens> {\n try {\n const fileContent = await fs.readFile(this.tokenPath, \"utf-8\");\n return JSON.parse(fileContent) as MultiAccountTokens;\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n return {};\n }\n throw error;\n }\n }\n\n private async saveMultiAccountTokens(multiAccountTokens: MultiAccountTokens): Promise<void> {\n return this.enqueueTokenWrite(async () => {\n await this.writeTokenFile(multiAccountTokens);\n });\n }\n\n private enqueueTokenWrite(operation: () => Promise<void>): Promise<void> {\n const pendingWrite = this.writeQueue\n .catch(() => undefined)\n .then(operation);\n\n this.writeQueue = pendingWrite\n .catch(error => {\n process.stderr.write(`Error writing token file: ${error instanceof Error ? error.message : error}\\n`);\n throw error;\n })\n .catch(() => undefined);\n\n return pendingWrite;\n }\n\n private setupTokenRefresh(): void {\n this.setupTokenRefreshForAccount(this.oauth2Client, this.accountMode);\n }\n\n /**\n * Set up token refresh handler for a specific account\n * Uses enqueueTokenWrite to prevent race conditions when multiple accounts refresh simultaneously\n */\n private setupTokenRefreshForAccount(client: OAuth2Client, accountId: string): void {\n client.on(\"tokens\", async (newTokens) => {\n try {\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const currentTokens = multiAccountTokens[accountId] || {};\n\n const updatedTokens = {\n ...currentTokens,\n ...newTokens,\n refresh_token: newTokens.refresh_token || currentTokens.refresh_token,\n };\n\n multiAccountTokens[accountId] = updatedTokens;\n await this.writeTokenFile(multiAccountTokens);\n });\n\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Tokens updated and saved for ${accountId} account\\n`);\n }\n } catch (error: unknown) {\n process.stderr.write(\"Error saving updated tokens: \");\n if (error instanceof Error) {\n process.stderr.write(error.message);\n } else if (typeof error === 'string') {\n process.stderr.write(error);\n }\n process.stderr.write(\"\\n\");\n }\n });\n }\n\n private async migrateLegacyTokens(): Promise<boolean> {\n const legacyPath = getLegacyTokenPath();\n try {\n // Check if legacy tokens exist\n if (!(await fs.access(legacyPath).then(() => true).catch(() => false))) {\n return false; // No legacy tokens to migrate\n }\n\n // Read legacy tokens\n const legacyTokens = JSON.parse(await fs.readFile(legacyPath, \"utf-8\"));\n \n if (!legacyTokens || typeof legacyTokens !== \"object\") {\n process.stderr.write(\"Invalid legacy token format, skipping migration\\n\");\n return false;\n }\n\n // Copy to new location (ensures directory exists)\n await this.writeTokenFile(legacyTokens);\n \n process.stderr.write(`Migrated tokens from legacy location: ${legacyPath} to: ${this.tokenPath}\\n`);\n \n // Optionally remove legacy file after successful migration\n try {\n await fs.unlink(legacyPath);\n process.stderr.write(\"Removed legacy token file\\n\");\n } catch (unlinkErr) {\n process.stderr.write(`Warning: Could not remove legacy token file: ${unlinkErr}\\n`);\n }\n \n return true;\n } catch (error) {\n process.stderr.write(`Error migrating legacy tokens: ${error}\\n`);\n return false;\n }\n }\n\n async loadSavedTokens(): Promise<boolean> {\n try {\n await this.ensureTokenDirectoryExists();\n \n // Check if current token file exists\n const tokenExists = await fs.access(this.tokenPath).then(() => true).catch(() => false);\n \n // If no current tokens, try to migrate from legacy location\n if (!tokenExists) {\n const migrated = await this.migrateLegacyTokens();\n if (!migrated) {\n process.stderr.write(`No token file found at: ${this.tokenPath}\\n`);\n return false;\n }\n }\n\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const tokens = multiAccountTokens[this.accountMode];\n\n if (!tokens || typeof tokens !== \"object\") {\n process.stderr.write(`No tokens found for ${this.accountMode} account in file: ${this.tokenPath}\\n`);\n return false;\n }\n\n this.oauth2Client.setCredentials(tokens);\n process.stderr.write(`Loaded tokens for ${this.accountMode} account\\n`);\n return true;\n } catch (error: unknown) {\n process.stderr.write(`Error loading tokens for ${this.accountMode} account: `);\n if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') { \n try { \n await fs.unlink(this.tokenPath); \n process.stderr.write(\"Removed potentially corrupted token file\\n\"); \n } catch (unlinkErr) { /* ignore */ } \n }\n return false;\n }\n }\n\n async refreshTokensIfNeeded(): Promise<boolean> {\n const expiryDate = this.oauth2Client.credentials.expiry_date;\n const isExpired = expiryDate\n ? Date.now() >= expiryDate - 5 * 60 * 1000 // 5 minute buffer\n : !this.oauth2Client.credentials.access_token; // No token means we need one\n\n if (isExpired && this.oauth2Client.credentials.refresh_token) {\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Auth token expired or nearing expiry for ${this.accountMode} account, refreshing...\\n`);\n }\n try {\n const response = await this.oauth2Client.refreshAccessToken();\n const newTokens = response.credentials;\n\n if (!newTokens.access_token) {\n throw new Error(\"Received invalid tokens during refresh\");\n }\n // The 'tokens' event listener should handle saving\n this.oauth2Client.setCredentials(newTokens);\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Token refreshed successfully for ${this.accountMode} account\\n`);\n }\n return true;\n } catch (refreshError) {\n if (refreshError instanceof GaxiosError && refreshError.response?.data?.error === 'invalid_grant') {\n process.stderr.write(`Error refreshing auth token for ${this.accountMode} account: Invalid grant. Token likely expired or revoked. Please re-authenticate.\\n`);\n return false; // Indicate failure due to invalid grant\n } else {\n // Handle other refresh errors\n process.stderr.write(`Error refreshing auth token for ${this.accountMode} account: `);\n if (refreshError instanceof Error) {\n process.stderr.write(refreshError.message);\n } else if (typeof refreshError === 'string') {\n process.stderr.write(refreshError);\n }\n process.stderr.write(\"\\n\");\n return false;\n }\n }\n } else if (!this.oauth2Client.credentials.access_token && !this.oauth2Client.credentials.refresh_token) {\n process.stderr.write(`No access or refresh token available for ${this.accountMode} account. Please re-authenticate.\\n`);\n return false;\n } else {\n // Token is valid or no refresh token available\n return true;\n }\n }\n\n async validateTokens(accountMode?: string): Promise<boolean> {\n // For unit tests that don't need real authentication, they should mock at the handler level\n // Integration tests always need real tokens\n\n const modeToValidate = accountMode || this.accountMode;\n const currentMode = this.accountMode;\n \n try {\n // Temporarily switch to the mode we want to validate if different\n if (modeToValidate !== currentMode) {\n this.accountMode = modeToValidate;\n }\n \n if (!this.oauth2Client.credentials || !this.oauth2Client.credentials.access_token) {\n // Try loading first if no credentials set\n if (!(await this.loadSavedTokens())) {\n return false; // No saved tokens to load\n }\n // Check again after loading\n if (!this.oauth2Client.credentials || !this.oauth2Client.credentials.access_token) {\n return false; // Still no token after loading\n }\n }\n \n const result = await this.refreshTokensIfNeeded();\n return result;\n } finally {\n // Always restore the original account mode\n if (modeToValidate !== currentMode) {\n this.accountMode = currentMode;\n }\n }\n }\n\n async saveTokens(tokens: Credentials, email?: string): Promise<void> {\n try {\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const cachedTokens: CachedCredentials = { ...tokens };\n\n // Cache the email if provided\n if (email) {\n cachedTokens.cached_email = email;\n }\n\n multiAccountTokens[this.accountMode] = cachedTokens;\n await this.writeTokenFile(multiAccountTokens);\n });\n this.oauth2Client.setCredentials(tokens);\n process.stderr.write(`Tokens saved successfully for ${this.accountMode} account to: ${this.tokenPath}\\n`);\n } catch (error: unknown) {\n process.stderr.write(`Error saving tokens for ${this.accountMode} account: ${error}\\n`);\n throw error;\n }\n }\n\n async clearTokens(): Promise<void> {\n try {\n this.oauth2Client.setCredentials({}); // Clear in memory\n\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n delete multiAccountTokens[this.accountMode];\n\n // If no accounts left, delete the entire file\n if (Object.keys(multiAccountTokens).length === 0) {\n await fs.unlink(this.tokenPath);\n process.stderr.write(`All tokens cleared, file deleted\\n`);\n } else {\n await this.writeTokenFile(multiAccountTokens);\n process.stderr.write(`Tokens cleared for ${this.accountMode} account\\n`);\n }\n });\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n // File already gone, which is fine\n process.stderr.write(\"Token file already deleted\\n\");\n } else {\n process.stderr.write(`Error clearing tokens for ${this.accountMode} account: ${error}\\n`);\n // Don't re-throw, clearing is best-effort\n }\n }\n }\n\n // Method to list available accounts\n async listAvailableAccounts(): Promise<string[]> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n return Object.keys(multiAccountTokens);\n } catch (error) {\n return [];\n }\n }\n\n /**\n * Remove a specific account's tokens from storage.\n * @param accountId - The account ID to remove\n * @throws Error if account doesn't exist or removal fails\n */\n async removeAccount(accountId: string): Promise<void> {\n const normalizedId = accountId.toLowerCase();\n\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n\n if (!multiAccountTokens[normalizedId]) {\n throw new Error(`Account \"${normalizedId}\" not found`);\n }\n\n delete multiAccountTokens[normalizedId];\n\n // If no accounts left, delete the entire file\n if (Object.keys(multiAccountTokens).length === 0) {\n await fs.unlink(this.tokenPath);\n process.stderr.write(`All tokens cleared, file deleted\\n`);\n } else {\n await this.writeTokenFile(multiAccountTokens);\n process.stderr.write(`Account \"${normalizedId}\" removed successfully\\n`);\n }\n\n // Remove from in-memory accounts map if present\n this.accounts.delete(normalizedId);\n });\n }\n\n // Method to switch to a different account (supports arbitrary account IDs)\n async switchAccount(newMode: string): Promise<boolean> {\n this.accountMode = newMode;\n return this.loadSavedTokens();\n }\n\n /**\n * Load all authenticated accounts from token file\n * Returns a Map of account ID to OAuth2Client\n *\n * Reuses existing OAuth2Client instances to prevent memory leaks\n * Sets up token refresh handlers for new accounts\n */\n async loadAllAccounts(): Promise<Map<string, OAuth2Client>> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n\n // Remove accounts that no longer exist in token file\n for (const accountId of this.accounts.keys()) {\n if (!multiAccountTokens[accountId]) {\n const client = this.accounts.get(accountId);\n if (client) {\n // Clean up event listeners before removing\n client.removeAllListeners('tokens');\n }\n this.accounts.delete(accountId);\n }\n }\n\n // Add or update accounts\n for (const [accountId, tokens] of Object.entries(multiAccountTokens)) {\n // Validate account ID\n try {\n const { validateAccountId } = await import('./paths.js') as any;\n validateAccountId(accountId);\n\n // Skip invalid token entries\n if (!tokens || typeof tokens !== 'object' || !tokens.access_token) {\n continue;\n }\n\n // Check if we already have a client for this account (reuse it to prevent memory leak)\n let client = this.accounts.get(accountId);\n\n if (!client) {\n // Create a new OAuth2Client for this account using stored credentials\n client = new OAuth2Client(\n this.credentials.clientId,\n this.credentials.clientSecret,\n this.credentials.redirectUri\n );\n\n // Set up token refresh handler for this new client\n this.setupTokenRefreshForAccount(client, accountId);\n\n this.accounts.set(accountId, client);\n }\n\n // Update credentials (for both new and existing clients)\n client.setCredentials(tokens);\n\n } catch (error) {\n // Skip invalid account IDs\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Skipping invalid account \"${accountId}\": ${error}\\n`);\n }\n continue;\n }\n }\n\n return this.accounts;\n } catch (error: any) {\n // Check for file not found error (works with both Error objects and plain objects)\n if (error && error.code === 'ENOENT') {\n // No token file exists, return empty map\n return new Map();\n }\n throw error;\n }\n }\n\n /**\n * Get OAuth2Client for a specific account\n * @param accountId The account ID to retrieve\n * @throws Error if account not found or invalid\n */\n getClient(accountId: string): OAuth2Client {\n // Validate account ID first\n const { validateAccountId } = require('./paths.js');\n validateAccountId(accountId);\n\n const client = this.accounts.get(accountId);\n if (!client) {\n throw new Error(`Account \"${accountId}\" not found. Please authenticate this account first.`);\n }\n\n return client;\n }\n\n /**\n * List all authenticated accounts with their email addresses, status, and calendars\n * Uses cached data when available to avoid repeated API calls\n */\n async listAccounts(): Promise<Array<{\n id: string;\n email: string;\n status: string;\n calendars: CachedCalendar[];\n }>> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const accountList: Array<{\n id: string;\n email: string;\n status: string;\n calendars: CachedCalendar[];\n }> = [];\n let tokensUpdated = false;\n\n // Cache TTL: 5 minutes for calendars\n const CALENDAR_CACHE_TTL = 5 * 60 * 1000;\n\n for (const [accountId, tokens] of Object.entries(multiAccountTokens)) {\n // Skip invalid entries\n if (!tokens || typeof tokens !== 'object') {\n continue;\n }\n\n let client: OAuth2Client | null = null;\n\n // Create client and refresh if needed\n if (tokens.access_token || tokens.refresh_token) {\n try {\n client = new OAuth2Client(\n this.credentials.clientId,\n this.credentials.clientSecret,\n this.credentials.redirectUri\n );\n client.setCredentials(tokens);\n\n // Try to refresh token if access token is expired or missing\n if (tokens.refresh_token && (!tokens.access_token || (tokens.expiry_date && tokens.expiry_date < Date.now()))) {\n try {\n const response = await client.refreshAccessToken();\n client.setCredentials(response.credentials);\n Object.assign(tokens, response.credentials);\n tokensUpdated = true;\n } catch {\n // Refresh failed\n }\n }\n } catch {\n client = null;\n }\n }\n\n // Get email address - use cached value if available\n let email = tokens.cached_email || 'unknown';\n if (!tokens.cached_email && client) {\n try {\n email = await this.getUserEmail(client);\n if (email !== 'unknown') {\n tokens.cached_email = email;\n tokensUpdated = true;\n }\n } catch {\n // Email retrieval failed\n }\n }\n\n // Get calendars - use cached if fresh, otherwise fetch\n let calendars: CachedCalendar[] = tokens.cached_calendars || [];\n const cacheExpired = !tokens.calendars_cached_at ||\n (Date.now() - tokens.calendars_cached_at) > CALENDAR_CACHE_TTL;\n\n if (cacheExpired && client) {\n try {\n calendars = await this.fetchCalendarsForClient(client);\n tokens.cached_calendars = calendars;\n tokens.calendars_cached_at = Date.now();\n tokensUpdated = true;\n } catch {\n // Calendar fetch failed, use cached or empty\n }\n }\n\n // Determine status\n let status = 'active';\n if (!tokens.refresh_token) {\n if (!tokens.access_token || (tokens.expiry_date && tokens.expiry_date < Date.now())) {\n status = 'expired';\n }\n }\n\n accountList.push({ id: accountId, email, status, calendars });\n }\n\n // Save updated tokens with cached data using atomic read-modify-write\n // This prevents race conditions when multiple listAccounts() calls run concurrently\n if (tokensUpdated) {\n await this.enqueueTokenWrite(async () => {\n // Re-read current token state to preserve any concurrent auth changes\n const latestTokens = await this.loadMultiAccountTokensRaw();\n\n // Merge our cached metadata updates into the latest token state\n for (const accountId of Object.keys(multiAccountTokens)) {\n const localUpdates = multiAccountTokens[accountId];\n const latestAccount = latestTokens[accountId];\n\n if (latestAccount && localUpdates) {\n // Only update cached metadata, not auth tokens\n if (localUpdates.cached_email) {\n latestAccount.cached_email = localUpdates.cached_email;\n }\n if (localUpdates.cached_calendars) {\n latestAccount.cached_calendars = localUpdates.cached_calendars;\n latestAccount.calendars_cached_at = localUpdates.calendars_cached_at;\n }\n }\n }\n\n await this.writeTokenFile(latestTokens);\n });\n }\n\n return accountList;\n } catch (error) {\n return [];\n }\n }\n\n /**\n * Fetch calendars for a specific OAuth2Client\n */\n private async fetchCalendarsForClient(client: OAuth2Client): Promise<CachedCalendar[]> {\n const { google } = await import('googleapis');\n const calendar = google.calendar({ version: 'v3', auth: client });\n const response = await calendar.calendarList.list();\n const items = response.data.items || [];\n\n const calendars: CachedCalendar[] = items.map(cal => ({\n id: cal.id || '',\n summary: cal.summary || '',\n summaryOverride: cal.summaryOverride || undefined,\n accessRole: cal.accessRole || 'reader',\n primary: cal.primary || false,\n backgroundColor: cal.backgroundColor || undefined\n }));\n\n // Sort: primary first, then by name\n calendars.sort((a, b) => {\n if (a.primary && !b.primary) return -1;\n if (!a.primary && b.primary) return 1;\n return (a.summaryOverride || a.summary).localeCompare(b.summaryOverride || b.summary);\n });\n\n return calendars;\n }\n\n /**\n * Get user email address from OAuth2Client\n * First tries getTokenInfo, then falls back to primary calendar ID\n */\n private async getUserEmail(client: OAuth2Client): Promise<string> {\n try {\n // Try getTokenInfo first (only works if token has email/openid scope)\n const tokenInfo = await client.getTokenInfo(client.credentials.access_token || '');\n if (tokenInfo.email) {\n return tokenInfo.email;\n }\n } catch {\n // Token info failed, try calendar fallback\n }\n\n // Fallback: Get primary calendar ID (usually the user's email)\n try {\n const { google } = await import('googleapis');\n const calendar = google.calendar({ version: 'v3', auth: client });\n const response = await calendar.calendars.get({ calendarId: 'primary' });\n const primaryId = response.data.id;\n // Primary calendar ID is typically the user's email\n if (primaryId && primaryId.includes('@')) {\n return primaryId;\n }\n } catch {\n // Calendar fallback also failed\n }\n\n return 'unknown';\n }\n} \n", "import fs from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Escape HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const htmlEscapes: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, char => htmlEscapes[char]);\n}\n\n/**\n * Load a file from the web directory (handles build vs source paths)\n *\n * When running from bundled code (build/index.js), __dirname is \"build/\"\n * and web files are in \"build/web/\". When running from source, __dirname\n * is \"src/web/\" and files are in the same directory.\n */\nexport async function loadWebFile(fileName: string): Promise<string> {\n // Possible locations for web files:\n // 1. Same directory as this file (source: src/web/)\n // 2. \"web\" subdirectory (bundled: build/web/)\n const locations = [\n path.join(__dirname, fileName), // src/web/file.html (source)\n path.join(__dirname, 'web', fileName), // build/web/file.html (bundled)\n ];\n\n for (const filePath of locations) {\n try {\n await fs.access(filePath);\n return fs.readFile(filePath, 'utf-8');\n } catch {\n // Try next location\n }\n }\n\n throw new Error(`Web file not found: ${fileName}. Tried: ${locations.join(', ')}`);\n}\n\n/**\n * Load a template file\n */\nasync function loadTemplate(templateName: string): Promise<string> {\n return loadWebFile(templateName);\n}\n\nexport interface AuthSuccessParams {\n accountId: string;\n email?: string;\n tokenPath?: string;\n showCloseButton?: boolean;\n postMessageOrigin?: string;\n}\n\n/**\n * Render the authentication success page\n */\nexport async function renderAuthSuccess(params: AuthSuccessParams): Promise<string> {\n const template = await loadTemplate('auth-success.html');\n const safeAccountId = escapeHtml(params.accountId);\n\n // Build account info section - email is prominent, account ID is secondary\n let accountInfoSection: string;\n if (params.email) {\n accountInfoSection = `\n <p class=\"account-email\">${escapeHtml(params.email)}</p>\n <p class=\"account-label\">Saved as <code>${safeAccountId}</code></p>`;\n } else {\n accountInfoSection = `\n <p class=\"account-email\">Account connected</p>\n <p class=\"account-label\">Saved as <code>${safeAccountId}</code></p>`;\n }\n\n const closeButtonSection = params.showCloseButton\n ? `<button onclick=\"window.close()\">Close Window</button>`\n : '';\n\n const scriptSection = params.postMessageOrigin\n ? `<script>\n if (window.opener) {\n window.opener.postMessage({ type: 'auth-success', accountId: '${safeAccountId}' }, '${escapeHtml(params.postMessageOrigin)}');\n }\n setTimeout(() => window.close(), 3000);\n </script>`\n : '';\n\n return template\n .replace('{{accountInfo}}', accountInfoSection)\n .replace('{{closeButton}}', closeButtonSection)\n .replace('{{script}}', scriptSection);\n}\n\nexport interface AuthErrorParams {\n errorMessage: string;\n showCloseButton?: boolean;\n}\n\n/**\n * Render the authentication error page\n */\nexport async function renderAuthError(params: AuthErrorParams): Promise<string> {\n const template = await loadTemplate('auth-error.html');\n const safeError = escapeHtml(params.errorMessage);\n\n const closeButtonSection = params.showCloseButton\n ? `<button onclick=\"window.close()\">Close Window</button>`\n : '';\n\n return template\n .replace('{{errorMessage}}', safeError)\n .replace('{{closeButton}}', closeButtonSection);\n}\n\nexport interface AuthLandingParams {\n accountId: string;\n authUrl: string;\n}\n\n/**\n * Render the authentication landing page (click to authenticate)\n */\nexport async function renderAuthLanding(params: AuthLandingParams): Promise<string> {\n const template = await loadTemplate('auth-landing.html');\n const safeAccountId = escapeHtml(params.accountId);\n const safeAuthUrl = escapeHtml(params.authUrl);\n\n return template\n .replace(/\\{\\{accountId\\}\\}/g, safeAccountId)\n .replace('{{authUrl}}', safeAuthUrl);\n}\n", "import { initializeOAuth2Client } from './auth/client.js';\nimport { AuthServer } from './auth/server.js';\n\n// Check for command line arguments\nconst args = process.argv.slice(2);\nif (args.length > 0) {\n // Assume the first argument is the account mode\n process.env.GOOGLE_ACCOUNT_MODE = args[0];\n}\n\nasync function runAuthServer() {\n let authServer: AuthServer | null = null; // Keep reference for cleanup\n try {\n const oauth2Client = await initializeOAuth2Client();\n \n authServer = new AuthServer(oauth2Client);\n \n const success = await authServer.start(true);\n \n if (!success && !authServer.authCompletedSuccessfully) {\n process.stderr.write('Authentication failed. Could not start server or validate existing tokens.\\n');\n process.exit(1);\n } else if (authServer.authCompletedSuccessfully) {\n process.stderr.write('Authentication successful.\\n');\n process.exit(0);\n }\n \n // If we reach here, the server started and is waiting for the browser callback\n process.stderr.write('Authentication server started. Please complete the authentication in your browser...\\n');\n \n\n process.stderr.write(`Waiting for OAuth callback on port ${authServer.getRunningPort()}...\\n`);\n \n // Poll for completion or handle SIGINT\n let lastDebugLog = 0;\n const pollInterval = setInterval(async () => {\n try {\n if (authServer?.authCompletedSuccessfully) {\n process.stderr.write('Authentication completed successfully detected. Stopping server...\\n');\n clearInterval(pollInterval);\n await authServer.stop();\n process.stderr.write('Authentication successful. Server stopped.\\n');\n process.exit(0);\n } else {\n // Add debug logging every 10 seconds to show we're still waiting\n const now = Date.now();\n if (now - lastDebugLog > 10000) {\n process.stderr.write('Still waiting for authentication to complete...\\n');\n lastDebugLog = now;\n }\n }\n } catch (error: unknown) {\n process.stderr.write(`Error in polling interval: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n clearInterval(pollInterval);\n if (authServer) await authServer.stop();\n process.exit(1);\n }\n }, 5000); // Check every second\n\n // Handle process termination (SIGINT)\n process.on('SIGINT', async () => {\n clearInterval(pollInterval); // Stop polling\n if (authServer) {\n await authServer.stop();\n }\n process.exit(0);\n });\n \n } catch (error: unknown) {\n process.stderr.write(`Authentication error: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n if (authServer) await authServer.stop(); // Attempt cleanup\n process.exit(1);\n }\n}\n\n// Run the auth server if this file is executed directly\nif (import.meta.url.endsWith('auth-server.js')) {\n runAuthServer().catch((error: unknown) => {\n process.stderr.write(`Unhandled error: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n process.exit(1);\n });\n}"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Shared path utilities for token management\n * This module provides consistent token path resolution across all scripts\n */\n\nimport path from 'path';\nimport { homedir } from 'os';\n\n/**\n * Get the secure token storage path\n * Priority: GOOGLE_CALENDAR_MCP_TOKEN_PATH > XDG_CONFIG_HOME > ~/.config\n */\nexport function getSecureTokenPath() {\n // Priority 1: Custom token path from environment variable\n if (process.env.GOOGLE_CALENDAR_MCP_TOKEN_PATH) {\n return path.resolve(process.env.GOOGLE_CALENDAR_MCP_TOKEN_PATH);\n }\n // Priority 2: XDG Base Directory specification\n const configDir = process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config');\n return path.join(configDir, 'google-calendar-mcp', 'tokens.json');\n}\n\n/**\n * Get the legacy token path (for migration purposes)\n */\nexport function getLegacyTokenPath() {\n return path.join(process.cwd(), '.gcp-saved-tokens.json');\n}\n\n/**\n * Reserved account names that cannot be used\n */\nconst RESERVED_NAMES = ['.', '..', 'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4',\n 'lpt1', 'lpt2', 'lpt3'];\n\n/**\n * Validate account ID format\n * Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only\n * Cannot be reserved names\n */\nexport function validateAccountId(accountId) {\n if (!accountId || accountId.length === 0) {\n throw new Error('Invalid account ID. Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only.');\n }\n\n // Check reserved names first (before regex, since \".\" and \"..\" won't match regex)\n if (RESERVED_NAMES.includes(accountId)) {\n throw new Error(`Account ID \"${accountId}\" is reserved and cannot be used.`);\n }\n\n // Check format: lowercase alphanumeric, dashes, underscores, 1-64 chars\n if (!/^[a-z0-9_-]{1,64}$/.test(accountId)) {\n throw new Error('Invalid account ID. Must be 1-64 characters: lowercase letters, numbers, dashes, underscores only.');\n }\n\n return accountId;\n}\n\n/**\n * Get current account mode from environment\n * Uses same logic as utils.ts but compatible with both JS and TS\n */\nexport function getAccountMode() {\n // If set explicitly via environment variable use that instead\n const explicitMode = process.env.GOOGLE_ACCOUNT_MODE;\n if (explicitMode !== undefined && explicitMode !== null) {\n // Validate the account ID (no lowercasing - must be lowercase already)\n return validateAccountId(explicitMode);\n }\n\n // Auto-detect test environment\n if (process.env.NODE_ENV === 'test') {\n return 'test';\n }\n\n // Default to normal for regular app usage\n return 'normal';\n}", "import { OAuth2Client } from 'google-auth-library';\nimport * as fs from 'fs/promises';\nimport { getKeysFilePath, generateCredentialsErrorMessage, OAuthCredentials } from './utils.js';\n\n// Load credentials from JSON string environment variable\nasync function loadCredentialsFromJSON(): Promise<OAuthCredentials> {\n const jsonString = process.env.GOOGLE_OAUTH_CREDENTIALS_JSON;\n if (!jsonString) {\n throw new Error('GOOGLE_OAUTH_CREDENTIALS_JSON environment variable is not set');\n }\n\n try {\n const keys = JSON.parse(jsonString);\n\n if (keys.installed) {\n // Standard OAuth credentials file format\n const { client_id, client_secret, redirect_uris } = keys.installed;\n return { client_id, client_secret, redirect_uris };\n } else if (keys.client_id && keys.client_secret) {\n // Direct format\n return {\n client_id: keys.client_id,\n client_secret: keys.client_secret,\n redirect_uris: keys.redirect_uris || ['http://localhost:3000/oauth2callback']\n };\n } else {\n throw new Error('Invalid credentials JSON format. Expected either \"installed\" object or direct client_id/client_secret fields.');\n }\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`Failed to parse GOOGLE_OAUTH_CREDENTIALS_JSON: Invalid JSON syntax - ${error.message}`);\n }\n throw error;\n }\n}\n\n// Load credentials from file path\nasync function loadCredentialsFromPath(): Promise<OAuthCredentials> {\n const keysContent = await fs.readFile(getKeysFilePath(), \"utf-8\");\n const keys = JSON.parse(keysContent);\n\n if (keys.installed) {\n // Standard OAuth credentials file format\n const { client_id, client_secret, redirect_uris } = keys.installed;\n return { client_id, client_secret, redirect_uris };\n } else if (keys.client_id && keys.client_secret) {\n // Direct format\n return {\n client_id: keys.client_id,\n client_secret: keys.client_secret,\n redirect_uris: keys.redirect_uris || ['http://localhost:3000/oauth2callback']\n };\n } else {\n throw new Error('Invalid credentials file format. Expected either \"installed\" object or direct client_id/client_secret fields.');\n }\n}\n\nasync function loadCredentialsWithFallback(): Promise<OAuthCredentials> {\n // Priority 1: JSON string from environment variable (highest priority)\n if (process.env.GOOGLE_OAUTH_CREDENTIALS_JSON) {\n try {\n return await loadCredentialsFromJSON();\n } catch (error) {\n throw new Error(`Error loading from GOOGLE_OAUTH_CREDENTIALS_JSON: ${error instanceof Error ? error.message : error}`);\n }\n }\n\n // Priority 2: File path from environment variable or default location\n try {\n return await loadCredentialsFromPath();\n } catch (fileError) {\n // Generate helpful error message\n const errorMessage = generateCredentialsErrorMessage();\n throw new Error(`${errorMessage}\\n\\nOriginal error: ${fileError instanceof Error ? fileError.message : fileError}`);\n }\n}\n\nexport async function initializeOAuth2Client(): Promise<OAuth2Client> {\n // Always use real OAuth credentials - no mocking.\n // Unit tests should mock at the handler level, integration tests need real credentials.\n try {\n const credentials = await loadCredentialsWithFallback();\n \n // Use the first redirect URI as the default for the base client\n return new OAuth2Client({\n clientId: credentials.client_id,\n clientSecret: credentials.client_secret,\n redirectUri: credentials.redirect_uris[0],\n });\n } catch (error) {\n throw new Error(`Error loading OAuth keys: ${error instanceof Error ? error.message : error}`);\n }\n}\n\nexport async function loadCredentials(): Promise<{ client_id: string; client_secret: string }> {\n try {\n const credentials = await loadCredentialsWithFallback();\n \n if (!credentials.client_id || !credentials.client_secret) {\n throw new Error('Client ID or Client Secret missing in credentials.');\n }\n return {\n client_id: credentials.client_id,\n client_secret: credentials.client_secret\n };\n } catch (error) {\n throw new Error(`Error loading credentials: ${error instanceof Error ? error.message : error}`);\n }\n}", "import * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport { getSecureTokenPath as getSharedSecureTokenPath, getLegacyTokenPath as getSharedLegacyTokenPath, getAccountMode as getSharedAccountMode } from './paths.js';\n\n// Helper to get the project root directory reliably\nfunction getProjectRoot(): string {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n // In build output (e.g., build/bundle.js), __dirname is .../build\n // Go up ONE level to get the project root\n const projectRoot = path.join(__dirname, \"..\"); // Corrected: Go up ONE level\n return path.resolve(projectRoot); // Ensure absolute path\n}\n\n// Get the current account mode - delegates to shared implementation\n// Now supports arbitrary account IDs instead of just 'normal' and 'test'\nexport function getAccountMode(): string {\n return getSharedAccountMode();\n}\n\n// Helper to detect if we're running in a test environment\nfunction isRunningInTestEnvironment(): boolean {\n // Simple and reliable: just check NODE_ENV\n return process.env.NODE_ENV === 'test';\n}\n\n// Returns the absolute path for the saved token file - delegates to shared implementation\nexport function getSecureTokenPath(): string {\n return getSharedSecureTokenPath();\n}\n\n// Returns the legacy token path for backward compatibility - delegates to shared implementation \nexport function getLegacyTokenPath(): string {\n return getSharedLegacyTokenPath();\n}\n\n// Returns the absolute path for the GCP OAuth keys file with priority:\n// 1. Environment variable GOOGLE_OAUTH_CREDENTIALS (highest priority)\n// 2. Default file path (lowest priority)\nexport function getKeysFilePath(): string {\n // Priority 1: Environment variable\n const envCredentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS;\n if (envCredentialsPath) {\n return path.resolve(envCredentialsPath);\n }\n\n // Priority 2: Default file path\n const projectRoot = getProjectRoot();\n const keysPath = path.join(projectRoot, \"gcp-oauth.keys.json\");\n return keysPath; // Already absolute from getProjectRoot\n}\n\n// Helper to determine if we're currently in test mode\nexport function isTestMode(): boolean {\n return getAccountMode() === 'test';\n}\n\n// Interface for OAuth credentials\nexport interface OAuthCredentials {\n client_id: string;\n client_secret: string;\n redirect_uris: string[];\n}\n\n// Interface for credentials file with project_id\nexport interface OAuthCredentialsWithProject {\n installed?: {\n project_id?: string;\n client_id?: string;\n client_secret?: string;\n redirect_uris?: string[];\n };\n project_id?: string;\n client_id?: string;\n client_secret?: string;\n redirect_uris?: string[];\n}\n\n// Get project ID from OAuth credentials\n// Returns undefined if credentials don't exist, are invalid, or missing project_id\nexport function getCredentialsProjectId(): string | undefined {\n try {\n let credentials: OAuthCredentialsWithProject;\n\n // Priority 1: JSON string from environment variable\n if (process.env.GOOGLE_OAUTH_CREDENTIALS_JSON) {\n try {\n credentials = JSON.parse(process.env.GOOGLE_OAUTH_CREDENTIALS_JSON);\n } catch (error) {\n // Invalid JSON, return undefined\n return undefined;\n }\n } else {\n // Priority 2: File path\n const credentialsPath = getKeysFilePath();\n if (!fs.existsSync(credentialsPath)) {\n return undefined;\n }\n const credentialsContent = fs.readFileSync(credentialsPath, 'utf-8');\n credentials = JSON.parse(credentialsContent);\n }\n\n // Extract project_id from installed format or direct format\n if (credentials.installed?.project_id) {\n return credentials.installed.project_id;\n } else if (credentials.project_id) {\n return credentials.project_id;\n }\n\n return undefined;\n } catch (error) {\n // If we can't read project ID, return undefined (backward compatibility)\n return undefined;\n }\n}\n\n// Generate helpful error message for missing credentials\nexport function generateCredentialsErrorMessage(): string {\n return `\nOAuth credentials not found. Please provide credentials using one of these methods (in priority order):\n\n1. JSON string (recommended for containers/CI):\n Set GOOGLE_OAUTH_CREDENTIALS_JSON with the full JSON content:\n export GOOGLE_OAUTH_CREDENTIALS_JSON='{\"installed\":{\"client_id\":\"...\",\"client_secret\":\"...\",\"redirect_uris\":[\"http://localhost:3000/oauth2callback\"]}}'\n\n2. File path via environment variable:\n Set GOOGLE_OAUTH_CREDENTIALS to the path of your credentials file:\n export GOOGLE_OAUTH_CREDENTIALS=\"/path/to/gcp-oauth.keys.json\"\n\n3. Default file path:\n Place your gcp-oauth.keys.json file in the package root directory.\n\nToken storage:\n- Tokens are saved to: ${getSecureTokenPath()}\n- To use a custom token location, set GOOGLE_CALENDAR_MCP_TOKEN_PATH environment variable\n\nTo get OAuth credentials:\n1. Go to the Google Cloud Console (https://console.cloud.google.com/)\n2. Create or select a project\n3. Enable the Google Calendar API\n4. Create OAuth 2.0 credentials\n5. Download the credentials file as gcp-oauth.keys.json\n`.trim();\n}\n", "import { OAuth2Client } from 'google-auth-library';\nimport { TokenManager } from './tokenManager.js';\nimport http from 'http';\nimport { URL } from 'url';\nimport open from 'open';\nimport { loadCredentials } from './client.js';\nimport { getAccountMode } from './utils.js';\nimport { renderAuthSuccess, renderAuthError, renderAuthLanding, loadWebFile } from '../web/templates.js';\n\nexport interface StartForMcpToolResult {\n success: boolean;\n authUrl?: string;\n callbackUrl?: string;\n error?: string;\n}\n\nexport class AuthServer {\n private baseOAuth2Client: OAuth2Client; // Used by TokenManager for validation/refresh\n private flowOAuth2Client: OAuth2Client | null = null; // Used specifically for the auth code flow\n private server: http.Server | null = null;\n private tokenManager: TokenManager;\n private portRange: { start: number; end: number };\n private activeConnections: Set<import('net').Socket> = new Set(); // Track active socket connections\n public authCompletedSuccessfully = false; // Flag for standalone script\n private mcpToolTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for MCP tool auth flow\n private autoShutdownOnSuccess = false; // Whether to auto-shutdown after successful auth\n\n constructor(oauth2Client: OAuth2Client) {\n this.baseOAuth2Client = oauth2Client;\n this.tokenManager = new TokenManager(oauth2Client);\n this.portRange = { start: 3500, end: 3505 };\n }\n\n /**\n * Creates the flow-specific OAuth2Client with the correct redirect URI.\n */\n private async createFlowOAuth2Client(port: number): Promise<OAuth2Client> {\n const { client_id, client_secret } = await loadCredentials();\n return new OAuth2Client(\n client_id,\n client_secret,\n `http://localhost:${port}/oauth2callback`\n );\n }\n\n /**\n * Generates an OAuth authorization URL with standard settings.\n */\n private generateOAuthUrl(client: OAuth2Client): string {\n return client.generateAuthUrl({\n access_type: 'offline',\n scope: ['https://www.googleapis.com/auth/calendar'],\n prompt: 'consent'\n });\n }\n\n private createServer(): http.Server {\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n \n if (url.pathname === '/styles.css') {\n // Serve shared CSS\n const css = await loadWebFile('styles.css');\n res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8' });\n res.end(css);\n\n } else if (url.pathname === '/') {\n // Root route - show auth link\n const clientForUrl = this.flowOAuth2Client || this.baseOAuth2Client;\n const authUrl = this.generateOAuthUrl(clientForUrl);\n const accountMode = getAccountMode();\n\n const landingHtml = await renderAuthLanding({\n accountId: accountMode,\n authUrl: authUrl\n });\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(landingHtml);\n\n } else if (url.pathname === '/oauth2callback') {\n // OAuth callback route\n const code = url.searchParams.get('code');\n if (!code) {\n const errorHtml = await renderAuthError({\n errorMessage: 'Authorization code missing'\n });\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n return;\n }\n\n if (!this.flowOAuth2Client) {\n const errorHtml = await renderAuthError({\n errorMessage: 'Authentication flow not properly initiated.'\n });\n res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n return;\n }\n \n try {\n const { tokens } = await this.flowOAuth2Client.getToken(code);\n await this.tokenManager.saveTokens(tokens);\n this.authCompletedSuccessfully = true;\n\n const tokenPath = this.tokenManager.getTokenPath();\n const accountMode = this.tokenManager.getAccountMode();\n\n // Auto-shutdown after successful auth if triggered by MCP tool\n if (this.autoShutdownOnSuccess) {\n // Clear the timeout since auth succeeded\n if (this.mcpToolTimeout) {\n clearTimeout(this.mcpToolTimeout);\n this.mcpToolTimeout = null;\n }\n // Give the browser time to render success page, then shutdown\n setTimeout(() => {\n this.stop().catch(() => {});\n }, 2000);\n }\n \n const successHtml = await renderAuthSuccess({\n accountId: accountMode,\n tokenPath: tokenPath\n });\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(successHtml);\n } catch (error: unknown) {\n this.authCompletedSuccessfully = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n process.stderr.write(`\u2717 Token save failed: ${message}\\n`);\n\n const errorHtml = await renderAuthError({\n errorMessage: message\n });\n res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(errorHtml);\n }\n } else {\n // 404 for other routes\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n }\n });\n\n // Track connections at server level\n server.on('connection', (socket) => {\n this.activeConnections.add(socket);\n socket.on('close', () => {\n this.activeConnections.delete(socket);\n });\n });\n \n return server;\n }\n\n async start(openBrowser = true): Promise<boolean> {\n // Add timeout wrapper to prevent hanging\n return Promise.race([\n this.startWithTimeout(openBrowser),\n new Promise<boolean>((_, reject) => {\n setTimeout(() => reject(new Error('Auth server start timed out after 10 seconds')), 10000);\n })\n ]).catch(() => false); // Return false on timeout instead of throwing\n }\n\n private async startWithTimeout(openBrowser = true): Promise<boolean> {\n if (await this.tokenManager.validateTokens()) {\n this.authCompletedSuccessfully = true;\n return true;\n }\n \n // Try to start the server and get the port\n const port = await this.startServerOnAvailablePort();\n if (port === null) {\n process.stderr.write(`Could not start auth server on available port. Please check port availability (${this.portRange.start}-${this.portRange.end}) and try again.\\n`);\n\n this.authCompletedSuccessfully = false;\n return false;\n }\n\n // Successfully started server on `port`. Now create the flow-specific OAuth client.\n try {\n this.flowOAuth2Client = await this.createFlowOAuth2Client(port);\n } catch (error) {\n // Could not load credentials, cannot proceed with auth flow\n this.authCompletedSuccessfully = false;\n await this.stop(); // Stop the server we just started\n return false;\n }\n\n // Generate Auth URL using the newly created flow client\n const authorizeUrl = this.generateOAuthUrl(this.flowOAuth2Client);\n \n // Always show the URL in console for easy access\n process.stderr.write(`\\n\uD83D\uDD17 Authentication URL: ${authorizeUrl}\\n\\n`);\n process.stderr.write(`Or visit: http://localhost:${port}\\n\\n`);\n \n if (openBrowser) {\n try {\n await open(authorizeUrl);\n process.stderr.write(`Browser opened automatically. If it didn't open, use the URL above.\\n`);\n } catch (error) {\n process.stderr.write(`Could not open browser automatically. Please use the URL above.\\n`);\n }\n } else {\n process.stderr.write(`Please visit the URL above to complete authentication.\\n`);\n }\n\n return true; // Auth flow initiated\n }\n\n private async startServerOnAvailablePort(): Promise<number | null> {\n for (let port = this.portRange.start; port <= this.portRange.end; port++) {\n try {\n await new Promise<void>((resolve, reject) => {\n const testServer = this.createServer();\n testServer.listen(port, () => {\n this.server = testServer; // Assign to class property *only* if successful\n resolve();\n });\n testServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n // Port is in use, close the test server and reject\n testServer.close(() => reject(err)); \n } else {\n // Other error, reject\n reject(err);\n }\n });\n });\n return port; // Port successfully bound\n } catch (error: unknown) {\n // Check if it's EADDRINUSE, otherwise rethrow or handle\n if (!(error instanceof Error && 'code' in error && error.code === 'EADDRINUSE')) {\n // An unexpected error occurred during server start\n return null;\n }\n // EADDRINUSE occurred, loop continues\n }\n }\n return null; // No port found\n }\n\n public getRunningPort(): number | null {\n if (this.server) {\n const address = this.server.address();\n if (typeof address === 'object' && address !== null) {\n return address.port;\n }\n }\n return null;\n }\n\n async stop(): Promise<void> {\n // Clear any pending MCP tool timeout\n if (this.mcpToolTimeout) {\n clearTimeout(this.mcpToolTimeout);\n this.mcpToolTimeout = null;\n }\n this.autoShutdownOnSuccess = false;\n\n return new Promise((resolve, reject) => {\n if (this.server) {\n // Force close all active connections\n for (const connection of this.activeConnections) {\n connection.destroy();\n }\n this.activeConnections.clear();\n\n // Add a timeout to force close if server doesn't close gracefully\n const timeout = setTimeout(() => {\n process.stderr.write('Server close timeout, forcing exit...\\n');\n this.server = null;\n resolve();\n }, 2000); // 2 second timeout\n\n this.server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n reject(err);\n } else {\n this.server = null;\n resolve();\n }\n });\n } else {\n resolve();\n }\n });\n }\n\n /**\n * Start the auth server for use by an MCP tool.\n *\n * Unlike the regular start() method:\n * - Does not open the browser automatically\n * - Returns the auth URL for the MCP tool to return to the user\n * - Auto-shutdowns after successful auth or timeout (5 minutes)\n * - Does not validate existing tokens (allows adding new accounts)\n *\n * @param accountId - The account ID to authenticate\n * @returns Result with auth URL on success, or error on failure\n */\n async startForMcpTool(accountId: string): Promise<StartForMcpToolResult> {\n // If server is already running, stop it first\n if (this.server) {\n await this.stop();\n }\n\n // Set the account mode\n this.tokenManager.setAccountMode(accountId);\n\n // Try to start the server and get the port\n const port = await this.startServerOnAvailablePort();\n if (port === null) {\n return {\n success: false,\n error: `Could not start auth server. Ports ${this.portRange.start}-${this.portRange.end} may be in use.`\n };\n }\n\n // Create the flow-specific OAuth client\n try {\n this.flowOAuth2Client = await this.createFlowOAuth2Client(port);\n } catch (error) {\n await this.stop();\n return {\n success: false,\n error: `Failed to load OAuth credentials: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n\n // Generate Auth URL\n const authUrl = this.generateOAuthUrl(this.flowOAuth2Client);\n\n // Enable auto-shutdown on success\n this.autoShutdownOnSuccess = true;\n this.authCompletedSuccessfully = false;\n\n // Set timeout to auto-shutdown if auth not completed (5 minutes)\n this.mcpToolTimeout = setTimeout(async () => {\n if (!this.authCompletedSuccessfully) {\n process.stderr.write(`Auth timeout for account \"${accountId}\" - shutting down auth server\\n`);\n await this.stop();\n }\n }, 5 * 60 * 1000);\n\n return {\n success: true,\n authUrl,\n callbackUrl: `http://localhost:${port}/oauth2callback`\n };\n }\n} ", "import { OAuth2Client, Credentials } from 'google-auth-library';\nimport fs from 'fs/promises';\nimport { getSecureTokenPath, getAccountMode, getLegacyTokenPath } from './utils.js';\nimport { GaxiosError } from 'gaxios';\nimport { mkdir } from 'fs/promises';\nimport { dirname } from 'path';\n\n// Cached calendar info\ninterface CachedCalendar {\n id: string;\n summary: string;\n summaryOverride?: string;\n accessRole: string;\n primary: boolean;\n backgroundColor?: string;\n}\n\n// Extended credentials with cached email and calendars\ninterface CachedCredentials extends Credentials {\n cached_email?: string;\n cached_calendars?: CachedCalendar[];\n calendars_cached_at?: number;\n}\n\n// Interface for multi-account token storage\n// Now supports arbitrary account IDs\ninterface MultiAccountTokens {\n [accountId: string]: CachedCredentials;\n}\n\nexport class TokenManager {\n private oauth2Client: OAuth2Client;\n private tokenPath: string;\n private accountMode: string;\n private accounts: Map<string, OAuth2Client> = new Map();\n private credentials: {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n };\n private writeQueue: Promise<void> = Promise.resolve();\n\n constructor(oauth2Client: OAuth2Client) {\n this.oauth2Client = oauth2Client;\n this.tokenPath = getSecureTokenPath();\n this.accountMode = getAccountMode();\n\n // Store credentials to avoid accessing private properties later\n this.credentials = {\n clientId: (oauth2Client as any)._clientId,\n clientSecret: (oauth2Client as any)._clientSecret,\n redirectUri: (oauth2Client as any)._redirectUri\n };\n\n this.setupTokenRefresh();\n }\n\n // Method to expose the token path\n public getTokenPath(): string {\n return this.tokenPath;\n }\n\n // Method to get current account mode\n public getAccountMode(): string {\n return this.accountMode;\n }\n\n // Method to switch account mode (supports arbitrary account IDs)\n public setAccountMode(mode: string): void {\n this.accountMode = mode;\n }\n\n private async ensureTokenDirectoryExists(): Promise<void> {\n try {\n await mkdir(dirname(this.tokenPath), { recursive: true });\n } catch (error) {\n process.stderr.write(`Failed to create token directory: ${error}\\n`);\n }\n }\n\n private isFileNotFoundError(error: unknown): boolean {\n return error instanceof Error && 'code' in error && (error as any).code === 'ENOENT';\n }\n\n private async writeTokenFile(tokens: MultiAccountTokens): Promise<void> {\n await this.ensureTokenDirectoryExists();\n await fs.writeFile(this.tokenPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });\n }\n\n private async loadMultiAccountTokens(): Promise<MultiAccountTokens> {\n try {\n const fileContent = await fs.readFile(this.tokenPath, \"utf-8\");\n const parsed = JSON.parse(fileContent);\n\n // Check if this is the old single-account format\n if (parsed.access_token || parsed.refresh_token) {\n // Convert old format to new multi-account format\n const multiAccountTokens: MultiAccountTokens = {\n normal: parsed\n };\n await this.saveMultiAccountTokens(multiAccountTokens);\n return multiAccountTokens;\n }\n\n // Already in multi-account format\n return parsed as MultiAccountTokens;\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n return {};\n }\n throw error;\n }\n }\n\n /**\n * Raw token file read without migration logic.\n * Used for atomic read-modify-write operations where we need to re-read current state.\n */\n private async loadMultiAccountTokensRaw(): Promise<MultiAccountTokens> {\n try {\n const fileContent = await fs.readFile(this.tokenPath, \"utf-8\");\n return JSON.parse(fileContent) as MultiAccountTokens;\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n return {};\n }\n throw error;\n }\n }\n\n private async saveMultiAccountTokens(multiAccountTokens: MultiAccountTokens): Promise<void> {\n return this.enqueueTokenWrite(async () => {\n await this.writeTokenFile(multiAccountTokens);\n });\n }\n\n private enqueueTokenWrite(operation: () => Promise<void>): Promise<void> {\n const pendingWrite = this.writeQueue\n .catch(() => undefined)\n .then(operation);\n\n this.writeQueue = pendingWrite\n .catch(error => {\n process.stderr.write(`Error writing token file: ${error instanceof Error ? error.message : error}\\n`);\n throw error;\n })\n .catch(() => undefined);\n\n return pendingWrite;\n }\n\n private setupTokenRefresh(): void {\n this.setupTokenRefreshForAccount(this.oauth2Client, this.accountMode);\n }\n\n /**\n * Set up token refresh handler for a specific account\n * Uses enqueueTokenWrite to prevent race conditions when multiple accounts refresh simultaneously\n */\n private setupTokenRefreshForAccount(client: OAuth2Client, accountId: string): void {\n client.on(\"tokens\", async (newTokens) => {\n try {\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const currentTokens = multiAccountTokens[accountId] || {};\n\n const updatedTokens = {\n ...currentTokens,\n ...newTokens,\n refresh_token: newTokens.refresh_token || currentTokens.refresh_token,\n };\n\n multiAccountTokens[accountId] = updatedTokens;\n await this.writeTokenFile(multiAccountTokens);\n });\n\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Tokens updated and saved for ${accountId} account\\n`);\n }\n } catch (error: unknown) {\n process.stderr.write(\"Error saving updated tokens: \");\n if (error instanceof Error) {\n process.stderr.write(error.message);\n } else if (typeof error === 'string') {\n process.stderr.write(error);\n }\n process.stderr.write(\"\\n\");\n }\n });\n }\n\n private async migrateLegacyTokens(): Promise<boolean> {\n const legacyPath = getLegacyTokenPath();\n try {\n // Check if legacy tokens exist\n if (!(await fs.access(legacyPath).then(() => true).catch(() => false))) {\n return false; // No legacy tokens to migrate\n }\n\n // Read legacy tokens\n const legacyTokens = JSON.parse(await fs.readFile(legacyPath, \"utf-8\"));\n \n if (!legacyTokens || typeof legacyTokens !== \"object\") {\n process.stderr.write(\"Invalid legacy token format, skipping migration\\n\");\n return false;\n }\n\n // Copy to new location (ensures directory exists)\n await this.writeTokenFile(legacyTokens);\n \n process.stderr.write(`Migrated tokens from legacy location: ${legacyPath} to: ${this.tokenPath}\\n`);\n \n // Optionally remove legacy file after successful migration\n try {\n await fs.unlink(legacyPath);\n process.stderr.write(\"Removed legacy token file\\n\");\n } catch (unlinkErr) {\n process.stderr.write(`Warning: Could not remove legacy token file: ${unlinkErr}\\n`);\n }\n \n return true;\n } catch (error) {\n process.stderr.write(`Error migrating legacy tokens: ${error}\\n`);\n return false;\n }\n }\n\n async loadSavedTokens(): Promise<boolean> {\n try {\n await this.ensureTokenDirectoryExists();\n \n // Check if current token file exists\n const tokenExists = await fs.access(this.tokenPath).then(() => true).catch(() => false);\n \n // If no current tokens, try to migrate from legacy location\n if (!tokenExists) {\n const migrated = await this.migrateLegacyTokens();\n if (!migrated) {\n process.stderr.write(`No token file found at: ${this.tokenPath}\\n`);\n return false;\n }\n }\n\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const tokens = multiAccountTokens[this.accountMode];\n\n if (!tokens || typeof tokens !== \"object\") {\n process.stderr.write(`No tokens found for ${this.accountMode} account in file: ${this.tokenPath}\\n`);\n return false;\n }\n\n this.oauth2Client.setCredentials(tokens);\n process.stderr.write(`Loaded tokens for ${this.accountMode} account\\n`);\n return true;\n } catch (error: unknown) {\n process.stderr.write(`Error loading tokens for ${this.accountMode} account: `);\n if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') { \n try { \n await fs.unlink(this.tokenPath); \n process.stderr.write(\"Removed potentially corrupted token file\\n\"); \n } catch (unlinkErr) { /* ignore */ } \n }\n return false;\n }\n }\n\n async refreshTokensIfNeeded(): Promise<boolean> {\n const expiryDate = this.oauth2Client.credentials.expiry_date;\n const isExpired = expiryDate\n ? Date.now() >= expiryDate - 5 * 60 * 1000 // 5 minute buffer\n : !this.oauth2Client.credentials.access_token; // No token means we need one\n\n if (isExpired && this.oauth2Client.credentials.refresh_token) {\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Auth token expired or nearing expiry for ${this.accountMode} account, refreshing...\\n`);\n }\n try {\n const response = await this.oauth2Client.refreshAccessToken();\n const newTokens = response.credentials;\n\n if (!newTokens.access_token) {\n throw new Error(\"Received invalid tokens during refresh\");\n }\n // The 'tokens' event listener should handle saving\n this.oauth2Client.setCredentials(newTokens);\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Token refreshed successfully for ${this.accountMode} account\\n`);\n }\n return true;\n } catch (refreshError) {\n if (refreshError instanceof GaxiosError && refreshError.response?.data?.error === 'invalid_grant') {\n process.stderr.write(`Error refreshing auth token for ${this.accountMode} account: Invalid grant. Token likely expired or revoked. Please re-authenticate.\\n`);\n return false; // Indicate failure due to invalid grant\n } else {\n // Handle other refresh errors\n process.stderr.write(`Error refreshing auth token for ${this.accountMode} account: `);\n if (refreshError instanceof Error) {\n process.stderr.write(refreshError.message);\n } else if (typeof refreshError === 'string') {\n process.stderr.write(refreshError);\n }\n process.stderr.write(\"\\n\");\n return false;\n }\n }\n } else if (!this.oauth2Client.credentials.access_token && !this.oauth2Client.credentials.refresh_token) {\n process.stderr.write(`No access or refresh token available for ${this.accountMode} account. Please re-authenticate.\\n`);\n return false;\n } else {\n // Token is valid or no refresh token available\n return true;\n }\n }\n\n async validateTokens(accountMode?: string): Promise<boolean> {\n // For unit tests that don't need real authentication, they should mock at the handler level\n // Integration tests always need real tokens\n\n const modeToValidate = accountMode || this.accountMode;\n const currentMode = this.accountMode;\n \n try {\n // Temporarily switch to the mode we want to validate if different\n if (modeToValidate !== currentMode) {\n this.accountMode = modeToValidate;\n }\n \n if (!this.oauth2Client.credentials || !this.oauth2Client.credentials.access_token) {\n // Try loading first if no credentials set\n if (!(await this.loadSavedTokens())) {\n return false; // No saved tokens to load\n }\n // Check again after loading\n if (!this.oauth2Client.credentials || !this.oauth2Client.credentials.access_token) {\n return false; // Still no token after loading\n }\n }\n \n const result = await this.refreshTokensIfNeeded();\n return result;\n } finally {\n // Always restore the original account mode\n if (modeToValidate !== currentMode) {\n this.accountMode = currentMode;\n }\n }\n }\n\n async saveTokens(tokens: Credentials, email?: string): Promise<void> {\n try {\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const cachedTokens: CachedCredentials = { ...tokens };\n\n // Cache the email if provided\n if (email) {\n cachedTokens.cached_email = email;\n }\n\n multiAccountTokens[this.accountMode] = cachedTokens;\n await this.writeTokenFile(multiAccountTokens);\n });\n this.oauth2Client.setCredentials(tokens);\n process.stderr.write(`Tokens saved successfully for ${this.accountMode} account to: ${this.tokenPath}\\n`);\n } catch (error: unknown) {\n process.stderr.write(`Error saving tokens for ${this.accountMode} account: ${error}\\n`);\n throw error;\n }\n }\n\n async clearTokens(): Promise<void> {\n try {\n this.oauth2Client.setCredentials({}); // Clear in memory\n\n // Wrap entire read-modify-write in the queue to prevent race conditions\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n delete multiAccountTokens[this.accountMode];\n\n // If no accounts left, delete the entire file\n if (Object.keys(multiAccountTokens).length === 0) {\n await fs.unlink(this.tokenPath);\n process.stderr.write(`All tokens cleared, file deleted\\n`);\n } else {\n await this.writeTokenFile(multiAccountTokens);\n process.stderr.write(`Tokens cleared for ${this.accountMode} account\\n`);\n }\n });\n } catch (error: unknown) {\n if (this.isFileNotFoundError(error)) {\n // File already gone, which is fine\n process.stderr.write(\"Token file already deleted\\n\");\n } else {\n process.stderr.write(`Error clearing tokens for ${this.accountMode} account: ${error}\\n`);\n // Don't re-throw, clearing is best-effort\n }\n }\n }\n\n // Method to list available accounts\n async listAvailableAccounts(): Promise<string[]> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n return Object.keys(multiAccountTokens);\n } catch (error) {\n return [];\n }\n }\n\n /**\n * Remove a specific account's tokens from storage.\n * @param accountId - The account ID to remove\n * @throws Error if account doesn't exist or removal fails\n */\n async removeAccount(accountId: string): Promise<void> {\n const normalizedId = accountId.toLowerCase();\n\n await this.enqueueTokenWrite(async () => {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n\n if (!multiAccountTokens[normalizedId]) {\n throw new Error(`Account \"${normalizedId}\" not found`);\n }\n\n delete multiAccountTokens[normalizedId];\n\n // If no accounts left, delete the entire file\n if (Object.keys(multiAccountTokens).length === 0) {\n await fs.unlink(this.tokenPath);\n process.stderr.write(`All tokens cleared, file deleted\\n`);\n } else {\n await this.writeTokenFile(multiAccountTokens);\n process.stderr.write(`Account \"${normalizedId}\" removed successfully\\n`);\n }\n\n // Remove from in-memory accounts map if present\n this.accounts.delete(normalizedId);\n });\n }\n\n // Method to switch to a different account (supports arbitrary account IDs)\n async switchAccount(newMode: string): Promise<boolean> {\n this.accountMode = newMode;\n return this.loadSavedTokens();\n }\n\n /**\n * Load all authenticated accounts from token file\n * Returns a Map of account ID to OAuth2Client\n *\n * Reuses existing OAuth2Client instances to prevent memory leaks\n * Sets up token refresh handlers for new accounts\n */\n async loadAllAccounts(): Promise<Map<string, OAuth2Client>> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n\n // Remove accounts that no longer exist in token file\n for (const accountId of this.accounts.keys()) {\n if (!multiAccountTokens[accountId]) {\n const client = this.accounts.get(accountId);\n if (client) {\n // Clean up event listeners before removing\n client.removeAllListeners('tokens');\n }\n this.accounts.delete(accountId);\n }\n }\n\n // Add or update accounts\n for (const [accountId, tokens] of Object.entries(multiAccountTokens)) {\n // Validate account ID\n try {\n const { validateAccountId } = await import('./paths.js') as any;\n validateAccountId(accountId);\n\n // Skip invalid token entries\n if (!tokens || typeof tokens !== 'object' || !tokens.access_token) {\n continue;\n }\n\n // Check if we already have a client for this account (reuse it to prevent memory leak)\n let client = this.accounts.get(accountId);\n\n if (!client) {\n // Create a new OAuth2Client for this account using stored credentials\n client = new OAuth2Client(\n this.credentials.clientId,\n this.credentials.clientSecret,\n this.credentials.redirectUri\n );\n\n // Set up token refresh handler for this new client\n this.setupTokenRefreshForAccount(client, accountId);\n\n this.accounts.set(accountId, client);\n }\n\n // Update credentials (for both new and existing clients)\n client.setCredentials(tokens);\n\n } catch (error) {\n // Skip invalid account IDs\n if (process.env.NODE_ENV !== 'test') {\n process.stderr.write(`Skipping invalid account \"${accountId}\": ${error}\\n`);\n }\n continue;\n }\n }\n\n return this.accounts;\n } catch (error: any) {\n // Check for file not found error (works with both Error objects and plain objects)\n if (error && error.code === 'ENOENT') {\n // No token file exists, return empty map\n return new Map();\n }\n throw error;\n }\n }\n\n /**\n * Get OAuth2Client for a specific account\n * @param accountId The account ID to retrieve\n * @throws Error if account not found or invalid\n */\n getClient(accountId: string): OAuth2Client {\n // Validate account ID first\n const { validateAccountId } = require('./paths.js');\n validateAccountId(accountId);\n\n const client = this.accounts.get(accountId);\n if (!client) {\n throw new Error(`Account \"${accountId}\" not found. Please authenticate this account first.`);\n }\n\n return client;\n }\n\n /**\n * List all authenticated accounts with their email addresses, status, and calendars\n * Uses cached data when available to avoid repeated API calls\n */\n async listAccounts(): Promise<Array<{\n id: string;\n email: string;\n status: string;\n calendars: CachedCalendar[];\n }>> {\n try {\n const multiAccountTokens = await this.loadMultiAccountTokens();\n const accountList: Array<{\n id: string;\n email: string;\n status: string;\n calendars: CachedCalendar[];\n }> = [];\n let tokensUpdated = false;\n\n // Cache TTL: 5 minutes for calendars\n const CALENDAR_CACHE_TTL = 5 * 60 * 1000;\n\n for (const [accountId, tokens] of Object.entries(multiAccountTokens)) {\n // Skip invalid entries\n if (!tokens || typeof tokens !== 'object') {\n continue;\n }\n\n let client: OAuth2Client | null = null;\n\n // Create client and refresh if needed\n if (tokens.access_token || tokens.refresh_token) {\n try {\n client = new OAuth2Client(\n this.credentials.clientId,\n this.credentials.clientSecret,\n this.credentials.redirectUri\n );\n client.setCredentials(tokens);\n\n // Try to refresh token if access token is expired or missing\n if (tokens.refresh_token && (!tokens.access_token || (tokens.expiry_date && tokens.expiry_date < Date.now()))) {\n try {\n const response = await client.refreshAccessToken();\n client.setCredentials(response.credentials);\n Object.assign(tokens, response.credentials);\n tokensUpdated = true;\n } catch {\n // Refresh failed\n }\n }\n } catch {\n client = null;\n }\n }\n\n // Get email address - use cached value if available\n let email = tokens.cached_email || 'unknown';\n if (!tokens.cached_email && client) {\n try {\n email = await this.getUserEmail(client);\n if (email !== 'unknown') {\n tokens.cached_email = email;\n tokensUpdated = true;\n }\n } catch {\n // Email retrieval failed\n }\n }\n\n // Get calendars - use cached if fresh, otherwise fetch\n let calendars: CachedCalendar[] = tokens.cached_calendars || [];\n const cacheExpired = !tokens.calendars_cached_at ||\n (Date.now() - tokens.calendars_cached_at) > CALENDAR_CACHE_TTL;\n\n if (cacheExpired && client) {\n try {\n calendars = await this.fetchCalendarsForClient(client);\n tokens.cached_calendars = calendars;\n tokens.calendars_cached_at = Date.now();\n tokensUpdated = true;\n } catch {\n // Calendar fetch failed, use cached or empty\n }\n }\n\n // Determine status\n let status = 'active';\n if (!tokens.refresh_token) {\n if (!tokens.access_token || (tokens.expiry_date && tokens.expiry_date < Date.now())) {\n status = 'expired';\n }\n }\n\n accountList.push({ id: accountId, email, status, calendars });\n }\n\n // Save updated tokens with cached data using atomic read-modify-write\n // This prevents race conditions when multiple listAccounts() calls run concurrently\n if (tokensUpdated) {\n await this.enqueueTokenWrite(async () => {\n // Re-read current token state to preserve any concurrent auth changes\n const latestTokens = await this.loadMultiAccountTokensRaw();\n\n // Merge our cached metadata updates into the latest token state\n for (const accountId of Object.keys(multiAccountTokens)) {\n const localUpdates = multiAccountTokens[accountId];\n const latestAccount = latestTokens[accountId];\n\n if (latestAccount && localUpdates) {\n // Only update cached metadata, not auth tokens\n if (localUpdates.cached_email) {\n latestAccount.cached_email = localUpdates.cached_email;\n }\n if (localUpdates.cached_calendars) {\n latestAccount.cached_calendars = localUpdates.cached_calendars;\n latestAccount.calendars_cached_at = localUpdates.calendars_cached_at;\n }\n }\n }\n\n await this.writeTokenFile(latestTokens);\n });\n }\n\n return accountList;\n } catch (error) {\n return [];\n }\n }\n\n /**\n * Fetch calendars for a specific OAuth2Client\n */\n private async fetchCalendarsForClient(client: OAuth2Client): Promise<CachedCalendar[]> {\n const { google } = await import('googleapis');\n const calendar = google.calendar({ version: 'v3', auth: client });\n const response = await calendar.calendarList.list();\n const items = response.data.items || [];\n\n const calendars: CachedCalendar[] = items.map(cal => ({\n id: cal.id || '',\n summary: cal.summary || '',\n summaryOverride: cal.summaryOverride || undefined,\n accessRole: cal.accessRole || 'reader',\n primary: cal.primary || false,\n backgroundColor: cal.backgroundColor || undefined\n }));\n\n // Sort: primary first, then by name\n calendars.sort((a, b) => {\n if (a.primary && !b.primary) return -1;\n if (!a.primary && b.primary) return 1;\n return (a.summaryOverride || a.summary).localeCompare(b.summaryOverride || b.summary);\n });\n\n return calendars;\n }\n\n /**\n * Get user email address from OAuth2Client\n * First tries getTokenInfo, then falls back to primary calendar ID\n */\n private async getUserEmail(client: OAuth2Client): Promise<string> {\n try {\n // Try getTokenInfo first (only works if token has email/openid scope)\n const tokenInfo = await client.getTokenInfo(client.credentials.access_token || '');\n if (tokenInfo.email) {\n return tokenInfo.email;\n }\n } catch {\n // Token info failed, try calendar fallback\n }\n\n // Fallback: Get primary calendar ID (usually the user's email)\n try {\n const { google } = await import('googleapis');\n const calendar = google.calendar({ version: 'v3', auth: client });\n const response = await calendar.calendars.get({ calendarId: 'primary' });\n const primaryId = response.data.id;\n // Primary calendar ID is typically the user's email\n if (primaryId && primaryId.includes('@')) {\n return primaryId;\n }\n } catch {\n // Calendar fallback also failed\n }\n\n return 'unknown';\n }\n} \n", "import fs from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Escape HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const htmlEscapes: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, char => htmlEscapes[char]);\n}\n\n/**\n * Load a file from the web directory (handles build vs source paths)\n *\n * When running from bundled code (build/index.js), __dirname is \"build/\"\n * and web files are in \"build/web/\". When running from source, __dirname\n * is \"src/web/\" and files are in the same directory.\n */\nexport async function loadWebFile(fileName: string): Promise<string> {\n // Possible locations for web files:\n // 1. Same directory as this file (source: src/web/)\n // 2. \"web\" subdirectory (bundled: build/web/)\n const locations = [\n path.join(__dirname, fileName), // src/web/file.html (source)\n path.join(__dirname, 'web', fileName), // build/web/file.html (bundled)\n ];\n\n for (const filePath of locations) {\n try {\n await fs.access(filePath);\n return fs.readFile(filePath, 'utf-8');\n } catch {\n // Try next location\n }\n }\n\n throw new Error(`Web file not found: ${fileName}. Tried: ${locations.join(', ')}`);\n}\n\n/**\n * Load a template file\n */\nasync function loadTemplate(templateName: string): Promise<string> {\n return loadWebFile(templateName);\n}\n\nexport interface AuthSuccessParams {\n accountId: string;\n email?: string;\n tokenPath?: string;\n showCloseButton?: boolean;\n postMessageOrigin?: string;\n}\n\n/**\n * Render the authentication success page\n */\nexport async function renderAuthSuccess(params: AuthSuccessParams): Promise<string> {\n const template = await loadTemplate('auth-success.html');\n const safeAccountId = escapeHtml(params.accountId);\n\n // Build account info section - email is prominent, account ID is secondary\n let accountInfoSection: string;\n if (params.email) {\n accountInfoSection = `\n <p class=\"account-email\">${escapeHtml(params.email)}</p>\n <p class=\"account-label\">Saved as <code>${safeAccountId}</code></p>`;\n } else {\n accountInfoSection = `\n <p class=\"account-email\">Account connected</p>\n <p class=\"account-label\">Saved as <code>${safeAccountId}</code></p>`;\n }\n\n const closeButtonSection = params.showCloseButton\n ? `<button onclick=\"window.close()\">Close Window</button>`\n : '';\n\n const scriptSection = params.postMessageOrigin\n ? `<script>\n if (window.opener) {\n window.opener.postMessage({ type: 'auth-success', accountId: '${safeAccountId}' }, '${escapeHtml(params.postMessageOrigin)}');\n }\n setTimeout(() => window.close(), 3000);\n </script>`\n : '';\n\n return template\n .replace('{{accountInfo}}', accountInfoSection)\n .replace('{{closeButton}}', closeButtonSection)\n .replace('{{script}}', scriptSection);\n}\n\nexport interface AuthErrorParams {\n errorMessage: string;\n showCloseButton?: boolean;\n}\n\n/**\n * Render the authentication error page\n */\nexport async function renderAuthError(params: AuthErrorParams): Promise<string> {\n const template = await loadTemplate('auth-error.html');\n const safeError = escapeHtml(params.errorMessage);\n\n const closeButtonSection = params.showCloseButton\n ? `<button onclick=\"window.close()\">Close Window</button>`\n : '';\n\n return template\n .replace('{{errorMessage}}', safeError)\n .replace('{{closeButton}}', closeButtonSection);\n}\n\nexport interface AuthLandingParams {\n accountId: string;\n authUrl: string;\n}\n\n/**\n * Render the authentication landing page (click to authenticate)\n */\nexport async function renderAuthLanding(params: AuthLandingParams): Promise<string> {\n const template = await loadTemplate('auth-landing.html');\n const safeAccountId = escapeHtml(params.accountId);\n const safeAuthUrl = escapeHtml(params.authUrl);\n\n return template\n .replace(/\\{\\{accountId\\}\\}/g, safeAccountId)\n .replace('{{authUrl}}', safeAuthUrl);\n}\n", "import { initializeOAuth2Client } from './auth/client.js';\nimport { AuthServer } from './auth/server.js';\n\n// Check for command line arguments\nconst args = process.argv.slice(2);\nif (args.length > 0) {\n // Assume the first argument is the account mode\n process.env.GOOGLE_ACCOUNT_MODE = args[0];\n}\n\nasync function runAuthServer() {\n let authServer: AuthServer | null = null; // Keep reference for cleanup\n try {\n const oauth2Client = await initializeOAuth2Client();\n \n authServer = new AuthServer(oauth2Client);\n \n const success = await authServer.start(true);\n \n if (!success && !authServer.authCompletedSuccessfully) {\n process.stderr.write('Authentication failed. Could not start server or validate existing tokens.\\n');\n process.exit(1);\n } else if (authServer.authCompletedSuccessfully) {\n process.stderr.write('Authentication successful.\\n');\n process.exit(0);\n }\n \n // If we reach here, the server started and is waiting for the browser callback\n process.stderr.write('Authentication server started. Please complete the authentication in your browser...\\n');\n \n\n process.stderr.write(`Waiting for OAuth callback on port ${authServer.getRunningPort()}...\\n`);\n \n // Poll for completion or handle SIGINT\n let lastDebugLog = 0;\n const pollInterval = setInterval(async () => {\n try {\n if (authServer?.authCompletedSuccessfully) {\n process.stderr.write('Authentication completed successfully detected. Stopping server...\\n');\n clearInterval(pollInterval);\n await authServer.stop();\n process.stderr.write('Authentication successful. Server stopped.\\n');\n process.exit(0);\n } else {\n // Add debug logging every 10 seconds to show we're still waiting\n const now = Date.now();\n if (now - lastDebugLog > 10000) {\n process.stderr.write('Still waiting for authentication to complete...\\n');\n lastDebugLog = now;\n }\n }\n } catch (error: unknown) {\n process.stderr.write(`Error in polling interval: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n clearInterval(pollInterval);\n if (authServer) await authServer.stop();\n process.exit(1);\n }\n }, 5000); // Check every second\n\n // Handle process termination (SIGINT)\n process.on('SIGINT', async () => {\n clearInterval(pollInterval); // Stop polling\n if (authServer) {\n await authServer.stop();\n }\n process.exit(0);\n });\n \n } catch (error: unknown) {\n process.stderr.write(`Authentication error: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n if (authServer) await authServer.stop(); // Attempt cleanup\n process.exit(1);\n }\n}\n\n// Run the auth server if this file is executed directly\nif (import.meta.url.endsWith('auth-server.js')) {\n runAuthServer().catch((error: unknown) => {\n process.stderr.write(`Unhandled error: ${error instanceof Error ? error.message : 'Unknown error'}\\n`);\n process.exit(1);\n });\n}"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,OAAO,UAAU;AACjB,SAAS,eAAe;AAMjB,SAAS,qBAAqB;AAEnC,MAAI,QAAQ,IAAI,gCAAgC;AAC9C,WAAO,KAAK,QAAQ,QAAQ,IAAI,8BAA8B;AAAA,EAChE;AAEA,QAAM,YAAY,QAAQ,IAAI,mBAAmB,KAAK,KAAK,QAAQ,GAAG,SAAS;AAC/E,SAAO,KAAK,KAAK,WAAW,uBAAuB,aAAa;AAClE;AAKO,SAAS,qBAAqB;AACnC,SAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,wBAAwB;AAC1D;AAaO,SAAS,kBAAkB,WAAW;AAC3C,MAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AACxC,UAAM,IAAI,MAAM,oGAAoG;AAAA,EACtH;AAGA,MAAI,eAAe,SAAS,SAAS,GAAG;AACtC,UAAM,IAAI,MAAM,eAAe,SAAS,mCAAmC;AAAA,EAC7E;AAGA,MAAI,CAAC,qBAAqB,KAAK,SAAS,GAAG;AACzC,UAAM,IAAI,MAAM,oGAAoG;AAAA,EACtH;AAEA,SAAO;AACT;AAMO,SAAS,iBAAiB;AAE/B,QAAM,eAAe,QAAQ,IAAI;AACjC,MAAI,iBAAiB,UAAa,iBAAiB,MAAM;AAEvD,WAAO,kBAAkB,YAAY;AAAA,EACvC;AAGA,MAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AA/EA,IAkCM;AAlCN;AAAA;AAAA;AAkCA,IAAM,iBAAiB;AAAA,MAAC;AAAA,MAAK;AAAA,MAAM;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAQ;AAAA,MAAQ;AAAA,MAC/D;AAAA,MAAQ;AAAA,MAAQ;AAAA,IAAM;AAAA;AAAA;;;ACnC9C,SAAS,oBAAoB;AAC7B,YAAY,QAAQ;;;ACDpB,YAAYA,WAAU;AAItB;AADA,SAAS,qBAAqB;AAI9B,SAAS,iBAAyB;AAChC,QAAMC,aAAiB,cAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,QAAM,cAAmB,WAAKA,YAAW,IAAI;AAC7C,SAAY,cAAQ,WAAW;AACjC;AAIO,SAASC,kBAAyB;AACvC,SAAO,eAAqB;AAC9B;AASO,SAASC,sBAA6B;AAC3C,SAAO,mBAAyB;AAClC;AAGO,SAASC,sBAA6B;AAC3C,SAAO,mBAAyB;AAClC;AAKO,SAAS,kBAA0B;AAExC,QAAM,qBAAqB,QAAQ,IAAI;AACvC,MAAI,oBAAoB;AACtB,WAAY,cAAQ,kBAAkB;AAAA,EACxC;AAGA,QAAM,cAAc,eAAe;AACnC,QAAM,WAAgB,WAAK,aAAa,qBAAqB;AAC7D,SAAO;AACT;AAmEO,SAAS,kCAA0C;AACxD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAegBC,oBAAmB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS3C,KAAK;AACP;;;AD3IA,eAAe,0BAAqD;AAClE,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AAEA,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,UAAU;AAElC,QAAI,KAAK,WAAW;AAElB,YAAM,EAAE,WAAW,eAAe,cAAc,IAAI,KAAK;AACzD,aAAO,EAAE,WAAW,eAAe,cAAc;AAAA,IACnD,WAAW,KAAK,aAAa,KAAK,eAAe;AAE/C,aAAO;AAAA,QACL,WAAW,KAAK;AAAA,QAChB,eAAe,KAAK;AAAA,QACpB,eAAe,KAAK,iBAAiB,CAAC,sCAAsC;AAAA,MAC9E;AAAA,IACF,OAAO;AACL,YAAM,IAAI,MAAM,+GAA+G;AAAA,IACjI;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,aAAa;AAChC,YAAM,IAAI,MAAM,wEAAwE,MAAM,OAAO,EAAE;AAAA,IACzG;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAe,0BAAqD;AAClE,QAAM,cAAc,MAAS,YAAS,gBAAgB,GAAG,OAAO;AAChE,QAAM,OAAO,KAAK,MAAM,WAAW;AAEnC,MAAI,KAAK,WAAW;AAElB,UAAM,EAAE,WAAW,eAAe,cAAc,IAAI,KAAK;AACzD,WAAO,EAAE,WAAW,eAAe,cAAc;AAAA,EACnD,WAAW,KAAK,aAAa,KAAK,eAAe;AAE/C,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,eAAe,KAAK;AAAA,MACpB,eAAe,KAAK,iBAAiB,CAAC,sCAAsC;AAAA,IAC9E;AAAA,EACF,OAAO;AACL,UAAM,IAAI,MAAM,+GAA+G;AAAA,EACjI;AACF;AAEA,eAAe,8BAAyD;AAEtE,MAAI,QAAQ,IAAI,+BAA+B;AAC7C,QAAI;AACF,aAAO,MAAM,wBAAwB;AAAA,IACvC,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,qDAAqD,iBAAiB,QAAQ,MAAM,UAAU,KAAK,EAAE;AAAA,IACvH;AAAA,EACF;AAGA,MAAI;AACF,WAAO,MAAM,wBAAwB;AAAA,EACvC,SAAS,WAAW;AAElB,UAAM,eAAe,gCAAgC;AACrD,UAAM,IAAI,MAAM,GAAG,YAAY;AAAA;AAAA,kBAAuB,qBAAqB,QAAQ,UAAU,UAAU,SAAS,EAAE;AAAA,EACpH;AACF;AAEA,eAAsB,yBAAgD;AAGpE,MAAI;AACF,UAAM,cAAc,MAAM,4BAA4B;AAGtD,WAAO,IAAI,aAAa;AAAA,MACtB,UAAU,YAAY;AAAA,MACtB,cAAc,YAAY;AAAA,MAC1B,aAAa,YAAY,cAAc,CAAC;AAAA,IAC1C,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,KAAK,EAAE;AAAA,EAC/F;AACF;AAEA,eAAsB,kBAAyE;AAC7F,MAAI;AACF,UAAM,cAAc,MAAM,4BAA4B;AAEtD,QAAI,CAAC,YAAY,aAAa,CAAC,YAAY,eAAe;AACtD,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACxE;AACA,WAAO;AAAA,MACL,WAAW,YAAY;AAAA,MACvB,eAAe,YAAY;AAAA,IAC7B;AAAA,EACF,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,KAAK,EAAE;AAAA,EAChG;AACF;;;AE5GA,SAAS,gBAAAC,qBAAoB;;;ACA7B,SAAS,gBAAAC,qBAAiC;AAC1C,OAAOC,SAAQ;AAEf,SAAS,mBAAmB;AAC5B,SAAS,aAAa;AACtB,SAAS,WAAAC,gBAAe;AAyBjB,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAsC,oBAAI,IAAI;AAAA,EAC9C;AAAA,EAKA,aAA4B,QAAQ,QAAQ;AAAA,EAEpD,YAAY,cAA4B;AACtC,SAAK,eAAe;AACpB,SAAK,YAAYC,oBAAmB;AACpC,SAAK,cAAcC,gBAAe;AAGlC,SAAK,cAAc;AAAA,MACjB,UAAW,aAAqB;AAAA,MAChC,cAAe,aAAqB;AAAA,MACpC,aAAc,aAAqB;AAAA,IACrC;AAEA,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA,EAGO,eAAuB;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGO,iBAAyB;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGO,eAAe,MAAoB;AACxC,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAc,6BAA4C;AACxD,QAAI;AACF,YAAM,MAAMF,SAAQ,KAAK,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1D,SAAS,OAAO;AACd,cAAQ,OAAO,MAAM,qCAAqC,KAAK;AAAA,CAAI;AAAA,IACrE;AAAA,EACF;AAAA,EAEQ,oBAAoB,OAAyB;AACnD,WAAO,iBAAiB,SAAS,UAAU,SAAU,MAAc,SAAS;AAAA,EAC9E;AAAA,EAEA,MAAc,eAAe,QAA2C;AACtE,UAAM,KAAK,2BAA2B;AACtC,UAAMG,IAAG,UAAU,KAAK,WAAW,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAAA,EACrF;AAAA,EAEA,MAAc,yBAAsD;AAClE,QAAI;AACF,YAAM,cAAc,MAAMA,IAAG,SAAS,KAAK,WAAW,OAAO;AAC7D,YAAM,SAAS,KAAK,MAAM,WAAW;AAGrC,UAAI,OAAO,gBAAgB,OAAO,eAAe;AAE/C,cAAM,qBAAyC;AAAA,UAC7C,QAAQ;AAAA,QACV;AACA,cAAM,KAAK,uBAAuB,kBAAkB;AACpD,eAAO;AAAA,MACT;AAGA,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,UAAI,KAAK,oBAAoB,KAAK,GAAG;AACnC,eAAO,CAAC;AAAA,MACV;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,4BAAyD;AACrE,QAAI;AACF,YAAM,cAAc,MAAMA,IAAG,SAAS,KAAK,WAAW,OAAO;AAC7D,aAAO,KAAK,MAAM,WAAW;AAAA,IAC/B,SAAS,OAAgB;AACvB,UAAI,KAAK,oBAAoB,KAAK,GAAG;AACnC,eAAO,CAAC;AAAA,MACV;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,uBAAuB,oBAAuD;AAC1F,WAAO,KAAK,kBAAkB,YAAY;AACxC,YAAM,KAAK,eAAe,kBAAkB;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEQ,kBAAkB,WAA+C;AACvE,UAAM,eAAe,KAAK,WACvB,MAAM,MAAM,MAAS,EACrB,KAAK,SAAS;AAEjB,SAAK,aAAa,aACf,MAAM,WAAS;AACd,cAAQ,OAAO,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,CAAI;AACpG,YAAM;AAAA,IACR,CAAC,EACA,MAAM,MAAM,MAAS;AAExB,WAAO;AAAA,EACT;AAAA,EAEQ,oBAA0B;AAChC,SAAK,4BAA4B,KAAK,cAAc,KAAK,WAAW;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,4BAA4B,QAAsB,WAAyB;AACjF,WAAO,GAAG,UAAU,OAAO,cAAc;AACvC,UAAI;AAEF,cAAM,KAAK,kBAAkB,YAAY;AACvC,gBAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,gBAAM,gBAAgB,mBAAmB,SAAS,KAAK,CAAC;AAExD,gBAAM,gBAAgB;AAAA,YACpB,GAAG;AAAA,YACH,GAAG;AAAA,YACH,eAAe,UAAU,iBAAiB,cAAc;AAAA,UAC1D;AAEA,6BAAmB,SAAS,IAAI;AAChC,gBAAM,KAAK,eAAe,kBAAkB;AAAA,QAC9C,CAAC;AAED,YAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,kBAAQ,OAAO,MAAM,gCAAgC,SAAS;AAAA,CAAY;AAAA,QAC5E;AAAA,MACF,SAAS,OAAgB;AACvB,gBAAQ,OAAO,MAAM,+BAA+B;AACpD,YAAI,iBAAiB,OAAO;AAC1B,kBAAQ,OAAO,MAAM,MAAM,OAAO;AAAA,QACpC,WAAW,OAAO,UAAU,UAAU;AACpC,kBAAQ,OAAO,MAAM,KAAK;AAAA,QAC5B;AACA,gBAAQ,OAAO,MAAM,IAAI;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,sBAAwC;AACpD,UAAM,aAAaC,oBAAmB;AACtC,QAAI;AAEF,UAAI,CAAE,MAAMD,IAAG,OAAO,UAAU,EAAE,KAAK,MAAM,IAAI,EAAE,MAAM,MAAM,KAAK,GAAI;AACtE,eAAO;AAAA,MACT;AAGA,YAAM,eAAe,KAAK,MAAM,MAAMA,IAAG,SAAS,YAAY,OAAO,CAAC;AAEtE,UAAI,CAAC,gBAAgB,OAAO,iBAAiB,UAAU;AACrD,gBAAQ,OAAO,MAAM,mDAAmD;AACxE,eAAO;AAAA,MACT;AAGA,YAAM,KAAK,eAAe,YAAY;AAEtC,cAAQ,OAAO,MAAM,yCAAyC,UAAU,QAAQ,KAAK,SAAS;AAAA,CAAI;AAGlG,UAAI;AACF,cAAMA,IAAG,OAAO,UAAU;AAC1B,gBAAQ,OAAO,MAAM,6BAA6B;AAAA,MACpD,SAAS,WAAW;AAClB,gBAAQ,OAAO,MAAM,gDAAgD,SAAS;AAAA,CAAI;AAAA,MACpF;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,OAAO,MAAM,kCAAkC,KAAK;AAAA,CAAI;AAChE,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,kBAAoC;AACxC,QAAI;AACF,YAAM,KAAK,2BAA2B;AAGtC,YAAM,cAAc,MAAMA,IAAG,OAAO,KAAK,SAAS,EAAE,KAAK,MAAM,IAAI,EAAE,MAAM,MAAM,KAAK;AAGtF,UAAI,CAAC,aAAa;AAChB,cAAM,WAAW,MAAM,KAAK,oBAAoB;AAChD,YAAI,CAAC,UAAU;AACb,kBAAQ,OAAO,MAAM,2BAA2B,KAAK,SAAS;AAAA,CAAI;AAClE,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,YAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,YAAM,SAAS,mBAAmB,KAAK,WAAW;AAElD,UAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,gBAAQ,OAAO,MAAM,uBAAuB,KAAK,WAAW,qBAAqB,KAAK,SAAS;AAAA,CAAI;AACnG,eAAO;AAAA,MACT;AAEA,WAAK,aAAa,eAAe,MAAM;AACvC,cAAQ,OAAO,MAAM,qBAAqB,KAAK,WAAW;AAAA,CAAY;AACtE,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,cAAQ,OAAO,MAAM,4BAA4B,KAAK,WAAW,YAAY;AAC7E,UAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU;AACtE,YAAI;AACA,gBAAMA,IAAG,OAAO,KAAK,SAAS;AAC9B,kBAAQ,OAAO,MAAM,4CAA4C;AAAA,QACnE,SAAS,WAAW;AAAA,QAAe;AAAA,MACzC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,wBAA0C;AAC9C,UAAM,aAAa,KAAK,aAAa,YAAY;AACjD,UAAM,YAAY,aACd,KAAK,IAAI,KAAK,aAAa,IAAI,KAAK,MACpC,CAAC,KAAK,aAAa,YAAY;AAEnC,QAAI,aAAa,KAAK,aAAa,YAAY,eAAe;AAC5D,UAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,gBAAQ,OAAO,MAAM,4CAA4C,KAAK,WAAW;AAAA,CAA2B;AAAA,MAC9G;AACA,UAAI;AACF,cAAM,WAAW,MAAM,KAAK,aAAa,mBAAmB;AAC5D,cAAM,YAAY,SAAS;AAE3B,YAAI,CAAC,UAAU,cAAc;AAC3B,gBAAM,IAAI,MAAM,wCAAwC;AAAA,QAC1D;AAEA,aAAK,aAAa,eAAe,SAAS;AAC1C,YAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,kBAAQ,OAAO,MAAM,oCAAoC,KAAK,WAAW;AAAA,CAAY;AAAA,QACvF;AACA,eAAO;AAAA,MACT,SAAS,cAAc;AACrB,YAAI,wBAAwB,eAAe,aAAa,UAAU,MAAM,UAAU,iBAAiB;AAC/F,kBAAQ,OAAO,MAAM,mCAAmC,KAAK,WAAW;AAAA,CAAqF;AAC7J,iBAAO;AAAA,QACX,OAAO;AAEH,kBAAQ,OAAO,MAAM,mCAAmC,KAAK,WAAW,YAAY;AACpF,cAAI,wBAAwB,OAAO;AACjC,oBAAQ,OAAO,MAAM,aAAa,OAAO;AAAA,UAC3C,WAAW,OAAO,iBAAiB,UAAU;AAC3C,oBAAQ,OAAO,MAAM,YAAY;AAAA,UACnC;AACA,kBAAQ,OAAO,MAAM,IAAI;AACzB,iBAAO;AAAA,QACX;AAAA,MACF;AAAA,IACF,WAAW,CAAC,KAAK,aAAa,YAAY,gBAAgB,CAAC,KAAK,aAAa,YAAY,eAAe;AACpG,cAAQ,OAAO,MAAM,4CAA4C,KAAK,WAAW;AAAA,CAAqC;AACtH,aAAO;AAAA,IACX,OAAO;AAEH,aAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,aAAwC;AAI3D,UAAM,iBAAiB,eAAe,KAAK;AAC3C,UAAM,cAAc,KAAK;AAEzB,QAAI;AAEF,UAAI,mBAAmB,aAAa;AAClC,aAAK,cAAc;AAAA,MACrB;AAEA,UAAI,CAAC,KAAK,aAAa,eAAe,CAAC,KAAK,aAAa,YAAY,cAAc;AAE/E,YAAI,CAAE,MAAM,KAAK,gBAAgB,GAAI;AACjC,iBAAO;AAAA,QACX;AAEA,YAAI,CAAC,KAAK,aAAa,eAAe,CAAC,KAAK,aAAa,YAAY,cAAc;AAC/E,iBAAO;AAAA,QACX;AAAA,MACJ;AAEA,YAAM,SAAS,MAAM,KAAK,sBAAsB;AAChD,aAAO;AAAA,IACT,UAAE;AAEA,UAAI,mBAAmB,aAAa;AAClC,aAAK,cAAc;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,QAAqB,OAA+B;AACnE,QAAI;AAEA,YAAM,KAAK,kBAAkB,YAAY;AACvC,cAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,cAAM,eAAkC,EAAE,GAAG,OAAO;AAGpD,YAAI,OAAO;AACT,uBAAa,eAAe;AAAA,QAC9B;AAEA,2BAAmB,KAAK,WAAW,IAAI;AACvC,cAAM,KAAK,eAAe,kBAAkB;AAAA,MAC9C,CAAC;AACD,WAAK,aAAa,eAAe,MAAM;AACvC,cAAQ,OAAO,MAAM,iCAAiC,KAAK,WAAW,gBAAgB,KAAK,SAAS;AAAA,CAAI;AAAA,IAC5G,SAAS,OAAgB;AACrB,cAAQ,OAAO,MAAM,2BAA2B,KAAK,WAAW,aAAa,KAAK;AAAA,CAAI;AACtF,YAAM;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,cAA6B;AACjC,QAAI;AACF,WAAK,aAAa,eAAe,CAAC,CAAC;AAGnC,YAAM,KAAK,kBAAkB,YAAY;AACvC,cAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,eAAO,mBAAmB,KAAK,WAAW;AAG1C,YAAI,OAAO,KAAK,kBAAkB,EAAE,WAAW,GAAG;AAChD,gBAAMA,IAAG,OAAO,KAAK,SAAS;AAC9B,kBAAQ,OAAO,MAAM;AAAA,CAAoC;AAAA,QAC3D,OAAO;AACL,gBAAM,KAAK,eAAe,kBAAkB;AAC5C,kBAAQ,OAAO,MAAM,sBAAsB,KAAK,WAAW;AAAA,CAAY;AAAA,QACzE;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,UAAI,KAAK,oBAAoB,KAAK,GAAG;AAEnC,gBAAQ,OAAO,MAAM,8BAA8B;AAAA,MACrD,OAAO;AACL,gBAAQ,OAAO,MAAM,6BAA6B,KAAK,WAAW,aAAa,KAAK;AAAA,CAAI;AAAA,MAE1F;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,wBAA2C;AAC/C,QAAI;AACF,YAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,aAAO,OAAO,KAAK,kBAAkB;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAc,WAAkC;AACpD,UAAM,eAAe,UAAU,YAAY;AAE3C,UAAM,KAAK,kBAAkB,YAAY;AACvC,YAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAE7D,UAAI,CAAC,mBAAmB,YAAY,GAAG;AACrC,cAAM,IAAI,MAAM,YAAY,YAAY,aAAa;AAAA,MACvD;AAEA,aAAO,mBAAmB,YAAY;AAGtC,UAAI,OAAO,KAAK,kBAAkB,EAAE,WAAW,GAAG;AAChD,cAAMA,IAAG,OAAO,KAAK,SAAS;AAC9B,gBAAQ,OAAO,MAAM;AAAA,CAAoC;AAAA,MAC3D,OAAO;AACL,cAAM,KAAK,eAAe,kBAAkB;AAC5C,gBAAQ,OAAO,MAAM,YAAY,YAAY;AAAA,CAA0B;AAAA,MACzE;AAGA,WAAK,SAAS,OAAO,YAAY;AAAA,IACnC,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,cAAc,SAAmC;AACrD,SAAK,cAAc;AACnB,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,kBAAsD;AAC1D,QAAI;AACF,YAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAG7D,iBAAW,aAAa,KAAK,SAAS,KAAK,GAAG;AAC5C,YAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,gBAAM,SAAS,KAAK,SAAS,IAAI,SAAS;AAC1C,cAAI,QAAQ;AAEV,mBAAO,mBAAmB,QAAQ;AAAA,UACpC;AACA,eAAK,SAAS,OAAO,SAAS;AAAA,QAChC;AAAA,MACF;AAGA,iBAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AAEpE,YAAI;AACF,gBAAM,EAAE,mBAAAE,mBAAkB,IAAI,MAAM;AACpC,UAAAA,mBAAkB,SAAS;AAG3B,cAAI,CAAC,UAAU,OAAO,WAAW,YAAY,CAAC,OAAO,cAAc;AACjE;AAAA,UACF;AAGA,cAAI,SAAS,KAAK,SAAS,IAAI,SAAS;AAExC,cAAI,CAAC,QAAQ;AAEX,qBAAS,IAAIC;AAAA,cACX,KAAK,YAAY;AAAA,cACjB,KAAK,YAAY;AAAA,cACjB,KAAK,YAAY;AAAA,YACnB;AAGA,iBAAK,4BAA4B,QAAQ,SAAS;AAElD,iBAAK,SAAS,IAAI,WAAW,MAAM;AAAA,UACrC;AAGA,iBAAO,eAAe,MAAM;AAAA,QAE9B,SAAS,OAAO;AAEd,cAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,oBAAQ,OAAO,MAAM,6BAA6B,SAAS,MAAM,KAAK;AAAA,CAAI;AAAA,UAC5E;AACA;AAAA,QACF;AAAA,MACF;AAEA,aAAO,KAAK;AAAA,IACd,SAAS,OAAY;AAEnB,UAAI,SAAS,MAAM,SAAS,UAAU;AAEpC,eAAO,oBAAI,IAAI;AAAA,MACjB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,WAAiC;AAEzC,UAAM,EAAE,mBAAAD,mBAAkB,IAAI;AAC9B,IAAAA,mBAAkB,SAAS;AAE3B,UAAM,SAAS,KAAK,SAAS,IAAI,SAAS;AAC1C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,YAAY,SAAS,sDAAsD;AAAA,IAC7F;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAKF;AACF,QAAI;AACF,YAAM,qBAAqB,MAAM,KAAK,uBAAuB;AAC7D,YAAM,cAKD,CAAC;AACN,UAAI,gBAAgB;AAGpB,YAAM,qBAAqB,IAAI,KAAK;AAEpC,iBAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AAEpE,YAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC;AAAA,QACF;AAEA,YAAI,SAA8B;AAGlC,YAAI,OAAO,gBAAgB,OAAO,eAAe;AAC/C,cAAI;AACF,qBAAS,IAAIC;AAAA,cACX,KAAK,YAAY;AAAA,cACjB,KAAK,YAAY;AAAA,cACjB,KAAK,YAAY;AAAA,YACnB;AACA,mBAAO,eAAe,MAAM;AAG5B,gBAAI,OAAO,kBAAkB,CAAC,OAAO,gBAAiB,OAAO,eAAe,OAAO,cAAc,KAAK,IAAI,IAAK;AAC7G,kBAAI;AACF,sBAAM,WAAW,MAAM,OAAO,mBAAmB;AACjD,uBAAO,eAAe,SAAS,WAAW;AAC1C,uBAAO,OAAO,QAAQ,SAAS,WAAW;AAC1C,gCAAgB;AAAA,cAClB,QAAQ;AAAA,cAER;AAAA,YACF;AAAA,UACF,QAAQ;AACN,qBAAS;AAAA,UACX;AAAA,QACF;AAGA,YAAI,QAAQ,OAAO,gBAAgB;AACnC,YAAI,CAAC,OAAO,gBAAgB,QAAQ;AAClC,cAAI;AACF,oBAAQ,MAAM,KAAK,aAAa,MAAM;AACtC,gBAAI,UAAU,WAAW;AACvB,qBAAO,eAAe;AACtB,8BAAgB;AAAA,YAClB;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAGA,YAAI,YAA8B,OAAO,oBAAoB,CAAC;AAC9D,cAAM,eAAe,CAAC,OAAO,uBAC1B,KAAK,IAAI,IAAI,OAAO,sBAAuB;AAE9C,YAAI,gBAAgB,QAAQ;AAC1B,cAAI;AACF,wBAAY,MAAM,KAAK,wBAAwB,MAAM;AACrD,mBAAO,mBAAmB;AAC1B,mBAAO,sBAAsB,KAAK,IAAI;AACtC,4BAAgB;AAAA,UAClB,QAAQ;AAAA,UAER;AAAA,QACF;AAGA,YAAI,SAAS;AACb,YAAI,CAAC,OAAO,eAAe;AACzB,cAAI,CAAC,OAAO,gBAAiB,OAAO,eAAe,OAAO,cAAc,KAAK,IAAI,GAAI;AACnF,qBAAS;AAAA,UACX;AAAA,QACF;AAEA,oBAAY,KAAK,EAAE,IAAI,WAAW,OAAO,QAAQ,UAAU,CAAC;AAAA,MAC9D;AAIA,UAAI,eAAe;AACjB,cAAM,KAAK,kBAAkB,YAAY;AAEvC,gBAAM,eAAe,MAAM,KAAK,0BAA0B;AAG1D,qBAAW,aAAa,OAAO,KAAK,kBAAkB,GAAG;AACvD,kBAAM,eAAe,mBAAmB,SAAS;AACjD,kBAAM,gBAAgB,aAAa,SAAS;AAE5C,gBAAI,iBAAiB,cAAc;AAEjC,kBAAI,aAAa,cAAc;AAC7B,8BAAc,eAAe,aAAa;AAAA,cAC5C;AACA,kBAAI,aAAa,kBAAkB;AACjC,8BAAc,mBAAmB,aAAa;AAC9C,8BAAc,sBAAsB,aAAa;AAAA,cACnD;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,KAAK,eAAe,YAAY;AAAA,QACxC,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,wBAAwB,QAAiD;AACrF,UAAM,EAAE,OAAO,IAAI,MAAM,OAAO,YAAY;AAC5C,UAAM,WAAW,OAAO,SAAS,EAAE,SAAS,MAAM,MAAM,OAAO,CAAC;AAChE,UAAM,WAAW,MAAM,SAAS,aAAa,KAAK;AAClD,UAAM,QAAQ,SAAS,KAAK,SAAS,CAAC;AAEtC,UAAM,YAA8B,MAAM,IAAI,UAAQ;AAAA,MACpD,IAAI,IAAI,MAAM;AAAA,MACd,SAAS,IAAI,WAAW;AAAA,MACxB,iBAAiB,IAAI,mBAAmB;AAAA,MACxC,YAAY,IAAI,cAAc;AAAA,MAC9B,SAAS,IAAI,WAAW;AAAA,MACxB,iBAAiB,IAAI,mBAAmB;AAAA,IAC1C,EAAE;AAGF,cAAU,KAAK,CAAC,GAAG,MAAM;AACvB,UAAI,EAAE,WAAW,CAAC,EAAE,QAAS,QAAO;AACpC,UAAI,CAAC,EAAE,WAAW,EAAE,QAAS,QAAO;AACpC,cAAQ,EAAE,mBAAmB,EAAE,SAAS,cAAc,EAAE,mBAAmB,EAAE,OAAO;AAAA,IACtF,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,aAAa,QAAuC;AAChE,QAAI;AAEF,YAAM,YAAY,MAAM,OAAO,aAAa,OAAO,YAAY,gBAAgB,EAAE;AACjF,UAAI,UAAU,OAAO;AACnB,eAAO,UAAU;AAAA,MACnB;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,OAAO,YAAY;AAC5C,YAAM,WAAW,OAAO,SAAS,EAAE,SAAS,MAAM,MAAM,OAAO,CAAC;AAChE,YAAM,WAAW,MAAM,SAAS,UAAU,IAAI,EAAE,YAAY,UAAU,CAAC;AACvE,YAAM,YAAY,SAAS,KAAK;AAEhC,UAAI,aAAa,UAAU,SAAS,GAAG,GAAG;AACxC,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,EACT;AACF;;;AD1tBA,OAAO,UAAU;AACjB,SAAS,WAAW;AACpB,OAAO,UAAU;;;AEJjB,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,iBAAAC,sBAAqB;AAE9B,IAAM,aAAaA,eAAc,YAAY,GAAG;AAChD,IAAM,YAAYD,MAAK,QAAQ,UAAU;AAKlC,SAAS,WAAW,MAAsB;AAC/C,QAAM,cAAsC;AAAA,IAC1C,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,SAAO,KAAK,QAAQ,YAAY,UAAQ,YAAY,IAAI,CAAC;AAC3D;AASA,eAAsB,YAAY,UAAmC;AAInE,QAAM,YAAY;AAAA,IAChBA,MAAK,KAAK,WAAW,QAAQ;AAAA;AAAA,IAC7BA,MAAK,KAAK,WAAW,OAAO,QAAQ;AAAA;AAAA,EACtC;AAEA,aAAW,YAAY,WAAW;AAChC,QAAI;AACF,YAAMD,IAAG,OAAO,QAAQ;AACxB,aAAOA,IAAG,SAAS,UAAU,OAAO;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,uBAAuB,QAAQ,YAAY,UAAU,KAAK,IAAI,CAAC,EAAE;AACnF;AAKA,eAAe,aAAa,cAAuC;AACjE,SAAO,YAAY,YAAY;AACjC;AAaA,eAAsB,kBAAkB,QAA4C;AAClF,QAAM,WAAW,MAAM,aAAa,mBAAmB;AACvD,QAAM,gBAAgB,WAAW,OAAO,SAAS;AAGjD,MAAI;AACJ,MAAI,OAAO,OAAO;AAChB,yBAAqB;AAAA,iCACQ,WAAW,OAAO,KAAK,CAAC;AAAA,gDACT,aAAa;AAAA,EAC3D,OAAO;AACL,yBAAqB;AAAA;AAAA,gDAEuB,aAAa;AAAA,EAC3D;AAEA,QAAM,qBAAqB,OAAO,kBAC9B,2DACA;AAEJ,QAAM,gBAAgB,OAAO,oBACzB;AAAA;AAAA,0EAEoE,aAAa,SAAS,WAAW,OAAO,iBAAiB,CAAC;AAAA;AAAA;AAAA,mBAI9H;AAEJ,SAAO,SACJ,QAAQ,mBAAmB,kBAAkB,EAC7C,QAAQ,mBAAmB,kBAAkB,EAC7C,QAAQ,cAAc,aAAa;AACxC;AAUA,eAAsB,gBAAgB,QAA0C;AAC9E,QAAM,WAAW,MAAM,aAAa,iBAAiB;AACrD,QAAM,YAAY,WAAW,OAAO,YAAY;AAEhD,QAAM,qBAAqB,OAAO,kBAC9B,2DACA;AAEJ,SAAO,SACJ,QAAQ,oBAAoB,SAAS,EACrC,QAAQ,mBAAmB,kBAAkB;AAClD;AAUA,eAAsB,kBAAkB,QAA4C;AAClF,QAAM,WAAW,MAAM,aAAa,mBAAmB;AACvD,QAAM,gBAAgB,WAAW,OAAO,SAAS;AACjD,QAAM,cAAc,WAAW,OAAO,OAAO;AAE7C,SAAO,SACJ,QAAQ,sBAAsB,aAAa,EAC3C,QAAQ,eAAe,WAAW;AACvC;;;AF3HO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA;AAAA,EACA,mBAAwC;AAAA;AAAA,EACxC,SAA6B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,oBAA+C,oBAAI,IAAI;AAAA;AAAA,EACxD,4BAA4B;AAAA;AAAA,EAC3B,iBAAuD;AAAA;AAAA,EACvD,wBAAwB;AAAA;AAAA,EAEhC,YAAY,cAA4B;AACtC,SAAK,mBAAmB;AACxB,SAAK,eAAe,IAAI,aAAa,YAAY;AACjD,SAAK,YAAY,EAAE,OAAO,MAAM,KAAK,KAAK;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBAAuB,MAAqC;AACxE,UAAM,EAAE,WAAW,cAAc,IAAI,MAAM,gBAAgB;AAC3D,WAAO,IAAIG;AAAA,MACT;AAAA,MACA;AAAA,MACA,oBAAoB,IAAI;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,QAA8B;AACrD,WAAO,OAAO,gBAAgB;AAAA,MAC5B,aAAa;AAAA,MACb,OAAO,CAAC,0CAA0C;AAAA,MAClD,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEQ,eAA4B;AAClC,UAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,YAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEhE,UAAI,IAAI,aAAa,eAAe;AAElC,cAAM,MAAM,MAAM,YAAY,YAAY;AAC1C,YAAI,UAAU,KAAK,EAAE,gBAAgB,0BAA0B,CAAC;AAChE,YAAI,IAAI,GAAG;AAAA,MAEb,WAAW,IAAI,aAAa,KAAK;AAE/B,cAAM,eAAe,KAAK,oBAAoB,KAAK;AACnD,cAAM,UAAU,KAAK,iBAAiB,YAAY;AAClD,cAAM,cAAcC,gBAAe;AAEnC,cAAM,cAAc,MAAM,kBAAkB;AAAA,UAC1C,WAAW;AAAA,UACX;AAAA,QACF,CAAC;AACD,YAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,YAAI,IAAI,WAAW;AAAA,MAErB,WAAW,IAAI,aAAa,mBAAmB;AAE7C,cAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,YAAI,CAAC,MAAM;AACT,gBAAM,YAAY,MAAM,gBAAgB;AAAA,YACtC,cAAc;AAAA,UAChB,CAAC;AACD,cAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,cAAI,IAAI,SAAS;AACjB;AAAA,QACF;AAEA,YAAI,CAAC,KAAK,kBAAkB;AAC1B,gBAAM,YAAY,MAAM,gBAAgB;AAAA,YACtC,cAAc;AAAA,UAChB,CAAC;AACD,cAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,cAAI,IAAI,SAAS;AACjB;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,EAAE,OAAO,IAAI,MAAM,KAAK,iBAAiB,SAAS,IAAI;AAC5D,gBAAM,KAAK,aAAa,WAAW,MAAM;AACzC,eAAK,4BAA4B;AAEjC,gBAAM,YAAY,KAAK,aAAa,aAAa;AACjD,gBAAM,cAAc,KAAK,aAAa,eAAe;AAGrD,cAAI,KAAK,uBAAuB;AAE9B,gBAAI,KAAK,gBAAgB;AACvB,2BAAa,KAAK,cAAc;AAChC,mBAAK,iBAAiB;AAAA,YACxB;AAEA,uBAAW,MAAM;AACf,mBAAK,KAAK,EAAE,MAAM,MAAM;AAAA,cAAC,CAAC;AAAA,YAC5B,GAAG,GAAI;AAAA,UACT;AAEA,gBAAM,cAAc,MAAM,kBAAkB;AAAA,YAC1C,WAAW;AAAA,YACX;AAAA,UACF,CAAC;AACD,cAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,cAAI,IAAI,WAAW;AAAA,QACrB,SAAS,OAAgB;AACvB,eAAK,4BAA4B;AACjC,gBAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,kBAAQ,OAAO,MAAM,6BAAwB,OAAO;AAAA,CAAI;AAExD,gBAAM,YAAY,MAAM,gBAAgB;AAAA,YACtC,cAAc;AAAA,UAChB,CAAC;AACD,cAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,cAAI,IAAI,SAAS;AAAA,QACnB;AAAA,MACF,OAAO;AAEL,YAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,YAAI,IAAI,WAAW;AAAA,MACrB;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,cAAc,CAAC,WAAW;AAClC,WAAK,kBAAkB,IAAI,MAAM;AACjC,aAAO,GAAG,SAAS,MAAM;AACvB,aAAK,kBAAkB,OAAO,MAAM;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MAAM,cAAc,MAAwB;AAEhD,WAAO,QAAQ,KAAK;AAAA,MAClB,KAAK,iBAAiB,WAAW;AAAA,MACjC,IAAI,QAAiB,CAAC,GAAG,WAAW;AAClC,mBAAW,MAAM,OAAO,IAAI,MAAM,8CAA8C,CAAC,GAAG,GAAK;AAAA,MAC3F,CAAC;AAAA,IACH,CAAC,EAAE,MAAM,MAAM,KAAK;AAAA,EACtB;AAAA,EAEA,MAAc,iBAAiB,cAAc,MAAwB;AACnE,QAAI,MAAM,KAAK,aAAa,eAAe,GAAG;AAC5C,WAAK,4BAA4B;AACjC,aAAO;AAAA,IACT;AAGA,UAAM,OAAO,MAAM,KAAK,2BAA2B;AACnD,QAAI,SAAS,MAAM;AACjB,cAAQ,OAAO,MAAM,kFAAkF,KAAK,UAAU,KAAK,IAAI,KAAK,UAAU,GAAG;AAAA,CAAoB;AAErK,WAAK,4BAA4B;AACjC,aAAO;AAAA,IACT;AAGA,QAAI;AACF,WAAK,mBAAmB,MAAM,KAAK,uBAAuB,IAAI;AAAA,IAChE,SAAS,OAAO;AAEZ,WAAK,4BAA4B;AACjC,YAAM,KAAK,KAAK;AAChB,aAAO;AAAA,IACX;AAGA,UAAM,eAAe,KAAK,iBAAiB,KAAK,gBAAgB;AAGhE,YAAQ,OAAO,MAAM;AAAA,gCAA4B,YAAY;AAAA;AAAA,CAAM;AACnE,YAAQ,OAAO,MAAM,8BAA8B,IAAI;AAAA;AAAA,CAAM;AAE7D,QAAI,aAAa;AACf,UAAI;AACF,cAAM,KAAK,YAAY;AACvB,gBAAQ,OAAO,MAAM;AAAA,CAAuE;AAAA,MAC9F,SAAS,OAAO;AACd,gBAAQ,OAAO,MAAM;AAAA,CAAmE;AAAA,MAC1F;AAAA,IACF,OAAO;AACL,cAAQ,OAAO,MAAM;AAAA,CAA0D;AAAA,IACjF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,6BAAqD;AACjE,aAAS,OAAO,KAAK,UAAU,OAAO,QAAQ,KAAK,UAAU,KAAK,QAAQ;AACxE,UAAI;AACF,cAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,gBAAM,aAAa,KAAK,aAAa;AACrC,qBAAW,OAAO,MAAM,MAAM;AAC5B,iBAAK,SAAS;AACd,YAAAA,SAAQ;AAAA,UACV,CAAC;AACD,qBAAW,GAAG,SAAS,CAAC,QAA+B;AACrD,gBAAI,IAAI,SAAS,cAAc;AAE7B,yBAAW,MAAM,MAAM,OAAO,GAAG,CAAC;AAAA,YACpC,OAAO;AAEL,qBAAO,GAAG;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AACD,eAAO;AAAA,MACT,SAAS,OAAgB;AAEvB,YAAI,EAAE,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,eAAe;AAE7E,iBAAO;AAAA,QACX;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEO,iBAAgC;AACrC,QAAI,KAAK,QAAQ;AACf,YAAM,UAAU,KAAK,OAAO,QAAQ;AACpC,UAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAsB;AAE1B,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,SAAK,wBAAwB;AAE7B,WAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,UAAI,KAAK,QAAQ;AAEf,mBAAW,cAAc,KAAK,mBAAmB;AAC/C,qBAAW,QAAQ;AAAA,QACrB;AACA,aAAK,kBAAkB,MAAM;AAG7B,cAAM,UAAU,WAAW,MAAM;AAC/B,kBAAQ,OAAO,MAAM,yCAAyC;AAC9D,eAAK,SAAS;AACd,UAAAA,SAAQ;AAAA,QACV,GAAG,GAAI;AAEP,aAAK,OAAO,MAAM,CAAC,QAAQ;AACzB,uBAAa,OAAO;AACpB,cAAI,KAAK;AACP,mBAAO,GAAG;AAAA,UACZ,OAAO;AACL,iBAAK,SAAS;AACd,YAAAA,SAAQ;AAAA,UACV;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,QAAAA,SAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,gBAAgB,WAAmD;AAEvE,QAAI,KAAK,QAAQ;AACf,YAAM,KAAK,KAAK;AAAA,IAClB;AAGA,SAAK,aAAa,eAAe,SAAS;AAG1C,UAAM,OAAO,MAAM,KAAK,2BAA2B;AACnD,QAAI,SAAS,MAAM;AACjB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,sCAAsC,KAAK,UAAU,KAAK,IAAI,KAAK,UAAU,GAAG;AAAA,MACzF;AAAA,IACF;AAGA,QAAI;AACF,WAAK,mBAAmB,MAAM,KAAK,uBAAuB,IAAI;AAAA,IAChE,SAAS,OAAO;AACd,YAAM,KAAK,KAAK;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACtG;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,iBAAiB,KAAK,gBAAgB;AAG3D,SAAK,wBAAwB;AAC7B,SAAK,4BAA4B;AAGjC,SAAK,iBAAiB,WAAW,YAAY;AAC3C,UAAI,CAAC,KAAK,2BAA2B;AACnC,gBAAQ,OAAO,MAAM,6BAA6B,SAAS;AAAA,CAAiC;AAC5F,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF,GAAG,IAAI,KAAK,GAAI;AAEhB,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,aAAa,oBAAoB,IAAI;AAAA,IACvC;AAAA,EACF;AACF;;;AG9VA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAI,KAAK,SAAS,GAAG;AAEnB,UAAQ,IAAI,sBAAsB,KAAK,CAAC;AAC1C;AAEA,eAAe,gBAAgB;AAC7B,MAAI,aAAgC;AACpC,MAAI;AACF,UAAM,eAAe,MAAM,uBAAuB;AAElD,iBAAa,IAAI,WAAW,YAAY;AAExC,UAAM,UAAU,MAAM,WAAW,MAAM,IAAI;AAE3C,QAAI,CAAC,WAAW,CAAC,WAAW,2BAA2B;AACrD,cAAQ,OAAO,MAAM,8EAA8E;AACnG,cAAQ,KAAK,CAAC;AAAA,IAChB,WAAW,WAAW,2BAA2B;AAC/C,cAAQ,OAAO,MAAM,8BAA8B;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,YAAQ,OAAO,MAAM,wFAAwF;AAG7G,YAAQ,OAAO,MAAM,sCAAsC,WAAW,eAAe,CAAC;AAAA,CAAO;AAG7F,QAAI,eAAe;AACnB,UAAM,eAAe,YAAY,YAAY;AAC3C,UAAI;AACF,YAAI,YAAY,2BAA2B;AACzC,kBAAQ,OAAO,MAAM,sEAAsE;AAC3F,wBAAc,YAAY;AAC1B,gBAAM,WAAW,KAAK;AACtB,kBAAQ,OAAO,MAAM,8CAA8C;AACnE,kBAAQ,KAAK,CAAC;AAAA,QAChB,OAAO;AAEL,gBAAM,MAAM,KAAK,IAAI;AACrB,cAAI,MAAM,eAAe,KAAO;AAC9B,oBAAQ,OAAO,MAAM,mDAAmD;AACxE,2BAAe;AAAA,UACjB;AAAA,QACF;AAAA,MACF,SAAS,OAAgB;AACvB,gBAAQ,OAAO,MAAM,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,CAAI;AAC/G,sBAAc,YAAY;AAC1B,YAAI,WAAY,OAAM,WAAW,KAAK;AACtC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,GAAG,GAAI;AAGP,YAAQ,GAAG,UAAU,YAAY;AAC/B,oBAAc,YAAY;AAC1B,UAAI,YAAY;AACd,cAAM,WAAW,KAAK;AAAA,MACxB;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EAEH,SAAS,OAAgB;AACvB,YAAQ,OAAO,MAAM,yBAAyB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,CAAI;AAC1G,QAAI,WAAY,OAAM,WAAW,KAAK;AACtC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,IAAI,YAAY,IAAI,SAAS,gBAAgB,GAAG;AAC9C,gBAAc,EAAE,MAAM,CAAC,UAAmB;AACxC,YAAQ,OAAO,MAAM,oBAAoB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,CAAI;AACrG,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
|
|
6
6
|
"names": ["path", "__dirname", "getAccountMode", "getSecureTokenPath", "getLegacyTokenPath", "getSecureTokenPath", "OAuth2Client", "OAuth2Client", "fs", "dirname", "getSecureTokenPath", "getAccountMode", "fs", "getLegacyTokenPath", "validateAccountId", "OAuth2Client", "fs", "path", "fileURLToPath", "OAuth2Client", "getAccountMode", "resolve"]
|
|
7
7
|
}
|