feat: add separate browser service
This commit is contained in:
9
browser/.dockerignore
Normal file
9
browser/.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.env
|
||||
.git
|
||||
.gitignore
|
||||
dist
|
||||
*.ts
|
||||
!*.d.ts
|
||||
tsconfig.json
|
||||
30
browser/Dockerfile
Normal file
30
browser/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY browser/package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy TypeScript source and config
|
||||
COPY browser/server.ts ./
|
||||
COPY browser/tsconfig.json ./
|
||||
|
||||
# Build TypeScript
|
||||
RUN npm run build
|
||||
|
||||
# Accept build arguments for ports (with defaults)
|
||||
ARG BROWSER_WS_PORT=3001
|
||||
ARG BROWSER_HEALTH_PORT=3002
|
||||
|
||||
# Set as environment variables
|
||||
ENV BROWSER_WS_PORT=${BROWSER_WS_PORT}
|
||||
ENV BROWSER_HEALTH_PORT=${BROWSER_HEALTH_PORT}
|
||||
|
||||
# Expose ports dynamically based on build args
|
||||
EXPOSE ${BROWSER_WS_PORT} ${BROWSER_HEALTH_PORT}
|
||||
|
||||
# Start the browser service (run compiled JS)
|
||||
CMD ["node", "dist/server.js"]
|
||||
21
browser/package.json
Normal file
21
browser/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "maxun-browser-service",
|
||||
"version": "1.0.0",
|
||||
"description": "Browser service that exposes Playwright browsers via WebSocket with stealth plugins",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/server.js",
|
||||
"dev": "ts-node server.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "1.57.0",
|
||||
"playwright-extra": "^4.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.7.9",
|
||||
"typescript": "^5.0.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
92
browser/server.ts
Normal file
92
browser/server.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { chromium } from 'playwright-extra';
|
||||
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||
import http from 'http';
|
||||
import type { BrowserServer } from 'playwright';
|
||||
|
||||
// Apply stealth plugin to chromium
|
||||
chromium.use(stealthPlugin());
|
||||
|
||||
let browserServer: BrowserServer | null = null;
|
||||
|
||||
// Configurable ports with defaults
|
||||
const BROWSER_WS_PORT = parseInt(process.env.BROWSER_WS_PORT || '3001', 10);
|
||||
const BROWSER_HEALTH_PORT = parseInt(process.env.BROWSER_HEALTH_PORT || '3002', 10);
|
||||
|
||||
async function start(): Promise<void> {
|
||||
console.log('Starting Maxun Browser Service...');
|
||||
console.log(`WebSocket port: ${BROWSER_WS_PORT}`);
|
||||
console.log(`Health check port: ${BROWSER_HEALTH_PORT}`);
|
||||
|
||||
try {
|
||||
// Launch browser server that exposes WebSocket endpoint
|
||||
browserServer = await chromium.launchServer({
|
||||
headless: true,
|
||||
args: [
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--disable-web-security',
|
||||
'--disable-features=IsolateOrigins,site-per-process',
|
||||
'--disable-site-isolation-trials',
|
||||
'--disable-extensions',
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-gpu',
|
||||
'--force-color-profile=srgb',
|
||||
'--force-device-scale-factor=2',
|
||||
'--ignore-certificate-errors',
|
||||
'--mute-audio'
|
||||
],
|
||||
port: BROWSER_WS_PORT,
|
||||
});
|
||||
|
||||
console.log(`✅ Browser WebSocket endpoint ready: ${browserServer.wsEndpoint()}`);
|
||||
console.log(`✅ Stealth plugin enabled`);
|
||||
|
||||
// Health check HTTP server
|
||||
const healthServer = http.createServer((req, res) => {
|
||||
if (req.url === '/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
status: 'healthy',
|
||||
wsEndpoint: browserServer?.wsEndpoint(),
|
||||
wsPort: BROWSER_WS_PORT,
|
||||
healthPort: BROWSER_HEALTH_PORT,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
} else if (req.url === '/') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end(`Maxun Browser Service\nWebSocket: ${browserServer?.wsEndpoint()}\nHealth: http://localhost:${BROWSER_HEALTH_PORT}/health`);
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
|
||||
healthServer.listen(BROWSER_HEALTH_PORT, () => {
|
||||
console.log(`✅ Health check server running on port ${BROWSER_HEALTH_PORT}`);
|
||||
console.log('Browser service is ready to accept connections!');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to start browser service:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
async function shutdown(): Promise<void> {
|
||||
console.log('Shutting down browser service...');
|
||||
if (browserServer) {
|
||||
try {
|
||||
await browserServer.close();
|
||||
console.log('Browser server closed');
|
||||
} catch (error) {
|
||||
console.error('Error closing browser server:', error);
|
||||
}
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
// Start the service
|
||||
start().catch(console.error);
|
||||
24
browser/tsconfig.json
Normal file
24
browser/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"ES2020"
|
||||
],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
"server.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user