From 837d6d270f5f73019b05089f905715a3af6d24ba Mon Sep 17 00:00:00 2001 From: kekwlboy12469 Date: Thu, 12 Mar 2026 21:51:36 +0530 Subject: [PATCH 1/4] proxy-override fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(robot): add robot-level proxy override support - Added proxy field to Robot model - Added proxy configuration in Robot edit UI - Updated storage API to send proxy value - Modified run execution flow to prioritize robot proxy over global proxy - Passed proxy options through controller → RemoteBrowser initialization - Ensured Playwright context launches with configured proxy Robot-level proxy now overrides global proxy during execution. --- legacy/src/RobotEdit.tsx | 79 +++++++---- server/src/api/record.ts | 25 ++-- .../classes/RemoteBrowser.ts | 123 ++++-------------- server/src/browser-management/controller.ts | 43 +++--- server/src/models/Robot.ts | 92 +++++++++---- src/api/storage.ts | 1 + 6 files changed, 177 insertions(+), 186 deletions(-) diff --git a/legacy/src/RobotEdit.tsx b/legacy/src/RobotEdit.tsx index 3b110ba17..f468d2333 100644 --- a/legacy/src/RobotEdit.tsx +++ b/legacy/src/RobotEdit.tsx @@ -44,6 +44,7 @@ export interface RobotSettings { google_access_token?: string | null; google_refresh_token?: string | null; schedule?: ScheduleConfig | null; + proxy?: string | null; } interface RobotSettingsProps { @@ -128,20 +129,19 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const extractedCredentials = extractInitialCredentials(robot.recording.workflow); setCredentials(extractedCredentials); setCredentialGroups(groupCredentialsByType(extractedCredentials)); - + findScrapeListLimits(robot.recording.workflow); } }, [robot]); const findScrapeListLimits = (workflow: WhereWhatPair[]) => { const limits: ScrapeListLimit[] = []; - + workflow.forEach((pair, pairIndex) => { if (!pair.what) return; - + pair.what.forEach((action, actionIndex) => { if (action.action === 'scrapeList' && action.args && action.args.length > 0) { - // Check if first argument has a limit property const arg = action.args[0]; if (arg && typeof arg === 'object' && 'limit' in arg) { limits.push({ @@ -154,7 +154,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin } }); }); - + setScrapeListLimits(limits); }; @@ -183,12 +183,12 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const selector = action.args[0]; - // Handle full word type actions first - if (action.action === 'type' && + if ( + action.action === 'type' && action.args?.length >= 2 && typeof action.args[1] === 'string' && - action.args[1].length > 1) { - + action.args[1].length > 1 + ) { if (!credentials[selector]) { credentials[selector] = { value: action.args[1], @@ -199,11 +199,11 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin continue; } - // Handle character-by-character sequences (both type and press) - if ((action.action === 'type' || action.action === 'press') && + if ( + (action.action === 'type' || action.action === 'press') && action.args?.length >= 2 && - typeof action.args[1] === 'string') { - + typeof action.args[1] === 'string' + ) { if (selector !== currentSelector) { if (currentSelector && currentValue) { credentials[currentSelector] = { @@ -231,9 +231,12 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin let j = i + 1; while (j < step.what.length) { const nextAction = step.what[j]; - if (!nextAction.action || !nextAction.args?.[0] || + if ( + !nextAction.action || + !nextAction.args?.[0] || nextAction.args[0] !== selector || - (nextAction.action !== 'type' && nextAction.action !== 'press')) { + (nextAction.action !== 'type' && nextAction.action !== 'press') + ) { break; } if (nextAction.args[1] === 'Backspace') { @@ -290,8 +293,11 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const getRobot = async () => { if (recordingId) { - const robot = await getStoredRecording(recordingId); - setRobot(robot); + const robotData = await getStoredRecording(recordingId); + setRobot({ + ...robotData, + proxy: robotData?.proxy ?? null + }); } else { notify('error', t('robot_edit.notifications.update_failed')); } @@ -310,6 +316,12 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin ); }; + const handleProxyChange = (newProxy: string) => { + setRobot((prev) => + prev ? { ...prev, proxy: newProxy } : prev + ); + }; + const handleCredentialChange = (selector: string, value: string) => { setCredentials(prev => ({ ...prev, @@ -333,12 +345,14 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin updatedWorkflow[pairIndex].what[actionIndex].args.length > argIndex ) { updatedWorkflow[pairIndex].what[actionIndex].args[argIndex].limit = newLimit; - - setScrapeListLimits(prev => { - return prev.map(item => { - if (item.pairIndex === pairIndex && - item.actionIndex === actionIndex && - item.argIndex === argIndex) { + + setScrapeListLimits(prevLimits => { + return prevLimits.map(item => { + if ( + item.pairIndex === pairIndex && + item.actionIndex === actionIndex && + item.argIndex === argIndex + ) { return { ...item, currentLimit: newLimit }; } return item; @@ -437,13 +451,13 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const renderScrapeListLimitFields = () => { if (scrapeListLimits.length === 0) return null; - + return ( <> {t('List Limits')} - + {scrapeListLimits.map((limitInfo, index) => ( + handleProxyChange(e.target.value)} + placeholder="http://username:password@host:port" + style={{ marginBottom: '20px' }} + /> + {renderScrapeListLimitFields()} {(Object.keys(credentials).length > 0) && ( @@ -573,7 +597,8 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin color: '#ff00c3 !important', borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', - }}> + }} + > {t('robot_edit.cancel')} diff --git a/server/src/api/record.ts b/server/src/api/record.ts index fe4f7f9ca..4661a4204 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -493,20 +493,27 @@ async function createWorkflowAndStoreMetadata(id: string, userId: string, isSDK: }; } - const proxyConfig = await getDecryptedProxyConfig(userId); let proxyOptions: any = {}; - if (proxyConfig.proxy_url) { + if ((recording as any).proxy) { proxyOptions = { - server: proxyConfig.proxy_url, - ...(proxyConfig.proxy_username && proxyConfig.proxy_password && { - username: proxyConfig.proxy_username, - password: proxyConfig.proxy_password, - }), + server: (recording as any).proxy, }; + } else { + const proxyConfig = await getDecryptedProxyConfig(userId); + + if (proxyConfig.proxy_url) { + proxyOptions = { + server: proxyConfig.proxy_url, + ...(proxyConfig.proxy_username && proxyConfig.proxy_password && { + username: proxyConfig.proxy_username, + password: proxyConfig.proxy_password, + }), + }; + } } - const browserId = createRemoteBrowserForRun(userId); + const browserId = createRemoteBrowserForRun(userId, proxyOptions); const runId = uuid(); @@ -543,7 +550,7 @@ async function createWorkflowAndStoreMetadata(id: string, userId: string, isSDK: runByAPI: plainRun.runByAPI || false, browserId: plainRun.browserId }; - + serverIo.of('/queued-run').to(`user-${userId}`).emit('run-started', runStartedData); logger.log('info', `API run started notification sent for run: ${plainRun.runId} to user-${userId}`); } catch (socketError: any) { diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 9d8560920..aca939af6 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -12,7 +12,6 @@ import { readFileSync } from "fs"; import { InterpreterSettings } from "../../types"; import { WorkflowGenerator } from "../../workflow-management/classes/Generator"; import { WorkflowInterpreter } from "../../workflow-management/classes/Interpreter"; -import { getDecryptedProxyConfig } from '../../routes/proxy'; import { getInjectableScript } from 'idcac-playwright'; import { FingerprintInjector } from "fingerprint-injector"; import { FingerprintGenerator } from "fingerprint-generator"; @@ -125,54 +124,6 @@ export class RemoteBrowser { this.isRecordingMode = isRecordingMode; } - // private initializeMemoryManagement(): void { - // this.memoryManagementInterval = setInterval(() => { - // const memoryUsage = process.memoryUsage(); - // const heapUsageRatio = memoryUsage.heapUsed / MEMORY_CONFIG.maxHeapSize; - - // if (heapUsageRatio > MEMORY_CONFIG.heapUsageThreshold * 1.2) { - // logger.warn( - // "Critical memory pressure detected, triggering emergency cleanup" - // ); - // this.performMemoryCleanup(); - // } else if (heapUsageRatio > MEMORY_CONFIG.heapUsageThreshold) { - // logger.warn("High memory usage detected, triggering cleanup"); - - // if ( - // global.gc && - // heapUsageRatio > MEMORY_CONFIG.heapUsageThreshold * 1.1 - // ) { - // global.gc(); - // } - // } - // }, MEMORY_CONFIG.gcInterval); - // } - - // private async performMemoryCleanup(): Promise { - // if (global.gc) { - // try { - // global.gc(); - // logger.info("Garbage collection requested"); - // } catch (error) { - // logger.error("Error during garbage collection:", error); - // } - // } - - // if (this.currentPage) { - // try { - // await new Promise((resolve) => setTimeout(resolve, 500)); - // logger.info("CDP session reset completed"); - // } catch (error) { - // logger.error("Error resetting CDP session:", error); - // } - // } - - // this.socket.emit("memory-cleanup", { - // userId: this.userId, - // timestamp: Date.now(), - // }); - // } - /** * Normalizes URLs to prevent navigation loops while maintaining consistent format */ @@ -414,26 +365,26 @@ export class RemoteBrowser { return userAgents[Math.floor(Math.random() * userAgents.length)]; } - /** - * Apply modern fingerprint-suite injection - */ - private async applyEnhancedFingerprinting(context: BrowserContext): Promise { - try { + /** + * Apply modern fingerprint-suite injection + */ + private async applyEnhancedFingerprinting(context: BrowserContext): Promise { try { - const fingerprintGenerator = new FingerprintGenerator(); - const fingerprint = fingerprintGenerator.getFingerprint(); - const fingerprintInjector = new FingerprintInjector(); + try { + const fingerprintGenerator = new FingerprintGenerator(); + const fingerprint = fingerprintGenerator.getFingerprint(); + const fingerprintInjector = new FingerprintInjector(); - await fingerprintInjector.attachFingerprintToPlaywright(context as any, fingerprint); + await fingerprintInjector.attachFingerprintToPlaywright(context as any, fingerprint); - logger.info("Enhanced fingerprinting applied successfully"); - } catch (fingerprintError: any) { - logger.warn(`Modern fingerprint injection failed: ${fingerprintError.message}. Using existing protection.`); + logger.info("Enhanced fingerprinting applied successfully"); + } catch (fingerprintError: any) { + logger.warn(`Modern fingerprint injection failed: ${fingerprintError.message}. Using existing protection.`); + } + } catch (error: any) { + logger.error(`Enhanced fingerprinting failed: ${error.message}`); } - } catch (error: any) { - logger.error(`Enhanced fingerprinting failed: ${error.message}`); } - } /** * An asynchronous constructor for asynchronously initialized properties. @@ -441,7 +392,10 @@ export class RemoteBrowser { * @param options remote browser options to be used when launching the browser * @returns {Promise} */ - public initialize = async (userId: string): Promise => { + public initialize = async ( + userId: string, + proxyOptions?: { server: string; username?: string; password?: string } + ): Promise => { const MAX_RETRIES = 3; const OVERALL_INIT_TIMEOUT = 120000; let retryCount = 0; @@ -464,36 +418,17 @@ export class RemoteBrowser { this.emitLoadingProgress(20, 0); - const proxyConfig = await getDecryptedProxyConfig(userId); - let proxyOptions: { server: string, username?: string, password?: string } = { server: '' }; - - if (proxyConfig.proxy_url) { - proxyOptions = { - server: proxyConfig.proxy_url, - ...(proxyConfig.proxy_username && proxyConfig.proxy_password && { - username: proxyConfig.proxy_username, - password: proxyConfig.proxy_password, - }), - }; - } - const contextOptions: any = { - // viewport: { height: 400, width: 900 }, - // recordVideo: { dir: 'videos/' } - // Force reduced motion to prevent animation issues reducedMotion: 'reduce', - // Force JavaScript to be enabled javaScriptEnabled: true, - // Set a reasonable timeout timeout: 50000, - // Disable hardware acceleration forcedColors: 'none', isMobile: false, hasTouch: false, userAgent: this.getUserAgent(), }; - if (proxyOptions.server) { + if (proxyOptions?.server) { contextOptions.proxy = { server: proxyOptions.server, username: proxyOptions.username ? proxyOptions.username : undefined, @@ -720,16 +655,6 @@ export class RemoteBrowser { public async switchOff(): Promise { this.isDOMStreamingActive = false; - // if (this.memoryCleanupInterval) { - // clearInterval(this.memoryCleanupInterval); - // this.memoryCleanupInterval = null; - // } - - // if (this.memoryManagementInterval) { - // clearInterval(this.memoryManagementInterval); - // this.memoryManagementInterval = null; - // } - this.removeAllSocketListeners(); try { @@ -850,7 +775,6 @@ export class RemoteBrowser { const workflow = this.generator.AddGeneratedFlags(this.generator.getWorkflowFile()); await this.initializeNewPage(); if (this.currentPage) { - // this.currentPage.setViewportSize({ height: 400, width: 900 }); const params = this.generator.getParams(); if (params) { this.interpreterSettings.params = params.reduce((acc, param) => { @@ -867,7 +791,6 @@ export class RemoteBrowser { (newPage: Page) => this.currentPage = newPage, this.interpreterSettings ); - // clear the active index from generator this.generator.clearLastIndex(); } else { logger.log('error', 'Could not get a new page, returned undefined'); @@ -899,10 +822,8 @@ export class RemoteBrowser { await this.setupPageEventListeners(this.currentPage); - //await this.currentPage.setViewportSize({ height: 400, width: 900 }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); - // Include userId in the URL change event - this.socket.emit('urlChanged', { + this.socket.emit('urlChanged', { url: this.currentPage.url(), userId: this.userId }); @@ -932,4 +853,4 @@ export class RemoteBrowser { logger.log('error', 'Could not get a new page, returned undefined'); } }; -} +} \ No newline at end of file diff --git a/server/src/browser-management/controller.ts b/server/src/browser-management/controller.ts index 47814c555..3abee3aea 100644 --- a/server/src/browser-management/controller.ts +++ b/server/src/browser-management/controller.ts @@ -34,7 +34,7 @@ export const initializeRemoteBrowserForRecording = (userId: string, mode: string } else { const browserSession = new RemoteBrowser(socket, userId, id, true); browserSession.interpreter.subscribeToPausing(); - + try { await browserSession.initialize(userId); await browserSession.registerEditorEvents(); @@ -81,15 +81,16 @@ export const initializeRemoteBrowserForRecording = (userId: string, mode: string * Creates a new {@link Socket} connection over a dedicated namespace. * Returns the new remote browser's generated id. * @param userId User ID for browser ownership + * @param proxyOptions Optional proxy options for this run * @returns string Browser ID * @category BrowserManagement-Controller */ -export const createRemoteBrowserForRun = (userId: string): string => { +export const createRemoteBrowserForRun = (userId: string, proxyOptions?: any): string => { if (!userId) { logger.log('error', 'createRemoteBrowserForRun: Missing required parameter userId'); throw new Error('userId is required'); } - + const id = uuid(); const slotReserved = browserPool.reserveBrowserSlotAtomic(id, userId, "run"); @@ -100,12 +101,12 @@ export const createRemoteBrowserForRun = (userId: string): string => { logger.log('info', `createRemoteBrowserForRun: Reserved slot ${id} for user ${userId}`); - initializeBrowserAsync(id, userId) + initializeBrowserAsync(id, userId, proxyOptions) .catch((error: any) => { logger.log('error', `Unhandled error in initializeBrowserAsync for browser ${id}: ${error.message}`); browserPool.failBrowserSlot(id); }); - + return id; }; @@ -292,12 +293,12 @@ export const stopRunningInterpretation = async (userId: string) => { } }; -const initializeBrowserAsync = async (id: string, userId: string) => { +const initializeBrowserAsync = async (id: string, userId: string, proxyOptions?: any) => { try { const namespace = io.of(id); let clientConnected = false; let connectionTimeout: NodeJS.Timeout; - + const waitForConnection = new Promise((resolve) => { namespace.on('connection', (socket: Socket) => { clientConnected = true; @@ -305,7 +306,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { logger.log('info', `Frontend connected to browser ${id} via socket ${socket.id}`); resolve(socket); }); - + connectionTimeout = setTimeout(() => { if (!clientConnected) { logger.log('warn', `No client connected to browser ${id} within timeout, proceeding with dummy socket`); @@ -322,7 +323,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { const connectWithRetry = async (maxRetries: number = 3): Promise => { let retryCount = 0; - + while (retryCount < maxRetries) { try { const socket = await waitForConnection; @@ -332,7 +333,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { } catch (error: any) { logger.log('warn', `Connection attempt ${retryCount + 1} failed for browser ${id}: ${error.message}`); } - + retryCount++; if (retryCount < maxRetries) { const delay = Math.pow(2, retryCount) * 1000; @@ -340,15 +341,15 @@ const initializeBrowserAsync = async (id: string, userId: string) => { await new Promise(resolve => setTimeout(resolve, delay)); } } - + return null; }; const socket = await connectWithRetry(3); - + try { let browserSession: RemoteBrowser; - + if (socket) { logger.log('info', `Using real socket for browser ${id}`); browserSession = new RemoteBrowser(socket, userId, id); @@ -361,7 +362,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { on: () => {}, id: `dummy-${id}`, } as any; - + browserSession = new RemoteBrowser(dummySocket, userId, id); } @@ -371,7 +372,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { const BROWSER_INIT_TIMEOUT = 45000; logger.log('info', `Browser initialization starting with ${BROWSER_INIT_TIMEOUT/1000}s timeout`); - const initPromise = browserSession.initialize(userId); + const initPromise = browserSession.initialize(userId, proxyOptions); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Browser initialization timeout')), BROWSER_INIT_TIMEOUT); }); @@ -397,9 +398,9 @@ const initializeBrowserAsync = async (id: string, userId: string) => { } throw new Error('Failed to upgrade reserved browser slot'); } - + await new Promise(resolve => setTimeout(resolve, 500)); - + if (socket) { socket.emit('ready-for-run'); } else { @@ -409,11 +410,11 @@ const initializeBrowserAsync = async (id: string, userId: string) => { } catch (error: any) { logger.log('error', `Error with dummy socket browser ${id}: ${error.message}`); } - }, 100); + }, 100); } - + logger.log('info', `Browser ${id} successfully initialized for run with ${socket ? 'real' : 'dummy'} socket`); - + } catch (error: any) { logger.log('error', `Error initializing browser ${id}: ${error.message}`); browserPool.failBrowserSlot(id); @@ -422,7 +423,7 @@ const initializeBrowserAsync = async (id: string, userId: string) => { } throw error; } - + } catch (error: any) { logger.log('error', `Error setting up browser ${id}: ${error.message}`); browserPool.failBrowserSlot(id); diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index d45ac502d..e55938d39 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -28,8 +28,21 @@ interface WebhookConfig { updatedAt: string; lastCalledAt?: string | null; retryAttempts?: number; - retryDelay?: number; - timeout?: number; + retryDelay?: number; + timeout?: number; +} + +interface ScheduleConfig { + runEvery: number; + runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; + startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; + atTimeStart?: string; + atTimeEnd?: string; + timezone: string; + lastRunAt?: Date; + nextRunAt?: Date; + dayOfMonth?: string; + cronExpression?: string; } interface RobotAttributes { @@ -37,52 +50,53 @@ interface RobotAttributes { userId?: number; recording_meta: RobotMeta; recording: RobotWorkflow; + + /** NEW: proxy for robot */ + proxy?: string | null; + google_sheet_email?: string | null; google_sheet_name?: string | null; google_sheet_id?: string | null; google_access_token?: string | null; google_refresh_token?: string | null; - airtable_base_id?: string | null; - airtable_base_name?: string | null; - airtable_table_name?: string | null; - airtable_access_token?: string | null; - airtable_refresh_token?: string | null; - schedule?: ScheduleConfig | null; + + airtable_base_id?: string | null; + airtable_base_name?: string | null; + airtable_table_name?: string | null; + airtable_access_token?: string | null; + airtable_refresh_token?: string | null; airtable_table_id?: string | null; - webhooks?: WebhookConfig[] | null; -} -interface ScheduleConfig { - runEvery: number; - runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; - startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; - atTimeStart?: string; - atTimeEnd?: string; - timezone: string; - lastRunAt?: Date; - nextRunAt?: Date; - dayOfMonth?: string; - cronExpression?: string; + schedule?: ScheduleConfig | null; + webhooks?: WebhookConfig[] | null; } -interface RobotCreationAttributes extends Optional { } +interface RobotCreationAttributes extends Optional {} class Robot extends Model implements RobotAttributes { + public id!: string; public userId!: number; + public recording_meta!: RobotMeta; public recording!: RobotWorkflow; + + /** NEW: proxy field */ + public proxy!: string | null; + public google_sheet_email!: string | null; public google_sheet_name!: string | null; public google_sheet_id!: string | null; public google_access_token!: string | null; public google_refresh_token!: string | null; - public airtable_base_id!: string | null; - public airtable_base_name!: string | null; - public airtable_table_name!: string | null; - public airtable_access_token!: string | null; - public airtable_refresh_token!: string | null; - public airtable_table_id!: string | null; + + public airtable_base_id!: string | null; + public airtable_base_name!: string | null; + public airtable_table_name!: string | null; + public airtable_access_token!: string | null; + public airtable_refresh_token!: string | null; + public airtable_table_id!: string | null; + public schedule!: ScheduleConfig | null; public webhooks!: WebhookConfig[] | null; } @@ -94,66 +108,88 @@ Robot.init( defaultValue: DataTypes.UUIDV4, primaryKey: true, }, + userId: { type: DataTypes.INTEGER, allowNull: false, }, + recording_meta: { type: DataTypes.JSONB, allowNull: false, }, + recording: { type: DataTypes.JSONB, allowNull: false, }, + + /** NEW proxy column */ + proxy: { + type: DataTypes.STRING, + allowNull: true, + }, + google_sheet_email: { type: DataTypes.STRING, allowNull: true, }, + google_sheet_name: { type: DataTypes.STRING, allowNull: true, }, + google_sheet_id: { type: DataTypes.STRING, allowNull: true, }, + google_access_token: { type: DataTypes.STRING, allowNull: true, }, + google_refresh_token: { type: DataTypes.STRING, allowNull: true, }, + airtable_base_id: { type: DataTypes.STRING, allowNull: true, }, + airtable_base_name: { type: DataTypes.STRING, allowNull: true, }, + airtable_table_name: { type: DataTypes.STRING, allowNull: true, }, + airtable_table_id: { type: DataTypes.STRING, allowNull: true, }, + airtable_access_token: { type: DataTypes.TEXT, allowNull: true, }, + airtable_refresh_token: { type: DataTypes.TEXT, allowNull: true, }, + schedule: { type: DataTypes.JSONB, allowNull: true, }, + webhooks: { type: DataTypes.JSONB, allowNull: true, diff --git a/src/api/storage.ts b/src/api/storage.ts index b8c751111..2e4360e18 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -103,6 +103,7 @@ export const updateRecording = async (id: string, data: { credentials?: Credentials; targetUrl?: string; workflow?: any[]; + proxy?: string | null; }): Promise => { try { const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data); From 78a529e2f7ac17d8cba15380be30a9bea6563737 Mon Sep 17 00:00:00 2001 From: kekwlboy12469 Date: Thu, 12 Mar 2026 23:22:55 +0530 Subject: [PATCH 2/4] changes fixed --- .../classes/RemoteBrowser.ts | 24 ++++++++----------- .../20260312120000-add-proxy-to-robot.js | 23 ++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 server/src/db/migrations/20260312120000-add-proxy-to-robot.js diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index aca939af6..d95a6152a 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -369,22 +369,18 @@ export class RemoteBrowser { * Apply modern fingerprint-suite injection */ private async applyEnhancedFingerprinting(context: BrowserContext): Promise { - try { - try { - const fingerprintGenerator = new FingerprintGenerator(); - const fingerprint = fingerprintGenerator.getFingerprint(); - const fingerprintInjector = new FingerprintInjector(); + try { + const fingerprintGenerator = new FingerprintGenerator(); + const fingerprint = fingerprintGenerator.getFingerprint(); + const fingerprintInjector = new FingerprintInjector(); - await fingerprintInjector.attachFingerprintToPlaywright(context as any, fingerprint); + await fingerprintInjector.attachFingerprintToPlaywright(context as any, fingerprint); - logger.info("Enhanced fingerprinting applied successfully"); - } catch (fingerprintError: any) { - logger.warn(`Modern fingerprint injection failed: ${fingerprintError.message}. Using existing protection.`); - } - } catch (error: any) { - logger.error(`Enhanced fingerprinting failed: ${error.message}`); - } - } + logger.info("Enhanced fingerprinting applied successfully"); + } catch (fingerprintError: any) { + logger.warn(`Modern fingerprint injection failed: ${fingerprintError.message}. Using existing protection.`); + } +} /** * An asynchronous constructor for asynchronously initialized properties. diff --git a/server/src/db/migrations/20260312120000-add-proxy-to-robot.js b/server/src/db/migrations/20260312120000-add-proxy-to-robot.js new file mode 100644 index 000000000..ccb5f4dab --- /dev/null +++ b/server/src/db/migrations/20260312120000-add-proxy-to-robot.js @@ -0,0 +1,23 @@ +'use strict'; + +module.exports = { + async up(queryInterface, Sequelize) { + const table = await queryInterface.describeTable('robot'); + + if (!table.proxy) { + await queryInterface.addColumn('robot', 'proxy', { + type: Sequelize.STRING, + allowNull: true, + comment: 'Optional proxy URL for this robot; overrides default proxy when set' + }); + } + }, + + async down(queryInterface, Sequelize) { + const table = await queryInterface.describeTable('robot'); + + if (table.proxy) { + await queryInterface.removeColumn('robot', 'proxy'); + } + } +}; \ No newline at end of file From 743df9a44215366923fa1e10d32fb16dd518f8ad Mon Sep 17 00:00:00 2001 From: kekwlboy12469 Date: Fri, 13 Mar 2026 21:27:26 +0530 Subject: [PATCH 3/4] changesfixed --- server/src/api/record.ts | 51 ++++++++++--------- server/src/api/sdk.ts | 1 + .../classes/RemoteBrowser.ts | 4 +- server/src/routes/storage.ts | 10 +++- .../workflow-management/classes/Generator.ts | 5 +- 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/server/src/api/record.ts b/server/src/api/record.ts index 4661a4204..a7d8242d4 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -1523,31 +1523,32 @@ router.post("/robots/:id/duplicate", requireAPIKey, async (req: Request, res: Re const currentTimestamp = new Date().toLocaleString(); const newRobot = await Robot.create({ - id: uuid(), - userId: originalRobot.userId, - recording_meta: { - ...originalRobot.recording_meta, - id: uuid(), - name: `${originalRobot.recording_meta.name} (${lastWord})`, - url: targetUrl, - createdAt: currentTimestamp, - updatedAt: currentTimestamp, - }, - recording: { ...originalRobot.recording, workflow }, - google_sheet_email: null, - google_sheet_name: null, - google_sheet_id: null, - google_access_token: null, - google_refresh_token: null, - airtable_base_id: null, - airtable_base_name: null, - airtable_table_name: null, - airtable_table_id: null, - airtable_access_token: null, - airtable_refresh_token: null, - webhooks: null, - schedule: null, - }); + id: uuid(), + userId: originalRobot.userId, + recording_meta: { + ...originalRobot.recording_meta, + id: uuid(), + name: `${originalRobot.recording_meta.name} (${lastWord})`, + url: targetUrl, + createdAt: currentTimestamp, + updatedAt: currentTimestamp, + }, + recording: { ...originalRobot.recording, workflow }, + proxy: originalRobot.proxy, + google_sheet_email: null, + google_sheet_name: null, + google_sheet_id: null, + google_access_token: null, + google_refresh_token: null, + airtable_base_id: null, + airtable_base_name: null, + airtable_table_name: null, + airtable_table_id: null, + airtable_access_token: null, + airtable_refresh_token: null, + webhooks: null, + schedule: null, +}); logger.log('info', `Robot with ID ${id} duplicated successfully as ${newRobot.id}.`); diff --git a/server/src/api/sdk.ts b/server/src/api/sdk.ts index 7a67c3103..4dba5d011 100644 --- a/server/src/api/sdk.ts +++ b/server/src/api/sdk.ts @@ -947,6 +947,7 @@ router.post("/sdk/extract/llm", requireAPIKey, async (req: AuthenticatedRequest, capture("maxun-oss-llm-robot-created", { robot_meta: robot.recording_meta, recording: robot.recording, + proxy: null, prompt: prompt }); diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index d95a6152a..76282d7bd 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -427,8 +427,8 @@ export class RemoteBrowser { if (proxyOptions?.server) { contextOptions.proxy = { server: proxyOptions.server, - username: proxyOptions.username ? proxyOptions.username : undefined, - password: proxyOptions.password ? proxyOptions.password : undefined, + username: proxyOptions.username, + password: proxyOptions.password, }; } diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 6b57bc6b9..a11b8ba59 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -419,6 +419,7 @@ router.post('/recordings/scrape', requireSignIn, async (req: AuthenticatedReques formats: formats, }, recording: { workflow: [] }, + proxy: null, google_sheet_email: null, google_sheet_name: null, google_sheet_id: null, @@ -531,6 +532,7 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, google_sheet_id: null, google_access_token: null, google_refresh_token: null, + proxy: null, schedule: null, }); @@ -674,6 +676,7 @@ router.post('/recordings/:id/duplicate', requireSignIn, async (req: Authenticate airtable_refresh_token: null, webhooks: null, schedule: null, + proxy: null, }); logger.log('info', `Robot with ID ${id} duplicated successfully as ${newRobot.id}.`); @@ -1463,7 +1466,9 @@ router.post('/recordings/crawl', requireSignIn, async (req: AuthenticatedRequest airtable_access_token: null, airtable_refresh_token: null, schedule: null, - webhooks: null + webhooks: null, + proxy: null, + }); logger.log('info', `Crawl robot created with id: ${newRobot.id}`); @@ -1550,7 +1555,8 @@ router.post('/recordings/search', requireSignIn, async (req: AuthenticatedReques airtable_access_token: null, airtable_refresh_token: null, schedule: null, - webhooks: null + webhooks: null, + proxy: null, }); logger.log('info', `Search robot created with id: ${newRobot.id}`); diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 25ecf9f76..181591bbf 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -71,6 +71,8 @@ export class WorkflowGenerator { private poolId: string | null = null; + private userId: string = ''; + private pageCloseListeners: Map void> = new Map(); /** @@ -1016,6 +1018,7 @@ export class WorkflowGenerator { userId, recording_meta: this.recordingMeta, recording: recording, + proxy: null, }); capture( 'maxun-oss-robot-created', @@ -1132,7 +1135,7 @@ export class WorkflowGenerator { */ public notifyUrlChange = (url: string) => { if (this.socket) { - this.socket.emit('urlChanged', url); + this.socket.emit('urlChanged', { url, userId: this.userId }) } } From b17de5764365a738f85207c39144269d80920095 Mon Sep 17 00:00:00 2001 From: kekwlboy12469 Date: Fri, 13 Mar 2026 21:50:03 +0530 Subject: [PATCH 4/4] fixeddd userid declaration --- server/src/browser-management/classes/RemoteBrowser.ts | 2 +- server/src/workflow-management/classes/Generator.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 76282d7bd..64d8788e2 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -120,7 +120,7 @@ export class RemoteBrowser { this.socket = socket; this.userId = userId; this.interpreter = new WorkflowInterpreter(socket); - this.generator = new WorkflowGenerator(socket, poolId); + this.generator = new WorkflowGenerator(socket, poolId, userId); this.isRecordingMode = isRecordingMode; } diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 181591bbf..138178657 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -81,9 +81,10 @@ export class WorkflowGenerator { * @param socket The socket used to communicate with the client. * @constructor */ - public constructor(socket: Socket, poolId: string) { + constructor(socket: Socket, poolId: string, userId: string) { this.socket = socket; this.poolId = poolId; + this.userId = userId; this.registerEventHandlers(socket); this.initializeSocketListeners(); this.initializeDOMListeners();