local.ts•4.53 kB
import logger from "../logger.js";
import { execSync } from "child_process";
import { Local } from "browserstack-local";
import config from "../config.js";
async function isBrowserStackLocalRunning() {
  // Check if BrowserStackLocal binary is already running
  try {
    if (process.platform === "win32") {
      const result = execSync(
        'tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"',
        {
          encoding: "utf8",
        },
      );
      if (result.includes("BrowserStackLocal.exe")) {
        logger.info("BrowserStackLocal binary is already running");
        return true;
      }
    } else {
      const result = execSync("pgrep -f BrowserStackLocal", {
        encoding: "utf8",
        stdio: "pipe",
      }).toString();
      if (result) {
        logger.info("BrowserStackLocal binary is already running");
        return true;
      }
    }
    logger.info("BrowserStackLocal binary is not running");
    return false;
  } catch (error) {
    logger.info(
      "Error checking BrowserStackLocal status, assuming not running ... " +
        error,
    );
    return false;
  }
}
export async function killExistingBrowserStackLocalProcesses() {
  const isRunning = await isBrowserStackLocalRunning();
  if (!isRunning) {
    return;
  }
  // Check and kill any existing BrowserStackLocal processes before starting new one
  try {
    if (process.platform === "win32") {
      // Check if process exists on Windows
      const checkResult = execSync(
        'tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"',
        { encoding: "utf8" },
      );
      if (checkResult.includes("BrowserStackLocal.exe")) {
        execSync("taskkill /F /IM BrowserStackLocal.exe", { stdio: "ignore" });
        logger.info("Successfully killed existing BrowserStackLocal processes");
      }
    } else {
      // Check if process exists on Unix-like systems
      const checkResult = execSync("pgrep -f BrowserStackLocal", {
        encoding: "utf8",
        stdio: "pipe",
      }).toString();
      if (checkResult) {
        execSync("pkill -f BrowserStackLocal", { stdio: "ignore" });
        logger.info("Successfully killed existing BrowserStackLocal processes");
      }
    }
  } catch (error) {
    logger.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
    // Continue execution as there may not be any processes running
  }
}
export async function ensureLocalBinarySetup(
  username: string,
  password: string,
  localIdentifier?: string,
): Promise<void> {
  logger.info(
    "Ensuring local binary setup as it is required for private URLs...",
  );
  if (config.USE_OWN_LOCAL_BINARY_PROCESS) {
    logger.info(
      "Using user's own BrowserStack Local binary process, checking if it's running...",
    );
    const isRunning = await isBrowserStackLocalRunning();
    if (!isRunning) {
      throw new Error(
        "USE_OWN_LOCAL_BINARY_PROCESS is enabled but BrowserStack Local process is not running. Please start your BrowserStack Local binary process first.",
      );
    }
    logger.info(
      "BrowserStack Local process is running, proceeding with user's own process.",
    );
    return;
  }
  const localBinary = new Local();
  await killExistingBrowserStackLocalProcesses();
  // Use a single options object from config and extend with required fields
  const bsLocalArgs: Record<string, any> = {
    ...(config.browserstackLocalOptions || {}),
    key: password,
    username,
  };
  if (localIdentifier) {
    bsLocalArgs.localIdentifier = localIdentifier;
  }
  return await new Promise((resolve, reject) => {
    localBinary.start(bsLocalArgs, (error?: Error) => {
      if (error) {
        logger.error(
          `Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`,
        );
        reject(
          new Error(
            `Unable to configure local tunnel binary, please check your credentials and try again. Error: ${error}`,
          ),
        );
      } else {
        logger.info("Successfully started BrowserStack Local");
        resolve();
      }
    });
  });
}
export function isLocalURL(url: string): boolean {
  try {
    const urlObj = new URL(url);
    const hostname = urlObj.hostname.toLowerCase();
    return (
      hostname === "localhost" ||
      hostname === "127.0.0.1" ||
      hostname.endsWith(".local") ||
      hostname.endsWith(".localhost")
    );
  } catch (error) {
    logger.error(`Error checking if URL is local: ${error}`);
    return false;
  }
}