From 7d1035f751eceb5c9495ab041594efd879863a3c Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 25 Nov 2020 23:21:22 +0000 Subject: [PATCH] Do our own EventSource reconnection, show icon to user --- static/remote.js | 52 ++++++++++++++++++++++++++++++++--------- static/remote.ts | 61 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/static/remote.js b/static/remote.js index 2dc8b62..64a47c5 100644 --- a/static/remote.js +++ b/static/remote.js @@ -1,4 +1,5 @@ "use strict"; +const messageBus = new EventTarget(); function main() { const url = new URL(location.href); if (url.searchParams.has("room")) { @@ -64,14 +65,32 @@ function watch(roomId, clientId, adminSecret, prnt) { if (adminSecret) { url.searchParams.set("admin_secret", adminSecret); } - const es = new EventSource(url.toString()); - renderControls(roomId, clientId, adminSecret, prnt, es); - renderTimers(roomId, adminSecret, prnt, es); + createEventSource(url); + renderControls(roomId, clientId, adminSecret, prnt); + renderTimers(roomId, adminSecret, prnt); if (adminSecret) { - renderAdmin(roomId, adminSecret, prnt, es); + renderAdmin(roomId, adminSecret, prnt); } } -function renderControls(roomId, clientId, adminSecret, prnt, es) { +function createEventSource(url) { + const es = new EventSource(url.toString()); + es.addEventListener("open", () => { + messageBus.dispatchEvent(new Event("open")); + }); + es.addEventListener("message", (e) => { + messageBus.dispatchEvent(new MessageEvent("message", { + data: e.data, + lastEventId: e.lastEventId, + })); + }); + es.addEventListener("error", () => { + console.warn("disconnected"); + es.close(); + setTimeout(() => createEventSource(url), 3000); + messageBus.dispatchEvent(new Event("error")); + }); +} +function renderControls(roomId, clientId, adminSecret, prnt) { const controls = create(prnt, "div", undefined, ["controls"]); const left = create(controls, "span", "<<<", ["control-button"]); left.addEventListener("click", () => control(roomId, clientId, controls, "left")); @@ -88,7 +107,8 @@ function renderControls(roomId, clientId, adminSecret, prnt, es) { break; } }); - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev; const event = JSON.parse(e.data); if (!event.standard_event) { return; @@ -105,10 +125,11 @@ function renderControls(roomId, clientId, adminSecret, prnt, es) { } }); } -function renderTimers(roomId, adminSecret, prnt, es) { +function renderTimers(roomId, adminSecret, prnt) { let overallStart = null; let meStart = null; - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev; const event = JSON.parse(e.data); if (!event.standard_event) { return; @@ -117,6 +138,14 @@ function renderTimers(roomId, adminSecret, prnt, es) { meStart = parseInt(event.standard_event.active_start || "0", 10) || null; }); const width = 10; + const statusDiv = create(prnt, "div", "Status: ".padStart(width, "\u00a0")); + const status = create(statusDiv, "span"); + messageBus.addEventListener("open", () => { + status.innerText = "\u{1f7e2}"; + }); + messageBus.addEventListener("error", () => { + status.innerText = "\u{1f534}"; + }); const clockDiv = create(prnt, "div", "Clock: ".padStart(width, "\u00a0")); const clock = create(clockDiv, "span"); const overallDiv = create(prnt, "div", "Overall: ".padStart(width, "\u00a0")); @@ -158,7 +187,7 @@ function renderTimers(roomId, adminSecret, prnt, es) { } }, 250); } -function renderAdmin(roomId, adminSecret, prnt, es) { +function renderAdmin(roomId, adminSecret, prnt) { const table = create(prnt, "table", undefined, ["users"]); const head = create(table, "thead"); const head1 = create(head, "tr"); @@ -169,11 +198,12 @@ function renderAdmin(roomId, adminSecret, prnt, es) { create(head1, "th", "🌟"); const body = create(table, "tbody"); const rows = new Map(); - es.addEventListener("open", () => { + messageBus.addEventListener("open", () => { rows.clear(); body.innerHTML = ""; }); - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev; const event = JSON.parse(e.data); if (!event.admin_event) { return; diff --git a/static/remote.ts b/static/remote.ts index c282102..9a70763 100644 --- a/static/remote.ts +++ b/static/remote.ts @@ -65,6 +65,8 @@ interface Client { active_start: string; } +const messageBus = new EventTarget(); + function main() { const url = new URL(location.href); @@ -143,18 +145,41 @@ function watch(roomId: string, clientId: string, adminSecret: string | null, prn if (adminSecret) { url.searchParams.set("admin_secret", adminSecret); } - const es = new EventSource(url.toString()); + createEventSource(url); - renderControls(roomId, clientId, adminSecret, prnt, es); + renderControls(roomId, clientId, adminSecret, prnt); - renderTimers(roomId, adminSecret, prnt, es); + renderTimers(roomId, adminSecret, prnt); if (adminSecret) { - renderAdmin(roomId, adminSecret, prnt, es); + renderAdmin(roomId, adminSecret, prnt); } } -function renderControls(roomId: string, clientId: string, adminSecret: string | null, prnt: HTMLElement, es: EventSource) { +function createEventSource(url: URL) { + const es = new EventSource(url.toString()); + + es.addEventListener("open", () => { + messageBus.dispatchEvent(new Event("open")); + }); + + es.addEventListener("message", (e) => { + messageBus.dispatchEvent(new MessageEvent("message", { + data: e.data, + lastEventId: e.lastEventId, + })); + }); + + es.addEventListener("error", () => { + console.warn("disconnected"); + es.close(); + setTimeout(() => createEventSource(url), 3000); + + messageBus.dispatchEvent(new Event("error")); + }); +} + +function renderControls(roomId: string, clientId: string, adminSecret: string | null, prnt: HTMLElement) { const controls = create(prnt, "div", undefined, ["controls"]) as HTMLDivElement; const left = create(controls, "span", "<<<", ["control-button"]) as HTMLDivElement; @@ -176,7 +201,8 @@ function renderControls(roomId: string, clientId: string, adminSecret: string | } }); - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev as MessageEvent; const event = JSON.parse(e.data) as Event; if (!event.standard_event) { @@ -196,11 +222,12 @@ function renderControls(roomId: string, clientId: string, adminSecret: string | }); } -function renderTimers(roomId: string, adminSecret: string | null, prnt: HTMLElement, es: EventSource) { +function renderTimers(roomId: string, adminSecret: string | null, prnt: HTMLElement) { let overallStart: number | null = null; let meStart: number | null = null; - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev as MessageEvent; const event = JSON.parse(e.data) as Event; if (!event.standard_event) { @@ -213,6 +240,17 @@ function renderTimers(roomId: string, adminSecret: string | null, prnt: HTMLElem const width = 10; + const statusDiv = create(prnt, "div", "Status: ".padStart(width, "\u00a0")); + const status = create(statusDiv, "span"); + + messageBus.addEventListener("open", () => { + status.innerText = "\u{1f7e2}"; + }); + + messageBus.addEventListener("error", () => { + status.innerText = "\u{1f534}"; + }); + const clockDiv = create(prnt, "div", "Clock: ".padStart(width, "\u00a0")); const clock = create(clockDiv, "span"); @@ -260,7 +298,7 @@ function renderTimers(roomId: string, adminSecret: string | null, prnt: HTMLElem }, 250); } -function renderAdmin(roomId: string, adminSecret: string, prnt: HTMLElement, es: EventSource) { +function renderAdmin(roomId: string, adminSecret: string, prnt: HTMLElement) { const table = create(prnt, "table", undefined, ["users"]) as HTMLTableElement; const head = create(table, "thead"); const head1 = create(head, "tr"); @@ -274,12 +312,13 @@ function renderAdmin(roomId: string, adminSecret: string, prnt: HTMLElement, es: const rows: Map = new Map(); - es.addEventListener("open", () => { + messageBus.addEventListener("open", () => { rows.clear(); body.innerHTML = ""; }); - es.addEventListener("message", (e) => { + messageBus.addEventListener("message", (ev) => { + const e = ev as MessageEvent; const event = JSON.parse(e.data) as Event; if (!event.admin_event) {