This commit is contained in:
@@ -7,6 +7,10 @@ releases/
|
||||
# Mirrored HRPC/hyperschema output for bare-pack (generated by npm run build:hrpc)
|
||||
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/bridge-swarm-host
|
||||
native-host/bridge-swarm-host.bat
|
||||
|
||||
@@ -87,11 +87,9 @@ npm run build:dist:package # all-platform host zips (needs bare-build; best
|
||||
|
||||
### Try the examples
|
||||
|
||||
```bash
|
||||
npm run examples
|
||||
```
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -326,11 +324,12 @@ Run `npm run build:hrpc` to generate the HRPC spec.
|
||||
### Build Commands
|
||||
|
||||
```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:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec)
|
||||
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:package # All platforms + zip archives (CI)
|
||||
```
|
||||
|
||||
+4
-2
@@ -6,13 +6,15 @@ Open these in your browser **after** installing the BridgeSwarm extension and na
|
||||
|
||||
## 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
|
||||
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`).
|
||||
|
||||
|
||||
+95
-6
@@ -55,7 +55,15 @@ const swarmRefCount = new Map();
|
||||
const activeConnections = new Map();
|
||||
|
||||
const SETTINGS_KEY = 'bridgeSwarmSettings';
|
||||
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
|
||||
let notifyOnDisconnect = false;
|
||||
let examplesServerEnabled = false;
|
||||
let examplesServerState = {
|
||||
running: false,
|
||||
url: null,
|
||||
error: null,
|
||||
rootFound: null,
|
||||
};
|
||||
|
||||
// Extension state for the UI
|
||||
const extensionState = {
|
||||
@@ -92,17 +100,67 @@ function updateExtensionState() {
|
||||
extensionState.stats.totalSwarms = extensionState.swarms.size;
|
||||
}
|
||||
|
||||
function loadNotifySetting() {
|
||||
browser.storage.local.get(SETTINGS_KEY, (result) => {
|
||||
const s = result[SETTINGS_KEY] || {};
|
||||
function applySettingsFromStorage(s) {
|
||||
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) => {
|
||||
if (areaName === 'local' && changes[SETTINGS_KEY]) {
|
||||
const s = changes[SETTINGS_KEY].newValue || {};
|
||||
notifyOnDisconnect = s.notifyOnDisconnect === true;
|
||||
applySettingsFromStorage(changes[SETTINGS_KEY].newValue || {});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -117,6 +175,9 @@ function connect() {
|
||||
}
|
||||
|
||||
reconnectDelay = INITIAL_RECONNECT_DELAY;
|
||||
if (examplesServerEnabled) {
|
||||
syncExamplesServer().catch(() => {});
|
||||
}
|
||||
|
||||
port.onMessage.addListener((msg) => {
|
||||
log('Received from native:', msg.type, msg.event || '');
|
||||
@@ -195,6 +256,12 @@ function connect() {
|
||||
activeConnections.clear();
|
||||
updateExtensionState();
|
||||
port = null;
|
||||
examplesServerState = {
|
||||
running: false,
|
||||
url: examplesServerEnabled ? EXAMPLES_URL : null,
|
||||
error: examplesServerEnabled ? 'Native host disconnected' : null,
|
||||
rootFound: examplesServerState.rootFound,
|
||||
};
|
||||
if (notifyOnDisconnect && browser.notifications) {
|
||||
browser.notifications.create('bridgeswarm-host-disconnect', {
|
||||
type: 'basic',
|
||||
@@ -243,7 +310,7 @@ function send(msg) {
|
||||
});
|
||||
}
|
||||
|
||||
loadNotifySetting();
|
||||
loadSettings();
|
||||
connect();
|
||||
|
||||
// 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 }));
|
||||
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
|
||||
|
||||
@@ -665,9 +665,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<button class="btn btn-primary" id="btnSaveSettings">Save Settings</button>
|
||||
<div class="settings-group">
|
||||
<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 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>
|
||||
|
||||
+75
-6
@@ -60,38 +60,87 @@ async function fetchState() {
|
||||
|
||||
// Load settings
|
||||
function loadSettings() {
|
||||
settingsLoading = true;
|
||||
chrome.storage.local.get(SETTINGS_KEY, (result) => {
|
||||
settings = result[SETTINGS_KEY] || {};
|
||||
updateSettingsUI();
|
||||
settingsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
|
||||
let settingsSaveTimer = null;
|
||||
let settingsLoading = false;
|
||||
|
||||
// Update settings UI
|
||||
function updateSettingsUI() {
|
||||
$('toggleNotify').classList.toggle('active', settings.notifyOnDisconnect === true);
|
||||
$('toggleDebug').classList.toggle('active', settings.debug === true);
|
||||
$('toggleDisableFileUrls')?.classList.toggle('active', settings.disableOnFileUrls === true);
|
||||
$('toggleExamplesServer')?.classList.toggle('active', settings.examplesServerEnabled === true);
|
||||
$('defaultAppName').value = settings.defaultAppName || 'bridge-swarm';
|
||||
$('defaultMaxPeers').value = settings.defaultMaxPeers || '';
|
||||
$('defaultRequestTimeoutMs').value = settings.defaultRequestTimeoutMs || '';
|
||||
$('readyTimeoutMs').value = settings.readyTimeoutMs || '';
|
||||
updateExamplesServerPanel();
|
||||
}
|
||||
|
||||
// Save settings
|
||||
function saveSettings() {
|
||||
function updateExamplesServerPanel() {
|
||||
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.debug = $('toggleDebug').classList.contains('active');
|
||||
settings.disableOnFileUrls = $('toggleDisableFileUrls')?.classList.contains('active') || false;
|
||||
settings.examplesServerEnabled = $('toggleExamplesServer')?.classList.contains('active') || false;
|
||||
settings.defaultAppName = $('defaultAppName').value || 'bridge-swarm';
|
||||
settings.defaultMaxPeers = parseInt($('defaultMaxPeers').value) || 0;
|
||||
settings.defaultRequestTimeoutMs = parseInt($('defaultRequestTimeoutMs').value) || 0;
|
||||
settings.readyTimeoutMs = parseInt($('readyTimeoutMs').value) || 0;
|
||||
|
||||
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
|
||||
function updateDashboard(state) {
|
||||
currentState = state;
|
||||
@@ -261,15 +310,35 @@ function setupEvents() {
|
||||
// Refresh button
|
||||
$('refreshBtn').addEventListener('click', refresh);
|
||||
|
||||
// Toggle switches
|
||||
// Toggle switches — autosave on change
|
||||
document.querySelectorAll('.toggle').forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
toggle.classList.toggle('active');
|
||||
scheduleSaveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
// Save settings
|
||||
$('btnSaveSettings').addEventListener('click', saveSettings);
|
||||
['defaultAppName', 'defaultMaxPeers', 'defaultRequestTimeoutMs', 'readyTimeoutMs'].forEach((id) => {
|
||||
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
|
||||
let logs = [];
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"protomux-bundle.js",
|
||||
"defaults.js",
|
||||
"dashboard.html",
|
||||
"dashboard.js"
|
||||
"dashboard.js",
|
||||
"examples/*",
|
||||
"examples/**/*"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
"protomux-bundle.js",
|
||||
"defaults.js",
|
||||
"dashboard.html",
|
||||
"dashboard.js"
|
||||
"dashboard.js",
|
||||
"examples/*",
|
||||
"examples/**/*"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
|
||||
+40
-2
@@ -75,11 +75,30 @@
|
||||
}
|
||||
.toast.show { opacity: 1; }
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
<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;">
|
||||
<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>
|
||||
</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>
|
||||
<h2>Debug</h2>
|
||||
<div class="field">
|
||||
@@ -157,10 +195,10 @@
|
||||
</section>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="primary" id="btnSave">Save</button>
|
||||
<button type="button" class="secondary" id="btnReset">Reset to defaults</button>
|
||||
<span class="toast" id="toast"></span>
|
||||
</div>
|
||||
<p class="autosave-hint">Settings save as you change them.</p>
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
|
||||
+118
-7
@@ -1,6 +1,7 @@
|
||||
(function () {
|
||||
const browser = typeof chrome !== 'undefined' && chrome.storage ? chrome : typeof browser !== 'undefined' ? browser : chrome;
|
||||
const KEY = 'bridgeSwarmSettings';
|
||||
const EXAMPLES_URL = 'http://127.0.0.1:4173/';
|
||||
const DEFAULTS = {
|
||||
defaultAppName: 'bridge-swarm',
|
||||
defaultRequestTimeoutMs: 0,
|
||||
@@ -8,7 +9,8 @@
|
||||
readyTimeoutMs: 0,
|
||||
disableOnFileUrls: false,
|
||||
notifyOnDisconnect: false,
|
||||
debug: false
|
||||
debug: false,
|
||||
examplesServerEnabled: false,
|
||||
};
|
||||
|
||||
const defaultAppNameEl = document.getElementById('defaultAppName');
|
||||
@@ -18,10 +20,18 @@
|
||||
const disableOnFileUrlsEl = document.getElementById('disableOnFileUrls');
|
||||
const notifyOnDisconnectEl = document.getElementById('notifyOnDisconnect');
|
||||
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 toastEl = document.getElementById('toast');
|
||||
|
||||
let saveTimer = null;
|
||||
let loading = false;
|
||||
|
||||
function showToast(msg, isError) {
|
||||
toastEl.textContent = msg;
|
||||
toastEl.className = 'toast show' + (isError ? ' err' : '');
|
||||
@@ -31,16 +41,69 @@
|
||||
}, 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() {
|
||||
loading = true;
|
||||
browser.storage.local.get(KEY, function (result) {
|
||||
const s = result[KEY] || {};
|
||||
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;
|
||||
readyTimeoutMsEl.value = s.readyTimeoutMs != null ? s.readyTimeoutMs : DEFAULTS.readyTimeoutMs;
|
||||
disableOnFileUrlsEl.checked = s.disableOnFileUrls === true;
|
||||
notifyOnDisconnectEl.checked = s.notifyOnDisconnect === true;
|
||||
debugEl.checked = s.debug === true;
|
||||
examplesServerEnabledEl.checked = s.examplesServerEnabled === true;
|
||||
loading = false;
|
||||
updateExamplesPanel();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,11 +139,12 @@
|
||||
readyTimeoutMs: readyTimeout,
|
||||
disableOnFileUrls: disableOnFileUrlsEl.checked,
|
||||
notifyOnDisconnect: notifyOnDisconnectEl.checked,
|
||||
debug: debugEl.checked
|
||||
debug: debugEl.checked,
|
||||
examplesServerEnabled: examplesServerEnabledEl.checked,
|
||||
};
|
||||
}
|
||||
|
||||
btnSave.addEventListener('click', function () {
|
||||
function saveNow(quiet) {
|
||||
const s = validate();
|
||||
if (!s) return;
|
||||
browser.storage.local.set({ [KEY]: s }, function () {
|
||||
@@ -88,8 +152,53 @@
|
||||
showToast('Save failed: ' + browser.runtime.lastError.message, true);
|
||||
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 () {
|
||||
@@ -100,12 +209,14 @@
|
||||
disableOnFileUrlsEl.checked = DEFAULTS.disableOnFileUrls;
|
||||
notifyOnDisconnectEl.checked = DEFAULTS.notifyOnDisconnect;
|
||||
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) {
|
||||
showToast('Reset failed: ' + browser.runtime.lastError.message, true);
|
||||
return;
|
||||
}
|
||||
showToast('Reset to defaults.');
|
||||
updateExamplesPanel();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -19,6 +19,7 @@ const minimalDefinition = require('./hyperdb-minimal-definition.js');
|
||||
const b4a = require('b4a');
|
||||
const capabilities = require('./capabilities/registry.js');
|
||||
const { getStorageRoot } = require('./capabilities/paths.js');
|
||||
const examplesServer = require('./examples-server.js');
|
||||
|
||||
// File-based logging
|
||||
const LOG_FILE = path.join(__dirname, 'bridge-swarm.log');
|
||||
@@ -1183,6 +1184,19 @@ async function handleMessageAsync(send, msg) {
|
||||
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': {
|
||||
reply({ ok: true, packs: capabilities.listPackIds() });
|
||||
break;
|
||||
@@ -1264,6 +1278,9 @@ async function handleMessageAsync(send, msg) {
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
try {
|
||||
examplesServer.stop().catch(() => {});
|
||||
} catch (_) {}
|
||||
try {
|
||||
const mediaPack = capabilities.getPack('media');
|
||||
if (mediaPack && typeof mediaPack.cleanup === 'function') mediaPack.cleanup();
|
||||
|
||||
Generated
+1
@@ -14,6 +14,7 @@
|
||||
"bare-fetch": "^3.2.0",
|
||||
"bare-ffmpeg": "^1.5.0",
|
||||
"bare-fs": "^4.7.4",
|
||||
"bare-http1": "^4.5.7",
|
||||
"bare-media": "^2.10.1",
|
||||
"bare-module": "^6.4.0",
|
||||
"bare-path": "^3.1.1",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"bare-fetch": "^3.2.0",
|
||||
"bare-ffmpeg": "^1.5.0",
|
||||
"bare-fs": "^4.7.4",
|
||||
"bare-http1": "^4.5.7",
|
||||
"bare-media": "^2.10.1",
|
||||
"bare-module": "^6.4.0",
|
||||
"bare-path": "^3.1.1",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"build:hyperdb": "node scripts/build-hyperdb.js",
|
||||
"build": "npm run build:host && npm run build:protomux && npm run build:hrpc && npm run build:hyperdb",
|
||||
"pack": "node scripts/pack-extension.js",
|
||||
"sync:examples": "node scripts/sync-examples.js",
|
||||
"examples": "node scripts/serve-examples.js",
|
||||
"repair:macos": "bash scripts/repair-macos-codesign.sh",
|
||||
"build:dist": "node scripts/build-distributable.js",
|
||||
|
||||
@@ -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) {
|
||||
const zipName = `${HOST_NAME}-${host}.zip`;
|
||||
const zipPath = path.join(RELEASES_DIR, zipName);
|
||||
@@ -295,6 +301,9 @@ async function createZipArchives(builtEntries) {
|
||||
} else {
|
||||
archive.file(file, { name: path.basename(file) });
|
||||
}
|
||||
if (fs.existsSync(path.join(examplesDir, 'index.html'))) {
|
||||
archive.directory(examplesDir, 'examples');
|
||||
}
|
||||
archive.finalize();
|
||||
});
|
||||
|
||||
@@ -311,6 +320,9 @@ async function createZipArchives(builtEntries) {
|
||||
async function build(hosts, doPackage) {
|
||||
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
|
||||
console.log('Building HRPC spec...');
|
||||
execSync('npm run build:hrpc', { cwd: ROOT, stdio: 'inherit' });
|
||||
|
||||
@@ -67,6 +67,9 @@ if [[ ! -f "$EXT_DIR/manifest.json" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Syncing examples into extension + native-host..."
|
||||
node "$REPO_ROOT/scripts/sync-examples.js"
|
||||
|
||||
if command -v pbcopy >/dev/null 2>&1; then
|
||||
echo "$EXT_DIR" | pbcopy
|
||||
elif command -v xclip >/dev/null 2>&1; then
|
||||
|
||||
@@ -13,6 +13,9 @@ else
|
||||
echo " node_modules exists, skipping npm install"
|
||||
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).
|
||||
BARE_PATH=""
|
||||
LOCAL_BARE="$HOST_DIR/node_modules/bare/bin/bare"
|
||||
|
||||
@@ -88,6 +88,7 @@ $Bat = @"
|
||||
@echo off
|
||||
set "DIR=%~dp0"
|
||||
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" %*
|
||||
"@
|
||||
Set-Content -Path $Launcher -Value $Bat -Encoding ASCII
|
||||
|
||||
+2
-2
@@ -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"
|
||||
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"
|
||||
HOST_BIN="$LAUNCHER"
|
||||
|
||||
@@ -206,7 +206,7 @@ if [[ "$PLATFORM" == "darwin" ]]; then
|
||||
fi
|
||||
echo " Signed ${SIGNED} native addons; main binary has library-validation disabled"
|
||||
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"
|
||||
HOST_BIN="$LAUNCHER"
|
||||
echo "Installing native messaging manifest..."
|
||||
|
||||
@@ -89,6 +89,9 @@ async function main() {
|
||||
const version = getVersion();
|
||||
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...');
|
||||
execSync('npm run build:protomux', { cwd: REPO_ROOT, stdio: 'inherit' });
|
||||
|
||||
|
||||
@@ -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"
|
||||
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"
|
||||
|
||||
echo "Extracting addons into $INSTALL_DIR/tmp ..."
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* Modern Chrome/Firefox treat each file:// URL as a unique opaque origin, which
|
||||
* 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
|
||||
* node scripts/serve-examples.js [port]
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user