1import spawnAsync from '@expo/spawn-async';
2import { type ChildProcess } from 'child_process';
3import open from 'open';
4import path from 'path';
5
6import {
7  LaunchBrowserTypes,
8  type LaunchBrowserImpl,
9  type LaunchBrowserInstance,
10} from './LaunchBrowser.types';
11
12/**
13 * Browser implementation for Linux
14 */
15export default class LaunchBrowserImplLinux implements LaunchBrowserImpl, LaunchBrowserInstance {
16  private _appId: string | undefined;
17  private _process: ChildProcess | undefined;
18
19  MAP = {
20    [LaunchBrowserTypes.CHROME]: ['google-chrome', 'google-chrome-stable', 'chromium'],
21    [LaunchBrowserTypes.EDGE]: ['microsoft-edge', 'microsoft-edge-dev'],
22  };
23
24  /**
25   * On Linux, the supported appId is an array, this function finds the available appId and caches it
26   */
27  private async getAppId(browserType: LaunchBrowserTypes): Promise<string> {
28    if (this._appId == null || !this.MAP[browserType].includes(this._appId)) {
29      for (const appId of this.MAP[browserType]) {
30        try {
31          const { status } = await spawnAsync('which', [appId], { stdio: 'ignore' });
32          if (status === 0) {
33            this._appId = appId;
34            break;
35          }
36        } catch {}
37      }
38    }
39
40    if (this._appId == null) {
41      throw new Error(
42        `Unable to find supported browser - tried[${this.MAP[browserType].join(', ')}]`
43      );
44    }
45
46    return this._appId;
47  }
48
49  async isSupportedBrowser(browserType: LaunchBrowserTypes): Promise<boolean> {
50    let result = false;
51    try {
52      await this.getAppId(browserType);
53      result = true;
54    } catch {
55      result = false;
56    }
57    return result;
58  }
59
60  async createTempBrowserDir(baseDirName: string) {
61    return path.join(require('temp-dir'), baseDirName);
62  }
63
64  async launchAsync(
65    browserType: LaunchBrowserTypes,
66    args: string[]
67  ): Promise<LaunchBrowserInstance> {
68    const appId = await this.getAppId(browserType);
69    this._process = await open.openApp(appId, { arguments: args });
70    return this;
71  }
72
73  async close(): Promise<void> {
74    this._process?.kill();
75    this._process = undefined;
76    this._appId = undefined;
77  }
78}
79