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.
This commit is contained in:
sua yoo
2026-02-11 23:58:28 -08:00
committed by GitHub
parent e4fd230694
commit 17539f3084
11 changed files with 136 additions and 54 deletions
+2 -1
View File
@@ -22,4 +22,5 @@ mkdocs/site/favicons
mkdocs/site/ruffle
mkdocs/site/adblock
ipfs-core.min.js
_site
_site
test-results/
+6 -5
View File
@@ -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<EmbedReplayEvent>("rwp-url-change", {
new CustomEvent<RwpUrlChangeEvent["detail"]>("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<RwpPageLoadingEvent["detail"]>("rwp-page-loading", {
detail: event.data,
}),
);
+25
View File
@@ -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<Pick<EmbedReplayData, "url" | "ts">> & {
type: "page-loading";
} & ReplayLoadingDetail
>;
+5 -1
View File
@@ -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";
+40 -24
View File
@@ -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<TabDataUpdate>) {
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<FavIconEventDetail>) {
+3 -3
View File
@@ -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<TabNavEvent["detail"]>("coll-tab-nav", {
bubbles: true,
composed: true,
detail: { data, reload },
+8 -4
View File
@@ -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<TabNavEvent["detail"]>("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.
+24 -6
View File
@@ -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<string, never> | 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<TabNavEvent["detail"]>("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<ReplayLoadingDetail>("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<ReplayLoadingDetail>("replay-loading", {
detail: {
loading: true,
},
}),
);
}
+11 -6
View File
@@ -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<TabNavEvent["detail"]>("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.
+3 -2
View File
@@ -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 = {
+9 -2
View File
@@ -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<TabNavEvent["detail"]>("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<TabNavEvent["detail"]>("coll-tab-nav", {
detail: { data },
}),
);
return false;
}
}