Keep track of client status in Client

This commit is contained in:
Simon Ser 2021-01-22 18:29:22 +01:00
parent 0261bc11e7
commit 41cd2153cf
4 changed files with 70 additions and 43 deletions

View file

@ -9,7 +9,7 @@ import Composer from "/components/composer.js";
import ScrollManager from "/components/scroll-manager.js"; import ScrollManager from "/components/scroll-manager.js";
import { html, Component, createRef } from "/lib/index.js"; import { html, Component, createRef } from "/lib/index.js";
import { strip as stripANSI } from "/lib/ansi.js"; import { strip as stripANSI } from "/lib/ansi.js";
import { SERVER_BUFFER, BufferType, ReceiptType, Status, Unread } from "/state.js"; import { SERVER_BUFFER, BufferType, ReceiptType, NetworkStatus, Unread } from "/state.js";
import commands from "/commands.js"; import commands from "/commands.js";
import { setup as setupKeybindings } from "/keybindings.js"; import { setup as setupKeybindings } from "/keybindings.js";
@ -150,7 +150,6 @@ export default class App extends Component {
autoconnect: false, autoconnect: false,
autojoin: [], autojoin: [],
}, },
status: Status.DISCONNECTED,
networks: new Map(), networks: new Map(),
buffers: new Map(), buffers: new Map(),
activeBuffer: null, activeBuffer: null,
@ -451,7 +450,7 @@ export default class App extends Component {
var networks = new Map(state.networks); var networks = new Map(state.networks);
networks.set(netID, { networks.set(netID, {
id: netID, id: netID,
status: Status.CONNECTING, status: NetworkStatus.CONNECTING,
isupport: new Map(), isupport: new Map(),
}); });
return { networks }; return { networks };
@ -469,8 +468,8 @@ export default class App extends Component {
this.clients.set(netID, client); this.clients.set(netID, client);
client.addEventListener("close", () => { client.addEventListener("status", () => {
this.handleClose(netID); this.handleStatus(netID, client.status);
}); });
client.addEventListener("message", (event) => { client.addEventListener("message", (event) => {
@ -487,18 +486,24 @@ export default class App extends Component {
this.switchBuffer({ network: netID, name: SERVER_BUFFER }); this.switchBuffer({ network: netID, name: SERVER_BUFFER });
} }
handleClose(netID) { handleStatus(netID, status) {
this.setNetworkState(netID, (state) => { this.setNetworkState(netID, (state) => {
if (state.status == Status.DISCONNECTED) { if (status !== Client.Status.DISCONNECTED) {
return { status };
}
if (state.status === Client.Status.DISCONNECTED) {
// User decided to logout // User decided to logout
return null; return null;
} }
console.log("Reconnecting to server in " + RECONNECT_DELAY_SEC + " seconds"); console.log("Reconnecting to server in " + RECONNECT_DELAY_SEC + " seconds");
clearTimeout(this.reconnectTimeoutID); clearTimeout(this.reconnectTimeoutID);
this.reconnectTimeoutID = setTimeout(() => { this.reconnectTimeoutID = setTimeout(() => {
this.connect(netID, this.state.connectParams); this.connect(netID, this.state.connectParams);
}, RECONNECT_DELAY_SEC * 1000); }, RECONNECT_DELAY_SEC * 1000);
return { status: Status.DISCONNECTED };
return { status };
}); });
} }
@ -515,12 +520,10 @@ export default class App extends Component {
var client = this.clients.get(netID); var client = this.clients.get(netID);
if (client) { if (client) {
// Prevent auto-reconnect from kicking in client.disconnect();
client.removeEventListener("close", this.handleClose);
client.close();
} }
this.setNetworkState(netID, { status: Status.DISCONNECTED }); this.setNetworkState(netID, { status: NetworkStatus.DISCONNECTED });
} }
reconnect(netID) { reconnect(netID) {
@ -538,8 +541,6 @@ export default class App extends Component {
var client = this.clients.get(netID); var client = this.clients.get(netID);
switch (msg.command) { switch (msg.command) {
case irc.RPL_WELCOME: case irc.RPL_WELCOME:
this.setNetworkState(netID, { status: Status.REGISTERED });
if (this.state.connectParams.autojoin.length > 0) { if (this.state.connectParams.autojoin.length > 0) {
client.send({ client.send({
command: "JOIN", command: "JOIN",
@ -1009,10 +1010,10 @@ export default class App extends Component {
activeNetwork = this.state.networks.get(activeBuffer.network); activeNetwork = this.state.networks.get(activeBuffer.network);
} }
if (!activeNetwork || (activeNetwork.status != Status.REGISTERED && !activeBuffer)) { if (!activeNetwork || (activeNetwork.status !== NetworkStatus.REGISTERED && !activeBuffer)) {
return html` return html`
<section id="connect"> <section id="connect">
<${Connect} error=${this.state.error} params=${this.state.connectParams} disabled=${this.state.status != Status.DISCONNECTED} onSubmit=${this.handleConnectSubmit}/> <${Connect} error=${this.state.error} params=${this.state.connectParams} disabled=${activeNetwork} onSubmit=${this.handleConnectSubmit}/>
</section> </section>
`; `;
} }

View file

@ -1,7 +1,7 @@
import { html, Component } from "/lib/index.js"; import { html, Component } from "/lib/index.js";
import linkify from "/lib/linkify.js"; import linkify from "/lib/linkify.js";
import { strip as stripANSI } from "/lib/ansi.js"; import { strip as stripANSI } from "/lib/ansi.js";
import { BufferType, Status } from "/state.js"; import { BufferType, NetworkStatus } from "/state.js";
const UserStatus = { const UserStatus = {
HERE: "here", HERE: "here",
@ -28,13 +28,16 @@ export default function BufferHeader(props) {
var description = null; var description = null;
if (props.buffer.serverInfo) { if (props.buffer.serverInfo) {
switch (props.network.status) { switch (props.network.status) {
case Status.DISCONNECTED: case NetworkStatus.DISCONNECTED:
description = "Disconnected"; description = "Disconnected";
break; break;
case Status.CONNECTING: case NetworkStatus.CONNECTING:
description = "Connecting..."; description = "Connecting...";
break; break;
case Status.REGISTERED: case NetworkStatus.REGISTERING:
description = "Logging in...";
break;
case NetworkStatus.REGISTERED:
var serverInfo = props.buffer.serverInfo; var serverInfo = props.buffer.serverInfo;
description = `Connected to ${serverInfo.name}`; description = `Connected to ${serverInfo.name}`;
break; break;

View file

@ -13,6 +13,14 @@ const permanentCaps = [
]; ];
export default class Client extends EventTarget { export default class Client extends EventTarget {
static Status = {
DISCONNECTED: "disconnected",
CONNECTING: "connecting",
REGISTERING: "registering",
REGISTERED: "registered",
};
status = Client.Status.DISCONNECTED;
ws = null; ws = null;
nick = null; nick = null;
params = { params = {
@ -23,7 +31,6 @@ export default class Client extends EventTarget {
pass: null, pass: null,
saslPlain: null, saslPlain: null,
}; };
registered = false;
availableCaps = {}; availableCaps = {};
enabledCaps = {}; enabledCaps = {};
batches = new Map(); batches = new Map();
@ -33,12 +40,19 @@ export default class Client extends EventTarget {
this.params = Object.assign(this.params, params); this.params = Object.assign(this.params, params);
this.reconnect();
}
reconnect() {
this.disconnect();
this.setStatus(Client.Status.CONNECTING);
try { try {
this.ws = new WebSocket(params.url); this.ws = new WebSocket(this.params.url);
} catch (err) { } catch (err) {
setTimeout(() => { setTimeout(() => {
this.dispatchEvent(new CustomEvent("error", { detail: "Failed to create connection: " + err })); this.dispatchEvent(new CustomEvent("error", { detail: "Failed to create connection: " + err }));
this.dispatchEvent(new CustomEvent("close")); this.setStatus(Client.Status.DISCONNECTED);
}, 0); }, 0);
return; return;
} }
@ -47,7 +61,8 @@ export default class Client extends EventTarget {
this.ws.addEventListener("close", () => { this.ws.addEventListener("close", () => {
console.log("Connection closed"); console.log("Connection closed");
this.dispatchEvent(new CustomEvent("close")); this.ws = null;
this.setStatus(Client.Status.DISCONNECTED);
}); });
this.ws.addEventListener("error", () => { this.ws.addEventListener("error", () => {
@ -55,8 +70,23 @@ export default class Client extends EventTarget {
}); });
} }
disconnect() {
if (this.ws) {
this.ws.close(1000);
}
}
setStatus(status) {
if (this.status === status) {
return;
}
this.status = status;
this.dispatchEvent(new CustomEvent("status"));
}
handleOpen() { handleOpen() {
console.log("Connection opened"); console.log("Connection opened");
this.setStatus(Client.Status.REGISTERING);
this.nick = this.params.nick; this.nick = this.params.nick;
@ -88,12 +118,12 @@ export default class Client extends EventTarget {
case irc.RPL_WELCOME: case irc.RPL_WELCOME:
if (this.params.saslPlain && this.availableCaps["sasl"] === undefined) { if (this.params.saslPlain && this.availableCaps["sasl"] === undefined) {
console.error("Server doesn't support SASL PLAIN"); console.error("Server doesn't support SASL PLAIN");
this.close(); this.disconnect();
return; return;
} }
console.log("Registration complete"); console.log("Registration complete");
this.registered = true; this.setStatus(Client.Status.REGISTERED);
break; break;
case "CAP": case "CAP":
this.handleCap(msg); this.handleCap(msg);
@ -109,7 +139,7 @@ export default class Client extends EventTarget {
break; break;
case irc.RPL_SASLSUCCESS: case irc.RPL_SASLSUCCESS:
console.log("SASL authentication success"); console.log("SASL authentication success");
if (!this.registered) { if (this.status != Client.Status.REGISTERED) {
this.send({ command: "CAP", params: ["END"] }); this.send({ command: "CAP", params: ["END"] });
} }
break; break;
@ -119,7 +149,7 @@ export default class Client extends EventTarget {
case irc.ERR_SASLABORTED: case irc.ERR_SASLABORTED:
case irc.ERR_SASLALREADY: case irc.ERR_SASLALREADY:
this.dispatchEvent(new CustomEvent("error", { detail: "SASL error (" + msg.command + "): " + msg.params[1] })); this.dispatchEvent(new CustomEvent("error", { detail: "SASL error (" + msg.command + "): " + msg.params[1] }));
this.close(); this.disconnect();
break; break;
case "PING": case "PING":
this.send({ command: "PONG", params: [msg.params[0]] }); this.send({ command: "PONG", params: [msg.params[0]] });
@ -148,7 +178,7 @@ export default class Client extends EventTarget {
break; break;
case "ERROR": case "ERROR":
this.dispatchEvent(new CustomEvent("error", { detail: "Fatal IRC error: " + msg.params[0] })); this.dispatchEvent(new CustomEvent("error", { detail: "Fatal IRC error: " + msg.params[0] }));
this.close(); this.disconnect();
break; break;
case irc.ERR_PASSWDMISMATCH: case irc.ERR_PASSWDMISMATCH:
case irc.ERR_ERRONEUSNICKNAME: case irc.ERR_ERRONEUSNICKNAME:
@ -158,8 +188,8 @@ export default class Client extends EventTarget {
case irc.ERR_NOPERMFORHOST: case irc.ERR_NOPERMFORHOST:
case irc.ERR_YOUREBANNEDCREEP: case irc.ERR_YOUREBANNEDCREEP:
this.dispatchEvent(new CustomEvent("error", { detail: "Error (" + msg.command + "): " + msg.params[msg.params.length - 1] })); this.dispatchEvent(new CustomEvent("error", { detail: "Error (" + msg.command + "): " + msg.params[msg.params.length - 1] }));
if (!this.registered) { if (this.status != Client.Status.REGISTERED) {
this.close(); this.disconnect();
} }
break; break;
} }
@ -229,7 +259,7 @@ export default class Client extends EventTarget {
this.requestCaps(reqCaps); this.requestCaps(reqCaps);
if (!this.registered && capEnd) { if (this.status != Client.Status.REGISTERED && capEnd) {
this.send({ command: "CAP", params: ["END"] }); this.send({ command: "CAP", params: ["END"] });
} }
} }
@ -261,7 +291,7 @@ export default class Client extends EventTarget {
break; break;
case "NAK": case "NAK":
console.log("Server nak'ed caps:", args[0]); console.log("Server nak'ed caps:", args[0]);
if (!this.registered) { if (this.status != Client.Status.REGISTERED) {
this.send({ command: "CAP", params: ["END"] }); this.send({ command: "CAP", params: ["END"] });
} }
break; break;
@ -287,11 +317,6 @@ export default class Client extends EventTarget {
console.log("Sent:", msg); console.log("Sent:", msg);
} }
close() {
this.ws.close(1000);
this.registered = false;
}
/* Execute a command that expects a response. `done` is called with message /* Execute a command that expects a response. `done` is called with message
* events until it returns a truthy value. */ * events until it returns a truthy value. */
roundtrip(msg, done) { roundtrip(msg, done) {

View file

@ -1,3 +1,5 @@
import Client from "/lib/client.js";
export const SERVER_BUFFER = "*"; export const SERVER_BUFFER = "*";
export const BufferType = { export const BufferType = {
@ -6,11 +8,7 @@ export const BufferType = {
NICK: "nick", NICK: "nick",
}; };
export const Status = { export const NetworkStatus = Client.Status;
DISCONNECTED: "disconnected",
CONNECTING: "connecting",
REGISTERED: "registered",
};
export const Unread = { export const Unread = {
NONE: "", NONE: "",