track state of connection to worker in main script
This commit is contained in:
parent
d189054616
commit
bd1b9a369b
136
v5shoutbox.js
136
v5shoutbox.js
|
@ -65,22 +65,48 @@ class IRCClientState {
|
|||
callbacks around the message-passing interface of the IRC client. */
|
||||
class SharedChat {
|
||||
|
||||
/* Chat state. This outlines the protocol for communicating with the service
|
||||
worker when loading pages, refreshing the worker, etc. */
|
||||
static State = Object.freeze({
|
||||
/* We are not using a service worker. */
|
||||
LOCAL: 0,
|
||||
/* We are waiting for a handshake response after loading the page. */
|
||||
HANDSHAKE_PAGE: 1,
|
||||
/* We are waiting for a handshake response after greeting a dynamically-
|
||||
updated controller */
|
||||
HANDSHAKE_UPDATE: 2,
|
||||
/* We are connected to the worker. (The worker itself might not be
|
||||
connected to the IRC server, though!) */
|
||||
RUNNING: 3,
|
||||
});
|
||||
|
||||
stateString() {
|
||||
switch(this.state) {
|
||||
case SharedChat.State.LOCAL: return "Local IRC client";
|
||||
case SharedChat.State.HANDSHAKE_PAGE: return "Connecting to worker...";
|
||||
case SharedChat.State.HANDSHAKE_UPDATE: return "Updating worker...";
|
||||
case SharedChat.State.RUNNING: return "Running";
|
||||
default: return `<State ${this.state.toString()}>`
|
||||
}
|
||||
}
|
||||
|
||||
/* Registers the service worker that runs the IRC client. Waits for
|
||||
registration to complete and returns true on success, false on error.
|
||||
If successful, sets up the onControllerChanged callback. */
|
||||
If successful, sets up the handleControllerChanged callback. */
|
||||
async registerServiceWorker() {
|
||||
if(!("serviceWorker" in navigator)) {
|
||||
console.warn("No service workers, shoutbox will use per-tab IRC client");
|
||||
this.state = SharedChat.State.LOCAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Call the update method once if the worker was already there */
|
||||
if(navigator.serviceWorker.controller !== null)
|
||||
this.onControllerChanged();
|
||||
this.handleControllerChanged();
|
||||
|
||||
/* Then call it after first install and every update */
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
this.onControllerChanged();
|
||||
this.handleControllerChanged();
|
||||
});
|
||||
|
||||
/* Explicitly disconnect before unloading (the worker will also disconnect
|
||||
|
@ -91,7 +117,7 @@ class SharedChat {
|
|||
});
|
||||
|
||||
navigator.serviceWorker.addEventListener("message", (e) => {
|
||||
this.onMessage(e.data);
|
||||
this.handleMessage(e.data);
|
||||
});
|
||||
|
||||
/* Register the service worker */
|
||||
|
@ -101,8 +127,10 @@ class SharedChat {
|
|||
console.error(`Service worker registration failed with ${error}`);
|
||||
return null;
|
||||
});
|
||||
if(reg === null)
|
||||
if(reg === null) {
|
||||
this.state = SharedChat.State.LOCAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Once registered, wait for it to be ready, and then check whether it's
|
||||
controlling us; if not, fall back to the local mode */
|
||||
|
@ -111,40 +139,46 @@ class SharedChat {
|
|||
|
||||
if(navigator.serviceWorker.controller === null) {
|
||||
console.warn("No controller (maybe Shift-refresh was used?)");
|
||||
this.state = SharedChat.State.LOCAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.state = SharedChat.State.HANDSHAKE_PAGE;
|
||||
return true;
|
||||
};
|
||||
|
||||
/* Handler called when the page is taken over by a service worker. Usually
|
||||
the worker manages the page immediately when it starts loading, but this
|
||||
also happens after the first install and during live worker updates. */
|
||||
onControllerChanged() {
|
||||
handleControllerChanged() {
|
||||
console.log("Service worker changed, sending new handshake");
|
||||
navigator.serviceWorker.controller.postMessage({ type: "handshake" });
|
||||
this.state = SharedChat.State.HANDSHAKE_UPDATE;
|
||||
this.onStateChanged();
|
||||
}
|
||||
|
||||
// TODO: Pick up already connected worker
|
||||
/* getClientState() {
|
||||
if(this.irc == null) {
|
||||
this.remoteCall({ type: "get_state" });
|
||||
return null;
|
||||
/* Handler called when a message is received from the worker. */
|
||||
handleMessage(e) {
|
||||
if(e.type == "welcome") {
|
||||
this.state = SharedChat.State.RUNNING;
|
||||
this.onStateChanged();
|
||||
}
|
||||
else
|
||||
return new IRCClientState(this.irc);
|
||||
} */
|
||||
this.onMessage(e);
|
||||
}
|
||||
|
||||
async init() {
|
||||
const ok = await this.registerServiceWorker(() => {
|
||||
});
|
||||
const ok = await this.registerServiceWorker();
|
||||
console.log("SharedChat service worker registration:", ok);
|
||||
|
||||
if(ok)
|
||||
this.irc = null;
|
||||
else {
|
||||
this.irc = new IRCClient("wss://irc.planet-casio.com:443", undefined);
|
||||
this.irc.onMessage = (msg) => this.onMessage(msg);
|
||||
this.irc.onMessage = (msg) => this.handleMessage(msg);
|
||||
}
|
||||
|
||||
console.log("Initialization finished; state:", this.stateString());
|
||||
this.onStateChanged();
|
||||
}
|
||||
|
||||
/** API for using the IRC client **/
|
||||
|
@ -173,8 +207,10 @@ class SharedChat {
|
|||
|
||||
/** Handlers for getting messages from the IRC client **/
|
||||
|
||||
/* Handler called when a message is received from the worker. */
|
||||
/* Notifier called when a message is received from the worker. */
|
||||
onMessage(e) {}
|
||||
/* Notifier called when the shared chat's connection state changes. */
|
||||
onStateChanged() {}
|
||||
}
|
||||
|
||||
/* Shoutbox entry point and DOM manipulation. */
|
||||
|
@ -196,7 +232,7 @@ let shoutbox = new function() {
|
|||
this.init = function(root, chat) {
|
||||
this.title = document.title;
|
||||
this.chat = chat;
|
||||
this.chat.onMessage = (e) => this.onMessage(e);
|
||||
this.chat.onMessage = (e) => this.handleMessage(e);
|
||||
|
||||
/* Channel views */
|
||||
this.eChannels = root.querySelector(".channels");
|
||||
|
@ -243,35 +279,32 @@ let shoutbox = new function() {
|
|||
shoutbox.focused = false;
|
||||
});
|
||||
|
||||
/* Initial client state */
|
||||
/* TODO: Pick up an already connected service worker */
|
||||
/* Initial client state. This will get replaced as soon as we get the
|
||||
handshake response from the worker */
|
||||
// TODO: Actually put a loading screen instead
|
||||
this.clientState = new IRCClientState(null);
|
||||
console.log(this.clientState);
|
||||
this.selectChannel("\\login");
|
||||
this.refreshView();
|
||||
}
|
||||
|
||||
/*** IRC callbacks ***/
|
||||
|
||||
this.onMessage = function(e) {
|
||||
this.handleMessage = function(e) {
|
||||
/* Update known client state based on info bundled with the message */
|
||||
if(e.state !== undefined)
|
||||
if(e.state !== undefined) {
|
||||
this.clientState = new IRCClientState(e.state);
|
||||
console.log("Client state changed, now", this.clientState);
|
||||
}
|
||||
|
||||
if(e.type == "log") {
|
||||
this.log(e.text);
|
||||
}
|
||||
else if(e.type == "state_changed") {
|
||||
console.log(e);
|
||||
this.refreshView();
|
||||
}
|
||||
else if(e.type == "welcome") {
|
||||
for(const [channel, info] of this.clientState.channels) {
|
||||
for(const [channel, info] of this.clientState.channels)
|
||||
this.createChannel(channel);
|
||||
/* If we are connected, select a channel */
|
||||
if(!this.isOnRemoteChannel())
|
||||
this.selectChannel(channel);
|
||||
}
|
||||
this.refreshView();
|
||||
}
|
||||
else if(e.type == "authenticated") {
|
||||
|
@ -283,7 +316,7 @@ let shoutbox = new function() {
|
|||
this.eAuthenticationError.style.display = "block";
|
||||
}
|
||||
else if(e.type == "new_message") {
|
||||
this.onNewMessageCallback(e.channel, e.date, e.author, e.message);
|
||||
this.handleNewMessage(e.channel, e.date, e.author, e.message);
|
||||
}
|
||||
else if(e.type == "channel_changed") {
|
||||
for(const [channel, info] of this.clientState.channels) {
|
||||
|
@ -325,7 +358,10 @@ let shoutbox = new function() {
|
|||
this.eStatus.insertBefore(code, this.eStatus.childNodes[0]);
|
||||
}
|
||||
else {
|
||||
this.eStatus.textContent = st.stateString();
|
||||
let str = this.chat.stateString();
|
||||
if(this.chat.state == SharedChat.State.RUNNING)
|
||||
str += "|" + st.stateString();
|
||||
this.eStatus.textContent = str;
|
||||
}
|
||||
|
||||
this.eShoutboxForm.style.display = running ? "flex" : "none";
|
||||
|
@ -344,7 +380,7 @@ let shoutbox = new function() {
|
|||
}
|
||||
};
|
||||
|
||||
this.onNewMessageCallback = function(channel, date, author, message) {
|
||||
this.handleNewMessage = function(channel, date, author, message) {
|
||||
shoutbox.addNewMessage(channel, date, author, message);
|
||||
|
||||
if(channel === this.channel && !shoutbox.focused) {
|
||||
|
@ -384,26 +420,28 @@ let shoutbox = new function() {
|
|||
this.createChannel = function(channel) {
|
||||
if(!availableChannels.includes(channel))
|
||||
return;
|
||||
if(this.getChannelView(channel) !== undefined)
|
||||
return;
|
||||
|
||||
let view = document.createElement("div");
|
||||
view.classList.add("channel");
|
||||
view.dataset.channel = channel;
|
||||
view.style.display = "none";
|
||||
this.eChannels.appendChild(view);
|
||||
if(this.getChannelView(channel) === undefined) {
|
||||
let view = document.createElement("div");
|
||||
view.classList.add("channel");
|
||||
view.dataset.channel = channel;
|
||||
view.style.display = "none";
|
||||
this.eChannels.appendChild(view);
|
||||
|
||||
let button = document.createElement("a");
|
||||
button.classList.add("tab");
|
||||
button.appendChild(document.createTextNode(channel));
|
||||
button.dataset.channel = channel;
|
||||
this.addChannelButton(button);
|
||||
let button = document.createElement("a");
|
||||
button.classList.add("tab");
|
||||
button.appendChild(document.createTextNode(channel));
|
||||
button.dataset.channel = channel;
|
||||
this.addChannelButton(button);
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
shoutbox.selectChannel(button.dataset.channel);
|
||||
});
|
||||
button.addEventListener("click", () => {
|
||||
shoutbox.selectChannel(button.dataset.channel);
|
||||
});
|
||||
}
|
||||
|
||||
if(this.channel === "\\login")
|
||||
/* Automatically select channels when they appear.
|
||||
TODO: Maybe not if we're looking at the log or config? */
|
||||
if(!this.isOnRemoteChannel())
|
||||
shoutbox.selectChannel(channel);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue