1234567891011121314151617import { 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}82838485868788export 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 113 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}