Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: allow embed <replay-web-page> to also support updating favicons from replay #287

Merged
merged 6 commits into from Mar 8, 2024
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
63 changes: 44 additions & 19 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 { TabData } from "./item";

type IframeMessage = MessageEvent<
| ({
type: "urlchange";
} & TabData)
| ({
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,41 @@ 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;
}

if (event.data.title) {
this.title = event.data.title;
switch (event.data.type) {
case "urlchange":
if (this.deepLink) {
this.handleUrlChangeMessage(event.data);
}
break;

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

if (!this.deepLink) {
return;
}
handleUrlChangeMessage(data: TabData) {
if (!data.view) {
return;
}

// 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);
if (data.title) {
this.title = data.title;
}

const currHash = new URLSearchParams(
Object.entries(data).map(([k, v]) => [k, v.toString()]),
);
const url = new URL(window.location.href);
url.hash = "#" + currHash.toString();
window.history.replaceState({}, "", url);
}

firstUpdated() {
Expand All @@ -149,7 +172,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
39 changes: 23 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,18 @@ export type LoadInfo = {
importCollId?: string;
};

export type TabData = {
view?: "story" | "pages" | "resources";
url?: string;
ts?: string;
title?: string;
multiTs?: string[];
currList?: number;
query?: string;
urlSearchType?: string;
currMime?: string;
};

// ===========================================================================
class Item extends LitElement {
@property({ type: Boolean })
Expand Down Expand Up @@ -112,16 +124,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 +331,10 @@ class Item extends LitElement {
}
}
if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, "*");
window.parent.postMessage(
{ type: "urlchange", ...this.tabData },
"*",
);
}
}
this._locUpdateNeeded = false;
Expand Down Expand Up @@ -1698,11 +1704,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;
}[];
};