Refactor ISUPPORT handling
Add a helper class to parse ISUPPORT tokens. Instead of having manual ISUPPORT handling all over the place, use pre-processed values.
This commit is contained in:
+20
-44
@@ -78,7 +78,7 @@ export default class Client extends EventTarget {
|
||||
supportsCap = false;
|
||||
availableCaps = {};
|
||||
enabledCaps = {};
|
||||
isupport = new Map();
|
||||
isupport = new irc.Isupport();
|
||||
|
||||
ws = null;
|
||||
params = {
|
||||
@@ -159,7 +159,7 @@ export default class Client extends EventTarget {
|
||||
Object.keys(this.pendingCmds).forEach((k) => {
|
||||
this.pendingCmds[k] = Promise.resolve(null);
|
||||
});
|
||||
this.isupport = new Map();
|
||||
this.isupport = new irc.Isupport();
|
||||
this.monitored = new irc.CaseMapMap(null, irc.CaseMapping.RFC1459);
|
||||
|
||||
if (this.autoReconnect) {
|
||||
@@ -282,22 +282,24 @@ export default class Client extends EventTarget {
|
||||
this.setStatus(Client.Status.REGISTERED);
|
||||
break;
|
||||
case irc.RPL_ISUPPORT:
|
||||
let prevMaxMonitorTargets = this.isupport.monitor();
|
||||
|
||||
let tokens = msg.params.slice(1, -1);
|
||||
let changed = irc.parseISUPPORT(tokens, this.isupport);
|
||||
if (changed.indexOf("CASEMAPPING") >= 0) {
|
||||
this.setCaseMapping(this.isupport.get("CASEMAPPING"));
|
||||
}
|
||||
if (changed.indexOf("MONITOR") >= 0 && this.isupport.has("MONITOR") && this.monitored.size > 0) {
|
||||
let targets = Array.from(this.monitored.keys()).slice(0, this.maxMonitorTargets());
|
||||
this.isupport.parse(tokens);
|
||||
this.updateCaseMapping();
|
||||
|
||||
let maxMonitorTargets = this.isupport.monitor();
|
||||
if (prevMaxMonitorTargets === 0 && this.monitored.size > 0 && maxMonitorTargets > 0) {
|
||||
let targets = Array.from(this.monitored.keys()).slice(0, maxMonitorTargets);
|
||||
this.send({ command: "MONITOR", params: ["+", targets.join(",")] });
|
||||
}
|
||||
break;
|
||||
case irc.RPL_ENDOFMOTD:
|
||||
case irc.ERR_NOMOTD:
|
||||
// These messages are used to indicate the end of the ISUPPORT list
|
||||
if (!this.isupport.has("CASEMAPPING")) {
|
||||
if (!this.isupport.raw.has("CASEMAPPING")) {
|
||||
// Server didn't send any CASEMAPPING token, assume RFC 1459
|
||||
this.setCaseMapping("rfc1459");
|
||||
this.updateCaseMapping();
|
||||
}
|
||||
break;
|
||||
case "CAP":
|
||||
@@ -451,7 +453,7 @@ export default class Client extends EventTarget {
|
||||
let params = [mask];
|
||||
|
||||
let fields = "", token = "";
|
||||
if (options && this.isupport.has("WHOX")) {
|
||||
if (options && this.isupport.whox()) {
|
||||
let match = ""; // Matches exact channel or nick
|
||||
|
||||
fields = "t"; // Always include token in reply
|
||||
@@ -685,13 +687,8 @@ export default class Client extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
setCaseMapping(name) {
|
||||
this.cm = irc.CaseMapping.byName(name);
|
||||
if (!this.cm) {
|
||||
console.error("Unsupported case-mapping '" + name + "', falling back to RFC 1459");
|
||||
this.cm = irc.CaseMapping.RFC1459;
|
||||
}
|
||||
|
||||
updateCaseMapping() {
|
||||
this.cm = this.isupport.caseMapping();
|
||||
this.pendingLists = new irc.CaseMapMap(this.pendingLists, this.cm);
|
||||
this.monitored = new irc.CaseMapMap(this.monitored, this.cm);
|
||||
}
|
||||
@@ -705,7 +702,7 @@ export default class Client extends EventTarget {
|
||||
}
|
||||
|
||||
isChannel(name) {
|
||||
let chanTypes = this.isupport.get("CHANTYPES") || irc.STD_CHANTYPES;
|
||||
let chanTypes = this.isupport.chanTypes();
|
||||
return chanTypes.indexOf(name[0]) >= 0;
|
||||
}
|
||||
|
||||
@@ -870,19 +867,9 @@ export default class Client extends EventTarget {
|
||||
return promise;
|
||||
}
|
||||
|
||||
chatHistoryPageSize() {
|
||||
if (this.isupport.has("CHATHISTORY")) {
|
||||
let pageSize = parseInt(this.isupport.get("CHATHISTORY"), 10);
|
||||
if (pageSize > 0) {
|
||||
return pageSize;
|
||||
}
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
/* Fetch one page of history before the given date. */
|
||||
fetchHistoryBefore(target, before, limit) {
|
||||
let max = Math.min(limit, this.chatHistoryPageSize());
|
||||
let max = Math.min(limit, this.isupport.chatHistory());
|
||||
let params = ["BEFORE", target, "timestamp=" + before, max];
|
||||
return this.roundtripChatHistory(params).then((messages) => {
|
||||
return { more: messages.length >= max };
|
||||
@@ -891,7 +878,7 @@ export default class Client extends EventTarget {
|
||||
|
||||
/* Fetch history in ascending order. */
|
||||
fetchHistoryBetween(target, after, before, limit) {
|
||||
let max = Math.min(limit, this.chatHistoryPageSize());
|
||||
let max = Math.min(limit, this.isupport.chatHistory());
|
||||
let params = ["AFTER", target, "timestamp=" + after.time, max];
|
||||
return this.roundtripChatHistory(params).then((messages) => {
|
||||
limit -= messages.length;
|
||||
@@ -943,17 +930,6 @@ export default class Client extends EventTarget {
|
||||
});
|
||||
}
|
||||
|
||||
maxMonitorTargets() {
|
||||
if (!this.isupport.has("MONITOR")) {
|
||||
return 0;
|
||||
}
|
||||
let v = this.isupport.get("MONITOR");
|
||||
if (v === "") {
|
||||
return Infinity;
|
||||
}
|
||||
return parseInt(v, 10);
|
||||
}
|
||||
|
||||
monitor(target) {
|
||||
if (this.monitored.has(target)) {
|
||||
return;
|
||||
@@ -962,7 +938,7 @@ export default class Client extends EventTarget {
|
||||
this.monitored.set(target, true);
|
||||
|
||||
// TODO: add poll-based fallback when MONITOR is not supported
|
||||
if (this.monitored.size + 1 > this.maxMonitorTargets()) {
|
||||
if (this.monitored.size + 1 > this.isupport.monitor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -976,7 +952,7 @@ export default class Client extends EventTarget {
|
||||
|
||||
this.monitored.delete(target);
|
||||
|
||||
if (!this.isupport.has("MONITOR")) {
|
||||
if (this.isupport.monitor() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+80
-20
@@ -382,28 +382,88 @@ function unescapeISUPPORTValue(s) {
|
||||
});
|
||||
}
|
||||
|
||||
export function parseISUPPORT(tokens, params) {
|
||||
let changed = [];
|
||||
tokens.forEach((tok) => {
|
||||
if (tok.startsWith("-")) {
|
||||
let k = tok.slice(1);
|
||||
params.delete(k.toUpperCase());
|
||||
return;
|
||||
export class Isupport {
|
||||
raw = new Map();
|
||||
|
||||
parse(tokens) {
|
||||
tokens.forEach((tok) => {
|
||||
if (tok.startsWith("-")) {
|
||||
let k = tok.slice(1);
|
||||
this.raw.delete(k.toUpperCase());
|
||||
return;
|
||||
}
|
||||
|
||||
let i = tok.indexOf("=");
|
||||
let k = tok, v = "";
|
||||
if (i >= 0) {
|
||||
k = tok.slice(0, i);
|
||||
v = unescapeISUPPORTValue(tok.slice(i + 1));
|
||||
}
|
||||
|
||||
k = k.toUpperCase();
|
||||
|
||||
this.raw.set(k, v);
|
||||
});
|
||||
}
|
||||
|
||||
caseMapping() {
|
||||
let name = this.raw.get("CASEMAPPING");
|
||||
if (!name) {
|
||||
return CaseMapping.RFC1459;
|
||||
}
|
||||
|
||||
let i = tok.indexOf("=");
|
||||
let k = tok, v = "";
|
||||
if (i >= 0) {
|
||||
k = tok.slice(0, i);
|
||||
v = unescapeISUPPORTValue(tok.slice(i + 1));
|
||||
let cm = CaseMapping.byName(name);
|
||||
if (!cm) {
|
||||
console.error("Unsupported case-mapping '" + name + "', falling back to RFC 1459");
|
||||
return CaseMapping.RFC1459;
|
||||
}
|
||||
return cm;
|
||||
}
|
||||
|
||||
k = k.toUpperCase();
|
||||
monitor() {
|
||||
if (!this.raw.has("MONITOR")) {
|
||||
return 0;
|
||||
}
|
||||
let v = this.raw.get("MONITOR");
|
||||
if (v === "") {
|
||||
return Infinity;
|
||||
}
|
||||
return parseInt(v, 10);
|
||||
}
|
||||
|
||||
params.set(k, v);
|
||||
changed.push(k);
|
||||
});
|
||||
return changed;
|
||||
whox() {
|
||||
return this.raw.has("WHOX");
|
||||
}
|
||||
|
||||
prefix() {
|
||||
return this.raw.get("PREFIX") || "";
|
||||
}
|
||||
|
||||
chanTypes() {
|
||||
return this.raw.get("CHANTYPES") || STD_CHANTYPES;
|
||||
}
|
||||
|
||||
statusMsg() {
|
||||
return this.raw.get("STATUSMSG");
|
||||
}
|
||||
|
||||
network() {
|
||||
return this.raw.get("NETWORK");
|
||||
}
|
||||
|
||||
chatHistory() {
|
||||
if (!this.raw.has("CHATHISTORY")) {
|
||||
return 0;
|
||||
}
|
||||
let n = parseInt(this.raw.get("CHATHISTORY"), 10);
|
||||
if (n <= 0) {
|
||||
return Infinity;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bouncerNetID() {
|
||||
return this.raw.get("BOUNCER_NETID");
|
||||
}
|
||||
}
|
||||
|
||||
export const CaseMapping = {
|
||||
@@ -612,8 +672,8 @@ export function getMessageLabel(msg) {
|
||||
}
|
||||
|
||||
export function forEachChannelModeUpdate(msg, isupport, callback) {
|
||||
let chanmodes = isupport.get("CHANMODES") || STD_CHANMODES;
|
||||
let prefix = isupport.get("PREFIX") || "";
|
||||
let chanmodes = isupport.chanModes();
|
||||
let prefix = isupport.prefix();
|
||||
|
||||
let typeByMode = new Map();
|
||||
let [a, b, c, d] = chanmodes.split(",");
|
||||
|
||||
Reference in New Issue
Block a user