@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build standalone distributable binaries for the native host.
|
||||
*
|
||||
* Uses bare-pack to bundle the JS module graph (with builtins for optional
|
||||
* deps that use try/catch), then bare-build's platform modules to embed the
|
||||
* bundle into a pre-built Bare runtime binary, producing a self-contained
|
||||
* executable with no external dependencies.
|
||||
*
|
||||
* Node.js built-ins (net, tls, util, events, crypto, fs, path, os, stream,
|
||||
* assert) used by node-rdpjs-2 and bunyan are mapped to their Bare equivalents
|
||||
* via the `imports` field in native-host/package.json.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/build-distributable.js # current host only
|
||||
* node scripts/build-distributable.js --all # all platforms
|
||||
* node scripts/build-distributable.js --host darwin-arm64 --host darwin-x64
|
||||
* node scripts/build-distributable.js --package # also create .zip archives
|
||||
*
|
||||
* Output:
|
||||
* releases/
|
||||
* ├── darwin-arm64/holesail-browser-host # macOS Apple Silicon
|
||||
* ├── darwin-x64/holesail-browser-host # macOS Intel
|
||||
* ├── linux-arm64/holesail-browser-host # Linux ARM64
|
||||
* ├── linux-x64/holesail-browser-host # Linux x64
|
||||
* └── win32-x64/holesail-browser-host.exe # Windows x64
|
||||
*/
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
const { execSync } = require('child_process')
|
||||
const { pathToFileURL } = require('url')
|
||||
|
||||
const ROOT = path.join(__dirname, '..')
|
||||
const NATIVE_HOST_DIR = path.join(ROOT, 'native-host')
|
||||
const RELEASES_DIR = path.join(ROOT, 'releases')
|
||||
const ENTRY = path.join(NATIVE_HOST_DIR, 'index.mjs')
|
||||
|
||||
const ALL_HOSTS = [
|
||||
'darwin-arm64',
|
||||
'darwin-x64',
|
||||
'linux-arm64',
|
||||
'linux-x64',
|
||||
'win32-x64'
|
||||
]
|
||||
|
||||
// Optional deps that use try/catch at runtime — skip static bundling.
|
||||
// bare-pack resolves these to builtin:<name> and the bundler ignores them.
|
||||
// tt-native and node-rdpjs-2 are NOT in this list — they are patched into the
|
||||
// bundle manually after packing (see patchBundle below).
|
||||
const BUILTINS = [
|
||||
'source-map-support', // optional in bunyan
|
||||
'dtrace-provider' // optional DTrace in bunyan
|
||||
]
|
||||
|
||||
function getCurrentHost() {
|
||||
const platform = os.platform()
|
||||
const arch = os.arch() === 'arm64' ? 'arm64' : 'x64'
|
||||
return `${platform}-${arch}`
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2)
|
||||
const hosts = []
|
||||
let all = false
|
||||
let doPackage = false
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--all') {
|
||||
all = true
|
||||
} else if (args[i] === '--package') {
|
||||
doPackage = true
|
||||
} else if (args[i] === '--host' && args[i + 1]) {
|
||||
hosts.push(args[++i])
|
||||
}
|
||||
}
|
||||
|
||||
if (all) return { hosts: ALL_HOSTS, doPackage }
|
||||
if (hosts.length > 0) return { hosts, doPackage }
|
||||
return { hosts: [getCurrentHost()], doPackage }
|
||||
}
|
||||
|
||||
function getPlatformModule(host) {
|
||||
const bareBuildDir = path.dirname(require.resolve('bare-build'))
|
||||
switch (host) {
|
||||
case 'darwin-arm64':
|
||||
case 'darwin-x64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/apple'))
|
||||
case 'linux-arm64':
|
||||
case 'linux-x64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/linux'))
|
||||
case 'win32-x64':
|
||||
case 'win32-arm64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/windows'))
|
||||
default:
|
||||
throw new Error(`Unknown host '${host}'`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the bundle after bare-pack to fix two compatibility issues:
|
||||
*
|
||||
* 1. tt-native addon detection:
|
||||
* bare-pack cannot detect `require('load-addon')(__dirname)` as an addon
|
||||
* import. We replace tt-native/binding.js with `module.exports = require.addon()`
|
||||
* and set its resolutions map to the prebuild path — the same structure that
|
||||
* bare-pack generates for addons using the standard `require.addon()` pattern.
|
||||
* bare-unpack will then extract the prebuild to a temp dir and remap the URL.
|
||||
*
|
||||
* 2. node-rdpjs-2 EventEmitter compatibility:
|
||||
* node-rdpjs-2 uses `util.inherits(Class, EventEmitter)` which calls
|
||||
* `EventEmitter.call(this)` — this throws on ES6 classes. We replace the
|
||||
* `events` bundle entry with a shim that wraps bare-events so EventEmitter
|
||||
* can be called as a plain constructor function via util.inherits.
|
||||
*/
|
||||
function patchBundle(bundle, hosts) {
|
||||
// ── Fix 1: tt-native addon ──────────────────────────────────────────────────
|
||||
const ttNativeBindingKey = '/node_modules/tt-native/binding.js'
|
||||
const ttNativePkgKey = '/node_modules/tt-native/package.json'
|
||||
|
||||
if (bundle.read(ttNativeBindingKey) !== null) {
|
||||
// Find the prebuild for each target host
|
||||
const addedPrebuilds = []
|
||||
|
||||
for (const host of hosts) {
|
||||
const prebuiltKey = `/node_modules/tt-native/prebuilds/${host}/tt-native.bare`
|
||||
const diskPath = path.join(NATIVE_HOST_DIR, prebuiltKey.slice(1))
|
||||
|
||||
if (!fs.existsSync(diskPath)) continue
|
||||
|
||||
// Write the prebuild binary into the bundle
|
||||
bundle.write(prebuiltKey, fs.readFileSync(diskPath), { addon: true })
|
||||
addedPrebuilds.push(prebuiltKey)
|
||||
}
|
||||
|
||||
if (addedPrebuilds.length > 0) {
|
||||
// Replace binding.js with the standard require.addon() pattern.
|
||||
// The resolutions map tells bare-unpack which prebuild to extract.
|
||||
// We use the first matching host's prebuild as the "." resolution.
|
||||
const primaryPrebuild = addedPrebuilds[0]
|
||||
bundle.write(
|
||||
ttNativeBindingKey,
|
||||
Buffer.from('module.exports = require.addon()\n'),
|
||||
{
|
||||
imports: {
|
||||
'#package': ttNativePkgKey,
|
||||
'.': primaryPrebuild
|
||||
}
|
||||
}
|
||||
)
|
||||
console.log(` Patched tt-native binding → ${primaryPrebuild}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fix 2b: bunyan runtime detection ────────────────────────────────────────
|
||||
// bunyan checks process.versions.node to detect Node.js. Bare doesn't set
|
||||
// this, so bunyan falls through to the browser branch and tries window.location.
|
||||
// Fix: patch bunyan.js to treat Bare as a node-like environment.
|
||||
const bunyanKey = '/node_modules/bunyan/lib/bunyan.js'
|
||||
const bunyanSrc = bundle.read(bunyanKey)
|
||||
if (bunyanSrc !== null) {
|
||||
let src = bunyanSrc.toString()
|
||||
// Insert a Bare-compatibility shim right before the runtimeEnv detection block.
|
||||
// This sets process.versions.node if running in Bare so bunyan picks the right branch.
|
||||
const BARE_SHIM = `// Bare runtime compatibility: treat Bare as node-like\nif (typeof process !== 'undefined' && process.versions && process.versions.bare && !process.versions.node) { process.versions.node = process.versions.bare; }\n`
|
||||
src = BARE_SHIM + src
|
||||
bundle.write(bunyanKey, Buffer.from(src))
|
||||
console.log(' Patched bunyan for Bare runtime detection')
|
||||
}
|
||||
|
||||
// ── Fix 2: node-rdpjs-2 EventEmitter compatibility ──────────────────────────
|
||||
// Find the events module key in the bundle (it's the bare-node-events wrapper)
|
||||
const eventsKey = '/node_modules/events/index.js'
|
||||
|
||||
if (bundle.read(eventsKey) !== null) {
|
||||
// Replace with a shim that makes EventEmitter callable as a plain function.
|
||||
// util.inherits does: SuperCtor.call(this) — which fails on ES6 classes.
|
||||
// The shim wraps the class in a function so both `new EventEmitter()` and
|
||||
// `EventEmitter.call(this)` work correctly.
|
||||
const eventShim = `
|
||||
'use strict';
|
||||
const BareEvents = require('bare-events');
|
||||
|
||||
// Wrap the ES6 class so it can be called as a plain constructor function.
|
||||
// node-rdpjs-2 uses util.inherits(Class, EventEmitter) which calls
|
||||
// EventEmitter.call(this) — this throws on ES6 classes without this wrapper.
|
||||
// bare-events constructor only does: this._events = Object.create(null)
|
||||
// We replicate that initialization directly instead of calling the class.
|
||||
function EventEmitter() {
|
||||
if (!(this instanceof EventEmitter)) return new EventEmitter();
|
||||
// Replicate bare-events constructor (cannot call ES6 class as function)
|
||||
this._events = Object.create(null);
|
||||
}
|
||||
|
||||
// Copy prototype methods from bare-events
|
||||
EventEmitter.prototype = Object.create(BareEvents.prototype, {
|
||||
constructor: { value: EventEmitter, writable: true, configurable: true }
|
||||
});
|
||||
|
||||
Object.setPrototypeOf(EventEmitter, BareEvents);
|
||||
|
||||
// Copy all static properties
|
||||
const staticKeys = Object.getOwnPropertyNames(BareEvents);
|
||||
for (const key of staticKeys) {
|
||||
if (key !== 'prototype' && key !== 'length' && key !== 'name') {
|
||||
try { EventEmitter[key] = BareEvents[key]; } catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter.EventEmitter = EventEmitter;
|
||||
module.exports = EventEmitter;
|
||||
`.trim()
|
||||
|
||||
bundle.write(eventsKey, Buffer.from(eventShim))
|
||||
console.log(' Patched events module for node-rdpjs-2 compatibility')
|
||||
}
|
||||
}
|
||||
|
||||
async function build(hosts, doPackage) {
|
||||
// Ensure native-host deps are installed
|
||||
if (!fs.existsSync(path.join(NATIVE_HOST_DIR, 'node_modules'))) {
|
||||
console.log('Installing native-host dependencies...')
|
||||
execSync('npm install', { cwd: NATIVE_HOST_DIR, stdio: 'inherit' })
|
||||
}
|
||||
|
||||
fs.mkdirSync(RELEASES_DIR, { recursive: true })
|
||||
|
||||
const pack = require('bare-pack')
|
||||
const { readModule, listPrefix } = require('bare-pack/fs')
|
||||
const traverse = require('bare-module-traverse')
|
||||
const bundleId = require('bare-bundle-id')
|
||||
|
||||
const pkg = require(path.join(NATIVE_HOST_DIR, 'package.json'))
|
||||
|
||||
console.log(`\nBuilding holesail-browser-host v${pkg.version}`)
|
||||
console.log(`Targets: ${hosts.join(', ')}`)
|
||||
console.log(`Entry: ${ENTRY}`)
|
||||
console.log(`Output: ${RELEASES_DIR}\n`)
|
||||
|
||||
// Bundle the module graph with bare-pack.
|
||||
// - `linked: false` so native addon prebuilds are embedded in the binary
|
||||
// - `builtins` marks optional try/catch deps as provided by runtime (skipped)
|
||||
// - The `imports` field in native-host/package.json maps Node.js built-ins
|
||||
// to their Bare equivalents (bare-node-* wrappers)
|
||||
console.log(' Bundling module graph...')
|
||||
let bundle = await pack(
|
||||
pathToFileURL(ENTRY),
|
||||
{
|
||||
hosts,
|
||||
linked: false,
|
||||
resolve: traverse.resolve.bare,
|
||||
builtins: BUILTINS
|
||||
},
|
||||
readModule,
|
||||
listPrefix
|
||||
)
|
||||
|
||||
bundle = bundle.unmount(pathToFileURL(NATIVE_HOST_DIR + '/'))
|
||||
|
||||
// Patch the bundle to fix two issues that bare-pack cannot handle automatically:
|
||||
//
|
||||
// 1. tt-native uses `require('load-addon')(__dirname)` which calls
|
||||
// `require.addon(referrer)` with a non-default specifier. bare-pack cannot
|
||||
// statically detect this pattern, so the addon prebuild is never added to
|
||||
// bundle.addons and bare-unpack never extracts it to disk.
|
||||
// Fix: replace tt-native/binding.js with `module.exports = require.addon()`
|
||||
// (same pattern as bare-tcp/binding.js) and set its resolutions map to point
|
||||
// to the prebuild — exactly what bare-pack would do for a normal addon.
|
||||
//
|
||||
// 2. node-rdpjs-2 uses `util.inherits(Class, EventEmitter)` which calls
|
||||
// EventEmitter.call(this) — this fails on bare-events' ES6 class.
|
||||
// Fix: replace the `events` module in the bundle with a shim that wraps the
|
||||
// ES6 class so it can be called as a plain function too.
|
||||
patchBundle(bundle, hosts)
|
||||
|
||||
bundle.id = bundleId(bundle).toString('hex')
|
||||
|
||||
const bundleSize = bundle.toBuffer().length
|
||||
console.log(` Bundle size: ${(bundleSize / 1024 / 1024).toFixed(1)} MB`)
|
||||
|
||||
// Group hosts by platform module (apple handles both darwin-arm64 + darwin-x64 together)
|
||||
const groups = new Map()
|
||||
for (const host of hosts) {
|
||||
const platform = getPlatformModule(host)
|
||||
if (!groups.has(platform)) groups.set(platform, [])
|
||||
groups.get(platform).push(host)
|
||||
}
|
||||
|
||||
const built = []
|
||||
|
||||
for (const [platform, platformHosts] of groups) {
|
||||
for await (const file of platform(NATIVE_HOST_DIR, bundle, null, {
|
||||
name: 'holesail-browser-host',
|
||||
version: pkg.version,
|
||||
description: pkg.description,
|
||||
hosts: platformHosts,
|
||||
out: RELEASES_DIR,
|
||||
standalone: true
|
||||
})) {
|
||||
console.log(' Built:', path.relative(ROOT, file))
|
||||
built.push(file)
|
||||
}
|
||||
}
|
||||
|
||||
if (doPackage) {
|
||||
await createZipArchives(hosts, built)
|
||||
}
|
||||
|
||||
console.log('\nDone.')
|
||||
return built
|
||||
}
|
||||
|
||||
async function createZipArchives(hosts, builtFiles) {
|
||||
let archiver
|
||||
try {
|
||||
archiver = require('archiver')
|
||||
} catch {
|
||||
console.warn(' Skipping zip archives: archiver not installed')
|
||||
return
|
||||
}
|
||||
|
||||
// Group built files by host. bare-build may output one file per host group
|
||||
// (e.g. apple produces one fat binary for all darwin hosts), so we zip each
|
||||
// unique built file once, named after the first host in its group.
|
||||
const seen = new Set()
|
||||
let hostIdx = 0
|
||||
|
||||
for (const file of builtFiles) {
|
||||
if (seen.has(file)) continue
|
||||
seen.add(file)
|
||||
|
||||
const host = hosts[hostIdx++] || 'unknown'
|
||||
const zipName = `holesail-browser-host-${host}.zip`
|
||||
const zipPath = path.join(RELEASES_DIR, zipName)
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const output = fs.createWriteStream(zipPath)
|
||||
const archive = archiver('zip', { zlib: { level: 9 } })
|
||||
|
||||
output.on('close', resolve)
|
||||
archive.on('error', reject)
|
||||
|
||||
archive.pipe(output)
|
||||
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
archive.directory(file, false)
|
||||
} else {
|
||||
archive.file(file, { name: path.basename(file) })
|
||||
}
|
||||
|
||||
archive.finalize()
|
||||
})
|
||||
|
||||
console.log(' Packaged:', zipName)
|
||||
}
|
||||
}
|
||||
|
||||
const { hosts, doPackage } = parseArgs()
|
||||
|
||||
build(hosts, doPackage).catch((err) => {
|
||||
console.error('\nBuild failed:', err.message || err)
|
||||
if (err.cause) console.error('Cause:', err.cause)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build script for the native host.
|
||||
* Generates the holesail-browser-host launcher script.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const nativeHostDir = path.join(__dirname, '..', 'native-host');
|
||||
const launcherPath = path.join(nativeHostDir, 'holesail-browser-host');
|
||||
|
||||
// Find node path
|
||||
const nodeBin = process.execPath;
|
||||
|
||||
// Try to find bare - check common locations
|
||||
let bareBin = null;
|
||||
try {
|
||||
bareBin = execSync('which bare', { encoding: 'utf8' }).trim();
|
||||
} catch (e) {}
|
||||
|
||||
if (!bareBin) {
|
||||
// Try common paths on macOS
|
||||
for (const p of ['/opt/homebrew/bin/bare', '/usr/local/bin/bare']) {
|
||||
try {
|
||||
fs.accessSync(p, fs.constants.X_OK);
|
||||
bareBin = p;
|
||||
break;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bareBin) {
|
||||
// Fall back to node's sibling
|
||||
bareBin = nodeBin.replace(/node$/, 'bare');
|
||||
}
|
||||
|
||||
const launcherContent = `#!/usr/bin/env bash
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec "${bareBin}" "$DIR/index.mjs" "$@"
|
||||
`;
|
||||
|
||||
fs.writeFileSync(launcherPath, launcherContent);
|
||||
fs.chmodSync(launcherPath, '755');
|
||||
|
||||
console.log('Native host launcher generated at:', launcherPath);
|
||||
console.log('Using bare:', bareBin);
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
/**
|
||||
* Generates a new extension ID and updates manifest.json and com.holesail.browser.json.
|
||||
* Run on install so each copy of the template gets a unique ID (no overwrites).
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.join(__dirname, '..');
|
||||
const manifestPath = path.join(repoRoot, 'extension', 'manifest.json');
|
||||
const nativeManifestPath = path.join(repoRoot, 'com.holesail.browser.json');
|
||||
|
||||
// Generate RSA 2048-bit key pair
|
||||
const { publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: 'spki', format: 'der' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'der' }
|
||||
});
|
||||
|
||||
// Chrome extension ID: first 16 bytes of SHA256(publicKey), each byte -> 2 chars a-p
|
||||
// (Chromium crx_id.py: HexToMPDecimal)
|
||||
const hash = crypto.createHash('sha256').update(publicKey).digest();
|
||||
const chromeId = Array.from(hash.slice(0, 16))
|
||||
.map(b => String.fromCharCode(97 + (b >> 4)) + String.fromCharCode(97 + (b & 0x0f)))
|
||||
.join('');
|
||||
|
||||
// Firefox ID: unique per install
|
||||
const firefoxId = 'holesail-browser-' + crypto.randomBytes(8).toString('hex') + '@example.org';
|
||||
|
||||
// Base64-encode public key for manifest
|
||||
const keyBase64 = publicKey.toString('base64');
|
||||
|
||||
// Update extension manifest
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
manifest.key = keyBase64;
|
||||
if (manifest.browser_specific_settings?.gecko) {
|
||||
manifest.browser_specific_settings.gecko.id = firefoxId;
|
||||
}
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
||||
|
||||
// Update native host manifest template
|
||||
const nativeManifest = JSON.parse(fs.readFileSync(nativeManifestPath, 'utf8'));
|
||||
nativeManifest.allowed_origins = ['chrome-extension://' + chromeId + '/'];
|
||||
nativeManifest.allowed_extensions = [firefoxId];
|
||||
fs.writeFileSync(nativeManifestPath, JSON.stringify(nativeManifest, null, 2) + '\n');
|
||||
|
||||
console.log('Generated extension ID:', chromeId, '(' + firefoxId + ')');
|
||||
@@ -0,0 +1,11 @@
|
||||
# Install only the extension (copy path, open browser). For full install use scripts/install.ps1.
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path
|
||||
$ExtDir = (Resolve-Path (Join-Path $RepoRoot "extension")).Path
|
||||
if (-not (Test-Path (Join-Path $ExtDir "manifest.json"))) { Write-Error "extension/manifest.json not found."; exit 1 }
|
||||
Set-Clipboard -Value $ExtDir
|
||||
$p = "${env:ProgramFiles}\Google\Chrome\Application\chrome.exe","${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe","${env:LocalAppData}\Google\Chrome\Application\chrome.exe" | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if ($p) { Start-Process $p -ArgumentList "chrome://extensions" }
|
||||
Write-Host "Extension path (clipboard): $ExtDir"
|
||||
Write-Host "Open the extension page, click 'Load unpacked', paste the path."
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install only the extension (copy path, open browser). For full install use scripts/install.sh.
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
EXT_DIR="$REPO_ROOT/extension"
|
||||
[[ ! -f "$EXT_DIR/manifest.json" ]] && echo "Error: extension/manifest.json not found." && exit 1
|
||||
|
||||
command -v pbcopy >/dev/null 2>&1 && echo "$EXT_DIR" | pbcopy
|
||||
command -v xclip >/dev/null 2>&1 && echo -n "$EXT_DIR" | xclip -selection clipboard 2>/dev/null || true
|
||||
command -v xsel >/dev/null 2>&1 && echo -n "$EXT_DIR" | xsel --clipboard 2>/dev/null || true
|
||||
|
||||
open_page() { local u="$1" a="$2"; [[ "$OSTYPE" == "darwin"* ]] && open -a "$a" "$u" 2>/dev/null && return 0; command -v xdg-open >/dev/null 2>&1 && xdg-open "$u" 2>/dev/null; }
|
||||
open_page "chrome://extensions" "Google Chrome" || open_page "chrome://extensions" "Chromium" || open_page "chrome://extensions" "Microsoft Edge" || true
|
||||
echo "Extension path (clipboard): $EXT_DIR"
|
||||
echo "Open the extension page, click 'Load unpacked', paste the path."
|
||||
@@ -0,0 +1,34 @@
|
||||
# Install only the native messaging host (Windows). For full install use scripts/install.ps1.
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path
|
||||
$HostDir = Join-Path $RepoRoot "native-host"
|
||||
Set-Location $HostDir
|
||||
|
||||
Write-Host "Installing native-host dependencies..."
|
||||
if (-not (Test-Path (Join-Path $HostDir "node_modules"))) {
|
||||
npm install --no-fund --no-audit 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { npm install }
|
||||
} else {
|
||||
Write-Host " node_modules exists, skipping npm install"
|
||||
}
|
||||
|
||||
$nodePath = (Get-Command node -ErrorAction SilentlyContinue).Source
|
||||
$barePath = (Get-Command bare -ErrorAction SilentlyContinue).Source
|
||||
if (-not $nodePath) { $nodePath = "node" }
|
||||
if (-not $barePath) { $barePath = "bare" }
|
||||
|
||||
$batContent = "@echo off`r`nset `"DIR=%~dp0`"`r`n`"$nodePath`" `"$barePath`" `"%DIR%index.mjs`" %*"
|
||||
Set-Content (Join-Path $HostDir "holesail-browser-host.bat") -Value $batContent -Encoding ASCII
|
||||
$HostPath = Join-Path $HostDir "holesail-browser-host.bat"
|
||||
|
||||
$manifest = Get-Content (Join-Path $RepoRoot "com.holesail.browser.json") -Raw | ConvertFrom-Json
|
||||
$manifest.path = $HostPath
|
||||
$manifestFile = Join-Path $env:LOCALAPPDATA "holesail-browser\com.holesail.browser.json"
|
||||
New-Item -ItemType Directory -Path (Split-Path $manifestFile) -Force | Out-Null
|
||||
$manifest | ConvertTo-Json -Depth 4 | Set-Content $manifestFile -Encoding UTF8
|
||||
New-Item -Path "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.holesail.browser" -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.holesail.browser" -Name "(Default)" -Value $manifestFile
|
||||
New-Item -Path "HKCU:\Software\Mozilla\NativeMessagingHosts\com.holesail.browser" -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKCU:\Software\Mozilla\NativeMessagingHosts\com.holesail.browser" -Name "(Default)" -Value $manifestFile
|
||||
Write-Host "Done. Manifest: $manifestFile"
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install only the native messaging host (macOS/Linux). For full install use scripts/install.sh.
|
||||
#
|
||||
# If a standalone distributable binary exists in releases/ it is used directly
|
||||
# (no bare runtime required). Otherwise falls back to a bare launcher script.
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
HOST_DIR="$REPO_ROOT/native-host"
|
||||
|
||||
# Detect current platform/arch for distributable lookup
|
||||
PLATFORM="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH="$(uname -m)"
|
||||
[[ "$ARCH" == "x86_64" ]] && ARCH="x64"
|
||||
[[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]] && ARCH="arm64"
|
||||
[[ "$PLATFORM" == "darwin" ]] && HOST_TRIPLE="darwin-$ARCH"
|
||||
[[ "$PLATFORM" == "linux" ]] && HOST_TRIPLE="linux-$ARCH"
|
||||
|
||||
# Check for a pre-built standalone binary in releases/
|
||||
DIST_BINARY="$REPO_ROOT/releases/holesail-browser-host"
|
||||
if [[ -x "$DIST_BINARY" ]]; then
|
||||
echo "Using standalone distributable binary: $DIST_BINARY"
|
||||
HOST_PATH="$DIST_BINARY"
|
||||
else
|
||||
echo "No distributable binary found, building bare launcher..."
|
||||
cd "$HOST_DIR"
|
||||
|
||||
echo "Installing native-host dependencies..."
|
||||
if [[ ! -d "$HOST_DIR/node_modules" ]]; then
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
else
|
||||
echo " node_modules exists, skipping npm install"
|
||||
fi
|
||||
|
||||
BARE_PATH="$(which bare 2>/dev/null)" || true
|
||||
[[ -z "$BARE_PATH" ]] && for c in /opt/homebrew/bin/bare /usr/local/bin/bare; do [[ -x "$c" ]] && BARE_PATH="$c" && break; done
|
||||
NODE_PATH=""
|
||||
if [[ -n "$BARE_PATH" ]]; then
|
||||
BARE_DIR="${BARE_PATH%/*}"
|
||||
for c in "$BARE_DIR/node" "$(which node 2>/dev/null)" /opt/homebrew/bin/node /usr/local/bin/node; do
|
||||
[[ -x "$c" ]] && NODE_PATH="$c" && break
|
||||
done
|
||||
fi
|
||||
[[ -z "$NODE_PATH" ]] && NODE_PATH="node"
|
||||
[[ -z "$BARE_PATH" ]] && BARE_PATH="bare"
|
||||
|
||||
cat > "$HOST_DIR/holesail-browser-host" << EOF
|
||||
#!/usr/bin/env bash
|
||||
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
|
||||
exec "$NODE_PATH" "$BARE_PATH" "\$DIR/index.mjs" "\$@"
|
||||
EOF
|
||||
chmod +x "$HOST_DIR/holesail-browser-host"
|
||||
HOST_PATH="$HOST_DIR/holesail-browser-host"
|
||||
echo "Done. Wrapper: $NODE_PATH $BARE_PATH"
|
||||
fi
|
||||
|
||||
CHROME_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/.config/chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
[[ "$OSTYPE" == "darwin"* ]] && CHROME_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" && CHROMIUM_DIR="$HOME/Library/Application Support/Chromium/NativeMessagingHosts" && FIREFOX_DIR="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"
|
||||
|
||||
MANIFEST_NAME="com.holesail.browser"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_PATH|g" "$REPO_ROOT/com.holesail.browser.json")
|
||||
for dir in "$CHROME_DIR" "$CHROMIUM_DIR" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null && echo "$MANIFEST_CONTENT" > "$dir/${MANIFEST_NAME}.json" && echo "Wrote $dir/${MANIFEST_NAME}.json"
|
||||
done
|
||||
echo "Done. Wrapper: $NODE_PATH $BARE_PATH"
|
||||
@@ -0,0 +1,87 @@
|
||||
# Unified installer for Holesail Browser (extension + native host).
|
||||
# Run from anywhere: .\scripts\install.ps1 or cd holesail-browser; .\scripts\install.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path
|
||||
|
||||
Write-Host "Holesail Browser – Install" -ForegroundColor Cyan
|
||||
Write-Host "============================="
|
||||
|
||||
# Require Node.js
|
||||
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Error: Node.js is required. Install from https://nodejs.org and run this script again." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Ensure bare is available
|
||||
if (-not (Get-Command bare -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing Bare runtime (required for native host)..."
|
||||
npm install -g bare 2>$null
|
||||
if (-not (Get-Command bare -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Warning: Could not install bare. Run: npm install -g bare" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Native host
|
||||
Write-Host ""
|
||||
Write-Host "1. Installing native host..."
|
||||
$HostDir = Join-Path $RepoRoot "native-host"
|
||||
Set-Location $HostDir
|
||||
npm install --no-fund --no-audit 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { npm install }
|
||||
|
||||
# Windows: use .bat that runs bare; Chrome may have limited PATH so use full path if we can find it
|
||||
$bareCmd = Get-Command bare -ErrorAction SilentlyContinue
|
||||
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
$nodePath = if ($nodeCmd) { $nodeCmd.Source } else { "node" }
|
||||
$barePath = if ($bareCmd) { $bareCmd.Source } else { "bare" }
|
||||
|
||||
$batContent = "@echo off`r`nset `"DIR=%~dp0`"`r`n`"$nodePath`" `"$barePath`" `"%DIR%index.mjs`" %*"
|
||||
$batContent | Set-Content (Join-Path $HostDir "holesail-browser-host.bat") -Encoding ASCII
|
||||
$HostPath = Join-Path $HostDir "holesail-browser-host.bat"
|
||||
|
||||
$manifestPath = Join-Path $RepoRoot "com.holesail.browser.json"
|
||||
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
|
||||
$manifest.path = $HostPath
|
||||
|
||||
$manifestFile = Join-Path $env:LOCALAPPDATA "holesail-browser\com.holesail.browser.json"
|
||||
$manifestDir = Split-Path $manifestFile
|
||||
if (-not (Test-Path $manifestDir)) { New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null }
|
||||
$manifest | ConvertTo-Json -Depth 4 | Set-Content $manifestFile -Encoding UTF8
|
||||
|
||||
$chromeKey = "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.holesail.browser"
|
||||
New-Item -Path $chromeKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $chromeKey -Name "(Default)" -Value $manifestFile
|
||||
$ffKey = "HKCU:\Software\Mozilla\NativeMessagingHosts\com.holesail.browser"
|
||||
New-Item -Path $ffKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $ffKey -Name "(Default)" -Value $manifestFile
|
||||
Write-Host " Native host manifest: $manifestFile"
|
||||
|
||||
# 2. Extension
|
||||
Write-Host ""
|
||||
Write-Host "2. Preparing extension..."
|
||||
$ExtDir = (Resolve-Path (Join-Path $RepoRoot "extension")).Path
|
||||
if (-not (Test-Path (Join-Path $ExtDir "manifest.json"))) {
|
||||
Write-Host "Error: extension/manifest.json not found." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Set-Clipboard -Value $ExtDir
|
||||
|
||||
$chromePaths = @(
|
||||
"${env:ProgramFiles}\Google\Chrome\Application\chrome.exe",
|
||||
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
|
||||
"${env:LocalAppData}\Google\Chrome\Application\chrome.exe"
|
||||
)
|
||||
foreach ($p in $chromePaths) {
|
||||
if (Test-Path $p) {
|
||||
Start-Process $p -ArgumentList "chrome://extensions"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next step: In the browser tab that opened, click 'Load unpacked' and paste this path:"
|
||||
Write-Host " $ExtDir"
|
||||
Write-Host "(Path is in your clipboard.) Then restart the browser."
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unified installer for Holesail Browser (extension + native host).
|
||||
# Run from anywhere: ./scripts/install.sh or cd holesail-browser && ./scripts/install.sh
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
echo "Holesail Browser – Install"
|
||||
echo "============================="
|
||||
|
||||
# Require Node.js
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "Error: Node.js is required. Install from https://nodejs.org and run this script again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure bare is available (required for native host)
|
||||
if ! command -v bare >/dev/null 2>&1; then
|
||||
echo "Installing Bare runtime (required for native host)..."
|
||||
if npm install -g bare 2>/dev/null; then
|
||||
echo "Bare installed."
|
||||
else
|
||||
echo "Warning: Could not install bare. Run: npm install -g bare"
|
||||
echo "Continuing; native host may not work until bare is installed."
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1. Native host
|
||||
echo ""
|
||||
echo "1. Installing native host..."
|
||||
HOST_DIR="$REPO_ROOT/native-host"
|
||||
cd "$HOST_DIR"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
|
||||
|
||||
# Build native host binary
|
||||
echo ""
|
||||
echo "1b. Building native host..."
|
||||
cd "$REPO_ROOT"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
npm run build:host
|
||||
cd "$HOST_DIR"
|
||||
|
||||
HOST_PATH="$HOST_DIR/holesail-browser-host"
|
||||
|
||||
CHROME_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/.config/chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
CHROME_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"
|
||||
fi
|
||||
|
||||
MANIFEST_NAME="com.holesail.browser"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_PATH|g" "$REPO_ROOT/com.holesail.browser.json")
|
||||
for dir in "$CHROME_DIR" "$CHROMIUM_DIR" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null && echo "$MANIFEST_CONTENT" > "$dir/${MANIFEST_NAME}.json" && echo " Native host manifest: $dir"
|
||||
done
|
||||
|
||||
# 2. Extension
|
||||
echo ""
|
||||
echo "2. Preparing extension..."
|
||||
EXT_DIR="$REPO_ROOT/extension"
|
||||
if [[ ! -f "$EXT_DIR/manifest.json" ]]; then
|
||||
echo "Error: extension/manifest.json not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v pbcopy >/dev/null 2>&1; then
|
||||
echo "$EXT_DIR" | pbcopy
|
||||
elif command -v xclip >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xclip -selection clipboard 2>/dev/null || true
|
||||
elif command -v xsel >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xsel --clipboard 2>/dev/null || true
|
||||
fi
|
||||
|
||||
open_page() {
|
||||
local url="$1" app="$2"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open -a "$app" "$url" 2>/dev/null && return 0
|
||||
fi
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$url" 2>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
open_page "chrome://extensions" "Google Chrome" || \
|
||||
open_page "chrome://extensions" "Chromium" || \
|
||||
open_page "chrome://extensions" "Microsoft Edge" || true
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo ""
|
||||
echo "Next step: In the browser tab that opened, click 'Load unpacked' and paste this path:"
|
||||
echo " $EXT_DIR"
|
||||
echo "(Path is in your clipboard.) Then restart the browser."
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Pack the browser extension into a zip (and .xpi) for distribution.
|
||||
* Output: releases/Holesail-Browser-<version>.zip, releases/Holesail-Browser-<version>.xpi
|
||||
* Excludes: *.map files
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const archiver = require('archiver');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const EXT_DIR = path.join(REPO_ROOT, 'extension');
|
||||
const RELEASES_DIR = path.join(REPO_ROOT, 'releases');
|
||||
|
||||
const EXCLUDE_EXTS = new Set(['.map']);
|
||||
|
||||
function getVersion() {
|
||||
const manifestPath = path.join(EXT_DIR, 'manifest.json');
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
if (!manifest.version) throw new Error('extension/manifest.json missing version');
|
||||
return manifest.version;
|
||||
}
|
||||
|
||||
function listExtensionFiles(dir, base = dir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
const files = [];
|
||||
for (const e of entries) {
|
||||
const full = path.join(dir, e.name);
|
||||
const rel = path.relative(base, full).replace(/\\/g, '/');
|
||||
if (e.isDirectory()) {
|
||||
files.push(...listExtensionFiles(full, base));
|
||||
} else {
|
||||
const ext = path.extname(e.name);
|
||||
if (!EXCLUDE_EXTS.has(ext)) files.push({ full, rel });
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function createZip(version) {
|
||||
const zipPath = path.join(RELEASES_DIR, `Holesail-Browser-${version}.zip`);
|
||||
const output = fs.createWriteStream(zipPath);
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
output.on('close', () => resolve(zipPath));
|
||||
archive.on('error', reject);
|
||||
archive.pipe(output);
|
||||
|
||||
const files = listExtensionFiles(EXT_DIR);
|
||||
for (const { full, rel } of files) {
|
||||
archive.file(full, { name: rel });
|
||||
}
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const version = getVersion();
|
||||
console.log('Holesail Browser version from manifest:', version);
|
||||
|
||||
if (!fs.existsSync(RELEASES_DIR)) {
|
||||
fs.mkdirSync(RELEASES_DIR, { recursive: true });
|
||||
console.log('Created releases/');
|
||||
}
|
||||
|
||||
console.log('Creating zip...');
|
||||
try {
|
||||
const zipPath = await createZip(version);
|
||||
console.log('Written:', zipPath);
|
||||
const xpiPath = path.join(RELEASES_DIR, `Holesail-Browser-${version}.xpi`);
|
||||
fs.copyFileSync(zipPath, xpiPath);
|
||||
console.log('Written:', xpiPath);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
var isWin = process.platform === 'win32';
|
||||
var script = path.join(__dirname, isWin ? 'install.ps1' : 'install.sh');
|
||||
var child = spawn(isWin ? 'powershell' : '/bin/sh', isWin ? ['-ExecutionPolicy', 'Bypass', '-File', script] : [script], {
|
||||
stdio: 'inherit',
|
||||
cwd: path.join(__dirname, '..'),
|
||||
});
|
||||
child.on('exit', function (code, sig) {
|
||||
process.exit(code !== null ? code : sig ? 1 : 0);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
# Update native host manifest with your Chrome extension ID (fix "Access forbidden").
|
||||
# Usage: .\scripts\update-native-manifest-extension-id.ps1 YOUR_EXTENSION_ID
|
||||
param([Parameter(Mandatory=$true)] [string] $ExtensionId)
|
||||
$ExtensionId = $ExtensionId -replace '^chrome-extension://', '' -replace '/$', ''
|
||||
$Origin = "chrome-extension://$ExtensionId/"
|
||||
$manifestFile = (Get-ItemProperty -Path "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.bridgeswarm" -ErrorAction SilentlyContinue).'(Default)'
|
||||
if (-not $manifestFile -or -not (Test-Path $manifestFile)) { Write-Host "Run scripts/install.ps1 first."; exit 1 }
|
||||
$m = Get-Content $manifestFile -Raw | ConvertFrom-Json
|
||||
$m.allowed_origins = @($Origin)
|
||||
$m | ConvertTo-Json -Depth 4 | Set-Content $manifestFile -Encoding UTF8
|
||||
Write-Host "Updated $manifestFile. Restart Chrome."
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update native host manifest with your Chrome extension ID (fix "Access forbidden").
|
||||
# Usage: ./scripts/update-native-manifest-extension-id.sh YOUR_EXTENSION_ID
|
||||
set -e
|
||||
[[ -z "$1" ]] && echo "Usage: $0 YOUR_CHROME_EXTENSION_ID (from chrome://extensions)" && exit 1
|
||||
EXT_ID="${1#chrome-extension://}"; EXT_ID="${EXT_ID%/}"
|
||||
ORIGIN="chrome-extension://${EXT_ID}/"
|
||||
CHROME_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/.config/chromium/NativeMessagingHosts"
|
||||
[[ "$OSTYPE" == "darwin"* ]] && CHROME_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" && CHROMIUM_DIR="$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
MANIFEST_NAME="com.holesail.browser"
|
||||
updated=0
|
||||
for dir in "$CHROME_DIR" "$CHROMIUM_DIR"; do
|
||||
f="$dir/${MANIFEST_NAME}.json"
|
||||
[[ ! -f "$f" ]] && continue
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync('$f','utf8'));j.allowed_origins=['$ORIGIN'];fs.writeFileSync('$f',JSON.stringify(j,null,2));"
|
||||
else
|
||||
sed -i.bak "s|\"chrome-extension://[^\"]*/\"|\"$ORIGIN\"|g" "$f" && rm -f "${f}.bak"
|
||||
fi
|
||||
echo "Updated $f"
|
||||
updated=1
|
||||
done
|
||||
[[ $updated -eq 0 ]] && echo "No manifest found. Run scripts/install.sh first." && exit 1
|
||||
echo "Done. Restart Chrome."
|
||||
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL https://ssh.surf/bridgeswarm/install.sh -o install.sh && bash install.sh
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ 🌉 BridgeSwarm Installer ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
This installer will set up BridgeSwarm on your computer:
|
||||
|
||||
• Native Messaging Host → Enables browser ↔ network communication
|
||||
• Browser Extension → Packaged to ~/Downloads for manual install
|
||||
• Fixed Extension ID → No random IDs, stable across updates
|
||||
|
||||
What's installed where:
|
||||
• Software: ~/.bridgeswarm/
|
||||
• Extension: ~/Downloads/BridgeSwarm-*.zip (Chrome) / *.xpi (Firefox)
|
||||
• Browser manifests: ~/.config/.../NativeMessagingHosts/ (Linux)
|
||||
~/Library/Application Support/.../NativeMessagingHosts/ (macOS)
|
||||
%LOCALAPPDATA%/bridge-swarm/ (Windows)
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
if [ -t 1 ]; then
|
||||
echo -n "Continue with installation? [y/N]: "
|
||||
read -r answer
|
||||
if [[ "${answer,,}" != "y" && "${answer,,}" != "yes" ]]; then
|
||||
echo "Installation cancelled."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 1. Install Node.js if missing
|
||||
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||
echo "📦 Installing Node.js..."
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
command -v brew >/dev/null 2>&1 || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew install node
|
||||
elif grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||
sudo apt-get update && sudo apt-get install -y nodejs
|
||||
elif grep -qi fedora /etc/os-release 2>/dev/null || command -v dnf >/dev/null; then
|
||||
sudo dnf install -y nodejs
|
||||
else
|
||||
echo "⚠️ Please install Node.js manually from https://nodejs.org"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. git
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "📦 Installing git..."
|
||||
[[ "$OSTYPE" == "darwin"* ]] && brew install git || sudo apt install -y git || sudo dnf install -y git || true
|
||||
fi
|
||||
|
||||
# 3. Clone / update to persistent location
|
||||
INSTALL_DIR="$HOME/.bridgeswarm"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cd "$INSTALL_DIR" || exit 1
|
||||
|
||||
if [ -d ".git" ]; then
|
||||
echo "📥 Updating existing installation..."
|
||||
git pull --ff-only origin main || { cd ..; rm -rf "$INSTALL_DIR"; git clone https://git.ssh.surf/snxraven/BridgeSwarm.git "$INSTALL_DIR"; cd "$INSTALL_DIR"; }
|
||||
else
|
||||
echo "📥 Cloning BridgeSwarm..."
|
||||
git clone https://git.ssh.surf/snxraven/BridgeSwarm.git .
|
||||
fi
|
||||
|
||||
# 4. Build
|
||||
echo "🔨 Building bundles and codegen..."
|
||||
npm ci --no-audit --prefer-offline --no-fund
|
||||
cd native-host && npm ci --no-audit --prefer-offline --no-fund || npm install --no-audit --no-fund
|
||||
cd ..
|
||||
npm run build
|
||||
|
||||
# 5. Package the extension
|
||||
echo "📦 Packaging extension..."
|
||||
npm run pack
|
||||
|
||||
# 6. Copy to ~/Downloads
|
||||
DOWNLOADS_DIR="$HOME/Downloads"
|
||||
mkdir -p "$DOWNLOADS_DIR"
|
||||
ZIP_FILE=$(ls releases/BridgeSwarm-*.zip 2>/dev/null | head -1)
|
||||
XPI_FILE=$(ls releases/BridgeSwarm-*.xpi 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$ZIP_FILE" ]; then
|
||||
cp "$ZIP_FILE" "$DOWNLOADS_DIR/"
|
||||
echo "✅ Extension saved to ~/Downloads/$(basename "$ZIP_FILE")"
|
||||
fi
|
||||
if [ -n "$XPI_FILE" ]; then
|
||||
cp "$XPI_FILE" "$DOWNLOADS_DIR/"
|
||||
echo "✅ Firefox extension saved to ~/Downloads/$(basename "$XPI_FILE")"
|
||||
fi
|
||||
|
||||
# 7. Install native host + manifest
|
||||
echo "🔧 Installing native messaging host..."
|
||||
|
||||
HOST_WRAPPER="$INSTALL_DIR/native-host/bridge-swarm-host"
|
||||
chmod +x "$HOST_WRAPPER" 2>/dev/null || true
|
||||
|
||||
MANIFEST_NAME="com.bridgeswarm"
|
||||
MANIFEST_TEMPLATE="$INSTALL_DIR/com.bridgeswarm.json"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_WRAPPER|g" "$MANIFEST_TEMPLATE")
|
||||
|
||||
CHROME_DIRS=(
|
||||
"$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
"$HOME/.config/chromium/NativeMessagingHosts"
|
||||
"$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
"$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
)
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
|
||||
for dir in "${CHROME_DIRS[@]}" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null
|
||||
echo "$MANIFEST_CONTENT" > "$dir/$MANIFEST_NAME.json"
|
||||
echo " ✅ Installed: $dir/$MANIFEST_NAME.json"
|
||||
done
|
||||
|
||||
# 8. Try open browser
|
||||
echo ""
|
||||
echo "🌐 Opening browser extension page..."
|
||||
|
||||
open_page() {
|
||||
local url="$1" app="$2"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open -a "$app" "$url" 2>/dev/null && return 0
|
||||
fi
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$url" 2>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
open_page "chrome://extensions" "Google Chrome" || \
|
||||
open_page "chrome://extensions" "Chromium" || \
|
||||
open_page "chrome://extensions" "Microsoft Edge" || {
|
||||
echo "⚠️ Could not auto-open browser. Please open manually:"
|
||||
echo " Chrome/Edge: chrome://extensions/"
|
||||
echo " Firefox: about:addons"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " ✅ Installation Complete!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
cat << INSTALL_EOF
|
||||
|
||||
📋 NEXT STEPS - Install the Extension
|
||||
|
||||
Chrome / Edge:
|
||||
1. In the browser that opened, enable "Developer mode" (top right)
|
||||
2. Click "Unpack extension"
|
||||
3. Extract ~/Downloads/BridgeSwarm-1.0.0.zip to a folder
|
||||
4. Click "Load unpacked" and select the extracted folder
|
||||
5. Your extension ID should be: fmcenppcipeikpnpopolicnllljclmmi
|
||||
|
||||
Firefox:
|
||||
1. Go to about:addons
|
||||
2. Click the gear icon → "Install Add-on From File"
|
||||
3. Select ~/Downloads/BridgeSwarm-1.0.0.xpi
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🧪 Test it:
|
||||
• Open: $INSTALL_DIR/examples/chat/index.html
|
||||
• In browser console: BridgeSwarm → should show the constructor
|
||||
|
||||
📁 Files:
|
||||
• Extension: ~/Downloads/BridgeSwarm-1.0.0.zip
|
||||
• Software: ~/.bridgeswarm/
|
||||
|
||||
🔄 To update later: run the same install command again
|
||||
|
||||
INSTALL_EOF
|
||||
|
||||
echo ""
|
||||
echo "📍 Extension file path copied to clipboard!"
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | pbcopy 2>/dev/null || \
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | xclip -sel clip 2>/dev/null || \
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | xsel -ib 2>/dev/null || true
|
||||
Reference in New Issue
Block a user