1import * as osascript from '@expo/osascript';
2import { spawn, type ChildProcess } from 'child_process';
3import { sync as globSync } from 'glob';
4import path from 'path';
5
6import {
7  LaunchBrowserTypes,
8  type LaunchBrowserImpl,
9  type LaunchBrowserInstance,
10} from './LaunchBrowser.types';
11
12/**
13 * Browser implementation for macOS
14 */
15export default class LaunchBrowserImplMacOS implements LaunchBrowserImpl, LaunchBrowserInstance {
16  private _process: ChildProcess | undefined;
17
18  MAP = {
19    [LaunchBrowserTypes.CHROME]: 'google chrome',
20    [LaunchBrowserTypes.EDGE]: 'microsoft edge',
21  };
22
23  async isSupportedBrowser(browserType: LaunchBrowserTypes): Promise<boolean> {
24    let result = false;
25    try {
26      await osascript.execAsync(`id of application "${this.MAP[browserType]}"`);
27      result = true;
28    } catch {
29      result = false;
30    }
31    return result;
32  }
33
34  async createTempBrowserDir(baseDirName: string) {
35    return path.join(require('temp-dir'), baseDirName);
36  }
37
38  async launchAsync(
39    browserType: LaunchBrowserTypes,
40    args: string[]
41  ): Promise<LaunchBrowserInstance> {
42    const appDirectory = await osascript.execAsync(
43      `POSIX path of (path to application "${this.MAP[browserType]}")`
44    );
45    const appPath = globSync('Contents/MacOS/*', { cwd: appDirectory.trim(), absolute: true })?.[0];
46    if (!appPath) {
47      throw new Error(`Cannot find application path from ${appDirectory}Contents/MacOS`);
48    }
49    this._process = spawn(appPath, args, { stdio: 'ignore' });
50
51    return this;
52  }
53
54  async close(): Promise<void> {
55    this._process?.kill();
56    this._process = undefined;
57  }
58}
59