Files
replayweb.page/src/replay.ts
T
Emma Segal-Grossman ad38694598 Typescript migration part 2 (#261)
Converts everything to typescript, adding `@ts-expect-error`s where
necessary. Also makes some progress converting elements to use
decorators, but we're not fully there yet — I think it'll make sense to
continue to do that in future PRs, rather than doing it all in one go,
just so that we can keep up with the dev branch.
2023-11-29 15:53:48 -05:00

426 lines
11 KiB
TypeScript

import { LitElement, html, css } from "lit";
import { property } from "lit/decorators.js";
import { wrapCss, rwpLogo } from "./misc";
import type { Item } from "./types";
// ===========================================================================
class Replay extends LitElement {
@property({ type: Object })
collInfo: Item | Record<string, never> | null = null;
@property({ type: String })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
sourceUrl: any = null;
// external url set from parent
@property({ type: String })
url = "";
@property({ type: String })
ts = "";
// actual replay url
@property({ type: String })
replayUrl = "";
@property({ type: String })
replayTS = "";
@property({ type: String })
actualTS = "";
@property({ type: String })
title = "";
@property({ type: String })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
iframeUrl: any = null;
@property({ type: Boolean })
showAuth = false;
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
authFileHandle: any = null;
private reauthWait: null | Promise<void> = null;
private _loadPoll: null | number = null;
firstUpdated() {
window.addEventListener("message", (event) => this.onReplayMessage(event));
navigator.serviceWorker.addEventListener("message", (event) =>
this.handleAuthMessage(event),
);
}
async handleAuthMessage(event) {
if (
event.data.type === "authneeded" &&
this.collInfo &&
event.data.coll === this.collInfo.coll
) {
if (event.data.fileHandle) {
this.authFileHandle = event.data.fileHandle;
try {
if (
(await this.authFileHandle.requestPermission({ mode: "read" })) ===
"granted"
) {
this.showAuth = false;
this.reauthWait = null;
this.refresh();
return;
}
} catch (e) {
console.warn(e);
}
} else {
this.authFileHandle = null;
}
if (this.reauthWait) {
await this.reauthWait;
} else {
this.showAuth = true;
}
}
}
doSetIframeUrl() {
this.iframeUrl =
this.url && this.collInfo
? `${this.collInfo.replayPrefix}/${this.ts || ""}mp_/${this.url}`
: "";
}
updated(changedProperties) {
if (
changedProperties.has("sourceUrl") ||
changedProperties.has("collInfo")
) {
this.reauthWait = null;
}
if (
this.url &&
(this.replayUrl != this.url || this.replayTS != this.ts) &&
(changedProperties.has("url") || changedProperties.has("ts"))
) {
this.replayUrl = this.url;
this.replayTS = this.ts;
this.showAuth = false;
this.reauthWait = null;
this.doSetIframeUrl();
}
if (this.iframeUrl && changedProperties.has("iframeUrl")) {
this.waitForLoad();
const detail = { title: "Archived Page", replayTitle: false };
this.dispatchEvent(
new CustomEvent("update-title", {
bubbles: true,
composed: true,
detail,
}),
);
}
if (
(this.replayUrl && changedProperties.has("replayUrl")) ||
(this.replayTS && changedProperties.has("replayTS"))
) {
const data = {
url: this.replayUrl,
ts: this.replayTS,
};
this.dispatchEvent(
new CustomEvent("coll-tab-nav", { detail: { replaceLoc: true, data } }),
);
}
if (
this.title &&
(changedProperties.has("title") || changedProperties.has("actualTS"))
) {
const detail = {
title: this.title,
url: this.replayUrl,
// send actual ts even if live
ts: this.actualTS,
replayTitle: true,
};
this.dispatchEvent(
new CustomEvent("update-title", {
bubbles: true,
composed: true,
detail,
}),
);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
setDisablePointer(disable: any) {
const iframe = this.renderRoot.querySelector("iframe");
if (iframe) {
iframe.style.pointerEvents = disable ? "none" : "all";
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
onReplayMessage(event: any) {
const iframe = this.renderRoot.querySelector("iframe");
if (iframe && event.source === iframe.contentWindow) {
if (
event.data.wb_type === "load" ||
event.data.wb_type === "replace-url"
) {
this.replayTS = event.data.is_live ? "" : event.data.ts;
this.actualTS = event.data.ts;
this.replayUrl = event.data.url;
this.title = event.data.title || this.title;
this.clearLoading(iframe.contentWindow);
if (event.data.icons) {
const icons = event.data.icons;
this.dispatchEvent(
new CustomEvent("replay-favicons", {
bubbles: true,
composed: true,
detail: { icons },
}),
);
}
} else if (event.data.wb_type === "title") {
this.title = event.data.title;
}
}
}
onReAuthed(event) {
this.reauthWait = (async () => {
if (!this.authFileHandle) {
// google drive reauth
const headers = event.detail.headers;
await fetch(`${this.collInfo!.apiPrefix}/updateAuth`, {
method: "POST",
body: JSON.stringify({ headers }),
});
} else {
if (
(await this.authFileHandle.requestPermission({ mode: "read" })) !==
"granted"
) {
this.reauthWait = null;
return;
}
this.authFileHandle = null;
}
if (this.showAuth) {
this.showAuth = false;
this.reauthWait = null;
}
this.refresh();
})();
}
waitForLoad() {
this.setLoading();
this._loadPoll = window.setInterval(() => {
const iframe = this.renderRoot.querySelector("iframe");
if (
!iframe ||
!iframe.contentDocument ||
!iframe.contentWindow ||
(iframe.contentDocument.readyState === "complete" &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
!(iframe.contentWindow as Window & { _WBWombat: any })._WBWombat)
) {
this.clearLoading(iframe && iframe.contentWindow);
}
}, 5000);
}
clearLoading(iframeWin) {
this.dispatchEvent(
new CustomEvent("replay-loading", { detail: { loading: false } }),
);
if (this._loadPoll) {
window.clearInterval(this._loadPoll);
this._loadPoll = null;
}
if (iframeWin) {
iframeWin.addEventListener("beforeunload", () => {
this.setLoading();
});
}
}
setLoading() {
this.dispatchEvent(
new CustomEvent("replay-loading", { detail: { loading: true } }),
);
}
refresh() {
const iframe = this.renderRoot.querySelector("iframe");
if (!iframe) {
return;
}
const oldIframeUrl = this.iframeUrl;
// set iframe url to expected, refresh if same url
this.doSetIframeUrl();
if (oldIframeUrl === this.iframeUrl || this.url === this.replayUrl) {
this.waitForLoad();
iframe.contentWindow?.location.reload();
}
}
static get styles() {
return wrapCss(css`
:host {
display: flex;
flex-direction: column;
height: 100%;
}
.iframe-container {
position: relative;
width: 100%;
height: 100%;
border: 0px;
}
.iframe-main {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 100%;
}
.intro-panel .panel-heading {
font-size: 1em;
display: inline-block;
}
.iframe-main.modal-bg {
z-index: 200;
background-color: rgba(10, 10, 10, 0.7);
}
#wrlogo {
vertical-align: middle;
}
.intro-panel .panel-block {
padding: 1em;
flex-direction: column;
line-height: 2.5em;
}
div.intro-panel.panel {
min-width: 40%;
display: flex;
flex-direction: column;
margin: 3em;
background-color: white;
}
`);
}
render() {
const title = `Replay of ${this.title ? `${this.title}:` : ""} ${this.url}`;
return html` <h1 id="replay-heading" class="is-sr-only">${title}</h1>
${!this.iframeUrl
? html` <div class="panel intro-panel">
<p class="panel-heading">Replay Web Page</p>
<div class="panel-block">
<p>Enter a URL above to replay it from the web archive!</p>
<p>
(Or, check out <a href="#view=pages">Pages</a> or
<a href="#view=resources">URLs</a> to explore the contents of
this archive.)
</p>
</div>
</div>`
: html`
<div class="iframe-container">
<iframe
class="iframe-main"
name="___wb_replay_top_frame"
@message="${this.onReplayMessage}"
allow="autoplay 'self'; fullscreen"
allowfullscreen
src="${this.iframeUrl}"
title="${title}"
></iframe>
${this.showAuth
? html`
<div class="iframe-main modal-bg">
<div class="panel intro-panel">
<p class="panel-heading">
<fa-icon
id="wrlogo"
size="1.5rem"
.svg=${rwpLogo}
aria-hidden="true"
></fa-icon>
Authorization Needed
</p>
<div class="panel-block">
${this.authFileHandle
? html`
<p>
This archive is loaded from a local file:
<b>${this.authFileHandle.name}</b>
</p>
<p>
The browser needs to confirm your permission
to continue loading from this file.
</p>
<button
class="button is-warning is-rounded"
@click="${this.onReAuthed}"
>
Show Confirmation
</button>
`
: html` <wr-gdrive
.sourceUrl="${this.sourceUrl}"
state="trymanual"
.reauth="${true}"
@load-ready="${this.onReAuthed}"
></wr-gdrive>`}
</div>
</div>
</div>
`
: ""}
</div>
`}`;
}
}
customElements.define("wr-coll-replay", Replay);
export { Replay };