lib/irc: add CapRegistry

This commit is contained in:
Simon Ser
2021-12-10 15:34:51 +01:00
parent f6895fed32
commit 4cabae89ff
5 changed files with 90 additions and 59 deletions

View File

@@ -109,8 +109,7 @@ export default class Client extends EventTarget {
serverPrefix = { name: "*" };
nick = null;
supportsCap = false;
availableCaps = {};
enabledCaps = {};
caps = new irc.CapRegistry();
isupport = new irc.Isupport();
ws = null;
@@ -187,8 +186,7 @@ export default class Client extends EventTarget {
this.setStatus(Client.Status.DISCONNECTED);
this.nick = null;
this.serverPrefix = null;
this.availableCaps = {};
this.enabledCaps = {};
this.caps = new irc.CapRegistry();
this.batches = new Map();
Object.keys(this.pendingCmds).forEach((k) => {
this.pendingCmds[k] = Promise.resolve(null);
@@ -602,21 +600,8 @@ export default class Client extends EventTarget {
});
}
addAvailableCaps(s) {
let l = s.split(" ");
l.forEach((s) => {
let i = s.indexOf("=");
let k = s, v = "";
if (i >= 0) {
k = s.slice(0, i);
v = s.slice(i + 1);
}
this.availableCaps[k.toLowerCase()] = v;
});
}
supportsSASL(mech) {
let saslCap = this.availableCaps["sasl"];
let saslCap = this.caps.available.get("sasl");
if (saslCap === undefined) {
return false;
}
@@ -624,7 +609,7 @@ export default class Client extends EventTarget {
}
checkAccountRegistrationCap(k) {
let v = this.availableCaps["draft/account-registration"];
let v = this.caps.available.get("draft/account-registration");
if (v === undefined) {
return false;
}
@@ -637,35 +622,30 @@ export default class Client extends EventTarget {
wantCaps.push("soju.im/bouncer-networks-notify");
}
let reqCaps = [];
wantCaps.forEach((cap) => {
if (this.availableCaps[cap] !== undefined && !this.enabledCaps[cap]) {
reqCaps.push(cap);
}
});
if (reqCaps.length > 0) {
this.send({ command: "CAP", params: ["REQ", reqCaps.join(" ")] });
let msg = this.caps.requestAvailable(wantCaps);
if (msg) {
this.send(msg);
}
}
handleCap(msg) {
this.caps.parse(msg);
let subCmd = msg.params[1];
let args = msg.params.slice(2);
switch (subCmd) {
case "LS":
this.supportsCap = true;
this.addAvailableCaps(args[args.length - 1]);
if (args[0] == "*") {
break;
}
console.log("Available server caps:", this.availableCaps);
console.log("Available server caps:", this.caps.available);
this.requestCaps();
if (this.status !== Client.Status.REGISTERED) {
if (this.availableCaps["sasl"] !== undefined) {
if (this.caps.available.has("sasl")) {
let promise;
if (this.params.saslPlain) {
promise = this.authenticate("PLAIN", this.params.saslPlain);
@@ -678,7 +658,7 @@ export default class Client extends EventTarget {
});
}
if (this.availableCaps["soju.im/bouncer-networks"] !== undefined && this.params.bouncerNetwork) {
if (this.caps.available.has("soju.im/bouncer-networks") && this.params.bouncerNetwork) {
this.send({ command: "BOUNCER", params: ["BIND", this.params.bouncerNetwork] });
}
@@ -686,28 +666,18 @@ export default class Client extends EventTarget {
}
break;
case "NEW":
this.addAvailableCaps(args[0]);
console.log("Server added available caps:", args[0]);
this.requestCaps();
break;
case "DEL":
args[0].split(" ").forEach((cap) => {
cap = cap.toLowerCase();
delete this.availableCaps[cap];
delete this.enabledCaps[cap];
});
console.log("Server removed available caps:", args[0]);
break;
case "ACK":
console.log("Server ack'ed caps:", args[0]);
args[0].split(" ").forEach((cap) => {
cap = cap.toLowerCase();
this.enabledCaps[cap] = true;
});
break;
case "NAK":
console.log("Server nak'ed caps:", args[0]);
if (this.status != Client.Status.REGISTERED) {
if (this.status !== Client.Status.REGISTERED) {
this.send({ command: "CAP", params: ["END"] });
}
break;
@@ -764,7 +734,7 @@ export default class Client extends EventTarget {
let cmd = msg.command;
let label;
if (this.enabledCaps["labeled-response"]) {
if (this.caps.enabled.has("labeled-response")) {
lastLabel++;
label = String(lastLabel);
msg.tags = { ...msg.tags, label };
@@ -950,10 +920,6 @@ export default class Client extends EventTarget {
}
listBouncerNetworks() {
if (!this.enabledCaps["soju.im/bouncer-networks"]) {
return Promise.reject(new Error("Server doesn't support the BOUNCER extension"));
}
let req = { command: "BOUNCER", params: ["LISTNETWORKS"] };
return this.fetchBatch(req, "soju.im/bouncer-networks").then((batch) => {
let networks = new Map();

View File

@@ -794,3 +794,67 @@ export function parseURL(str) {
return { host, enttype, entity };
}
export class CapRegistry {
available = new Map();
enabled = new Set();
addAvailable(s) {
let l = s.split(" ");
l.forEach((s) => {
let i = s.indexOf("=");
let k = s, v = "";
if (i >= 0) {
k = s.slice(0, i);
v = s.slice(i + 1);
}
this.available.set(k.toLowerCase(), v);
});
}
parse(msg) {
if (msg.command !== "CAP") {
return;
}
let subCmd = msg.params[1];
let args = msg.params.slice(2);
switch (subCmd) {
case "LS":
this.addAvailable(args[args.length - 1]);
break;
case "NEW":
this.addAvailable(args[0]);
break;
case "DEL":
args[0].split(" ").forEach((cap) => {
cap = cap.toLowerCase();
this.available.delete(cap);
this.enabled.delete(cap);
});
break;
case "ACK":
// TODO: handle `ACK -cap` to
args[0].split(" ").forEach((cap) => {
cap = cap.toLowerCase();
if (cap.startsWith("-")) {
this.enabled.delete(cap.slice(1));
} else {
this.enabled.add(cap);
}
});
break;
}
}
requestAvailable(l) {
l = l.filter((cap) => {
return this.available.has(cap) && !this.enabled.has(cap);
});
if (l.length === 0) {
return null;
}
return { command: "CAP", params: ["REQ", l.join(" ")] };
}
}