evert: remove experimental my.dash.board PWA virtual host
CI / Build & Test (push) Successful in 2m52s

Reverts the experimental feature that attempted to serve the dashboard
via a local HTTP server registered as a hardcoded `my.dash.board` virtual
host, intended to satisfy Chrome's PWA "secure origin" installability
requirement. The approach caused proxy connection errors and file-not-found
issues that were not worth resolving.

Removed: native-host/dashboard-server.js, native-host/test-chain.mjs
Reverted: startup.js, message-router.js, virtual-hosts.js, index.js,
          build-distributable.js, extension/pages/virtual-hosts.js,
          CI workflows (ci.yml, release.yml)
This commit is contained in:
Raven Scott
2026-03-01 04:00:29 -05:00
parent 9e70c48d48
commit f069058c98
10 changed files with 6 additions and 377 deletions
-8
View File
@@ -76,7 +76,6 @@ jobs:
node --check native-host/https-proxy.js node --check native-host/https-proxy.js
node --check native-host/connect-proxy.js node --check native-host/connect-proxy.js
node --check native-host/messenger.js node --check native-host/messenger.js
node --check native-host/dashboard-server.js
node --check native-host/host/paths.js node --check native-host/host/paths.js
node --check native-host/host/logger.js node --check native-host/host/logger.js
node --check native-host/host/startup.js node --check native-host/host/startup.js
@@ -149,13 +148,6 @@ jobs:
echo "$OUTPUT" | grep -i "not available" echo "$OUTPUT" | grep -i "not available"
fi fi
if echo "$OUTPUT" | grep -q "\[dashboard-server\] listening on"; then
echo "PASS: dashboard server started (my.dash.board virtual host registered)"
else
echo "FAIL: dashboard server did not start — bundle assets may be missing"
exit 1
fi
echo "PASS: smoke test complete" echo "PASS: smoke test complete"
- name: Generate checksums - name: Generate checksums
-7
View File
@@ -71,13 +71,6 @@ jobs:
exit 1 exit 1
fi fi
if echo "$OUTPUT" | grep -q "\[dashboard-server\] listening on"; then
echo "PASS: dashboard server started (my.dash.board virtual host registered)"
else
echo "FAIL: dashboard server did not start — bundle assets may be missing"
exit 1
fi
- name: List release artifacts - name: List release artifacts
run: | run: |
echo "=== releases/ ===" echo "=== releases/ ==="
+5 -8
View File
@@ -54,23 +54,20 @@ function updateConnectionsTable(state) {
const openUrl = `https://${hostname}`; const openUrl = `https://${hostname}`;
const safeHostname = hostname.replace(/"/g, '"'); const safeHostname = hostname.replace(/"/g, '"');
const needsReconnect = v.state === 'error' || v.state === 'closed'; const needsReconnect = v.state === 'error' || v.state === 'closed';
const isBuiltIn = v.type === 'local';
return ` return `
<tr> <tr>
<td style="width:32px;">${isBuiltIn ? '' : `<input type="checkbox" class="vhost-row-cb" data-hostname="${safeHostname}">`}</td> <td style="width:32px;"><input type="checkbox" class="vhost-row-cb" data-hostname="${safeHostname}"></td>
<td> <td>
<span class="mono-chip">${escapeHtml(hostname)}</span> <span class="mono-chip">${escapeHtml(hostname)}</span>
${isBuiltIn ? '<span style="font-size:10px;color:var(--text4);margin-left:4px;">built-in</span>' : ''}
</td> </td>
<td> <td>
${isBuiltIn ? '<span style="color:var(--text4);font-size:11px;">—</span>' : `
<div style="display:flex;align-items:center;gap:6px;"> <div style="display:flex;align-items:center;gap:6px;">
<span class="mono" title="${escapeHtml(hsUrl)}" style="color:var(--text3);font-size:11px;">${truncate(hsUrl, 28)}</span> <span class="mono" title="${escapeHtml(hsUrl)}" style="color:var(--text3);font-size:11px;">${truncate(hsUrl, 28)}</span>
<button class="btn-icon copy-btn" data-copy="${escapeHtml(hsUrl)}" title="Copy hs:// URL" style="flex-shrink:0;"> <button class="btn-icon copy-btn" data-copy="${escapeHtml(hsUrl)}" title="Copy hs:// URL" style="flex-shrink:0;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
<span class="copy-tooltip">Copied!</span> <span class="copy-tooltip">Copied!</span>
</button> </button>
</div>`} </div>
</td> </td>
<td class="mono" style="font-size:11px;color:var(--text3);">${escapeHtml(backend)}</td> <td class="mono" style="font-size:11px;color:var(--text3);">${escapeHtml(backend)}</td>
<td> <td>
@@ -83,14 +80,14 @@ function updateConnectionsTable(state) {
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
Open Open
</a> </a>
${!isBuiltIn && needsReconnect ? `<button class="btn btn-secondary btn-sm" data-reconnect-vhost="${safeHostname}" data-hs-url="${escapeHtml(hsUrl)}" title="Reconnect tunnel"> ${needsReconnect ? `<button class="btn btn-secondary btn-sm" data-reconnect-vhost="${safeHostname}" data-hs-url="${escapeHtml(hsUrl)}" title="Reconnect tunnel">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23,4 23,10 17,10"/><path d="M20.49 15a9 9 0 1 1-.07-8.13"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23,4 23,10 17,10"/><path d="M20.49 15a9 9 0 1 1-.07-8.13"/></svg>
Reconnect Reconnect
</button>` : ''} </button>` : ''}
${isBuiltIn ? '' : `<button class="btn btn-danger btn-sm" data-remove-vhost="${safeHostname}"> <button class="btn btn-danger btn-sm" data-remove-vhost="${safeHostname}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3,6 5,6 21,6"/><path d="M19,6l-1,14a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2L5,6"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3,6 5,6 21,6"/><path d="M19,6l-1,14a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2L5,6"/></svg>
Remove Remove
</button>`} </button>
</div> </div>
</td> </td>
</tr>`; </tr>`;
-249
View File
@@ -1,249 +0,0 @@
/**
* Local HTTP server that serves the Holesail dashboard as a static site.
*
* In distribution mode (running as a standalone Bare binary), dashboard files
* are read from the bare-bundle assets embedded at build time via `require.bundle()`.
*
* In development mode (`bare index.mjs`), `require.bundle()` is not available,
* so files are read from disk relative to the source directory.
*
* The server is registered as the backend for the `my.dash.board` virtual host
* so the HTTPS proxy can serve the dashboard over a trusted HTTPS origin,
* satisfying Chrome's PWA installability requirement.
*/
const http1 = require('bare-http1');
const tcp = require('bare-tcp');
const path = require('bare-path');
const EventEmitter = require('bare-events');
const { allocateTunnelPort, releaseTunnelPort } = require('./holesail-manager/port-allocator.js');
// Resolve the extension/dashboard directory for dev-mode file serving.
// Uses the same logic as host/paths.js: in dev mode __dirname is the real
// native-host/ directory; in distribution mode the bundle read takes priority
// and this path is only used as a last-resort fallback.
function _resolveDevDashboardDir() {
// In dev mode __dirname is the real native-host/ path
if (process.stderr) process.stderr.write('[dashboard-server] __dirname=' + __dirname + '\n');
if (__dirname && !__dirname.startsWith('bare:')) {
const d = path.join(__dirname, '..', 'extension', 'dashboard');
if (process.stderr) process.stderr.write('[dashboard-server] DEV_DASHBOARD_DIR (from __dirname)=' + d + '\n');
return d;
}
// Distribution mode: try executable directory as fallback
try {
const os = require('bare-os');
const execPath = os.execPath();
if (process.stderr) process.stderr.write('[dashboard-server] execPath=' + execPath + '\n');
if (execPath && !execPath.startsWith('bare:')) {
const d = path.join(path.dirname(execPath), 'extension', 'dashboard');
if (process.stderr) process.stderr.write('[dashboard-server] DEV_DASHBOARD_DIR (from execPath)=' + d + '\n');
return d;
}
} catch (e) {
if (process.stderr) process.stderr.write('[dashboard-server] execPath error: ' + e.message + '\n');
}
if (process.stderr) process.stderr.write('[dashboard-server] DEV_DASHBOARD_DIR=null\n');
return null;
}
const DEV_DASHBOARD_DIR = _resolveDevDashboardDir();
const DASHBOARD_HOSTNAME = 'my.dash.board';
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.webmanifest': 'application/manifest+json; charset=utf-8',
'.png': 'image/png',
'.ico': 'image/x-icon',
'.svg': 'image/svg+xml; charset=utf-8',
'.woff': 'font/woff',
'.woff2': 'font/woff2'
};
let _server = null;
let _port = null;
function _getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
/**
* Read a file by its bundle key and disk-relative path.
* In distribution mode, reads from the bare-bundle asset embedded at build time.
* In development mode, reads from disk relative to DEV_DASHBOARD_DIR.
* @param {string} bundleKey - Key in the bare-bundle (e.g. '/dashboard/dashboard.html').
* @param {string} diskRelPath - Path relative to DEV_DASHBOARD_DIR (e.g. 'dashboard.html' or '../icons/48.png').
* @returns {Buffer|null} File contents or null if not found.
*/
function _readFileByKey(bundleKey, diskRelPath) {
// Distribution mode: read from bare-bundle assets
try {
if (typeof require.bundle === 'function') {
const b = require.bundle();
if (process.stderr) process.stderr.write('[dashboard-server] bundle=' + (b ? 'object' : 'null') + ' key=' + bundleKey + '\n');
if (b) {
const buf = b.read(bundleKey);
if (process.stderr) process.stderr.write('[dashboard-server] bundle.read=' + (buf ? buf.length + ' bytes' : 'null') + '\n');
if (buf !== null) return buf;
}
} else {
if (process.stderr) process.stderr.write('[dashboard-server] require.bundle not a function (type=' + typeof require.bundle + ')\n');
}
} catch (e) {
if (process.stderr) process.stderr.write('[dashboard-server] bundle error: ' + e.message + '\n');
}
// Disk fallback (dev mode, or distribution mode without bundle assets)
if (DEV_DASHBOARD_DIR) {
try {
const fs = require('bare-fs');
const fullPath = path.join(DEV_DASHBOARD_DIR, diskRelPath);
if (process.stderr) process.stderr.write('[dashboard-server] disk read: ' + fullPath + '\n');
const buf = fs.readFileSync(fullPath);
if (process.stderr) process.stderr.write('[dashboard-server] disk read OK: ' + buf.length + ' bytes\n');
return buf;
} catch (e) {
if (process.stderr) process.stderr.write('[dashboard-server] disk read error: ' + e.message + '\n');
}
} else {
if (process.stderr) process.stderr.write('[dashboard-server] DEV_DASHBOARD_DIR is null, skipping disk read\n');
}
return null;
}
/**
* Start the dashboard HTTP server.
* @returns {Promise<{port: number}>}
*/
function start() {
return new Promise((resolve, reject) => {
if (_server) {
resolve({ port: _port });
return;
}
_port = allocateTunnelPort();
const tcpServer = tcp.createServer();
// Minimal server object required by bare-http1 ServerConnection.
// ServerConnection emits 'request' on this object, not on itself.
const httpServer = new EventEmitter();
httpServer.timeout = 0;
httpServer.closing = false;
httpServer.on('request', (req, res) => {
let urlPath = (req.url || '/').split('?')[0];
if (urlPath === '/') urlPath = '/dashboard.html';
// Serve a dynamically generated manifest with absolute https://my.dash.board/ URLs
// so Chrome accepts start_url, id, scope, and protocol_handlers (same-origin requirement).
if (urlPath === '/manifest.webmanifest') {
const manifest = {
id: 'https://' + DASHBOARD_HOSTNAME + '/',
name: 'Holesail Browser',
short_name: 'Holesail',
description: 'P2P Holesail tunnels in the browser',
start_url: 'https://' + DASHBOARD_HOSTNAME + '/',
scope: 'https://' + DASHBOARD_HOSTNAME + '/',
display: 'standalone',
display_override: ['window-controls-overlay', 'standalone'],
background_color: '#0f1117',
theme_color: '#0f1117',
protocol_handlers: [
{ protocol: 'web+holesail', url: 'https://' + DASHBOARD_HOSTNAME + '/?lookup=%s' }
],
screenshots: [
{ src: 'https://' + DASHBOARD_HOSTNAME + '/screenshots/wide.png', sizes: '1024x515', type: 'image/png', form_factor: 'wide', label: 'Holesail Browser Dashboard' },
{ src: 'https://' + DASHBOARD_HOSTNAME + '/screenshots/narrow.png', sizes: '390x844', type: 'image/png', form_factor: 'narrow', label: 'Holesail Browser Dashboard' }
],
icons: [
{ src: 'https://' + DASHBOARD_HOSTNAME + '/icons/48.png', sizes: '48x48', type: 'image/png', purpose: 'any' },
{ src: 'https://' + DASHBOARD_HOSTNAME + '/icons/128.png', sizes: '128x128', type: 'image/png', purpose: 'any' },
{ src: 'https://' + DASHBOARD_HOSTNAME + '/icons/192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
{ src: 'https://' + DASHBOARD_HOSTNAME + '/icons/512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }
]
};
const body = Buffer.from(JSON.stringify(manifest, null, 2));
res.statusCode = 200;
res.setHeader('Content-Type', 'application/manifest+json; charset=utf-8');
res.setHeader('Content-Length', body.length);
res.end(body);
return;
}
// Strip leading slash for file lookup
let relPath = urlPath.replace(/^\/+/, '');
// Icons are stored in extension/icons/ (one level up from dashboard/).
// Remap /icons/<file> → ../icons/<file> for the bundle key and disk path.
let bundleKey = '/dashboard/' + relPath;
let diskRelPath = relPath;
if (relPath.startsWith('icons/')) {
bundleKey = '/' + relPath; // stored as /icons/<file> in bundle
diskRelPath = '../icons/' + relPath.slice('icons/'.length);
}
const buf = _readFileByKey(bundleKey, diskRelPath);
if (!buf) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not found: ' + relPath);
return;
}
res.statusCode = 200;
res.setHeader('Content-Type', _getMimeType(relPath));
res.setHeader('Content-Length', buf.length);
res.end(buf);
});
tcpServer.on('connection', (socket) => {
// eslint-disable-next-line no-new
new http1.ServerConnection(httpServer, socket, {});
});
tcpServer.on('error', (err) => {
releaseTunnelPort(_port);
_port = null;
_server = null;
reject(err);
});
tcpServer.listen(_port, '127.0.0.1', () => {
_server = tcpServer;
if (process.stderr) {
process.stderr.write('[dashboard-server] listening on 127.0.0.1:' + _port + '\n');
}
resolve({ port: _port });
});
});
}
/**
* Stop the dashboard HTTP server and release its port.
*/
function stop() {
if (_server) {
try { _server.close(); } catch (_) {}
_server = null;
}
if (_port != null) {
releaseTunnelPort(_port);
_port = null;
}
}
/** @returns {string} The fixed hostname for the dashboard virtual host. */
function getHostname() { return DASHBOARD_HOSTNAME; }
/** @returns {number|null} The port the server is listening on, or null if not started. */
function getPort() { return _port; }
module.exports = { start, stop, getHostname, getPort };
-2
View File
@@ -30,7 +30,6 @@ function saveState() {
id: s.id, port: s.port, host: s.host, secure: s.secure, udp: s.udp || false, label: s.label || '' id: s.id, port: s.port, host: s.host, secure: s.secure, udp: s.udp || false, label: s.label || ''
})); }));
const virtualHostsList = vhostsModule.getVirtualHosts() const virtualHostsList = vhostsModule.getVirtualHosts()
.filter(v => v.type !== 'local') // never persist built-in local hosts
.map(v => ({ hostname: v.hostname, hsUrl: v.hsUrl })); .map(v => ({ hostname: v.hostname, hsUrl: v.hsUrl }));
const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({ const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({
id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort
@@ -125,7 +124,6 @@ module.exports = {
getServers: serversModule.getServers, getServers: serversModule.getServers,
// Virtual hosts // Virtual hosts
setVirtualHost: vhostsModule.setVirtualHost, setVirtualHost: vhostsModule.setVirtualHost,
setLocalVirtualHost: vhostsModule.setLocalVirtualHost,
removeVirtualHost: vhostsModule.removeVirtualHost, removeVirtualHost: vhostsModule.removeVirtualHost,
getVirtualHosts: vhostsModule.getVirtualHosts, getVirtualHosts: vhostsModule.getVirtualHosts,
getLocalPortForHostname: vhostsModule.getLocalPortForHostname, getLocalPortForHostname: vhostsModule.getLocalPortForHostname,
+1 -25
View File
@@ -129,29 +129,6 @@ async function setVirtualHost(payload) {
} }
} }
/**
* Register a built-in local virtual host that is served by an internal HTTP
* server rather than a Holesail P2P tunnel. The entry is marked `type: 'local'`
* so it is never persisted to state.json and cannot be removed by the user.
* @param {string} hostname - The hostname (e.g. `my.dash.board`).
* @param {number} localPort - The local port the internal server is listening on.
*/
function setLocalVirtualHost(hostname, localPort) {
virtualHosts.set(hostname, {
hostname,
hsUrl: null,
holesail: null,
localHost: TUNNEL_HOST,
localPort,
state: 'ready',
type: 'local',
createdAt: Date.now(),
reconnectTimer: null,
reconnectDelay: 0
});
debugLog('setLocalVirtualHost: hostname=', hostname, 'localPort=', localPort);
}
/** /**
* Remove a virtual host, close its tunnel, and release its local port. * Remove a virtual host, close its tunnel, and release its local port.
* @param {object} payload * @param {object} payload
@@ -163,7 +140,6 @@ async function removeVirtualHost(payload) {
debugLog('removeVirtualHost: hostname=', hostname); debugLog('removeVirtualHost: hostname=', hostname);
const entry = virtualHosts.get(hostname); const entry = virtualHosts.get(hostname);
if (!entry) return { ok: false, error: 'Virtual host not found' }; if (!entry) return { ok: false, error: 'Virtual host not found' };
if (entry.type === 'local') return { ok: false, error: 'Built-in host cannot be removed' };
if (entry.reconnectTimer) { clearTimeout(entry.reconnectTimer); entry.reconnectTimer = null; } if (entry.reconnectTimer) { clearTimeout(entry.reconnectTimer); entry.reconnectTimer = null; }
if (entry.localPort) releaseTunnelPort(entry.localPort); if (entry.localPort) releaseTunnelPort(entry.localPort);
if (entry.holesail) { try { await entry.holesail.close(); } catch (_) {} } if (entry.holesail) { try { await entry.holesail.close(); } catch (_) {} }
@@ -229,4 +205,4 @@ async function cleanupVirtualHosts() {
virtualHosts.clear(); virtualHosts.clear();
} }
module.exports = { init, setVirtualHost, setLocalVirtualHost, removeVirtualHost, getVirtualHosts, getLocalPortForHostname, getLocalBackend, getVirtualHostMap, cleanupVirtualHosts, RECONNECT_BASE_MS, RECONNECT_MAX_MS }; module.exports = { init, setVirtualHost, removeVirtualHost, getVirtualHosts, getLocalPortForHostname, getLocalBackend, getVirtualHostMap, cleanupVirtualHosts, RECONNECT_BASE_MS, RECONNECT_MAX_MS };
-2
View File
@@ -16,7 +16,6 @@ const certificateAuthority = require('../certificate-authority.js');
const httpsProxy = require('../https-proxy.js'); const httpsProxy = require('../https-proxy.js');
const connectProxy = require('../connect-proxy.js'); const connectProxy = require('../connect-proxy.js');
const sshManager = require('../ssh-manager.js'); const sshManager = require('../ssh-manager.js');
const dashboardServer = require('../dashboard-server.js');
const backupManager = require('../backup-manager.js'); const backupManager = require('../backup-manager.js');
backupManager.setStoragePath(STORAGE_PATH); backupManager.setStoragePath(STORAGE_PATH);
backupManager.setCertsPath(certificateAuthority.getCertsDir()); backupManager.setCertsPath(certificateAuthority.getCertsDir());
@@ -344,7 +343,6 @@ async function handleMessageAsync(send, msg) {
function cleanup() { function cleanup() {
if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; } if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; }
dashboardServer.stop();
httpsProxy.stop(() => {}); httpsProxy.stop(() => {});
connectProxy.stop(() => {}); connectProxy.stop(() => {});
sshManager.cleanup(); sshManager.cleanup();
-12
View File
@@ -5,7 +5,6 @@
*/ */
const { log, debugLog } = require('./logger.js'); const { log, debugLog } = require('./logger.js');
const dashboardServer = require('../dashboard-server.js');
const PROXY_PORT = 8443; const PROXY_PORT = 8443;
const CONNECT_PROXY_PORT = 8442; const CONNECT_PROXY_PORT = 8442;
@@ -113,17 +112,6 @@ function initStartup(holesailManager, certificateAuthority, httpsProxy, connectP
log('Proxies startup complete: HTTPS', savedProxyPort, 'CONNECT', connectProxy.getPort() ?? 'FAILED'); log('Proxies startup complete: HTTPS', savedProxyPort, 'CONNECT', connectProxy.getPort() ?? 'FAILED');
// Start the dashboard HTTP server and register it as the my.dash.board
// local virtual host so the HTTPS proxy can serve it over a trusted
// HTTPS origin (satisfying Chrome's PWA installability requirement).
try {
const { port: dashPort } = await dashboardServer.start();
holesailManager.setLocalVirtualHost(dashboardServer.getHostname(), dashPort);
log('Dashboard server ready on port', dashPort, '→ https://' + dashboardServer.getHostname() + '/');
} catch (e) {
log('Dashboard server startup failed:', e.message);
}
// Assign tunnelsRestoredPromise BEFORE resolving proxiesReadyPromise so // Assign tunnelsRestoredPromise BEFORE resolving proxiesReadyPromise so
// that any getState call awaiting proxiesReadyPromise immediately sees the // that any getState call awaiting proxiesReadyPromise immediately sees the
// non-null promise rather than the one-microtask window where it is null. // non-null promise rather than the one-microtask window where it is null.
-11
View File
@@ -1,11 +0,0 @@
import 'bare-process/global';
import { createRequire } from 'bare-module';
const require = createRequire(import.meta.url);
// Simulate: startup.js (in host/) requires dashboard-server.js (in native-host/)
const startup_require = createRequire(new URL('./host/startup.js', import.meta.url));
const dashServer = startup_require('../dashboard-server.js');
console.log('dashServer.getHostname():', dashServer.getHostname());
const result = await dashServer.start();
console.log('start result:', result);
await dashServer.stop();
-53
View File
@@ -36,8 +36,6 @@ const ROOT = path.join(__dirname, '..')
const NATIVE_HOST_DIR = path.join(ROOT, 'native-host') const NATIVE_HOST_DIR = path.join(ROOT, 'native-host')
const RELEASES_DIR = path.join(ROOT, 'releases') const RELEASES_DIR = path.join(ROOT, 'releases')
const ENTRY = path.join(NATIVE_HOST_DIR, 'index.mjs') const ENTRY = path.join(NATIVE_HOST_DIR, 'index.mjs')
const EXTENSION_DASHBOARD_DIR = path.join(ROOT, 'extension', 'dashboard')
const EXTENSION_ICONS_DIR = path.join(ROOT, 'extension', 'icons')
const ALL_HOSTS = [ const ALL_HOSTS = [
'darwin-arm64', 'darwin-arm64',
@@ -100,23 +98,6 @@ function getPlatformModule(host) {
} }
} }
/**
* Recursively list all files under a directory.
* @param {string} dir - Absolute path to the directory.
* @returns {string[]} Absolute paths to all files.
*/
function walkDir(dir) {
const results = []
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name)
if (entry.isDirectory()) {
results.push(...walkDir(full))
} else {
results.push(full)
}
}
return results
}
/** /**
* Patch bare-build's apple/sign.js to skip codesign when not on macOS. * Patch bare-build's apple/sign.js to skip codesign when not on macOS.
@@ -266,40 +247,6 @@ module.exports = EventEmitter;
console.log(' Patched events module for node-rdpjs-2 compatibility') console.log(' Patched events module for node-rdpjs-2 compatibility')
} }
// ── Embed dashboard static files as bundle assets ───────────────────────────
// The dashboard-server.js reads these via require.bundle().read('/dashboard/<rel>')
// at runtime, so no separate file copy step is needed in the installer.
if (fs.existsSync(EXTENSION_DASHBOARD_DIR)) {
const dashFiles = walkDir(EXTENSION_DASHBOARD_DIR)
let dashCount = 0
for (const file of dashFiles) {
const rel = path.relative(EXTENSION_DASHBOARD_DIR, file).replace(/\\/g, '/')
const key = '/dashboard/' + rel
bundle.write(key, fs.readFileSync(file), { asset: true })
dashCount++
}
console.log(` Embedded ${dashCount} dashboard assets into bundle`)
} else {
console.warn(' WARNING: extension/dashboard not found — dashboard assets not embedded')
}
// ── Embed extension icons as bundle assets ───────────────────────────────────
// Icons are served at https://my.dash.board/icons/<file> by dashboard-server.js.
// They live in extension/icons/ (outside the dashboard dir) so they need a
// separate embedding pass with keys like /icons/<file>.
if (fs.existsSync(EXTENSION_ICONS_DIR)) {
const iconFiles = walkDir(EXTENSION_ICONS_DIR)
let iconCount = 0
for (const file of iconFiles) {
const rel = path.relative(EXTENSION_ICONS_DIR, file).replace(/\\/g, '/')
const key = '/icons/' + rel
bundle.write(key, fs.readFileSync(file), { asset: true })
iconCount++
}
console.log(` Embedded ${iconCount} icon assets into bundle`)
} else {
console.warn(' WARNING: extension/icons not found — icon assets not embedded')
}
} }
async function build(hosts, doPackage) { async function build(hosts, doPackage) {