add window message notifications for embeds: (fixes #333) (#335)

- add 'replayNotFoundError' field to 'urlchange' (propagating
'archive-not-found' postMessage message from wabac.js) to embed frame, with url/ts, for 404
pages
- propagate 'page-loading' with loading true/false, to indicate when a
page started/finished loading. (URL not available here)
- Only send one urlchange event per URL change (keep track of change).
- Also ensure multi timestamp change uses replaceHistory to avoid an extra
navigation.
- replay-web-page embed: send rwp-url-change and rwp-page-loading custom events from webcomponent

Fixes #333
This commit is contained in:
Ilya Kreymer
2024-06-21 14:47:18 -07:00
committed by Ilya Kreymer
parent f3506a1575
commit 105562abf9
3 changed files with 99 additions and 41 deletions
+16 -4
View File
@@ -11,15 +11,14 @@ import { wrapCss, updateFaviconLinks } from "./misc";
import { SWManager } from "./swmanager"; import { SWManager } from "./swmanager";
import { property } from "lit/decorators.js"; import { property } from "lit/decorators.js";
import type { FavIconEventDetail } from "./types"; import type { FavIconEventDetail } from "./types";
import type { EmbedReplayData } from "./item"; import type { EmbedReplayData, EmbedReplayEvent } from "./item";
type IframeMessage = MessageEvent< type IframeMessage = MessageEvent<
| ({ | EmbedReplayEvent
type: "urlchange";
} & EmbedReplayData)
| ({ | ({
type: "favicons"; type: "favicons";
} & FavIconEventDetail) } & FavIconEventDetail)
| { loading: boolean; type: "page-loading" }
>; >;
const scriptSrc = const scriptSrc =
@@ -136,6 +135,19 @@ class Embed extends LitElement {
if (this.deepLink) { if (this.deepLink) {
this.handleUrlChangeMessage(event.data); this.handleUrlChangeMessage(event.data);
} }
this.dispatchEvent(
new CustomEvent<EmbedReplayEvent>("rwp-url-change", {
detail: event.data,
}),
);
break;
case "page-loading":
this.dispatchEvent(
new CustomEvent<{ loading: boolean }>("rwp-page-loading", {
detail: event.data,
}),
);
break; break;
case "favicons": case "favicons":
+70 -35
View File
@@ -93,6 +93,11 @@ export type EmbedReplayData = {
query?: string; query?: string;
}; };
export type EmbedReplayEvent = EmbedReplayData & {
type: "urlchange";
replayNotFoundError: boolean;
};
export type TabData = EmbedReplayData & { export type TabData = EmbedReplayData & {
multiTs?: string[]; multiTs?: string[];
currList?: number; currList?: number;
@@ -100,6 +105,13 @@ export type TabData = EmbedReplayData & {
currMime?: string; currMime?: string;
}; };
export type TabDataUpdate = {
reload: boolean;
data: TabData;
replaceLoc: boolean;
replayNotFoundError: boolean;
};
// =========================================================================== // ===========================================================================
class Item extends LitElement { class Item extends LitElement {
@property({ type: Boolean }) @property({ type: Boolean })
@@ -174,6 +186,9 @@ class Item extends LitElement {
@property({ type: String }) @property({ type: String })
swName: string | null = null; swName: string | null = null;
@property({ type: Boolean })
replayNotFoundError = false;
private splitter: Split.Instance | null = null; private splitter: Split.Instance | null = null;
private _replaceLoc = false; private _replaceLoc = false;
@@ -194,6 +209,8 @@ class Item extends LitElement {
resources: "URLs", resources: "URLs",
}; };
private _lastUrlUpdate: EmbedReplayData | null = null;
constructor() { constructor() {
super(); super();
@@ -248,19 +265,16 @@ class Item extends LitElement {
return; return;
} }
const json = await resp.json(); const json = await resp.json();
this.updateTabData({ multiTs: json.timestamps }); this.updateTabData({ multiTs: json.timestamps }, true);
} }
willUpdate(changedProperties: Map<string, Record<string, unknown>>) { willUpdate(changedProperties: Map<string, Record<string, unknown>>) {
// TODO: Fix this the next time the file is edited. if (changedProperties.has("tabData")) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (changedProperties.has("tabData") && this.tabData) {
// Format tab data from URL query params // Format tab data from URL query params
const tabData = {}; const tabData: TabData = {};
Object.entries(this.tabData).forEach(([key, value]) => { Object.entries(this.tabData).forEach(([key, value]) => {
if (!value) return; if (!value) return;
if (key === "multiTs" && typeof value === "string") { if (key === "multiTs" && typeof value === "string") {
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7053 - Element implicitly has an 'any' type because expression of type '"multiTs"' can't be used to index type '{}'.
tabData[key] = value.split(","); tabData[key] = value.split(",");
} else { } else {
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7053 - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7053 - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
@@ -271,9 +285,7 @@ class Item extends LitElement {
const prevTabData = changedProperties.get("tabData"); const prevTabData = changedProperties.get("tabData");
if (this.tabData.url && this.tabData.url !== prevTabData?.url) { if (this.tabData.url && this.tabData.url !== prevTabData?.url) {
// TODO: Fix this the next time the file is edited. void this.getMultiTimestamps();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.getMultiTimestamps();
} }
} }
} }
@@ -332,10 +344,28 @@ class Item extends LitElement {
} }
if (this.embed && window.parent !== window) { if (this.embed && window.parent !== window) {
const { url, ts, view, query, title }: EmbedReplayData = this.tabData; const { url, ts, view, query, title }: EmbedReplayData = this.tabData;
window.parent.postMessage( const lastUpdate = this._lastUrlUpdate;
{ type: "urlchange", url, ts, view, query, title }, const replayNotFoundError = this.replayNotFoundError;
"*", if (
); !lastUpdate ||
lastUpdate.url !== url ||
lastUpdate.ts !== ts ||
lastUpdate.view !== view ||
lastUpdate.query !== query ||
lastUpdate.title !== title
) {
const newUpdate: EmbedReplayEvent = {
type: "urlchange",
url,
ts,
view,
query,
title,
replayNotFoundError,
};
window.parent.postMessage(newUpdate, "*");
this._lastUrlUpdate = newUpdate;
}
} }
} }
this._locUpdateNeeded = false; this._locUpdateNeeded = false;
@@ -542,35 +572,28 @@ class Item extends LitElement {
return false; return false;
} }
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onItemTabNav(event: CustomEvent<TabDataUpdate>) {
onItemTabNav(event) {
if (event.detail.reload) { if (event.detail.reload) {
this.onRefresh(null, true); this.onRefresh(null, true);
return; return;
} }
const targetId = (event.target as HTMLElement).id;
const { data, replaceLoc, replayNotFoundError } = event.detail;
this.replayNotFoundError = replayNotFoundError;
if ( if (
event.target.id === this.tabData.view || targetId === this.tabData.view ||
(event.target.id === "replay" && this.tabData.url) (targetId === "replay" && this.tabData.url) ||
(this.showSidebar && this.tabData.url)
) { ) {
this.updateTabData( this.updateTabData(data, replaceLoc);
event.detail.data,
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
event.detail.replaceLoc /*, false */,
);
} else if (this.showSidebar && this.tabData.url) {
this.updateTabData(
event.detail.data,
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
event.detail.replaceLoc /*, true */,
);
} }
} }
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'data' implicitly has an 'any' type. updateTabData(data: TabData, replaceLoc = false) {
updateTabData(data, replaceLoc = false /*, merge = false*/) {
this.tabData = { ...this.tabData, ...data }; this.tabData = { ...this.tabData, ...data };
if (this.tabData.url) { if (this.tabData.url) {
this.url = this.tabData.url || ""; this.url = this.tabData.url || "";
@@ -961,8 +984,7 @@ class Item extends LitElement {
ts="${this.tabData.ts || ""}" ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onItemTabNav}" @coll-tab-nav="${this.onItemTabNav}"
id="replay" id="replay"
@replay-loading="${(e: CustomEvent<{ loading: boolean }>) => @replay-loading="${this.onReplayLoading}"
(this.isLoading = e.detail.loading)}"
@replay-favicons="${this.onFavIcons}" @replay-favicons="${this.onFavIcons}"
> >
</wr-coll-replay> </wr-coll-replay>
@@ -1653,6 +1675,19 @@ class Item extends LitElement {
this.showSidebar = false; this.showSidebar = false;
} }
async onReplayLoading(
event: CustomEvent<{ loading: boolean; url: string; ts: string }>,
) {
if (
this.embed &&
window.parent !== window &&
this.isLoading !== event.detail.loading
) {
window.parent.postMessage({ type: "page-loading", ...event.detail }, "*");
}
this.isLoading = event.detail.loading;
}
async onFavIcons(event: CustomEvent<FavIconEventDetail>) { async onFavIcons(event: CustomEvent<FavIconEventDetail>) {
if (this.embed && window.parent !== window) { if (this.embed && window.parent !== window) {
window.parent.postMessage({ type: "favicons", ...event.detail }, "*"); window.parent.postMessage({ type: "favicons", ...event.detail }, "*");
@@ -1749,7 +1784,7 @@ class Item extends LitElement {
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'value' implicitly has an 'any' type. // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'value' implicitly has an 'any' type.
navigateTo(value) { navigateTo(value) {
let data; let data: TabData;
if (value.startsWith("http://") || value.startsWith("https://")) { if (value.startsWith("http://") || value.startsWith("https://")) {
data = { url: value }; data = { url: value };
+13 -2
View File
@@ -39,6 +39,9 @@ class Replay extends LitElement {
@property({ type: Boolean }) @property({ type: Boolean })
showAuth = false; showAuth = false;
@property({ type: Boolean })
replayNotFoundError = false;
@property({ type: Object }) @property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- requestPermission() type mismatch // eslint-disable-next-line @typescript-eslint/no-explicit-any -- requestPermission() type mismatch
authFileHandle: any = null; authFileHandle: any = null;
@@ -140,7 +143,13 @@ class Replay extends LitElement {
}; };
this.dispatchEvent( this.dispatchEvent(
new CustomEvent("coll-tab-nav", { detail: { replaceLoc: true, data } }), new CustomEvent("coll-tab-nav", {
detail: {
replaceLoc: true,
data,
replayNotFoundError: this.replayNotFoundError,
},
}),
); );
} }
@@ -180,12 +189,14 @@ class Replay extends LitElement {
if (iframe && event.source === iframe.contentWindow) { if (iframe && event.source === iframe.contentWindow) {
if ( if (
event.data.wb_type === "load" || event.data.wb_type === "load" ||
event.data.wb_type === "replace-url" event.data.wb_type === "replace-url" ||
event.data.wb_type === "archive-not-found"
) { ) {
this.replayTS = event.data.is_live ? "" : event.data.ts; this.replayTS = event.data.is_live ? "" : event.data.ts;
this.actualTS = event.data.ts; this.actualTS = event.data.ts;
this.replayUrl = event.data.url; this.replayUrl = event.data.url;
this.title = event.data.title || this.title; this.title = event.data.title || this.title;
this.replayNotFoundError = event.data.wb_type === "archive-not-found";
this.clearLoading(iframe.contentWindow); this.clearLoading(iframe.contentWindow);
if (event.data.icons) { if (event.data.icons) {