Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Dockerfile.backend
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@ COPY --from=builder /app/public ./public

EXPOSE ${BACKEND_PORT:-8080}

CMD ["npm", "run", "server"]
# CMD ["sh", "-c", "npm run migrate && npm run server"]
CMD ["sh", "-c", "npm run prepare-schema && npm run migrate && npm run server"]
2 changes: 2 additions & 0 deletions package.backend.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"lodash": "4.17.21",
"maxun-core": "0.0.37",
"minio": "8.0.5",
"mammoth": "1.12.1",
"moment-timezone": "0.5.48",
"multer": "2.1.1",
"node-cron": "3.0.3",
Expand Down Expand Up @@ -92,6 +93,7 @@
"scripts": {
"build": "tsc -p server/tsconfig.json",
"build:server": "tsc -p server/tsconfig.json",
"prepare-schema": "node server/dist/server/src/db/prepareSchema.js",
"server": "cross-env NODE_OPTIONS='--max-old-space-size=4096' node server/dist/server/src/server.js",
"migrate": "sequelize-cli db:migrate",
"migrate:undo": "sequelize-cli db:migrate:undo",
Expand Down
43 changes: 43 additions & 0 deletions server/src/api/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createRemoteBrowserForRun, destroyRemoteBrowser } from "../browser-mana
import logger from "../logger";
import { browserPool, io as serverIo } from "../server";
import { io, Socket } from "socket.io-client";
import jwt from "jsonwebtoken";
import { BinaryOutputService } from "../storage/mino";
import { AuthenticatedRequest } from "../routes/record"
import {capture} from "../utils/analytics";
Expand Down Expand Up @@ -1389,6 +1390,30 @@ async function executeRun(id: string, userId: string) {
}
}

async function executeSdkRunWhenReady(runId: string, browserId: string, userId: string): Promise<void> {
const timeoutMs = 60_000;
const startedAt = Date.now();

while (true) {
const browser = browserPool.getRemoteBrowser(browserId);
if (browser) break;

const status = browserPool.getBrowserStatus(browserId);
if (status === null) throw new Error(`Browser slot ${browserId} does not exist in pool`);
if (status === 'failed') throw new Error(`Browser ${browserId} initialization failed`);
if (Date.now() - startedAt >= timeoutMs) {
throw new Error(`Browser ${browserId} was not ready within ${timeoutMs / 1000}s`);
}

await new Promise(resolve => setTimeout(resolve, 500));
}

const result = await executeRun(runId, userId);
if (!result?.success) {
throw new Error(result?.error || `Failed to execute run ${runId}`);
}
}

export async function handleRunRecording(id: string, userId: string, runSource: 'api' | 'sdk' | 'mcp' | 'cli' = 'api', requestedFormats?: OutputFormats[], promptInstructions?: string) {
let socket: Socket | null = null;

Expand All @@ -1409,12 +1434,30 @@ export async function handleRunRecording(id: string, userId: string, runSource:
throw new Error('browserId is undefined for non-document robot');
}

// SDK execution is server-to-server. Execute directly after the local
// browser pool reports readiness instead of racing a Socket.IO
// namespace that may not exist yet. UI/API runs retain the interactive
// socket path below.
if (runSource === 'sdk') {
await executeSdkRunWhenReady(newRunId, browserId, userId);
return newRunId;
}

const CONNECTION_TIMEOUT = 30000;

const jwtSecret = process.env.JWT_SECRET;
if (!jwtSecret) throw new Error('JWT_SECRET is required for internal browser run sockets');
const internalToken = jwt.sign({
id: userId,
purpose: 'maxun-internal-run',
browserId,
}, jwtSecret, { expiresIn: '60s' });

socket = io(`${process.env.BACKEND_URL ? process.env.BACKEND_URL : 'http://localhost:8080'}/${browserId}`, {
transports: ['websocket'],
rejectUnauthorized: false,
timeout: CONNECTION_TIMEOUT,
...(internalToken ? { auth: { token: internalToken } } : {}),
});

const readyHandler = () => readyForRunHandler(browserId, newRunId, userId, socket!);
Expand Down
Loading