Steel / api /src /services /selenium.service.ts
supernovagateway's picture
Upload folder using huggingface_hub
fb38ec5 verified
import { EventEmitter } from "events";
import { ChildProcess, spawn } from "child_process";
import { BrowserLauncherOptions, BrowserEvent, BrowserEventType } from "../types/index.js";
import path, { dirname } from "path";
import { FastifyBaseLogger } from "fastify";
import { fileURLToPath } from "url";
export class SeleniumService extends EventEmitter {
private seleniumProcess: ChildProcess | null = null;
private seleniumServerUrl: string = "http://localhost:4444";
private port: number = 4444;
private launchOptions?: BrowserLauncherOptions;
private logger: FastifyBaseLogger;
constructor(logger: FastifyBaseLogger) {
super();
this.logger = logger;
}
public async getChromeArgs(): Promise<string[]> {
const { options, userAgent } = this.launchOptions ?? {};
return [
"disable-dev-shm-usage",
"no-sandbox",
"enable-javascript",
userAgent ? `user-agent=${userAgent}` : "",
options?.proxyUrl ? `proxy-server=${options.proxyUrl}` : "",
...(options?.args?.map((arg) => (arg.startsWith("--") ? arg.slice(2) : arg)) || []),
].filter(Boolean);
}
public async launch(launchOptions: BrowserLauncherOptions): Promise<void> {
this.launchOptions = launchOptions;
if (this.seleniumProcess) {
await this.close();
}
const projectRoot = path.resolve(dirname(fileURLToPath(import.meta.url)), "../../");
const seleniumServerPath = path.join(projectRoot, "selenium", "server", "selenium-server.jar");
const seleniumArgs = ["-jar", seleniumServerPath, "standalone"];
this.seleniumProcess = spawn("java", seleniumArgs);
this.seleniumServerUrl = `http://localhost:${this.port}`;
this.seleniumProcess.stdout?.on("data", (data) => {
this.logger.info(`Selenium stdout: ${data}`);
this.postLog({
type: BrowserEventType.Console,
text: JSON.stringify({ type: BrowserEventType.Console, message: `${data}` }),
timestamp: new Date(),
});
});
this.seleniumProcess.stderr?.on("data", (data) => {
this.logger.error(`Selenium stderr: ${data}`);
this.postLog({
type: BrowserEventType.Error,
text: JSON.stringify({ type: BrowserEventType.Error, error: `${data}` }),
timestamp: new Date(),
});
});
this.seleniumProcess.on("close", (code) => {
this.logger.info(`Selenium process exited with code ${code}`);
this.seleniumProcess = null;
});
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Selenium server failed to start within the timeout period"));
}, 15000); // 15 seconds timeout
this.seleniumProcess!.stdout?.on("data", (data) => {
if (data.toString().includes("Started Selenium Standalone")) {
clearTimeout(timeout);
resolve();
}
});
});
}
public close(): void {
if (this.seleniumProcess) {
this.seleniumProcess.kill("SIGINT");
this.seleniumProcess = null;
}
}
public getSeleniumServerUrl(): string {
return this.seleniumServerUrl;
}
private async postLog(browserLog: BrowserEvent) {
if (!this.launchOptions?.logSinkUrl) {
return;
}
await fetch(this.launchOptions.logSinkUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(browserLog),
});
}
}