Updates and add examples host
CI / Build & Test (push) Successful in 8m7s

This commit is contained in:
Raven Scott
2026-07-26 23:50:17 -04:00
parent bb3b46a987
commit 83af298eb5
24 changed files with 678 additions and 39 deletions
+4
View File
@@ -7,6 +7,10 @@ releases/
# Mirrored HRPC/hyperschema output for bare-pack (generated by npm run build:hrpc) # Mirrored HRPC/hyperschema output for bare-pack (generated by npm run build:hrpc)
native-host/spec/ native-host/spec/
# Synced copies of examples/ (source of truth is repo-root examples/)
extension/examples/
native-host/examples/
# Native host: generated launchers (paths are machine-specific) # Native host: generated launchers (paths are machine-specific)
native-host/bridge-swarm-host native-host/bridge-swarm-host
native-host/bridge-swarm-host.bat native-host/bridge-swarm-host.bat
+5 -6
View File
@@ -87,11 +87,9 @@ npm run build:dist:package # all-platform host zips (needs bare-build; best
### Try the examples ### Try the examples
```bash In the extension **Settings** (or Dashboard → Settings), enable **Examples server**. Open **http://127.0.0.1:4173/** (do not use `file://`). Pick a demo and open it in two tabs.
npm run examples
```
Opens **http://127.0.0.1:4173/** (do not use `file://` — browsers treat each local file as a unique origin). Pick a demo and open it in two tabs. Dev alternative from the repo: `npm run examples` (same URL).
### Your First P2P App ### Your First P2P App
@@ -326,11 +324,12 @@ Run `npm run build:hrpc` to generate the HRPC spec.
### Build Commands ### Build Commands
```bash ```bash
npm run examples # Serve demos at http://127.0.0.1:4173/ (not file://) npm run examples # Dev: serve demos at http://127.0.0.1:4173/ (or use Settings toggle)
npm run sync:examples # Copy examples/ into extension + native-host for packaging
npm run build:protomux # Rebuild Protomux bundle npm run build:protomux # Rebuild Protomux bundle
npm run build:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec) npm run build:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec)
npm run build # host launcher + protomux + hrpc npm run build # host launcher + protomux + hrpc
npm run pack # Package extension → releases/BridgeSwarm-*.zip|.xpi npm run pack # Package extension (includes examples) → releases/
npm run build:dist # Standalone host binary (current platform) npm run build:dist # Standalone host binary (current platform)
npm run build:dist:package # All platforms + zip archives (CI) npm run build:dist:package # All platforms + zip archives (CI)
``` ```
+4 -2
View File
@@ -6,13 +6,15 @@ Open these in your browser **after** installing the BridgeSwarm extension and na
## Run the examples server ## Run the examples server
From the BridgeSwarm repo root: **Recommended:** In the BridgeSwarm extension, open **Settings** (or the Dashboard → Settings) and enable **Examples server**. The native host serves the bundled demos at **http://127.0.0.1:4173/**. Settings save automatically.
From the BridgeSwarm repo root (dev alternative):
```bash ```bash
npm run examples npm run examples
``` ```
Serves `examples/` at **http://127.0.0.1:4173/**. Use two browser tabs for P2P demos. Either way, use **http://127.0.0.1:4173/** (two browser tabs for P2P demos). Do not open `file://` or `chrome-extension://` copies for demos that need BridgeSwarm injection.
Shared chrome lives in [`shared/`](shared/) (`theme.css`, `chrome.css`, `boot.js`). Shared chrome lives in [`shared/`](shared/) (`theme.css`, `chrome.css`, `boot.js`).
+95 -6
View File
@@ -55,7 +55,15 @@ const swarmRefCount = new Map();
const activeConnections = new Map(); const activeConnections = new Map();
const SETTINGS_KEY = 'bridgeSwarmSettings'; const SETTINGS_KEY = 'bridgeSwarmSettings';
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
let notifyOnDisconnect = false; let notifyOnDisconnect = false;
let examplesServerEnabled = false;
let examplesServerState = {
running: false,
url: null,
error: null,
rootFound: null,
};
// Extension state for the UI // Extension state for the UI
const extensionState = { const extensionState = {
@@ -92,17 +100,67 @@ function updateExtensionState() {
extensionState.stats.totalSwarms = extensionState.swarms.size; extensionState.stats.totalSwarms = extensionState.swarms.size;
} }
function loadNotifySetting() { function applySettingsFromStorage(s) {
browser.storage.local.get(SETTINGS_KEY, (result) => {
const s = result[SETTINGS_KEY] || {};
notifyOnDisconnect = s.notifyOnDisconnect === true; notifyOnDisconnect = s.notifyOnDisconnect === true;
const wantExamples = s.examplesServerEnabled === true;
if (wantExamples !== examplesServerEnabled) {
examplesServerEnabled = wantExamples;
syncExamplesServer().catch((err) => log('examples server sync failed:', err.message || err));
} else if (wantExamples && port) {
syncExamplesServer().catch(() => {});
}
}
function loadSettings() {
browser.storage.local.get(SETTINGS_KEY, (result) => {
applySettingsFromStorage(result[SETTINGS_KEY] || {});
}); });
} }
async function syncExamplesServer() {
if (!port) {
examplesServerState = {
running: false,
url: examplesServerEnabled ? EXAMPLES_URL : null,
error: examplesServerEnabled ? 'Native host not connected' : null,
rootFound: null,
};
return examplesServerState;
}
try {
if (examplesServerEnabled) {
const res = await send({ type: 'examplesServer.start', payload: { host: '127.0.0.1', port: 4173 } });
examplesServerState = {
running: !!res?.running,
url: res?.url || EXAMPLES_URL,
error: res?.ok === false ? res.error || 'Failed to start' : null,
rootFound: res?.rootFound,
};
if (res?.ok !== false) log('Examples server:', examplesServerState.url);
} else {
const res = await send({ type: 'examplesServer.stop', payload: {} });
examplesServerState = {
running: false,
url: null,
error: res?.ok === false ? res.error || null : null,
rootFound: res?.rootFound,
};
log('Examples server stopped');
}
} catch (err) {
examplesServerState = {
running: false,
url: examplesServerEnabled ? EXAMPLES_URL : null,
error: err.message || String(err),
rootFound: null,
};
}
return examplesServerState;
}
browser.storage.onChanged.addListener((changes, areaName) => { browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'local' && changes[SETTINGS_KEY]) { if (areaName === 'local' && changes[SETTINGS_KEY]) {
const s = changes[SETTINGS_KEY].newValue || {}; applySettingsFromStorage(changes[SETTINGS_KEY].newValue || {});
notifyOnDisconnect = s.notifyOnDisconnect === true;
} }
}); });
@@ -117,6 +175,9 @@ function connect() {
} }
reconnectDelay = INITIAL_RECONNECT_DELAY; reconnectDelay = INITIAL_RECONNECT_DELAY;
if (examplesServerEnabled) {
syncExamplesServer().catch(() => {});
}
port.onMessage.addListener((msg) => { port.onMessage.addListener((msg) => {
log('Received from native:', msg.type, msg.event || ''); log('Received from native:', msg.type, msg.event || '');
@@ -195,6 +256,12 @@ function connect() {
activeConnections.clear(); activeConnections.clear();
updateExtensionState(); updateExtensionState();
port = null; port = null;
examplesServerState = {
running: false,
url: examplesServerEnabled ? EXAMPLES_URL : null,
error: examplesServerEnabled ? 'Native host disconnected' : null,
rootFound: examplesServerState.rootFound,
};
if (notifyOnDisconnect && browser.notifications) { if (notifyOnDisconnect && browser.notifications) {
browser.notifications.create('bridgeswarm-host-disconnect', { browser.notifications.create('bridgeswarm-host-disconnect', {
type: 'basic', type: 'basic',
@@ -243,7 +310,7 @@ function send(msg) {
}); });
} }
loadNotifySetting(); loadSettings();
connect(); connect();
// Handle tab closure - destroy swarms only when last tab using them closes // Handle tab closure - destroy swarms only when last tab using them closes
@@ -357,6 +424,28 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
.catch((err) => sendResponse({ ok: false, error: err.message })); .catch((err) => sendResponse({ ok: false, error: err.message }));
return true; return true;
} }
if (message.action === 'examplesServerStatus') {
syncExamplesServer()
.then((state) =>
sendResponse({
ok: !state.error,
enabled: examplesServerEnabled,
...state,
url: state.url || (examplesServerEnabled ? EXAMPLES_URL : null),
})
)
.catch((err) =>
sendResponse({
ok: false,
enabled: examplesServerEnabled,
running: false,
url: examplesServerEnabled ? EXAMPLES_URL : null,
error: err.message,
})
);
return true;
}
}); });
// Open dashboard when extension icon is clicked // Open dashboard when extension icon is clicked
+19 -2
View File
@@ -665,9 +665,26 @@
</div> </div>
</div> </div>
<div style="margin-top: 1.5rem;"> <div class="settings-group">
<button class="btn btn-primary" id="btnSaveSettings">Save Settings</button> <h3>Examples</h3>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Examples Server</div>
<div class="setting-desc">Serve bundled demos at http://127.0.0.1:4173/</div>
</div> </div>
<div class="toggle" id="toggleExamplesServer" data-setting="examplesServerEnabled"></div>
</div>
<div id="examplesServerPanel" style="display: none; margin-top: 0.75rem; padding: 0.75rem; background: #18181b; border-radius: 8px; border: 1px solid #3f3f46;">
<div style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; color: #93c5fd; word-break: break-all;" id="examplesServerUrl">http://127.0.0.1:4173/</div>
<div style="font-size: 0.8rem; color: #71717a; margin: 0.4rem 0 0.75rem;" id="examplesServerStatus">Off</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" id="btnOpenExamples">Open examples</button>
<button class="btn btn-secondary btn-sm" id="btnCopyExamplesUrl">Copy URL</button>
</div>
</div>
</div>
<p style="margin-top: 1.25rem; font-size: 0.8rem; color: #71717a;">Settings save automatically when you change them.</p>
</div> </div>
</div> </div>
</div> </div>
+75 -6
View File
@@ -60,38 +60,87 @@ async function fetchState() {
// Load settings // Load settings
function loadSettings() { function loadSettings() {
settingsLoading = true;
chrome.storage.local.get(SETTINGS_KEY, (result) => { chrome.storage.local.get(SETTINGS_KEY, (result) => {
settings = result[SETTINGS_KEY] || {}; settings = result[SETTINGS_KEY] || {};
updateSettingsUI(); updateSettingsUI();
settingsLoading = false;
}); });
} }
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
let settingsSaveTimer = null;
let settingsLoading = false;
// Update settings UI // Update settings UI
function updateSettingsUI() { function updateSettingsUI() {
$('toggleNotify').classList.toggle('active', settings.notifyOnDisconnect === true); $('toggleNotify').classList.toggle('active', settings.notifyOnDisconnect === true);
$('toggleDebug').classList.toggle('active', settings.debug === true); $('toggleDebug').classList.toggle('active', settings.debug === true);
$('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true); $('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true);
$('toggleExamplesServer')?.classList.toggle('active', settings.examplesServerEnabled === true);
$('defaultAppName').value = settings.defaultAppName || 'bridge-swarm'; $('defaultAppName').value = settings.defaultAppName || 'bridge-swarm';
$('defaultMaxPeers').value = settings.defaultMaxPeers || ''; $('defaultMaxPeers').value = settings.defaultMaxPeers || '';
$('defaultRequestTimeoutMs').value = settings.defaultRequestTimeoutMs || ''; $('defaultRequestTimeoutMs').value = settings.defaultRequestTimeoutMs || '';
$('readyTimeoutMs').value = settings.readyTimeoutMs || ''; $('readyTimeoutMs').value = settings.readyTimeoutMs || '';
updateExamplesServerPanel();
} }
// Save settings function updateExamplesServerPanel() {
function saveSettings() { const panel = $('examplesServerPanel');
const enabled = settings.examplesServerEnabled === true;
if (panel) panel.style.display = enabled ? 'block' : 'none';
const urlEl = $('examplesServerUrl');
const statusEl = $('examplesServerStatus');
if (urlEl) urlEl.textContent = EXAMPLES_URL;
if (!enabled) {
if (statusEl) statusEl.textContent = 'Off';
return;
}
if (statusEl) statusEl.textContent = 'Starting…';
chrome.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'examplesServerStatus' },
(res) => {
if (chrome.runtime.lastError) {
if (statusEl) statusEl.textContent = chrome.runtime.lastError.message;
return;
}
if (!res) {
if (statusEl) statusEl.textContent = 'No response';
return;
}
if (urlEl && res.url) urlEl.textContent = res.url;
if (!statusEl) return;
if (res.error) statusEl.textContent = res.error;
else if (res.running) statusEl.textContent = 'Running at ' + (res.url || EXAMPLES_URL);
else if (res.enabled) statusEl.textContent = 'Enabled — waiting for native host…';
else statusEl.textContent = 'Off';
}
);
}
// Save settings (autosave)
function saveSettings(quiet) {
settings.notifyOnDisconnect = $('toggleNotify').classList.contains('active'); settings.notifyOnDisconnect = $('toggleNotify').classList.contains('active');
settings.debug = $('toggleDebug').classList.contains('active'); settings.debug = $('toggleDebug').classList.contains('active');
settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') || false; settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') || false;
settings.examplesServerEnabled = $('toggleExamplesServer')?.classList.contains('active') || false;
settings.defaultAppName = $('defaultAppName').value || 'bridge-swarm'; settings.defaultAppName = $('defaultAppName').value || 'bridge-swarm';
settings.defaultMaxPeers = parseInt($('defaultMaxPeers').value) || 0; settings.defaultMaxPeers = parseInt($('defaultMaxPeers').value) || 0;
settings.defaultRequestTimeoutMs = parseInt($('defaultRequestTimeoutMs').value) || 0; settings.defaultRequestTimeoutMs = parseInt($('defaultRequestTimeoutMs').value) || 0;
settings.readyTimeoutMs = parseInt($('readyTimeoutMs').value) || 0; settings.readyTimeoutMs = parseInt($('readyTimeoutMs').value) || 0;
chrome.storage.local.set({ [SETTINGS_KEY]: settings }, () => { chrome.storage.local.set({ [SETTINGS_KEY]: settings }, () => {
log('Settings saved'); if (!quiet) log('Settings saved');
updateExamplesServerPanel();
}); });
} }
function scheduleSaveSettings() {
if (settingsLoading) return;
clearTimeout(settingsSaveTimer);
settingsSaveTimer = setTimeout(() => saveSettings(true), 250);
}
// Update dashboard // Update dashboard
function updateDashboard(state) { function updateDashboard(state) {
currentState = state; currentState = state;
@@ -261,15 +310,35 @@ function setupEvents() {
// Refresh button // Refresh button
$('refreshBtn').addEventListener('click', refresh); $('refreshBtn').addEventListener('click', refresh);
// Toggle switches // Toggle switches — autosave on change
document.querySelectorAll('.toggle').forEach(toggle => { document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => { toggle.addEventListener('click', () => {
toggle.classList.toggle('active'); toggle.classList.toggle('active');
scheduleSaveSettings();
}); });
}); });
// Save settings ['defaultAppName', 'defaultMaxPeers', 'defaultRequestTimeoutMs', 'readyTimeoutMs'].forEach((id) => {
$('btnSaveSettings').addEventListener('click', saveSettings); const el = $(id);
if (!el) return;
el.addEventListener('change', scheduleSaveSettings);
el.addEventListener('input', scheduleSaveSettings);
});
$('btnOpenExamples')?.addEventListener('click', () => {
const url = $('examplesServerUrl')?.textContent || EXAMPLES_URL;
chrome.tabs.create({ url });
});
$('btnCopyExamplesUrl')?.addEventListener('click', async () => {
const url = $('examplesServerUrl')?.textContent || EXAMPLES_URL;
try {
await navigator.clipboard.writeText(url);
log('Examples URL copied');
} catch (_) {
log('Could not copy examples URL');
}
});
// Logs functionality // Logs functionality
let logs = []; let logs = [];
+3 -1
View File
@@ -36,7 +36,9 @@
"protomux-bundle.js", "protomux-bundle.js",
"defaults.js", "defaults.js",
"dashboard.html", "dashboard.html",
"dashboard.js" "dashboard.js",
"examples/*",
"examples/**/*"
], ],
"matches": [ "matches": [
"<all_urls>" "<all_urls>"
+3 -1
View File
@@ -35,7 +35,9 @@
"protomux-bundle.js", "protomux-bundle.js",
"defaults.js", "defaults.js",
"dashboard.html", "dashboard.html",
"dashboard.js" "dashboard.js",
"examples/*",
"examples/**/*"
], ],
"matches": [ "matches": [
"<all_urls>" "<all_urls>"
+40 -2
View File
@@ -75,11 +75,30 @@
} }
.toast.show { opacity: 1; } .toast.show { opacity: 1; }
.toast.err { color: #f7768e; } .toast.err { color: #f7768e; }
.examples-panel {
margin-top: 0.75rem;
padding: 0.75rem 0.9rem;
background: #24283b;
border: 1px solid #3b4261;
border-radius: 8px;
}
.examples-panel[hidden] { display: none !important; }
.examples-url {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.9rem;
color: #7aa2f7;
word-break: break-all;
}
.examples-status { font-size: 0.8rem; color: #565f89; margin: 0.4rem 0 0.75rem; }
.examples-status.ok { color: #9ece6a; }
.examples-status.err { color: #f7768e; }
.examples-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.autosave-hint { font-size: 0.8rem; color: #565f89; margin-top: 0.25rem; }
</style> </style>
</head> </head>
<body> <body>
<h1>BridgeSwarm</h1> <h1>BridgeSwarm</h1>
<p class="sub">Extension settings. Changes apply to new swarms and requests.</p> <p class="sub">Extension settings. Changes save automatically and apply to new swarms and requests.</p>
<p style="margin-bottom: 1.5rem;"> <p style="margin-bottom: 1.5rem;">
<a href="dashboard.html" target="_blank" style="color: #7aa2f7; text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;"> <a href="dashboard.html" target="_blank" style="color: #7aa2f7; text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;">
@@ -145,6 +164,25 @@
</div> </div>
</section> </section>
<section>
<h2>Examples</h2>
<div class="field">
<div class="checkbox-row">
<input type="checkbox" id="examplesServerEnabled" aria-describedby="examplesHint">
<label for="examplesServerEnabled">Enable examples server</label>
</div>
<p class="hint" id="examplesHint">Starts a local HTTP server (via the native host) that serves the bundled demos. Open the URL below in a normal tab so BridgeSwarm can inject.</p>
<div class="examples-panel" id="examplesPanel" hidden>
<div>Open: <a class="examples-url" id="examplesUrl" href="http://127.0.0.1:4173/" target="_blank" rel="noopener">http://127.0.0.1:4173/</a></div>
<p class="examples-status" id="examplesStatus">Off</p>
<div class="examples-actions">
<button type="button" class="primary" id="btnOpenExamples">Open examples</button>
<button type="button" class="secondary" id="btnCopyExamplesUrl">Copy URL</button>
</div>
</div>
</div>
</section>
<section> <section>
<h2>Debug</h2> <h2>Debug</h2>
<div class="field"> <div class="field">
@@ -157,10 +195,10 @@
</section> </section>
<div class="actions"> <div class="actions">
<button type="button" class="primary" id="btnSave">Save</button>
<button type="button" class="secondary" id="btnReset">Reset to defaults</button> <button type="button" class="secondary" id="btnReset">Reset to defaults</button>
<span class="toast" id="toast"></span> <span class="toast" id="toast"></span>
</div> </div>
<p class="autosave-hint">Settings save as you change them.</p>
<script src="options.js"></script> <script src="options.js"></script>
</body> </body>
+118 -7
View File
@@ -1,6 +1,7 @@
(function () { (function () {
const browser = typeof chrome !== 'undefined' && chrome.storage ? chrome : typeof browser !== 'undefined' ? browser : chrome; const browser = typeof chrome !== 'undefined' && chrome.storage ? chrome : typeof browser !== 'undefined' ? browser : chrome;
const KEY = 'bridgeSwarmSettings'; const KEY = 'bridgeSwarmSettings';
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
const DEFAULTS = { const DEFAULTS = {
defaultAppName: 'bridge-swarm', defaultAppName: 'bridge-swarm',
defaultRequestTimeoutMs: 0, defaultRequestTimeoutMs: 0,
@@ -8,7 +9,8 @@
readyTimeoutMs: 0, readyTimeoutMs: 0,
disableOnFileUrls: false, disableOnFileUrls: false,
notifyOnDisconnect: false, notifyOnDisconnect: false,
debug: false debug: false,
examplesServerEnabled: false,
}; };
const defaultAppNameEl = document.getElementById('defaultAppName'); const defaultAppNameEl = document.getElementById('defaultAppName');
@@ -18,10 +20,18 @@
const disableOnFileUrlsEl = document.getElementById('disableOnFileUrls'); const disableOnFileUrlsEl = document.getElementById('disableOnFileUrls');
const notifyOnDisconnectEl = document.getElementById('notifyOnDisconnect'); const notifyOnDisconnectEl = document.getElementById('notifyOnDisconnect');
const debugEl = document.getElementById('debug'); const debugEl = document.getElementById('debug');
const btnSave = document.getElementById('btnSave'); const examplesServerEnabledEl = document.getElementById('examplesServerEnabled');
const examplesPanelEl = document.getElementById('examplesPanel');
const examplesUrlEl = document.getElementById('examplesUrl');
const examplesStatusEl = document.getElementById('examplesStatus');
const btnOpenExamples = document.getElementById('btnOpenExamples');
const btnCopyExamplesUrl = document.getElementById('btnCopyExamplesUrl');
const btnReset = document.getElementById('btnReset'); const btnReset = document.getElementById('btnReset');
const toastEl = document.getElementById('toast'); const toastEl = document.getElementById('toast');
let saveTimer = null;
let loading = false;
function showToast(msg, isError) { function showToast(msg, isError) {
toastEl.textContent = msg; toastEl.textContent = msg;
toastEl.className = 'toast show' + (isError ? ' err' : ''); toastEl.className = 'toast show' + (isError ? ' err' : '');
@@ -31,16 +41,69 @@
}, 2500); }, 2500);
} }
function updateExamplesPanel() {
const on = examplesServerEnabledEl.checked;
examplesPanelEl.hidden = !on;
examplesUrlEl.textContent = EXAMPLES_URL;
examplesUrlEl.href = EXAMPLES_URL;
if (!on) {
examplesStatusEl.textContent = 'Off';
examplesStatusEl.className = 'examples-status';
return;
}
examplesStatusEl.textContent = 'Starting…';
examplesStatusEl.className = 'examples-status';
browser.runtime.sendMessage(
{ target: 'bridge-swarm-native', action: 'examplesServerStatus' },
function (res) {
if (browser.runtime.lastError) {
examplesStatusEl.textContent = browser.runtime.lastError.message;
examplesStatusEl.className = 'examples-status err';
return;
}
if (!res) {
examplesStatusEl.textContent = 'No response from background';
examplesStatusEl.className = 'examples-status err';
return;
}
if (res.error) {
examplesStatusEl.textContent = res.error;
examplesStatusEl.className = 'examples-status err';
return;
}
if (res.running) {
examplesStatusEl.textContent = 'Running at ' + (res.url || EXAMPLES_URL);
examplesStatusEl.className = 'examples-status ok';
} else if (res.enabled) {
examplesStatusEl.textContent = 'Enabled — waiting for native host…';
examplesStatusEl.className = 'examples-status';
} else {
examplesStatusEl.textContent = 'Off';
examplesStatusEl.className = 'examples-status';
}
if (res.url) {
examplesUrlEl.textContent = res.url;
examplesUrlEl.href = res.url;
}
}
);
}
function load() { function load() {
loading = true;
browser.storage.local.get(KEY, function (result) { browser.storage.local.get(KEY, function (result) {
const s = result[KEY] || {}; const s = result[KEY] || {};
defaultAppNameEl.value = s.defaultAppName != null ? s.defaultAppName : DEFAULTS.defaultAppName; defaultAppNameEl.value = s.defaultAppName != null ? s.defaultAppName : DEFAULTS.defaultAppName;
defaultRequestTimeoutMsEl.value = s.defaultRequestTimeoutMs != null ? s.defaultRequestTimeoutMs : DEFAULTS.defaultRequestTimeoutMs; defaultRequestTimeoutMsEl.value =
s.defaultRequestTimeoutMs != null ? s.defaultRequestTimeoutMs : DEFAULTS.defaultRequestTimeoutMs;
defaultMaxPeersEl.value = s.defaultMaxPeers != null ? s.defaultMaxPeers : DEFAULTS.defaultMaxPeers; defaultMaxPeersEl.value = s.defaultMaxPeers != null ? s.defaultMaxPeers : DEFAULTS.defaultMaxPeers;
readyTimeoutMsEl.value = s.readyTimeoutMs != null ? s.readyTimeoutMs : DEFAULTS.readyTimeoutMs; readyTimeoutMsEl.value = s.readyTimeoutMs != null ? s.readyTimeoutMs : DEFAULTS.readyTimeoutMs;
disableOnFileUrlsEl.checked = s.disableOnFileUrls === true; disableOnFileUrlsEl.checked = s.disableOnFileUrls === true;
notifyOnDisconnectEl.checked = s.notifyOnDisconnect === true; notifyOnDisconnectEl.checked = s.notifyOnDisconnect === true;
debugEl.checked = s.debug === true; debugEl.checked = s.debug === true;
examplesServerEnabledEl.checked = s.examplesServerEnabled === true;
loading = false;
updateExamplesPanel();
}); });
} }
@@ -76,11 +139,12 @@
readyTimeoutMs: readyTimeout, readyTimeoutMs: readyTimeout,
disableOnFileUrls: disableOnFileUrlsEl.checked, disableOnFileUrls: disableOnFileUrlsEl.checked,
notifyOnDisconnect: notifyOnDisconnectEl.checked, notifyOnDisconnect: notifyOnDisconnectEl.checked,
debug: debugEl.checked debug: debugEl.checked,
examplesServerEnabled: examplesServerEnabledEl.checked,
}; };
} }
btnSave.addEventListener('click', function () { function saveNow(quiet) {
const s = validate(); const s = validate();
if (!s) return; if (!s) return;
browser.storage.local.set({ [KEY]: s }, function () { browser.storage.local.set({ [KEY]: s }, function () {
@@ -88,8 +152,53 @@
showToast('Save failed: ' + browser.runtime.lastError.message, true); showToast('Save failed: ' + browser.runtime.lastError.message, true);
return; return;
} }
showToast('Saved.'); if (!quiet) showToast('Saved.');
updateExamplesPanel();
}); });
}
function scheduleSave() {
if (loading) return;
clearTimeout(saveTimer);
saveTimer = setTimeout(function () {
saveNow(true);
}, 250);
}
const inputs = [
defaultAppNameEl,
defaultRequestTimeoutMsEl,
defaultMaxPeersEl,
readyTimeoutMsEl,
disableOnFileUrlsEl,
notifyOnDisconnectEl,
debugEl,
examplesServerEnabledEl,
];
for (const el of inputs) {
el.addEventListener('change', scheduleSave);
if (el.tagName === 'INPUT' && (el.type === 'text' || el.type === 'number')) {
el.addEventListener('input', scheduleSave);
}
}
examplesServerEnabledEl.addEventListener('change', function () {
updateExamplesPanel();
});
btnOpenExamples.addEventListener('click', function () {
const url = examplesUrlEl.href || EXAMPLES_URL;
browser.tabs.create({ url: url });
});
btnCopyExamplesUrl.addEventListener('click', async function () {
const url = examplesUrlEl.textContent || EXAMPLES_URL;
try {
await navigator.clipboard.writeText(url);
showToast('URL copied.');
} catch (_) {
showToast('Could not copy URL.', true);
}
}); });
btnReset.addEventListener('click', function () { btnReset.addEventListener('click', function () {
@@ -100,12 +209,14 @@
disableOnFileUrlsEl.checked = DEFAULTS.disableOnFileUrls; disableOnFileUrlsEl.checked = DEFAULTS.disableOnFileUrls;
notifyOnDisconnectEl.checked = DEFAULTS.notifyOnDisconnect; notifyOnDisconnectEl.checked = DEFAULTS.notifyOnDisconnect;
debugEl.checked = DEFAULTS.debug; debugEl.checked = DEFAULTS.debug;
browser.storage.local.set({ [KEY]: DEFAULTS }, function () { examplesServerEnabledEl.checked = DEFAULTS.examplesServerEnabled;
browser.storage.local.set({ [KEY]: Object.assign({}, DEFAULTS) }, function () {
if (browser.runtime.lastError) { if (browser.runtime.lastError) {
showToast('Reset failed: ' + browser.runtime.lastError.message, true); showToast('Reset failed: ' + browser.runtime.lastError.message, true);
return; return;
} }
showToast('Reset to defaults.'); showToast('Reset to defaults.');
updateExamplesPanel();
}); });
}); });
+210
View File
@@ -0,0 +1,210 @@
/**
* Local static HTTP server for BridgeSwarm examples (127.0.0.1).
* Started/stopped via native messaging (examplesServer.start / stop / status).
*/
const http = require('bare-http1');
const fs = require('bare-fs');
const path = require('bare-path');
const DEFAULT_HOST = '127.0.0.1';
const DEFAULT_PORT = 4173;
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.map': 'application/json',
'.txt': 'text/plain; charset=utf-8',
'.md': 'text/markdown; charset=utf-8',
};
/** @type {import('bare-http1').Server | null} */
let server = null;
let boundHost = DEFAULT_HOST;
let boundPort = DEFAULT_PORT;
let examplesRoot = null;
function exists(p) {
try {
return fs.existsSync(p);
} catch (_) {
return false;
}
}
function resolveExamplesRoot() {
if (process.env.BRIDGESWARM_EXAMPLES_DIR) {
return path.resolve(process.env.BRIDGESWARM_EXAMPLES_DIR);
}
const candidates = [];
if (process.env.BRIDGE_SWARM_STORAGE) {
candidates.push(path.join(path.dirname(process.env.BRIDGE_SWARM_STORAGE), 'examples'));
}
candidates.push(path.join(process.cwd(), 'examples'));
candidates.push(path.join(__dirname, 'examples'));
candidates.push(path.join(__dirname, '..', 'examples'));
for (const dir of candidates) {
if (exists(path.join(dir, 'index.html'))) return dir;
}
return null;
}
function safeJoin(root, urlPath) {
const decoded = decodeURIComponent((urlPath || '/').split('?')[0]);
const cleaned = path.normalize(decoded).replace(/^(\.\.[/\\])+/, '');
const full = path.join(root, cleaned);
const rootNorm = path.resolve(root);
const fullNorm = path.resolve(full);
if (fullNorm !== rootNorm && !fullNorm.startsWith(rootNorm + path.sep)) return null;
return fullNorm;
}
function contentType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME[ext] || 'application/octet-stream';
}
function send(res, status, body, headers = {}) {
const buf = typeof body === 'string' ? Buffer.from(body) : body;
res.statusCode = status;
res.setHeader('Cache-Control', 'no-store');
for (const [name, value] of Object.entries(headers)) {
res.setHeader(name, value);
}
res.end(buf);
}
function handleRequest(req, res) {
const root = examplesRoot;
if (!root) {
send(res, 503, 'Examples not found on disk');
return;
}
let urlPath = req.url || '/';
if (urlPath === '/') urlPath = '/index.html';
const filePath = safeJoin(root, urlPath);
if (!filePath) {
send(res, 403, 'Forbidden');
return;
}
let st;
try {
st = fs.statSync(filePath);
} catch (_) {
send(res, 404, 'Not found');
return;
}
let target = filePath;
if (st.isDirectory()) {
target = path.join(filePath, 'index.html');
if (!exists(target)) {
send(res, 404, 'Not found');
return;
}
}
try {
const data = fs.readFileSync(target);
send(res, 200, data, { 'Content-Type': contentType(target) });
} catch (_) {
send(res, 404, 'Not found');
}
}
function status() {
const root = examplesRoot || resolveExamplesRoot();
const url = server ? `http://${boundHost}:${boundPort}/` : null;
return {
ok: true,
running: !!server,
url,
host: boundHost,
port: boundPort,
root: root || null,
rootFound: !!(root && exists(path.join(root, 'index.html'))),
};
}
function start(opts = {}) {
if (server) {
return { ...status(), ok: true };
}
const root = resolveExamplesRoot();
if (!root) {
return {
ok: false,
error:
'Examples directory not found. Reinstall BridgeSwarm or set BRIDGESWARM_EXAMPLES_DIR.',
running: false,
url: null,
root: null,
rootFound: false,
};
}
examplesRoot = root;
boundHost = typeof opts.host === 'string' && opts.host ? opts.host : DEFAULT_HOST;
boundPort = typeof opts.port === 'number' && opts.port > 0 ? opts.port : DEFAULT_PORT;
return new Promise((resolve) => {
const s = http.createServer(handleRequest);
const onError = (err) => {
s.removeListener('listening', onListening);
server = null;
resolve({
ok: false,
error: err.message || String(err),
running: false,
url: null,
host: boundHost,
port: boundPort,
root: examplesRoot,
rootFound: true,
});
};
const onListening = () => {
s.removeListener('error', onError);
server = s;
resolve({ ...status(), ok: true });
};
s.once('error', onError);
s.once('listening', onListening);
s.listen(boundPort, boundHost);
});
}
function stop() {
if (!server) {
return Promise.resolve({ ...status(), ok: true, running: false, url: null });
}
const s = server;
server = null;
return new Promise((resolve) => {
try {
s.close(() => resolve({ ...status(), ok: true, running: false, url: null }));
} catch (_) {
resolve({ ...status(), ok: true, running: false, url: null });
}
});
}
module.exports = { start, stop, status, resolveExamplesRoot, DEFAULT_HOST, DEFAULT_PORT };
+17
View File
@@ -19,6 +19,7 @@ const minimalDefinition = require('./hyperdb-minimal-definition.js');
const b4a = require('b4a'); const b4a = require('b4a');
const capabilities = require('./capabilities/registry.js'); const capabilities = require('./capabilities/registry.js');
const { getStorageRoot } = require('./capabilities/paths.js'); const { getStorageRoot } = require('./capabilities/paths.js');
const examplesServer = require('./examples-server.js');
// File-based logging // File-based logging
const LOG_FILE = path.join(__dirname, 'bridge-swarm.log'); const LOG_FILE = path.join(__dirname, 'bridge-swarm.log');
@@ -1183,6 +1184,19 @@ async function handleMessageAsync(send, msg) {
break; break;
} }
case 'examplesServer.start': {
reply(await examplesServer.start(payload || {}));
break;
}
case 'examplesServer.stop': {
reply(await examplesServer.stop());
break;
}
case 'examplesServer.status': {
reply(examplesServer.status());
break;
}
case 'capabilities.list': { case 'capabilities.list': {
reply({ ok: true, packs: capabilities.listPackIds() }); reply({ ok: true, packs: capabilities.listPackIds() });
break; break;
@@ -1264,6 +1278,9 @@ async function handleMessageAsync(send, msg) {
} }
function cleanup() { function cleanup() {
try {
examplesServer.stop().catch(() => {});
} catch (_) {}
try { try {
const mediaPack = capabilities.getPack('media'); const mediaPack = capabilities.getPack('media');
if (mediaPack && typeof mediaPack.cleanup === 'function') mediaPack.cleanup(); if (mediaPack && typeof mediaPack.cleanup === 'function') mediaPack.cleanup();
+1
View File
@@ -14,6 +14,7 @@
"bare-fetch": "^3.2.0", "bare-fetch": "^3.2.0",
"bare-ffmpeg": "^1.5.0", "bare-ffmpeg": "^1.5.0",
"bare-fs": "^4.7.4", "bare-fs": "^4.7.4",
"bare-http1": "^4.5.7",
"bare-media": "^2.10.1", "bare-media": "^2.10.1",
"bare-module": "^6.4.0", "bare-module": "^6.4.0",
"bare-path": "^3.1.1", "bare-path": "^3.1.1",
+1
View File
@@ -15,6 +15,7 @@
"bare-fetch": "^3.2.0", "bare-fetch": "^3.2.0",
"bare-ffmpeg": "^1.5.0", "bare-ffmpeg": "^1.5.0",
"bare-fs": "^4.7.4", "bare-fs": "^4.7.4",
"bare-http1": "^4.5.7",
"bare-media": "^2.10.1", "bare-media": "^2.10.1",
"bare-module": "^6.4.0", "bare-module": "^6.4.0",
"bare-path": "^3.1.1", "bare-path": "^3.1.1",
+1
View File
@@ -12,6 +12,7 @@
"build:hyperdb": "node scripts/build-hyperdb.js", "build:hyperdb": "node scripts/build-hyperdb.js",
"build": "npm run build:host && npm run build:protomux && npm run build:hrpc && npm run build:hyperdb", "build": "npm run build:host && npm run build:protomux && npm run build:hrpc && npm run build:hyperdb",
"pack": "node scripts/pack-extension.js", "pack": "node scripts/pack-extension.js",
"sync:examples": "node scripts/sync-examples.js",
"examples": "node scripts/serve-examples.js", "examples": "node scripts/serve-examples.js",
"repair:macos": "bash scripts/repair-macos-codesign.sh", "repair:macos": "bash scripts/repair-macos-codesign.sh",
"build:dist": "node scripts/build-distributable.js", "build:dist": "node scripts/build-distributable.js",
+12
View File
@@ -280,6 +280,12 @@ async function createZipArchives(builtEntries) {
} }
} }
const examplesDir = path.join(NATIVE_HOST_DIR, 'examples');
if (!fs.existsSync(path.join(examplesDir, 'index.html'))) {
console.log(' Syncing examples for host packages...');
execSync('node scripts/sync-examples.js', { cwd: ROOT, stdio: 'inherit' });
}
for (const [host, file] of byHost) { for (const [host, file] of byHost) {
const zipName = `${HOST_NAME}-${host}.zip`; const zipName = `${HOST_NAME}-${host}.zip`;
const zipPath = path.join(RELEASES_DIR, zipName); const zipPath = path.join(RELEASES_DIR, zipName);
@@ -295,6 +301,9 @@ async function createZipArchives(builtEntries) {
} else { } else {
archive.file(file, { name: path.basename(file) }); archive.file(file, { name: path.basename(file) });
} }
if (fs.existsSync(path.join(examplesDir, 'index.html'))) {
archive.directory(examplesDir, 'examples');
}
archive.finalize(); archive.finalize();
}); });
@@ -311,6 +320,9 @@ async function createZipArchives(builtEntries) {
async function build(hosts, doPackage) { async function build(hosts, doPackage) {
patchBareBuildSignForLinux(); patchBareBuildSignForLinux();
console.log('Syncing examples into native-host...');
execSync('node scripts/sync-examples.js', { cwd: ROOT, stdio: 'inherit' });
// Ensure HRPC spec is mirrored into native-host before packing // Ensure HRPC spec is mirrored into native-host before packing
console.log('Building HRPC spec...'); console.log('Building HRPC spec...');
execSync('npm run build:hrpc', { cwd: ROOT, stdio: 'inherit' }); execSync('npm run build:hrpc', { cwd: ROOT, stdio: 'inherit' });
+3
View File
@@ -67,6 +67,9 @@ if [[ ! -f "$EXT_DIR/manifest.json" ]]; then
exit 1 exit 1
fi fi
echo " Syncing examples into extension + native-host..."
node "$REPO_ROOT/scripts/sync-examples.js"
if command -v pbcopy >/dev/null 2>&1; then if command -v pbcopy >/dev/null 2>&1; then
echo "$EXT_DIR" | pbcopy echo "$EXT_DIR" | pbcopy
elif command -v xclip >/dev/null 2>&1; then elif command -v xclip >/dev/null 2>&1; then
+3
View File
@@ -13,6 +13,9 @@ else
echo " node_modules exists, skipping npm install" echo " node_modules exists, skipping npm install"
fi fi
echo "Syncing examples into extension and native-host..."
node "$REPO_ROOT/scripts/sync-examples.js" || true
# Prefer Bare bundled with native-host (npm package `bare` → bare-runtime). # Prefer Bare bundled with native-host (npm package `bare` → bare-runtime).
BARE_PATH="" BARE_PATH=""
LOCAL_BARE="$HOST_DIR/node_modules/bare/bin/bare" LOCAL_BARE="$HOST_DIR/node_modules/bare/bin/bare"
+1
View File
@@ -88,6 +88,7 @@ $Bat = @"
@echo off @echo off
set "DIR=%~dp0" set "DIR=%~dp0"
if not defined BRIDGE_SWARM_STORAGE set "BRIDGE_SWARM_STORAGE=%DIR%bridge-swarm-storage" if not defined BRIDGE_SWARM_STORAGE set "BRIDGE_SWARM_STORAGE=%DIR%bridge-swarm-storage"
if not defined BRIDGESWARM_EXAMPLES_DIR set "BRIDGESWARM_EXAMPLES_DIR=%DIR%examples"
"%DIR%bridge-swarm-host.exe" %* "%DIR%bridge-swarm-host.exe" %*
"@ "@
Set-Content -Path $Launcher -Value $Bat -Encoding ASCII Set-Content -Path $Launcher -Value $Bat -Encoding ASCII
+2 -2
View File
@@ -184,7 +184,7 @@ if [[ "$PLATFORM" == "darwin" ]]; then
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST" printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST"
codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" 2>/dev/null || true codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" 2>/dev/null || true
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER" printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'export BRIDGESWARM_EXAMPLES_DIR="${BRIDGESWARM_EXAMPLES_DIR:-${DIR}/examples}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
chmod +x "$LAUNCHER" chmod +x "$LAUNCHER"
HOST_BIN="$LAUNCHER" HOST_BIN="$LAUNCHER"
@@ -206,7 +206,7 @@ if [[ "$PLATFORM" == "darwin" ]]; then
fi fi
echo " Signed ${SIGNED} native addons; main binary has library-validation disabled" echo " Signed ${SIGNED} native addons; main binary has library-validation disabled"
else else
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER" printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'export BRIDGESWARM_EXAMPLES_DIR="${BRIDGESWARM_EXAMPLES_DIR:-${DIR}/examples}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
chmod +x "$LAUNCHER" chmod +x "$LAUNCHER"
HOST_BIN="$LAUNCHER" HOST_BIN="$LAUNCHER"
echo "Installing native messaging manifest..." echo "Installing native messaging manifest..."
+3
View File
@@ -89,6 +89,9 @@ async function main() {
const version = getVersion(); const version = getVersion();
console.log('BridgeSwarm version from manifest:', version); console.log('BridgeSwarm version from manifest:', version);
console.log('Syncing examples into extension...');
execSync('node scripts/sync-examples.js', { cwd: REPO_ROOT, stdio: 'inherit' });
console.log('Running npm run build:protomux...'); console.log('Running npm run build:protomux...');
execSync('npm run build:protomux', { cwd: REPO_ROOT, stdio: 'inherit' }); execSync('npm run build:protomux', { cwd: REPO_ROOT, stdio: 'inherit' });
+1 -1
View File
@@ -40,7 +40,7 @@ ENTITLEMENTS_PLIST="$INSTALL_DIR/entitlements.plist"
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST" printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST"
codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" || true codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" || true
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER" printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'export BRIDGESWARM_EXAMPLES_DIR="${BRIDGESWARM_EXAMPLES_DIR:-${DIR}/examples}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
chmod +x "$LAUNCHER" chmod +x "$LAUNCHER"
echo "Extracting addons into $INSTALL_DIR/tmp ..." echo "Extracting addons into $INSTALL_DIR/tmp ..."
+3
View File
@@ -4,6 +4,9 @@
* Modern Chrome/Firefox treat each file:// URL as a unique opaque origin, which * Modern Chrome/Firefox treat each file:// URL as a unique opaque origin, which
* breaks local HTML demos (scripts, styles, extension injection). * breaks local HTML demos (scripts, styles, extension injection).
* *
* Prefer the extension Settings toggle ("Enable examples server") which starts
* the same URL via the native host. This script is for repo/dev use without that.
*
* Usage: npm run examples * Usage: npm run examples
* node scripts/serve-examples.js [port] * node scripts/serve-examples.js [port]
*/ */
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env node
/**
* Copy repo examples/ into the extension (and optionally native-host) so they
* ship with packed builds. Source of truth remains examples/ at the repo root.
*
* Usage: node scripts/sync-examples.js
*/
'use strict';
const fs = require('fs');
const path = require('path');
const REPO_ROOT = path.resolve(__dirname, '..');
const SRC = path.join(REPO_ROOT, 'examples');
const DESTINATIONS = [
path.join(REPO_ROOT, 'extension', 'examples'),
path.join(REPO_ROOT, 'native-host', 'examples'),
];
function rmrf(dir) {
if (!fs.existsSync(dir)) return;
fs.rmSync(dir, { recursive: true, force: true });
}
function copyDir(src, dest) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
if (entry.name === '.DS_Store') continue;
const from = path.join(src, entry.name);
const to = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDir(from, to);
} else {
fs.copyFileSync(from, to);
}
}
}
function main() {
if (!fs.existsSync(path.join(SRC, 'index.html'))) {
throw new Error('examples/index.html missing — nothing to sync');
}
for (const dest of DESTINATIONS) {
rmrf(dest);
copyDir(SRC, dest);
console.log('Synced examples →', path.relative(REPO_ROOT, dest));
}
}
main();