extract-from-existing-browser.jsā¢14.6 kB
#!/usr/bin/env node
/**
* Extract Authentication from Existing Browser Session v1.0.0
* Connect to existing Chromium browser with active Composer session
*
* Purpose: Extract localStorage data from already-open browser session
* Status: UTILITY - One-time extraction for direct API configuration
*
* @version 1.0.0 (January 15, 2025)
*/
import { chromium } from 'playwright';
import fs from 'fs';
import path from 'path';
class ExistingBrowserExtractor {
constructor() {
this.extractedData = null;
this.configPath = path.join(process.cwd(), 'config', 'direct-api-auth.json');
this.envPath = path.join(process.cwd(), 'config', 'direct-api.env');
}
/**
* Connect to existing browser and extract auth data
*/
async extractFromExistingBrowser() {
console.log('š Existing Browser Authentication Extractor v1.0.0');
console.log('Purpose: Extract auth data from active Composer session');
console.log('Status: Current v5.2.0 system preserved\n');
let browser = null;
let context = null;
let page = null;
try {
// Common CDP endpoints for various Chromium-based browsers
const cdpEndpoints = [
'http://localhost:9222',
'http://127.0.0.1:9222',
'http://localhost:9333',
'http://127.0.0.1:9333',
'http://localhost:9515',
'http://127.0.0.1:9515'
];
console.log('š Attempting to connect to existing browser...');
let connected = false;
let connectionError = null;
// Try each CDP endpoint
for (const endpoint of cdpEndpoints) {
try {
console.log(` Trying ${endpoint}...`);
browser = await chromium.connectOverCDP(endpoint);
console.log(` ā
Connected successfully to ${endpoint}`);
connected = true;
break;
} catch (error) {
connectionError = error.message;
// Continue to next endpoint
}
}
if (!connected) {
console.log('\nā Could not connect to existing browser.');
console.log('\nš Manual Instructions to Enable Connection:');
console.log(' 1. Close all Chromium/Chrome browsers');
console.log(' 2. Start Chromium with remote debugging:');
console.log(' macOS: /Applications/Chromium.app/Contents/MacOS/Chromium --remote-debugging-port=9222');
console.log(' OR: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222');
console.log(' 3. Navigate to https://composer.euconquisto.com and log in');
console.log(' 4. Run this script again');
console.log('\n Last error:', connectionError);
return null;
}
// Get all browser contexts
const contexts = browser.contexts();
console.log(`š Found ${contexts.length} browser context(s)`);
// Find context with Composer page
let composerPage = null;
for (const ctx of contexts) {
const pages = ctx.pages();
for (const p of pages) {
const url = p.url();
console.log(` Checking page: ${url}`);
if (url.includes('composer.euconquisto.com')) {
composerPage = p;
console.log(' ā
Found Composer page!');
break;
}
}
if (composerPage) break;
}
if (!composerPage) {
console.log('\nā No Composer page found in browser');
console.log(' Please navigate to https://composer.euconquisto.com');
return null;
}
console.log('\nš Extracting authentication data from localStorage...');
const authData = await composerPage.evaluate(() => {
console.log('=== EXISTING BROWSER AUTH EXTRACTION v1.0.0 ===');
const activeProject = localStorage.getItem('rdp-composer-active-project');
const userData = localStorage.getItem('rdp-composer-user-data');
console.log('Raw activeProject present:', !!activeProject);
console.log('Raw userData present:', !!userData);
if (!activeProject) {
return { success: false, error: 'Active project data not found in localStorage' };
}
if (!userData) {
return { success: false, error: 'User data not found in localStorage' };
}
let projectData, userDataParsed;
try {
projectData = JSON.parse(activeProject);
userDataParsed = JSON.parse(userData);
} catch (e) {
return { success: false, error: 'Failed to parse localStorage data: ' + e.message };
}
if (!projectData.uid) {
return { success: false, error: 'Project UID not found in active project data' };
}
if (!userDataParsed.access_token) {
return { success: false, error: 'Access token not found in user data' };
}
if (!projectData.connectors || !Array.isArray(projectData.connectors)) {
return { success: false, error: 'Connectors array not found or invalid in project data' };
}
// Extract all necessary data
const result = {
success: true,
extractedAt: new Date().toISOString(),
accessToken: userDataParsed.access_token,
tokenType: userDataParsed.token_type || 'Bearer',
projectUid: projectData.uid,
projectName: projectData.name || 'Unknown Project',
connectors: projectData.connectors.map(c => ({
uid: c.uid,
name: c.name,
type: c.type,
permissions: c.permissions || []
})),
userInfo: {
uid: userDataParsed.uid,
name: userDataParsed.name,
email: userDataParsed.email
}
};
console.log('Extraction successful:', {
projectUid: result.projectUid,
projectName: result.projectName,
connectorsCount: result.connectors.length,
hasAccessToken: !!result.accessToken,
tokenType: result.tokenType,
tokenLength: result.accessToken.length,
userUid: result.userInfo.uid,
userName: result.userInfo.name
});
return result;
});
this.extractedData = authData;
if (authData.success) {
console.log('\nā
Authentication data extracted successfully!');
console.log(` Project: ${authData.projectName} (${authData.projectUid})`);
console.log(` User: ${authData.userInfo.name} (${authData.userInfo.email})`);
console.log(` Token Type: ${authData.tokenType}`);
console.log(` Token Length: ${authData.accessToken.length} characters`);
console.log(` Connectors: ${authData.connectors.length} available`);
// List connectors
console.log('\nš” Available Connectors:');
authData.connectors.forEach((connector, index) => {
console.log(` ${index + 1}. ${connector.name || 'Unnamed'} (${connector.uid})`);
if (connector.permissions.length > 0) {
console.log(` Permissions: ${connector.permissions.join(', ')}`);
}
});
await this.saveConfiguration();
await this.generateEnvironmentFile();
await this.generateMCPConfig();
} else {
console.log('\nā Failed to extract authentication data:');
console.log(` Error: ${authData.error}`);
}
} catch (error) {
console.error('ā Extraction error:', error.message);
this.extractedData = { success: false, error: error.message };
} finally {
// Note: We don't close the browser since it's the user's active session
console.log('\n⨠Browser session remains open (not closing existing session)');
}
return this.extractedData;
}
/**
* Save configuration to JSON file
*/
async saveConfiguration() {
if (!this.extractedData || !this.extractedData.success) {
return;
}
try {
// Create config directory if it doesn't exist
const configDir = path.dirname(this.configPath);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Prepare config data (remove sensitive full tokens from saved file)
const configData = {
extractedAt: this.extractedData.extractedAt,
tokenType: this.extractedData.tokenType,
projectUid: this.extractedData.projectUid,
projectName: this.extractedData.projectName,
connectors: this.extractedData.connectors,
userInfo: this.extractedData.userInfo,
// Store token preview only in JSON file
accessTokenPreview: this.extractedData.accessToken.substring(0, 50) + '...',
note: 'Full access token stored in environment file for security'
};
fs.writeFileSync(this.configPath, JSON.stringify(configData, null, 2));
console.log(`\nš¾ Configuration saved to: ${this.configPath}`);
} catch (error) {
console.error('ā Failed to save configuration:', error.message);
}
}
/**
* Generate environment file with full authentication data
*/
async generateEnvironmentFile() {
if (!this.extractedData || !this.extractedData.success) {
return;
}
try {
const envContent = [
'# EuConquisto Composer Direct API Authentication',
'# Generated on: ' + new Date().toISOString(),
'# Source: Active Composer session localStorage extraction',
'',
'# Full JWT Access Token',
`EUCONQUISTO_ACCESS_TOKEN="${this.extractedData.accessToken}"`,
'',
'# Token Type (usually Bearer)',
`EUCONQUISTO_TOKEN_TYPE="${this.extractedData.tokenType}"`,
'',
'# Project Information',
`EUCONQUISTO_PROJECT_UID="${this.extractedData.projectUid}"`,
`EUCONQUISTO_PROJECT_NAME="${this.extractedData.projectName}"`,
'',
'# Connectors (JSON format)',
`EUCONQUISTO_CONNECTORS='${JSON.stringify(this.extractedData.connectors)}'`,
'',
'# User Information (optional)',
`EUCONQUISTO_USER_UID="${this.extractedData.userInfo.uid}"`,
`EUCONQUISTO_USER_NAME="${this.extractedData.userInfo.name}"`,
`EUCONQUISTO_USER_EMAIL="${this.extractedData.userInfo.email}"`,
'',
'# Usage:',
'# source config/direct-api.env',
'# export $(grep -v "^#" config/direct-api.env | xargs)',
''
].join('\n');
fs.writeFileSync(this.envPath, envContent);
console.log(`š¾ Environment file saved to: ${this.envPath}`);
console.log(' Use: source config/direct-api.env');
} catch (error) {
console.error('ā Failed to generate environment file:', error.message);
}
}
/**
* Generate MCP configuration for Claude Desktop
*/
async generateMCPConfig() {
if (!this.extractedData || !this.extractedData.success) {
return;
}
try {
const mcpConfig = {
"mcpServers": {
"euconquisto-composer-direct": {
"command": "node",
"args": [
"--max-old-space-size=4096",
path.resolve(process.cwd(), "dist", "index.js")
],
"env": {
"NODE_ENV": "production",
"EUCONQUISTO_ACCESS_TOKEN": this.extractedData.accessToken,
"EUCONQUISTO_TOKEN_TYPE": this.extractedData.tokenType,
"EUCONQUISTO_PROJECT_UID": this.extractedData.projectUid,
"EUCONQUISTO_CONNECTORS": JSON.stringify(this.extractedData.connectors),
"EUCONQUISTO_USER_UID": this.extractedData.userInfo.uid,
"EUCONQUISTO_USER_NAME": this.extractedData.userInfo.name
}
}
}
};
const mcpConfigPath = path.join(process.cwd(), 'config', 'claude-desktop-config-direct-api.json');
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
console.log(`š¾ MCP configuration saved to: ${mcpConfigPath}`);
console.log(' Add this to your Claude Desktop configuration');
} catch (error) {
console.error('ā Failed to generate MCP configuration:', error.message);
}
}
/**
* Output summary
*/
outputSummary() {
console.log('\n' + '='.repeat(70));
console.log('š AUTHENTICATION EXTRACTION SUMMARY');
console.log('='.repeat(70));
if (this.extractedData && this.extractedData.success) {
console.log('\nā
EXTRACTION SUCCESSFUL');
console.log(` Project: ${this.extractedData.projectName}`);
console.log(` User: ${this.extractedData.userInfo.name}`);
console.log(` Connectors: ${this.extractedData.connectors.length} available`);
console.log(` Extracted: ${this.extractedData.extractedAt}`);
console.log('\nš FILES CREATED:');
console.log(` Configuration: ${this.configPath}`);
console.log(` Environment: ${this.envPath}`);
console.log(` MCP Config: config/claude-desktop-config-direct-api.json`);
console.log('\nšÆ NEXT STEPS:');
console.log(' 1. Load environment: source config/direct-api.env');
console.log(' 2. Test direct API: node src/tools/direct-api-client.js');
console.log(' 3. Update Claude Desktop config if desired');
console.log(' 4. Consider replacing JWT redirect server with direct API calls');
console.log('\nš SECURITY NOTE:');
console.log(' ⢠Full JWT token stored in config/direct-api.env');
console.log(' ⢠Add config/direct-api.env to .gitignore');
console.log(' ⢠Token expires - re-extract when needed');
} else {
console.log('\nā EXTRACTION FAILED');
if (this.extractedData) {
console.log(` Error: ${this.extractedData.error}`);
}
}
console.log('\nš CURRENT PROJECT STATUS:');
console.log(' v5.2.0 FULLY OPERATIONAL system is preserved and unchanged');
console.log(' This extraction enables optional direct API approach');
console.log('\n' + '='.repeat(70) + '\n');
}
}
// Execute extraction if run directly
if (import.meta.url === `file://${process.argv[1]}`) {
const extractor = new ExistingBrowserExtractor();
extractor.extractFromExistingBrowser()
.then(() => {
extractor.outputSummary();
})
.catch(error => {
console.error('ā Extraction process error:', error.message);
process.exit(1);
});
}
export { ExistingBrowserExtractor };