mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-07 05:13:01 +00:00
Preserve autoplay preference on fallback; reload open comment thread after login
### Motivation
- Prevent a browser-policy muted-autoplay retry from overwriting a viewer's explicit mute/unmute preference and ensure fallback mutes are transient.
- Ensure that when a user logs in while a video comment modal is open, the open thread is reloaded (not just refreshed) so comments continue to display correctly after relay-backed services are reconfigured.
### Description
- Add `js/utils/autoplayPreference.js` with `muteForAutoplayFallback` and `consumeAutoplayFallbackMute` helpers to mark and consume transient fallback mutes.
- Update `js/services/playbackService.js` to treat sound as default unless the viewer explicitly chose mute, persist explicit choices via `localStorage`, ignore fallback-generated volume changes using `consumeAutoplayFallbackMute`, and expose `muteForAutoplayFallback` delegating to the new util.
- Replace direct `videoElement.muted = true` calls with `playbackService.muteForAutoplayFallback` in `js/app/playbackCoordinator.js` and `js/webtorrent.js` so fallback mutes are marked as transient.
- Change `js/app/authSessionCoordinator.js` to reload the currently open video comment thread with `commentController.load(activeModalVideo)` after login if a modal video is active, instead of only calling `refreshAuthState`.
- Update tests in `tests/services/playbackService.test.mjs` and add a test in `tests/app/login-defers-dms.test.mjs` to cover the login behavior that reloads an open comment thread.
### Testing
- Ran the `PlaybackService` unit tests (`tests/services/playbackService.test.mjs`) which assert `prepareVideoElement` behavior, explicit mute persistence, and the transient nature of fallback mute, and they passed.
- Ran the auth login unit tests including `tests/app/login-defers-dms.test.mjs` which verifies DM deferral and reload of an open comment thread, and they passed.
(cherry picked from commit ce14004725)
This commit is contained in:
1 parent
d8e5113efd
commit
f0a3ede3e5
7 files changed
+241
-23
No files matched your search
@@ -1,7 +1,10 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { resolveVerifiedBitloginPubkey } from "../js/ui/bitloginModalIntegration.js";
|
||||
import {
|
||||
resolveVerifiedBitloginPubkey,
|
||||
wireBitloginLogin,
|
||||
} from "../js/ui/bitloginModalIntegration.js";
|
||||
|
||||
const PUBKEY = "a".repeat(64);
|
||||
|
||||
@@ -19,3 +22,22 @@ test("BitLogin rejects malformed claims and widgets without a worker-backed publ
|
||||
assert.equal(await resolveVerifiedBitloginPubkey({}, PUBKEY), "");
|
||||
assert.equal(await resolveVerifiedBitloginPubkey({ getPublicKey: async () => PUBKEY }, "npub1nope"), "");
|
||||
});
|
||||
|
||||
test("BitLogin bootstrap safely defers when MutationObserver is unavailable", () => {
|
||||
const originalDocument = globalThis.document;
|
||||
const originalObserver = globalThis.MutationObserver;
|
||||
globalThis.document = {
|
||||
body: {},
|
||||
defaultView: {},
|
||||
getElementById: () => null,
|
||||
};
|
||||
try {
|
||||
delete globalThis.MutationObserver;
|
||||
assert.doesNotThrow(() => wireBitloginLogin({ authService: {} }));
|
||||
} finally {
|
||||
if (originalDocument === undefined) delete globalThis.document;
|
||||
else globalThis.document = originalDocument;
|
||||
if (originalObserver === undefined) delete globalThis.MutationObserver;
|
||||
else globalThis.MutationObserver = originalObserver;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createWhitelistedAuthorsNostrSource } from "../js/feedEngine/sources.js";
|
||||
|
||||
test("Most Zapped discovery fetches only whitelisted creators and caches the catalog", async () => {
|
||||
const calls = [];
|
||||
const approved = "a".repeat(64);
|
||||
const unapproved = "b".repeat(64);
|
||||
let now = 1_000;
|
||||
const source = createWhitelistedAuthorsNostrSource({
|
||||
now: () => now,
|
||||
service: {
|
||||
async fetchVideosByAuthors(authors) {
|
||||
calls.push(authors);
|
||||
return [
|
||||
{ id: "approved", pubkey: approved, created_at: 20 },
|
||||
{ id: "unapproved", pubkey: unapproved, created_at: 30 },
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const runtime = { whitelistedAuthors: [approved.toUpperCase()] };
|
||||
const first = await source({ runtime });
|
||||
const rerank = await source({ runtime });
|
||||
|
||||
assert.deepEqual(calls, [[approved]], "relay lookup is author-scoped once");
|
||||
assert.deepEqual(first.map((entry) => entry.video.id), ["approved"]);
|
||||
assert.deepEqual(rerank.map((entry) => entry.video.id), ["approved"]);
|
||||
|
||||
now += 2 * 60 * 1000;
|
||||
await source({ runtime });
|
||||
assert.equal(calls.length, 2, "catalog refreshes after its bounded TTL");
|
||||
});
|
||||
|
||||
test("Most Zapped discovery is empty without approved creators and never queries globally", async () => {
|
||||
let calls = 0;
|
||||
const source = createWhitelistedAuthorsNostrSource({
|
||||
service: {
|
||||
async fetchVideosByAuthors() {
|
||||
calls += 1;
|
||||
return [{ id: "must-not-leak", pubkey: "c".repeat(64) }];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await source({ runtime: { whitelistedAuthors: [] } });
|
||||
assert.deepEqual(result, []);
|
||||
assert.equal(calls, 0);
|
||||
});
|
||||
|
||||
test("Most Zapped never leaks a removed creator while an earlier lookup is in flight", async () => {
|
||||
const removed = "d".repeat(64);
|
||||
const remaining = "e".repeat(64);
|
||||
let resolveFetch;
|
||||
const source = createWhitelistedAuthorsNostrSource({
|
||||
service: {
|
||||
fetchVideosByAuthors() {
|
||||
return new Promise((resolve) => {
|
||||
resolveFetch = resolve;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const first = source({ runtime: { whitelistedAuthors: [removed] } });
|
||||
const afterRemoval = source({ runtime: { whitelistedAuthors: [remaining] } });
|
||||
resolveFetch([{ id: "removed-video", pubkey: removed }]);
|
||||
|
||||
assert.deepEqual((await first).map((entry) => entry.video.id), ["removed-video"]);
|
||||
assert.deepEqual(await afterRemoval, []);
|
||||
});
|
||||
Reference in new issue
Block a user