fix(csp,smoke): allow ws://localhost relays; fix smoke publish await

Two pre-existing issues surfaced by running the smoke test:

1. CSP connect-src allowed `wss:` but not `ws:`, so ALL insecure-websocket
   relays were blocked — including ws://localhost (dev + local relays + the
   smoke mock relay). Add `ws://localhost:* ws://127.0.0.1:*` to connect-src in
   index.html and embed.html. Remote cleartext ws:// stays blocked (deliberate
   security posture — flagged for product decision).

2. smoke-test.mjs published via Promise.any(relays.map(url => pool.publish([url],
   evt))). SimplePool.publish returns an ARRAY of per-relay promises, so this
   Promise.any'd arrays-of-arrays — resolving on the array object immediately
   without awaiting the publish and swallowing errors. Fixed to await
   pool.publish(relays, evt) via Promise.allSettled and surface per-relay
   results.

Smoke test now passes (publish -> relay verify). CSP policy test green; build green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-19 22:24:58 -04:00
1 parent 126ac75a25
commit b844b71f55
3 files changed
+22 -28

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@
-->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'"
content="default-src 'self'; script-src 'self' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: ws://localhost:* ws://127.0.0.1:* blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Embed</title>
+1 -1
View File
@@ -19,7 +19,7 @@
-->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'"
content="default-src 'self'; script-src 'self' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: ws://localhost:* ws://127.0.0.1:* blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Decentralized Video Sharing</title>
+20 -26
View File
@@ -182,37 +182,31 @@ async function main() {
const signedVideo = finalizeEvent(videoEvent, sk);
// Inject and publish via harness/client
// Inject and publish the externally-signed event through the app's pool to
// verify pool -> relay connectivity.
const pubResult = await page.evaluate(async (evt) => {
// Use client directly to publish
const client = window.__bitvidTest__.nostrClient;
// We can use signAndPublishEvent if we want to test that flow,
// but since we signed it externally (to avoid exposing key to window if not needed, though we just did loginWithNsec),
// let's just publish.
// Wait, we logged in, so the client has the signer.
// Let's use the client to sign and publish to exercise the full stack.
// Construct unsigned event for client to sign
const unsigned = { ...evt };
delete unsigned.id;
delete unsigned.sig;
delete unsigned.pubkey; // client adds this
// Actually, let's use the raw event we signed externally to be sure it's valid first?
// No, verifying the APP flow means using the APP's signer.
// But `nostrClient` API might be `publishEvent(event)` taking a signed event.
// Let's check `js/nostr/client.js` or `nostrClientFacade`.
// `publishEvent(event)` usually takes signed event.
// `signAndPublishEvent(event)` takes unsigned.
// Let's use `publishEvent` with our externally signed event to test connectivity first.
const pool = client.pool;
const relays = client.writeRelays;
return Promise.any(relays.map((url) => pool.publish([url], evt)));
const relays = client.writeRelays || [];
// SimplePool.publish(relays, event) returns an ARRAY of per-relay
// promises — it must be awaited directly. (The old code wrapped each url
// in its own publish() and then Promise.any'd the resulting arrays, which
// resolves on the array object immediately without awaiting the publish
// and silently swallows publish errors.)
const settled = await Promise.allSettled(pool.publish(relays, evt));
return {
relays,
fulfilled: settled.filter((r) => r.status === "fulfilled").length,
errors: settled
.filter((r) => r.status === "rejected")
.map((r) => String(r.reason)),
};
}, signedVideo);
log("Publish command sent.");
log(
`Publish sent to [${pubResult.relays.join(", ")}] — ${pubResult.fulfilled} ok` +
(pubResult.errors.length ? `, errors: ${pubResult.errors.join("; ")}` : "")
);
// Verify on relay side
await new Promise((r) => setTimeout(r, 1000)); // Give it a sec
const relayEvents = relayServer ? relayServer.getEvents() : null;