feat: unified create and run robot route
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { createRemoteBrowserForRun, getActiveBrowserIdByState } from "../browser-management/controller";
|
import { createRemoteBrowserForRun, destroyRemoteBrowser, getActiveBrowserIdByState } from "../browser-management/controller";
|
||||||
import { chromium } from 'playwright-extra';
|
import { chromium } from 'playwright-extra';
|
||||||
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
|
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||||
import { browserPool } from "../server";
|
import { browserPool } from "../server";
|
||||||
@@ -517,98 +517,131 @@ router.put('/runs/:id', requireSignIn, async (req: AuthenticatedRequest, res) =>
|
|||||||
return res.status(401).send({ error: 'Unauthorized' });
|
return res.status(401).send({ error: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyConfig = await getDecryptedProxyConfig(req.user.id);
|
|
||||||
let proxyOptions: any = {};
|
|
||||||
|
|
||||||
if (proxyConfig.proxy_url) {
|
|
||||||
proxyOptions = {
|
|
||||||
server: proxyConfig.proxy_url,
|
|
||||||
...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
|
|
||||||
username: proxyConfig.proxy_username,
|
|
||||||
password: proxyConfig.proxy_password,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Proxy config for run: ${JSON.stringify(proxyOptions)}`);
|
|
||||||
|
|
||||||
// Generate runId first
|
// Generate runId first
|
||||||
const runId = uuid();
|
const runId = uuid();
|
||||||
|
|
||||||
// Check if user has reached browser limit
|
const canCreateBrowser = await browserPool.hasAvailableBrowserSlots(req.user.id, "run");
|
||||||
const userBrowserIds = browserPool.getAllBrowserIdsForUser(req.user.id);
|
|
||||||
const canCreateBrowser = userBrowserIds.length < 2;
|
|
||||||
|
|
||||||
if (canCreateBrowser) {
|
if (canCreateBrowser) {
|
||||||
// User has available browser slots, create it directly
|
let browserId: string;
|
||||||
const id = createRemoteBrowserForRun(req.user.id);
|
|
||||||
|
|
||||||
const run = await Run.create({
|
try {
|
||||||
status: 'running',
|
browserId = await createRemoteBrowserForRun(req.user.id);
|
||||||
|
|
||||||
|
if (!browserId || browserId.trim() === '') {
|
||||||
|
throw new Error('Failed to generate valid browser ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('info', `Created browser ${browserId} for run ${runId}`);
|
||||||
|
|
||||||
|
} catch (browserError: any) {
|
||||||
|
logger.log('error', `Failed to create browser: ${browserError.message}`);
|
||||||
|
return res.status(500).send({ error: 'Failed to create browser instance' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Run.create({
|
||||||
|
status: 'running',
|
||||||
|
name: recording.recording_meta.name,
|
||||||
|
robotId: recording.id,
|
||||||
|
robotMetaId: recording.recording_meta.id,
|
||||||
|
startedAt: new Date().toLocaleString(),
|
||||||
|
finishedAt: '',
|
||||||
|
browserId: browserId,
|
||||||
|
interpreterSettings: req.body,
|
||||||
|
log: '',
|
||||||
|
runId,
|
||||||
|
runByUserId: req.user.id,
|
||||||
|
serializableOutput: {},
|
||||||
|
binaryOutput: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log('info', `Created run ${runId} with browser ${browserId}`);
|
||||||
|
|
||||||
|
} catch (dbError: any) {
|
||||||
|
logger.log('error', `Database error creating run: ${dbError.message}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await destroyRemoteBrowser(browserId, req.user.id);
|
||||||
|
} catch (cleanupError: any) {
|
||||||
|
logger.log('warn', `Failed to cleanup browser after run creation failure: ${cleanupError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(500).send({ error: 'Failed to create run record' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userQueueName = `execute-run-user-${req.user.id}`;
|
||||||
|
await pgBoss.createQueue(userQueueName);
|
||||||
|
|
||||||
|
const jobId = await pgBoss.send(userQueueName, {
|
||||||
|
emailId: req.user.email,
|
||||||
|
userId: req.user.id,
|
||||||
|
runId: runId,
|
||||||
|
browserId: browserId,
|
||||||
|
interpreterSettings: req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log('info', `Queued run execution job with ID: ${jobId} for run: ${runId}`);
|
||||||
|
} catch (queueError: any) {
|
||||||
|
logger.log('error', `Failed to queue run execution: ${queueError.message}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Run.update({
|
||||||
|
status: 'failed',
|
||||||
|
finishedAt: new Date().toLocaleString(),
|
||||||
|
log: 'Failed to queue execution job'
|
||||||
|
}, { where: { runId: runId } });
|
||||||
|
|
||||||
|
await destroyRemoteBrowser(browserId, req.user.id);
|
||||||
|
} catch (cleanupError: any) {
|
||||||
|
logger.log('warn', `Failed to cleanup after queue error: ${cleanupError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(503).send({ error: 'Unable to queue run, please try again later' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
browserId: browserId,
|
||||||
|
runId: runId,
|
||||||
|
robotMetaId: recording.recording_meta.id,
|
||||||
|
queued: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const browserId = uuid();
|
||||||
|
|
||||||
|
await Run.create({
|
||||||
|
status: 'queued',
|
||||||
name: recording.recording_meta.name,
|
name: recording.recording_meta.name,
|
||||||
robotId: recording.id,
|
robotId: recording.id,
|
||||||
robotMetaId: recording.recording_meta.id,
|
robotMetaId: recording.recording_meta.id,
|
||||||
startedAt: new Date().toLocaleString(),
|
startedAt: new Date().toLocaleString(),
|
||||||
finishedAt: '',
|
finishedAt: '',
|
||||||
browserId: id,
|
browserId,
|
||||||
interpreterSettings: req.body,
|
interpreterSettings: req.body,
|
||||||
log: '',
|
log: 'Run queued - waiting for available browser slot',
|
||||||
runId,
|
runId,
|
||||||
runByUserId: req.user.id,
|
runByUserId: req.user.id,
|
||||||
serializableOutput: {},
|
serializableOutput: {},
|
||||||
binaryOutput: {},
|
binaryOutput: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const plainRun = run.toJSON();
|
|
||||||
|
|
||||||
return res.send({
|
return res.send({
|
||||||
browserId: id,
|
browserId: browserId,
|
||||||
runId: plainRun.runId,
|
runId: runId,
|
||||||
robotMetaId: recording.recording_meta.id,
|
robotMetaId: recording.recording_meta.id,
|
||||||
queued: false
|
queued: true
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
const browserId = getActiveBrowserIdByState(req.user.id, "run")
|
|
||||||
|
|
||||||
if (browserId) {
|
|
||||||
// User has reached the browser limit, queue the run
|
|
||||||
try {
|
|
||||||
// Create the run record with 'queued' status
|
|
||||||
await Run.create({
|
|
||||||
status: 'queued',
|
|
||||||
name: recording.recording_meta.name,
|
|
||||||
robotId: recording.id,
|
|
||||||
robotMetaId: recording.recording_meta.id,
|
|
||||||
startedAt: new Date().toLocaleString(),
|
|
||||||
finishedAt: '',
|
|
||||||
browserId: browserId, // Random will be updated later
|
|
||||||
interpreterSettings: req.body,
|
|
||||||
log: 'Run queued - waiting for available browser slot',
|
|
||||||
runId,
|
|
||||||
runByUserId: req.user.id,
|
|
||||||
serializableOutput: {},
|
|
||||||
binaryOutput: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.send({
|
|
||||||
browserId: browserId,
|
|
||||||
runId: runId,
|
|
||||||
robotMetaId: recording.recording_meta.id,
|
|
||||||
queued: true,
|
|
||||||
});
|
|
||||||
} catch (queueError: any) {
|
|
||||||
logger.log('error', `Failed to queue run job: ${queueError.message}`);
|
|
||||||
return res.status(503).send({ error: 'Unable to queue run, please try again later' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log('info', "Browser id does not exist");
|
|
||||||
return res.send('');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while creating a run with robot id: ${req.params.id} - ${message}`);
|
logger.log('error', `Error while creating a run with robot id: ${req.params.id} - ${message}`);
|
||||||
return res.send('');
|
|
||||||
|
if (message.includes('invalid input syntax for type uuid')) {
|
||||||
|
return res.status(400).send({ error: 'Invalid UUID format detected' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(500).send({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user