fix(embed): implement robust EmbedPlayerModal and guard resetTorrentStats

- Add `js/ui/components/EmbedPlayerModal.js` to implement the `VideoModal` interface for embed contexts (including `resetStats`).
- Update `js/ui/ModalManager.js` to detect embed mode and use `EmbedPlayerModal`.
- Clean up `js/embed.js` to remove local modal definition and add `postMessage` diagnostic logging.
- Update `js/app.js` `resetTorrentStats` log message and verify guard.
- Add unit tests for `EmbedPlayerModal` and `resetTorrentStats` logic.
- Add E2E reproduction script `tests/e2e/repro_issue.spec.ts`.
This commit is contained in:
google-labs-jules[bot] committed 2026-01-24 22:09:03 +00:00
1 parent d71d2812f4
commit 981fac785a
7 files changed
+379 -117

No files matched your search

+27 -19
View File
@@ -7,6 +7,7 @@ import { VideoModal } from "./components/VideoModal.js";
import { RevertModal } from "./components/RevertModal.js";
import { ShareNostrModal } from "./components/ShareNostrModal.js";
import { EmbedVideoModal } from "./components/EmbedVideoModal.js";
import { EmbedPlayerModal } from "./components/EmbedPlayerModal.js";
import initUploadModal from "./initUploadModal.js";
import initEditModal from "./initEditModal.js";
import initDeleteModal from "./initDeleteModal.js";
@@ -195,29 +196,36 @@ export default class ModalManager {
});
app.embedVideoModal = this.embedVideoModal;
const isEmbedMode = typeof window !== "undefined" && window.location.pathname.endsWith("/embed.html");
this.videoModal =
(typeof this.ui.videoModal === "function"
? this.ui.videoModal({ app })
: this.ui.videoModal) ||
new VideoModal({
removeTrackingScripts,
setGlobalModalState,
document: doc,
logger: {
log: (message, ...args) => app.log(message, ...args),
},
mediaLoader: app.mediaLoader,
assets: {
fallbackThumbnailSrc: this.assets.fallbackThumbnailSrc,
},
state: {
loadedThumbnails: app.loadedThumbnails,
},
helpers: {
safeEncodeNpub: (pubkey) => app.safeEncodeNpub(pubkey),
formatShortNpub: (value) => formatShortNpub(value),
},
});
(isEmbedMode
? new EmbedPlayerModal({
document: doc,
logger: { log: (...a) => app.log(...a) },
})
: new VideoModal({
removeTrackingScripts,
setGlobalModalState,
document: doc,
logger: {
log: (message, ...args) => app.log(message, ...args),
},
mediaLoader: app.mediaLoader,
assets: {
fallbackThumbnailSrc: this.assets.fallbackThumbnailSrc,
},
state: {
loadedThumbnails: app.loadedThumbnails,
},
helpers: {
safeEncodeNpub: (pubkey) => app.safeEncodeNpub(pubkey),
formatShortNpub: (value) => formatShortNpub(value),
},
}));
if (
this.videoModal &&
+106
View File
@@ -0,0 +1,106 @@
export class EmbedPlayerModal {
constructor({ document: doc, logger } = {}) {
this.document = doc || (typeof document !== "undefined" ? document : null);
this.logger = logger || console;
this.root = null;
this.video = null;
this.status = null;
this.eventTarget = new EventTarget();
this.toneClasses = {
default: "text-muted",
error: "text-danger",
success: "text-success",
};
}
load() {
if (this.document) {
this.root = this.document.getElementById("embedRoot");
this.video = this.document.getElementById("embedVideo");
this.status = this.document.getElementById("embedStatus");
}
return Promise.resolve(this.root);
}
getRoot() {
return this.root;
}
getVideoElement() {
return this.video;
}
setVideoElement(videoElement) {
if (videoElement) {
this.video = videoElement;
}
return this.video;
}
resetStats() {
// no-op for embed view
}
setStatus(message, tone = "default") {
if (!this.status) {
return;
}
const normalizedMessage = typeof message === "string" ? message.trim() : "";
this.status.textContent = normalizedMessage;
Object.values(this.toneClasses).forEach((className) => {
if (className) {
this.status.classList.remove(className);
}
});
const toneClass = this.toneClasses[tone] || this.toneClasses.default;
if (toneClass) {
this.status.classList.add(toneClass);
}
}
updateStatus(message) {
this.setStatus(message, "default");
}
applyLoadingPoster() {
this.setStatus("Preparing playback…", "default");
}
forceRemovePoster() {
return false;
}
clearPosterCleanup() {}
setTorrentStatsVisibility(isVisible) {
if (!this.root) {
return;
}
this.root.dataset.torrentStats = isVisible ? "true" : "false";
}
open() {
return this.root;
}
close() {}
updateMetadata() {}
setShareEnabled() {}
addEventListener(type, listener, options) {
this.eventTarget.addEventListener(type, listener, options);
}
removeEventListener(type, listener, options) {
this.eventTarget.removeEventListener(type, listener, options);
}
dispatch(type, detail) {
return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
}
}