evert: remove experimental my.dash.board PWA virtual host
CI / Build & Test (push) Successful in 2m52s
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:
@@ -76,7 +76,6 @@ jobs:
|
||||
node --check native-host/https-proxy.js
|
||||
node --check native-host/connect-proxy.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/logger.js
|
||||
node --check native-host/host/startup.js
|
||||
@@ -149,13 +148,6 @@ jobs:
|
||||
echo "$OUTPUT" | grep -i "not available"
|
||||
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"
|
||||
|
||||
- name: Generate checksums
|
||||
|
||||
@@ -71,13 +71,6 @@ jobs:
|
||||
exit 1
|
||||
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
|
||||
run: |
|
||||
echo "=== releases/ ==="
|
||||
|
||||
@@ -54,23 +54,20 @@ function updateConnectionsTable(state) {
|
||||
const openUrl = `https://${hostname}`;
|
||||
const safeHostname = hostname.replace(/"/g, '"');
|
||||
const needsReconnect = v.state === 'error' || v.state === 'closed';
|
||||
const isBuiltIn = v.type === 'local';
|
||||
return `
|
||||
<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>
|
||||
<span class="mono-chip">${escapeHtml(hostname)}</span>
|
||||
${isBuiltIn ? '<span style="font-size:10px;color:var(--text4);margin-left:4px;">built-in</span>' : ''}
|
||||
</td>
|
||||
<td>
|
||||
${isBuiltIn ? '<span style="color:var(--text4);font-size:11px;">—</span>' : `
|
||||
<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>
|
||||
<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>
|
||||
<span class="copy-tooltip">Copied!</span>
|
||||
</button>
|
||||
</div>`}
|
||||
</div>
|
||||
</td>
|
||||
<td class="mono" style="font-size:11px;color:var(--text3);">${escapeHtml(backend)}</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>
|
||||
Open
|
||||
</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>
|
||||
Reconnect
|
||||
</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>
|
||||
Remove
|
||||
</button>`}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 || ''
|
||||
}));
|
||||
const virtualHostsList = vhostsModule.getVirtualHosts()
|
||||
.filter(v => v.type !== 'local') // never persist built-in local hosts
|
||||
.map(v => ({ hostname: v.hostname, hsUrl: v.hsUrl }));
|
||||
const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({
|
||||
id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort
|
||||
@@ -125,7 +124,6 @@ module.exports = {
|
||||
getServers: serversModule.getServers,
|
||||
// Virtual hosts
|
||||
setVirtualHost: vhostsModule.setVirtualHost,
|
||||
setLocalVirtualHost: vhostsModule.setLocalVirtualHost,
|
||||
removeVirtualHost: vhostsModule.removeVirtualHost,
|
||||
getVirtualHosts: vhostsModule.getVirtualHosts,
|
||||
getLocalPortForHostname: vhostsModule.getLocalPortForHostname,
|
||||
|
||||
@@ -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.
|
||||
* @param {object} payload
|
||||
@@ -163,7 +140,6 @@ async function removeVirtualHost(payload) {
|
||||
debugLog('removeVirtualHost: hostname=', hostname);
|
||||
const entry = virtualHosts.get(hostname);
|
||||
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.localPort) releaseTunnelPort(entry.localPort);
|
||||
if (entry.holesail) { try { await entry.holesail.close(); } catch (_) {} }
|
||||
@@ -229,4 +205,4 @@ async function cleanupVirtualHosts() {
|
||||
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 };
|
||||
|
||||
@@ -16,7 +16,6 @@ const certificateAuthority = require('../certificate-authority.js');
|
||||
const httpsProxy = require('../https-proxy.js');
|
||||
const connectProxy = require('../connect-proxy.js');
|
||||
const sshManager = require('../ssh-manager.js');
|
||||
const dashboardServer = require('../dashboard-server.js');
|
||||
const backupManager = require('../backup-manager.js');
|
||||
backupManager.setStoragePath(STORAGE_PATH);
|
||||
backupManager.setCertsPath(certificateAuthority.getCertsDir());
|
||||
@@ -344,7 +343,6 @@ async function handleMessageAsync(send, msg) {
|
||||
|
||||
function cleanup() {
|
||||
if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; }
|
||||
dashboardServer.stop();
|
||||
httpsProxy.stop(() => {});
|
||||
connectProxy.stop(() => {});
|
||||
sshManager.cleanup();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
const { log, debugLog } = require('./logger.js');
|
||||
const dashboardServer = require('../dashboard-server.js');
|
||||
|
||||
const PROXY_PORT = 8443;
|
||||
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');
|
||||
|
||||
// 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
|
||||
// that any getState call awaiting proxiesReadyPromise immediately sees the
|
||||
// non-null promise rather than the one-microtask window where it is null.
|
||||
|
||||
@@ -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();
|
||||
@@ -36,8 +36,6 @@ 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 EXTENSION_DASHBOARD_DIR = path.join(ROOT, 'extension', 'dashboard')
|
||||
const EXTENSION_ICONS_DIR = path.join(ROOT, 'extension', 'icons')
|
||||
|
||||
const ALL_HOSTS = [
|
||||
'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.
|
||||
@@ -266,40 +247,6 @@ module.exports = EventEmitter;
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user