From 17539f308461725b67f0ad233dbc27aff7ce84a5 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 11 Feb 2026 23:58:28 -0800 Subject: [PATCH] feat: Add "not found" error detail on page load (#482) Resolves https://github.com/webrecorder/replayweb.page/issues/481 with workaround Related to https://github.com/webrecorder/browsertrix/issues/2865 ## Changes - Includes `replayNotFoundError` in `"page-loading"` event when page is finished loading as a workaround for the actual `replayNotFoundError` value not being available on `urlchange`. - Minor refactor to more strongly typed events. --- .gitignore | 3 ++- src/embed.ts | 11 ++++---- src/events.ts | 25 +++++++++++++++++ src/index.ts | 6 ++++- src/item.ts | 64 +++++++++++++++++++++++++++----------------- src/pageentry.ts | 6 ++--- src/pages.ts | 12 ++++++--- src/replay.ts | 30 ++++++++++++++++----- src/story.ts | 17 +++++++----- src/types.ts | 5 ++-- src/url-resources.ts | 11 ++++++-- 11 files changed, 136 insertions(+), 54 deletions(-) create mode 100644 src/events.ts diff --git a/.gitignore b/.gitignore index cc3634f..0e416df 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ mkdocs/site/favicons mkdocs/site/ruffle mkdocs/site/adblock ipfs-core.min.js -_site \ No newline at end of file +_site +test-results/ \ No newline at end of file diff --git a/src/embed.ts b/src/embed.ts index 0799436..ccd47c5 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -11,14 +11,15 @@ import { wrapCss, updateFaviconLinks, apiPrefix } from "./misc"; import { SWManager } from "./swmanager"; import { property, query } from "lit/decorators.js"; import type { FavIconEventDetail } from "./types"; -import type { EmbedReplayData, EmbedReplayEvent } from "./item"; +import type { EmbedReplayData } from "./item"; +import type { RwpPageLoadingEvent, RwpUrlChangeEvent } from "./events"; type IframeMessage = MessageEvent< - | EmbedReplayEvent + | RwpUrlChangeEvent["detail"] + | RwpPageLoadingEvent["detail"] | ({ type: "favicons"; } & FavIconEventDetail) - | { loading: boolean; type: "page-loading" } >; const scriptSrc = @@ -157,7 +158,7 @@ class Embed extends LitElement { this.handleUrlChangeMessage(event.data); } this.dispatchEvent( - new CustomEvent("rwp-url-change", { + new CustomEvent("rwp-url-change", { detail: event.data, }), ); @@ -165,7 +166,7 @@ class Embed extends LitElement { case "page-loading": this.dispatchEvent( - new CustomEvent<{ loading: boolean }>("rwp-page-loading", { + new CustomEvent("rwp-page-loading", { detail: event.data, }), ); diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 0000000..3daa5d2 --- /dev/null +++ b/src/events.ts @@ -0,0 +1,25 @@ +import type { EmbedReplayData, TabData } from "./item"; + +export type TabNavEvent = CustomEvent<{ + data: TabData; + reload?: boolean; + replaceLoc?: boolean; + replayNotFoundError?: boolean; +}>; + +export type RwpUrlChangeEvent = CustomEvent< + EmbedReplayData & { + type: "urlchange"; + replayNotFoundError: boolean; + } +>; + +export type ReplayLoadingDetail = + | { loading: true } + | { loading: false; replayNotFoundError: boolean }; + +export type RwpPageLoadingEvent = CustomEvent< + Required> & { + type: "page-loading"; + } & ReplayLoadingDetail +>; diff --git a/src/index.ts b/src/index.ts index 854fb08..12de6e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,7 +58,11 @@ export { export type { ItemType, URLResource } from "./types"; -export type { EmbedReplayEvent } from "./item"; +export type { + RwpUrlChangeEvent as EmbedReplayEvent, + RwpUrlChangeEvent, + RwpPageLoadingEvent, +} from "./events"; export * from "./misc"; diff --git a/src/item.ts b/src/item.ts index bc8ab3d..cdad0b7 100644 --- a/src/item.ts +++ b/src/item.ts @@ -57,6 +57,12 @@ import { ifDefined } from "lit/directives/if-defined.js"; import "./item-info"; import { dateTimeFormatter } from "./utils/dateTimeFormatter"; +import type { + ReplayLoadingDetail, + RwpPageLoadingEvent, + RwpUrlChangeEvent, + TabNavEvent, +} from "./events"; const RWP_SCHEME = "search://"; @@ -90,25 +96,17 @@ export type EmbedReplayData = { waczhash?: string; }; -export type EmbedReplayEvent = EmbedReplayData & { - type: "urlchange"; - replayNotFoundError: boolean; -}; - export type TabData = EmbedReplayData & { currList?: number; urlSearchType?: string; currMime?: string; }; -export type TabDataUpdate = { - reload: boolean; - data: TabData; - replaceLoc: boolean; - replayNotFoundError: boolean; -}; - -// =========================================================================== +/** + * @fires coll-loaded + * @fires update-title + * @fires about-show + */ class Item extends LitElement { @property({ type: Boolean }) inited = false; @@ -347,7 +345,7 @@ class Item extends LitElement { lastUpdate.query !== query || lastUpdate.title !== title ) { - const newUpdate: EmbedReplayEvent = { + const newUpdate: RwpUrlChangeEvent["detail"] = { type: "urlchange", url, ts, @@ -573,7 +571,7 @@ class Item extends LitElement { return false; } - onItemTabNav(event: CustomEvent) { + onItemTabNav(event: TabNavEvent) { if (event.detail.reload) { this.onRefresh(null, true); return; @@ -583,7 +581,9 @@ class Item extends LitElement { const { data, replaceLoc, replayNotFoundError } = event.detail; - this.replayNotFoundError = replayNotFoundError; + if (replayNotFoundError !== undefined) { + this.replayNotFoundError = replayNotFoundError; + } if ( targetId === this.tabData.view || @@ -1778,16 +1778,32 @@ class Item extends LitElement { } async onReplayLoading( - event: CustomEvent<{ loading: boolean; url: string; ts: string }>, + event: CustomEvent<{ loading: boolean; replayNotFoundError?: boolean }>, ) { - if ( - this.embed && - window.parent !== window && - this.isLoading !== event.detail.loading - ) { - window.parent.postMessage({ type: "page-loading", ...event.detail }, "*"); + const { loading, replayNotFoundError } = event.detail; + if (this.embed && window.parent !== window && this.isLoading !== loading) { + let loadingDetail: ReplayLoadingDetail; + + if (loading) { + loadingDetail = { + loading, + }; + } else { + loadingDetail = { + loading, + replayNotFoundError: replayNotFoundError ?? false, + }; + } + const msg = { + ...loadingDetail, + type: "page-loading", + url: this.tabData.url ?? "", + ts: this.tabData.ts ?? "", + } satisfies RwpPageLoadingEvent["detail"]; + + window.parent.postMessage(msg, "*"); } - this.isLoading = event.detail.loading; + this.isLoading = loading; } async onFavIcons(event: CustomEvent) { diff --git a/src/pageentry.ts b/src/pageentry.ts index 05d56e9..eb975a0 100644 --- a/src/pageentry.ts +++ b/src/pageentry.ts @@ -14,6 +14,7 @@ import { dateTimeFormatter, timeFormatter, } from "./utils/dateTimeFormatter"; +import type { TabNavEvent } from "./events"; // =========================================================================== class PageEntry extends LitElement { @@ -384,10 +385,9 @@ class PageEntry extends LitElement { return this.onReplay(event, true); } - // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'data' implicitly has an 'any' type. - sendChangeEvent(data, reload: boolean) { + sendChangeEvent(data: URLTsChange, reload: boolean) { this.dispatchEvent( - new CustomEvent("coll-tab-nav", { + new CustomEvent("coll-tab-nav", { bubbles: true, composed: true, detail: { data, reload }, diff --git a/src/pages.ts b/src/pages.ts index 29b8b4c..a8f5efb 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -18,7 +18,8 @@ import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg"; import type { Sorter } from "./sorter"; import type { PageEntry } from "./pageentry"; import type { Id, Index } from "flexsearch"; -import type { ItemType, Page } from "./types"; +import type { ItemType, Page, URLTsChange } from "./types"; +import type { TabNavEvent } from "./events"; const DYNAMIC_PAGE_SIZE = 25; @@ -360,9 +361,12 @@ class Pages extends LitElement { this.filteredPages = curated; } - // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'data' implicitly has an 'any' type. - sendChangeEvent(data) { - this.dispatchEvent(new CustomEvent("coll-tab-nav", { detail: { data } })); + sendChangeEvent(data: URLTsChange) { + this.dispatchEvent( + new CustomEvent("coll-tab-nav", { + detail: { data }, + }), + ); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'pages' implicitly has an 'any' type. diff --git a/src/replay.ts b/src/replay.ts index a2de375..a9134ac 100644 --- a/src/replay.ts +++ b/src/replay.ts @@ -4,9 +4,18 @@ import { property } from "lit/decorators.js"; import { wrapCss } from "./misc"; import rwpLogo from "~assets/brand/replaywebpage-icon-color.svg"; -import type { ItemType, URLTsChange } from "./types"; +import type { ItemType } from "./types"; +import type { ReplayLoadingDetail, TabNavEvent } from "./events"; -// =========================================================================== +/** + * @fires update-title + * @fires coll-tab-nav + * @fires update-title + * @fires replay-favicons + * @fires replay-loading ReplayLoadingDetail + * @fires cancel-click-download + * @fires update-download-res-url + */ class Replay extends LitElement { @property({ type: Object }) collInfo: ItemType | Record | null = null; @@ -149,14 +158,14 @@ class Replay extends LitElement { (this.replayUrl && changedProperties.has("replayUrl")) || (this.replayTS && changedProperties.has("replayTS")) ) { - const data: URLTsChange = { + const data = { url: this.replayUrl, ts: this.replayTS, waczhash: this.waczhash, }; this.dispatchEvent( - new CustomEvent("coll-tab-nav", { + new CustomEvent("coll-tab-nav", { detail: { replaceLoc: true, data, @@ -279,7 +288,12 @@ class Replay extends LitElement { clearLoading(iframe: HTMLIFrameElement | null) { this.dispatchEvent( - new CustomEvent("replay-loading", { detail: { loading: false } }), + new CustomEvent("replay-loading", { + detail: { + loading: false, + replayNotFoundError: this.replayNotFoundError, + }, + }), ); if (this._loadPoll) { @@ -303,7 +317,11 @@ class Replay extends LitElement { setLoading() { this.clearHilite(true); this.dispatchEvent( - new CustomEvent("replay-loading", { detail: { loading: true } }), + new CustomEvent("replay-loading", { + detail: { + loading: true, + }, + }), ); } diff --git a/src/story.ts b/src/story.ts index 513f37f..c1585fd 100644 --- a/src/story.ts +++ b/src/story.ts @@ -8,9 +8,10 @@ import { marked } from "marked"; import { getTS, getReplayLink } from "./pageutils"; import Split from "split.js"; -import { type ItemType } from "./types"; +import type { URLTsChange, ItemType } from "./types"; import { customElement, property } from "lit/decorators.js"; import { dateTimeFormatter } from "./utils/dateTimeFormatter"; +import type { TabNavEvent } from "./events"; // =========================================================================== @customElement("wr-coll-story") @@ -375,16 +376,20 @@ class Story extends LitElement { onReplay(event: Event) { event.preventDefault(); const data = { - url: (event.currentTarget as Element).getAttribute("data-url"), - ts: (event.currentTarget as Element).getAttribute("data-ts"), + url: + (event.currentTarget as Element).getAttribute("data-url") ?? undefined, + ts: (event.currentTarget as Element).getAttribute("data-ts") ?? undefined, }; this.sendChangeEvent(data); return false; } - // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'data' implicitly has an 'any' type. - sendChangeEvent(data) { - this.dispatchEvent(new CustomEvent("coll-tab-nav", { detail: { data } })); + sendChangeEvent(data: URLTsChange) { + this.dispatchEvent( + new CustomEvent("coll-tab-nav", { + detail: { data }, + }), + ); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. diff --git a/src/types.ts b/src/types.ts index cadb14e..b330d64 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,9 +27,10 @@ export type Page = { }; export type URLTsChange = { - url: string; - ts: string; + url?: string; + ts?: string; waczhash?: string; + currList?: number; }; export type ItemType = { diff --git a/src/url-resources.ts b/src/url-resources.ts index f091eeb..248877a 100644 --- a/src/url-resources.ts +++ b/src/url-resources.ts @@ -10,6 +10,7 @@ import "keyword-mark-element/lib/keyword-mark.js"; import { type ItemType } from "./types"; import { type URLResource } from "./types"; import { dateTimeFormatter } from "./utils/dateTimeFormatter"; +import type { TabNavEvent } from "./events"; // =========================================================================== /** @@ -145,7 +146,9 @@ class URLResources extends LitElement { !changedProperties.has("currMime") && !changedProperties.has("urlSearchType"); this.dispatchEvent( - new CustomEvent("coll-tab-nav", { detail: { replaceLoc, data } }), + new CustomEvent("coll-tab-nav", { + detail: { replaceLoc, data }, + }), ); } @@ -613,7 +616,11 @@ class URLResources extends LitElement { ts: event.currentTarget.getAttribute("data-ts"), }; - this.dispatchEvent(new CustomEvent("coll-tab-nav", { detail: { data } })); + this.dispatchEvent( + new CustomEvent("coll-tab-nav", { + detail: { data }, + }), + ); return false; } }