Skip to content

Commit

Permalink
feature: allow embed <replay-web-page> to also support updating favic…
Browse files Browse the repository at this point in the history
…ons from replay (#287)

It may be useful to be able to have <replay-web-page> embed also take
over the favicons for the page, propagating them from the replay (eg. if
in a single page app where replay takes up most of the view).

This PR adds a `updateFavicons` attribute, used as `<replay-web-page ...
updateFavicons>` to have icons propagated from the replay to the
top-level frame. (This may be especially useful with `deepLink` option,
which allows for linking directly to archived pages)

Details:
  - enabled via 'updateFavicons' attribute on <replay-web-page>
- pass icons via postMessage() update, adding type to embed message to
allow different types of message
  - currently supporting 'urlchange' and 'favicons' message types
- add shared updateFaviconLinks() in misc.ts, used by both embed and
main app

---------
Co-authored-by: emma <hi@emma.cafe>
  • Loading branch information
ikreymer and emma-sg committed Mar 8, 2024
1 parent 5c7b914 commit 6051a2a
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 52 deletions.
19 changes: 4 additions & 15 deletions src/appmain.ts
Expand Up @@ -14,6 +14,7 @@ import {
IS_APP,
VERSION,
clickOnSpacebarPress,
updateFaviconLinks,
} from "./misc";

import fasHelp from "@fortawesome/fontawesome-free/svgs/solid/question-circle.svg";
Expand All @@ -24,6 +25,7 @@ import "./item";
import "./item-index";
import "./chooser";
import { type LoadInfo } from "./item";
import type { FavIconEventDetail } from "./types";

// ===========================================================================
@customElement("replay-app-main")
Expand Down Expand Up @@ -480,21 +482,8 @@ export class ReplayWebApp extends LitElement {
}
}

// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
onFavIcons(event) {
const head = document.querySelector("head")!;
const oldLinks = document.querySelectorAll("link[rel*='icon']");

for (const link of oldLinks) {
head.removeChild(link);
}

for (const icon of event.detail.icons) {
const link = document.createElement("link");
link.rel = icon.rel;
link.href = icon.href;
head.appendChild(link);
}
onFavIcons(event: CustomEvent<FavIconEventDetail>) {
updateFaviconLinks(event.detail);
}

// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
Expand Down
84 changes: 63 additions & 21 deletions src/embed.ts
Expand Up @@ -7,9 +7,20 @@ import {
} from "lit";
import { ifDefined } from "lit/directives/if-defined.js";

import { wrapCss, rwpLogo } from "./misc";
import { wrapCss, rwpLogo, updateFaviconLinks } from "./misc";
import { SWManager } from "./swmanager";
import { property } from "lit/decorators.js";
import type { FavIconEventDetail } from "./types";
import type { EmbedReplayData } from "./item";

type IframeMessage = MessageEvent<
| ({
type: "urlchange";
} & EmbedReplayData)
| ({
type: "favicons";
} & FavIconEventDetail)
>;

const scriptSrc =
document.currentScript && (document.currentScript as HTMLScriptElement).src;
Expand Down Expand Up @@ -46,6 +57,7 @@ class Embed extends LitElement {
@property({ type: String }) hashString: string | undefined;

@property({ type: Boolean }) deepLink = false;
@property({ type: Boolean }) updateFavicons = false;
@property({ type: Boolean }) sandbox = false;
@property({ type: Boolean }) noSandbox: boolean | null = null;
@property({ type: Boolean }) noWebWorker = false;
Expand Down Expand Up @@ -113,30 +125,53 @@ class Embed extends LitElement {
}
}

// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
handleMessage(event) {
handleMessage(event: IframeMessage) {
const iframe = this.renderRoot.querySelector("iframe");

if (iframe && event.source === iframe.contentWindow) {
if (!event.data.view) {
return;
switch (event.data.type) {
case "urlchange":
if (this.deepLink) {
this.handleUrlChangeMessage(event.data);
}
break;

case "favicons":
if (this.updateFavicons) {
updateFaviconLinks(event.data);
}
break;
}
}
}

if (event.data.title) {
this.title = event.data.title;
}
handleUrlChangeMessage(data: EmbedReplayData) {
const { url, ts, view, query, title } = data;

if (!this.deepLink) {
return;
}
if (title) {
this.title = title;
}

// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const currHash = new URLSearchParams(event.data);
const url = new URL(window.location.href);
url.hash = "#" + currHash.toString();
window.history.replaceState({}, "", url);
const params: Record<string, string> = {};

if (url) {
params.url = url;
}
if (ts) {
params.ts = ts;
}
if (query) {
params.query = query;
}
if (view && !url) {
params.view = view;
}

const currHash = new URLSearchParams(params);

const fullUrl = new URL(window.location.href);
fullUrl.hash = "#" + currHash.toString();
window.history.replaceState({}, "", fullUrl);
}

firstUpdated() {
Expand All @@ -149,7 +184,9 @@ class Embed extends LitElement {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.doRegister();

window.addEventListener("message", (event) => this.handleMessage(event));
window.addEventListener("message", (event: IframeMessage) =>
this.handleMessage(event),
);

if (this.deepLink) {
this.updateFromHash();
Expand Down Expand Up @@ -295,12 +332,17 @@ class Embed extends LitElement {
params as unknown as Record<string, string>,
).toString();

this.hashString = new URLSearchParams({
const hashParams: Record<string, string> = {
url: this.url,
ts: this.ts,
query: this.query,
view: this.view,
}).toString();
};

if (!this.url) {
hashParams.view = this.view;
}

this.hashString = new URLSearchParams(hashParams).toString();
}
}

Expand Down
43 changes: 27 additions & 16 deletions src/item.ts
Expand Up @@ -57,7 +57,7 @@ import fasCaretDown from "@fortawesome/fontawesome-free/svgs/solid/caret-down.sv
import { RWPEmbedReceipt } from "./embed-receipt";
import Split from "split.js";

import type { ItemType, URLResource } from "./types";
import type { FavIconEventDetail, ItemType, URLResource } from "./types";
import type { Replay } from "./replay";
import { ifDefined } from "lit/directives/if-defined.js";

Expand Down Expand Up @@ -85,6 +85,21 @@ export type LoadInfo = {
importCollId?: string;
};

export type EmbedReplayData = {
view?: "story" | "pages" | "resources";
url?: string;
ts?: string;
title?: string;
query?: string;
};

export type TabData = EmbedReplayData & {
multiTs?: string[];
currList?: number;
urlSearchType?: string;
currMime?: string;
};

// ===========================================================================
class Item extends LitElement {
@property({ type: Boolean })
Expand Down Expand Up @@ -112,16 +127,7 @@ class Item extends LitElement {
isLoading = false;

@property({ type: Object, attribute: false })
tabData: {
view?: "story" | "pages" | "resources";
url?: string;
ts?: string;
multiTs?: string[];
currList?: number;
query?: string;
urlSearchType?: string;
currMime?: string;
} = {};
tabData: TabData = {};

@property({ type: String })
url = "";
Expand Down Expand Up @@ -328,7 +334,11 @@ class Item extends LitElement {
}
}
if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, "*");
const { url, ts, view, query, title }: EmbedReplayData = this.tabData;
window.parent.postMessage(
{ type: "urlchange", url, ts, view, query, title },
"*",
);
}
}
this._locUpdateNeeded = false;
Expand Down Expand Up @@ -1698,11 +1708,12 @@ class Item extends LitElement {
this.showSidebar = false;
}

// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
async onFavIcons(event) {
async onFavIcons(event: CustomEvent<FavIconEventDetail>) {
if (this.embed && window.parent !== window) {
window.parent.postMessage({ type: "favicons", ...event.detail }, "*");
}

for (const icon of event.detail.icons) {
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const resp = await fetch(icon.href);
if (resp.status === 200) {
const ct = resp.headers.get("Content-Type");
Expand Down
20 changes: 20 additions & 0 deletions src/misc.ts
Expand Up @@ -7,6 +7,7 @@ import { styleMap } from "lit/directives/style-map.js";
import allCssRaw from "../assets/main.scss";

import rwpLogo from "../assets/logo.svg";
import type { FavIconEventDetail } from "./types";

const apiPrefix = "./w/api";
const replayPrefix = "./w";
Expand Down Expand Up @@ -37,6 +38,25 @@ function clickOnSpacebarPress(event) {
}
}

// Update favicon links from an array of {rel, href} objects
// remove all existing icon links
// used by both embed and main app
export function updateFaviconLinks(data: FavIconEventDetail) {
const head = document.querySelector("head")!;
const oldLinks = document.querySelectorAll("link[rel*='icon']");

for (const link of oldLinks) {
head.removeChild(link);
}

for (const icon of data.icons) {
const link = document.createElement("link");
link.rel = icon.rel;
link.href = icon.href;
head.appendChild(link);
}
}

// ===========================================================================
class FaIcon extends LitElement {
constructor() {
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Expand Up @@ -50,3 +50,10 @@ export type ItemType = {
totalSize?: unknown;
size?: number | string;
};

export type FavIconEventDetail = {
icons: {
rel: string;
href: string;
}[];
};

0 comments on commit 6051a2a

Please sign in to comment.