install-native-sync-host.mjs

5.7 KB · raw

1#!/usr/bin/env node2// Installs the com.peachpasswords.sync native messaging host manifest into3// the per-browser NativeMessagingHosts directory so chrome.runtime.connectNative4// can find the Peach sync host binary at runtime.5//6// Unix (macOS / Linux): writes a JSON file under the user's NativeMessagingHosts7// directory. Windows requires a registry entry and is not handled directly —8// the script prints the JSON path and the registry key the user must add.9//10// Usage:11//   node scripts/install-native-sync-host.mjs --browser chrome --extension-id <id>12//   node scripts/install-native-sync-host.mjs --browser firefox13//14// Optional flags:15//   --host-path <abs-path>   Override the binary path (default: release build)16//   --dry-run                Print what would be written; do not touch the FS17import { mkdir, writeFile } from "node:fs/promises";18import os from "node:os";19import path from "node:path";20import process from "node:process";21import { pathToFileURL } from "node:url";2223import { buildNativeManifest } from "./generate-native-sync-manifest.mjs";2425const HOST_FILENAME = "com.peachpasswords.sync.json";2627function usage() {28  return [29    "Usage:",30    "  node scripts/install-native-sync-host.mjs --browser chrome --extension-id <id>",31    "  node scripts/install-native-sync-host.mjs --browser firefox [--extension-id <gecko-id>]",32    "",33    "Optional:",34    "  --host-path <abs-path>   Override binary path (default: release build)",35    "  --dry-run                Print the destination only; do not write",36  ].join("\n");37}3839function parseArgs(argv) {40  const options = {};41  for (let i = 0; i < argv.length; i += 1) {42    const arg = argv[i];43    if (arg === "--help" || arg === "-h") {44      options.help = true;45      continue;46    }47    if (arg === "--dry-run") {48      options.dryRun = true;49      continue;50    }51    if (arg === "--show-dir") {52      options.showDir = true;53      continue;54    }55    if (arg === "--platform-override") {56      options.platformOverride = argv[i + 1];57      i += 1;58      continue;59    }60    if (arg === "--home-override") {61      options.homeOverride = argv[i + 1];62      i += 1;63      continue;64    }65    if (!arg.startsWith("--")) {66      throw new Error(`unexpected argument: ${arg}`);67    }68    const key = arg.slice(2);69    const value = argv[i + 1];70    if (!value || value.startsWith("--")) {71      throw new Error(`missing value for --${key}`);72    }73    options[toCamel(key)] = value;74    i += 1;75  }76  return options;77}7879function toCamel(value) {80  return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());81}8283/**84 * Resolves the OS-specific NativeMessagingHosts directory for the requested85 * browser. Returns `{ dir, registryHint }` — `registryHint` is only set on86 * Windows where a registry write is also required.87 */88export function resolveInstallDir(browser, platform = process.platform, home = os.homedir()) {89  if (browser !== "chrome" && browser !== "firefox") {90    throw new Error("--browser must be chrome or firefox");91  }9293  if (platform === "darwin") {94    if (browser === "chrome") {95      return {96        dir: path.join(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts"),97      };98    }99    return {100      dir: path.join(home, "Library", "Application Support", "Mozilla", "NativeMessagingHosts"),101    };102  }103104  if (platform === "linux") {105    if (browser === "chrome") {106      return { dir: path.join(home, ".config", "google-chrome", "NativeMessagingHosts") };107    }108    return { dir: path.join(home, ".mozilla", "native-messaging-hosts") };109  }110111  if (platform === "win32") {112    // Windows uses the registry. We still write the JSON to a stable path so113    // the registry value can point at it.114    const dir = path.join(home, "AppData", "Local", "Peach", "NativeMessagingHosts");115    const registryRoot =116      browser === "chrome"117        ? "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.peachpasswords.sync"118        : "HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\com.peachpasswords.sync";119    return {120      dir,121      registryHint: `Add a registry value at ${registryRoot} with the (Default) string set to ${path.join(122        dir,123        HOST_FILENAME124      )}`,125    };126  }127128  throw new Error(`unsupported platform: ${platform}`);129}130131async function main() {132  const options = parseArgs(process.argv.slice(2));133  if (options.help) {134    console.log(usage());135    return;136  }137  if (!options.browser) {138    throw new Error("--browser is required");139  }140141  const platform = options.platformOverride ?? process.platform;142  const home = options.homeOverride ?? os.homedir();143  const { dir, registryHint } = resolveInstallDir(options.browser, platform, home);144145  if (options.showDir) {146    process.stdout.write(JSON.stringify({ dir, registryHint: registryHint ?? null }));147    return;148  }149150  const targetPath = path.join(dir, HOST_FILENAME);151  const manifest = await buildNativeManifest(options);152  const json = `${JSON.stringify(manifest, null, 2)}\n`;153154  if (options.dryRun) {155    console.log(`Would write ${targetPath}`);156    process.stdout.write(json);157    if (registryHint) console.log(`\n${registryHint}`);158    return;159  }160161  await mkdir(dir, { recursive: true });162  await writeFile(targetPath, json, { mode: 0o644 });163  console.log(`Installed ${targetPath}`);164  if (registryHint) {165    console.log(registryHint);166    console.log(167      "Run `reg add` with the path above, or use the Registry Editor — Windows requires this entry to discover the host."168    );169  }170}171172if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {173  main().catch((err) => {174    console.error(err instanceof Error ? err.message : String(err));175    console.error(usage());176    process.exitCode = 1;177  });178}