move flag and rename to protocol layer

This means that 'turn' is now the only thing not sent by the protocol layer.
This commit is contained in:
modeco80
2024-08-22 03:50:04 -04:00
parent c9edb174f1
commit 5dc53116b2
3 changed files with 111 additions and 34 deletions

View File

@@ -18,7 +18,7 @@ import { ReaderModel } from '@maxmind/geoip2-node';
import { Size, Rect } from './Utilities.js'; import { Size, Rect } from './Utilities.js';
import pino from 'pino'; import pino from 'pino';
import { BanManager } from './BanManager.js'; import { BanManager } from './BanManager.js';
import { IProtocolHandlers, ListEntry, ProtocolAddUser, TheProtocolManager } from './Protocol.js'; import { IProtocolHandlers, ListEntry, ProtocolAddUser, ProtocolFlag, ProtocolRenameStatus, ProtocolUpgradeCapability, TheProtocolManager } from './Protocol.js';
// Instead of strange hacks we can just use nodejs provided // Instead of strange hacks we can just use nodejs provided
// import.meta properties, which have existed since LTS if not before // import.meta properties, which have existed since LTS if not before
@@ -193,9 +193,11 @@ export default class CollabVMServer implements IProtocolHandlers {
user.protocol.sendAuth(this.Config.auth.apiEndpoint); user.protocol.sendAuth(this.Config.auth.apiEndpoint);
} }
// convert these to proto user.protocol.sendAddUser(this.getAddUser());
user.protocol.sendAddUser(this.getAdduserMsg()); if (this.Config.geoip.enabled) {
if (this.Config.geoip.enabled) user.sendMsg(this.getFlagMsg()); let flags = this.getFlags();
user.protocol.sendFlag(flags);
}
} }
private connectionClosed(user: User) { private connectionClosed(user: User) {
@@ -246,7 +248,7 @@ export default class CollabVMServer implements IProtocolHandlers {
if (!this.Config.auth.enabled) return; if (!this.Config.auth.enabled) return;
if (!user.connectedToNode) { if (!user.connectedToNode) {
user.sendMsg(cvm.guacEncode('login', '0', 'You must connect to the VM before logging in.')); user.protocol.sendLoginResponse(false, 'You must connect to the VM before logging in.');
return; return;
} }
@@ -268,7 +270,7 @@ export default class CollabVMServer implements IProtocolHandlers {
if (user.countryCode !== null && user.noFlag) { if (user.countryCode !== null && user.noFlag) {
// privacy // privacy
for (let cl of this.clients.filter((c) => c !== user)) { for (let cl of this.clients.filter((c) => c !== user)) {
cl.sendMsg(cvm.guacEncode('remuser', '1', user.username!)); cl.protocol.sendRemUser([user.username!]);
} }
this.renameUser(user, res.username, false); this.renameUser(user, res.username, false);
} else this.renameUser(user, res.username, true); } else this.renameUser(user, res.username, true);
@@ -279,7 +281,14 @@ export default class CollabVMServer implements IProtocolHandlers {
} else if (user.rank === Rank.Moderator) { } else if (user.rank === Rank.Moderator) {
user.protocol.sendAdminLoginResponse(true, this.ModPerms); user.protocol.sendAdminLoginResponse(true, this.ModPerms);
} }
this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('adduser', '1', user.username!, user.rank.toString()))); this.clients.forEach((c) =>
c.protocol.sendAddUser([
{
username: user.username!,
rank: user.rank
}
])
);
} else { } else {
user.protocol.sendLoginResponse(false, res.error!); user.protocol.sendLoginResponse(false, res.error!);
if (res.error === 'You are banned') { if (res.error === 'You are banned') {
@@ -302,10 +311,13 @@ export default class CollabVMServer implements IProtocolHandlers {
onCapabilityUpgrade(user: User, capability: String[]): boolean { onCapabilityUpgrade(user: User, capability: String[]): boolean {
if (user.connectedToNode) return false; if (user.connectedToNode) return false;
let enabledCaps = [];
for (let cap of capability) { for (let cap of capability) {
switch (cap) { switch (cap) {
// binary 1.0 (msgpack rects) // binary 1.0 (msgpack rects)
case 'bin': case ProtocolUpgradeCapability.BinRects:
enabledCaps.push(cap as ProtocolUpgradeCapability);
user.Capabilities.bin = true; user.Capabilities.bin = true;
user.protocol.dispose(); user.protocol.dispose();
user.protocol = TheProtocolManager.createProtocol('binary1', user); user.protocol = TheProtocolManager.createProtocol('binary1', user);
@@ -315,6 +327,8 @@ export default class CollabVMServer implements IProtocolHandlers {
break; break;
} }
} }
user.protocol.sendCapabilities(enabledCaps);
return true; return true;
} }
@@ -706,47 +720,64 @@ export default class CollabVMServer implements IProtocolHandlers {
renameUser(client: User, newName?: string, announce: boolean = true) { renameUser(client: User, newName?: string, announce: boolean = true) {
// This shouldn't need a ternary but it does for some reason // This shouldn't need a ternary but it does for some reason
var hadName: boolean = client.username ? true : false; let hadName = client.username ? true : false;
var oldname: any; let oldname: any;
if (hadName) oldname = client.username; if (hadName) oldname = client.username;
var status = '0';
if (!newName) { if (!newName) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
} else { } else {
newName = newName.trim(); newName = newName.trim();
if (hadName && newName === oldname) { if (hadName && newName === oldname) {
client.sendMsg(cvm.guacEncode('rename', '0', '0', client.username!, client.rank.toString())); client.protocol.sendSelfRename(ProtocolRenameStatus.Ok, client.username!, client.rank);
return; return;
} }
let status = ProtocolRenameStatus.Ok;
if (this.getUsernameList().indexOf(newName) !== -1) { if (this.getUsernameList().indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
if (client.connectedToNode) { if (client.connectedToNode) {
status = '1'; status = ProtocolRenameStatus.UsernameTaken;
} }
} else if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName) || newName.length > 20 || newName.length < 3) { } else if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName) || newName.length > 20 || newName.length < 3) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
status = '2'; status = ProtocolRenameStatus.UsernameInvalid;
} else if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) { } else if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
status = '3'; status = ProtocolRenameStatus.UsernameNotAllowed;
} else client.username = newName; } else client.username = newName;
client.protocol.sendSelfRename(status, client.username!, client.rank);
} }
client.sendMsg(cvm.guacEncode('rename', '0', status, client.username!, client.rank.toString()));
if (hadName) { if (hadName) {
this.logger.info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`); this.logger.info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
if (announce) this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('rename', '1', oldname, client.username!, client.rank.toString()))); if (announce) this.clients.forEach((c) => c.protocol.sendRename(oldname, client.username!, client.rank));
} else { } else {
this.logger.info(`Rename ${client.IP.address} to ${client.username}`); this.logger.info(`Rename ${client.IP.address} to ${client.username}`);
if (announce) if (announce)
this.clients.forEach((c) => { this.clients.forEach((c) => {
c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString())); c.protocol.sendAddUser([
if (client.countryCode !== null) c.sendMsg(cvm.guacEncode('flag', client.username!, client.countryCode)); {
username: client.username!,
rank: client.rank
}
]);
if (client.countryCode !== null) {
c.protocol.sendFlag([
{
username: client.username!,
countryCode: client.countryCode
}
]);
}
}); });
} }
} }
getAdduserMsg(): ProtocolAddUser[] { private getAddUser(): ProtocolAddUser[] {
return this.clients return this.clients
.filter((c) => c.username) .filter((c) => c.username)
.map((c) => { .map((c) => {
@@ -757,12 +788,15 @@ export default class CollabVMServer implements IProtocolHandlers {
}); });
} }
getFlagMsg(): string { private getFlags(): ProtocolFlag[] {
var arr = ['flag']; let arr = [];
for (let c of this.clients.filter((cl) => cl.countryCode !== null && cl.username && (!cl.noFlag || cl.rank === Rank.Unregistered))) { for (let c of this.clients.filter((cl) => cl.countryCode !== null && cl.username && (!cl.noFlag || cl.rank === Rank.Unregistered))) {
arr.push(c.username!, c.countryCode!); arr.push({
username: c.username!,
countryCode: c.countryCode!
});
} }
return cvm.guacEncode(...arr); return arr;
} }
private sendTurnUpdate(client?: User) { private sendTurnUpdate(client?: User) {

View File

@@ -1,6 +1,6 @@
import pino from 'pino'; import pino from 'pino';
import { IProtocol, IProtocolHandlers, ListEntry, ProtocolAddUser, ProtocolBase, ProtocolChatHistory, ScreenRect } from './Protocol.js'; import { IProtocol, IProtocolHandlers, ListEntry, ProtocolAddUser, ProtocolBase, ProtocolChatHistory, ProtocolFlag, ProtocolRenameStatus, ProtocolUpgradeCapability, ScreenRect } from './Protocol.js';
import { User } from './User'; import { Rank, User } from './User';
import * as cvm from '@cvmts/cvm-rs'; import * as cvm from '@cvmts/cvm-rs';
@@ -209,6 +209,11 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
this.user?.sendMsg(cvm.guacEncode('sync', now.toString())); this.user?.sendMsg(cvm.guacEncode('sync', now.toString()));
} }
sendCapabilities(caps: ProtocolUpgradeCapability[]): void {
let arr = ['cap', ...caps];
this?.user?.sendMsg(cvm.guacEncode(...arr));
}
sendConnectFailResponse(): void { sendConnectFailResponse(): void {
this.user?.sendMsg(cvm.guacEncode('connect', '0')); this.user?.sendMsg(cvm.guacEncode('connect', '0'));
} }
@@ -253,8 +258,7 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
sendChatHistoryMessage(history: ProtocolChatHistory[]): void { sendChatHistoryMessage(history: ProtocolChatHistory[]): void {
let arr = ['chat']; let arr = ['chat'];
for (let a of history) { for (let a of history) {
arr.push(a.user); arr.push(a.user, a.msg);
arr.push(a.msg);
} }
this.user?.sendMsg(cvm.guacEncode(...arr)); this.user?.sendMsg(cvm.guacEncode(...arr));
@@ -280,6 +284,21 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
this.user?.sendMsg(cvm.guacEncode(...arr)); this.user?.sendMsg(cvm.guacEncode(...arr));
} }
sendFlag(flag: ProtocolFlag[]): void {
// Basically this does the same as the above manual for of things
// but in one line of code
let arr = ['flag', ...flag.flatMap((flag) => [flag.username, flag.countryCode])];
this.user?.sendMsg(cvm.guacEncode(...arr));
}
sendSelfRename(status: ProtocolRenameStatus, newUsername: string, rank: Rank): void {
this.user?.sendMsg(cvm.guacEncode('rename', '0', status.toString(), newUsername, rank.toString()));
}
sendRename(oldUsername: string, newUsername: string, rank: Rank): void {
this.user?.sendMsg(cvm.guacEncode('rename', '1', oldUsername, newUsername, rank.toString()));
}
sendListResponse(list: ListEntry[]): void { sendListResponse(list: ListEntry[]): void {
let arr = ['list']; let arr = ['list'];
for (let node of list) { for (let node of list) {

View File

@@ -1,8 +1,15 @@
import { Rank, User } from './User'; import { Rank, User } from './User';
// We should probably put this in the binproto repository or something // We should probably put this in the binproto repository or something
enum UpgradeCapability { export enum ProtocolUpgradeCapability {
Binary = 'bin' BinRects = 'bin'
}
export enum ProtocolRenameStatus {
Ok = 0,
UsernameTaken = 1,
UsernameInvalid = 2,
UsernameNotAllowed = 3
} }
export interface ScreenRect { export interface ScreenRect {
@@ -27,6 +34,11 @@ export interface ProtocolAddUser {
rank: Rank; rank: Rank;
} }
export interface ProtocolFlag {
username: string;
countryCode: string;
}
// Protocol handlers. This is implemented by a layer that wants to listen to CollabVM protocol messages. // Protocol handlers. This is implemented by a layer that wants to listen to CollabVM protocol messages.
export interface IProtocolHandlers { export interface IProtocolHandlers {
onNop(user: User): void; onNop(user: User): void;
@@ -74,7 +86,9 @@ export interface IProtocolHandlers {
onMouse(user: User, x: number, y: number, buttonMask: number): void; onMouse(user: User, x: number, y: number, buttonMask: number): void;
} }
// Abstracts away all of the CollabVM protocol details // Abstracts away all of the protocol details from the CollabVM server,
// allowing it to be protocol-independent (as long as the client and server
// are able to speak the same protocol.)
export interface IProtocol { export interface IProtocol {
// don't implement this yourself, extend from ProtocolBase // don't implement this yourself, extend from ProtocolBase
init(u: User): void; init(u: User): void;
@@ -85,9 +99,11 @@ export interface IProtocol {
// Protocol implementation stuff // Protocol implementation stuff
// Parses a single CollabVM protocol message and fires the given handler. // Parses a single message and fires the given handler with deserialized arguments.
// This function does not catch any thrown errors; it is the caller's responsibility // This function does not catch any thrown errors; it is the caller's responsibility
// to handle errors. It should, however, catch invalid parameters without failing. // to handle errors. It should, however, catch invalid parameters without failing.
//
// This function will perform conversion to text if it is required.
processMessage(buffer: Buffer): boolean; processMessage(buffer: Buffer): boolean;
// Senders // Senders
@@ -97,6 +113,8 @@ export interface IProtocol {
sendAuth(authServer: string): void; sendAuth(authServer: string): void;
sendCapabilities(caps: ProtocolUpgradeCapability[]): void;
sendConnectFailResponse(): void; sendConnectFailResponse(): void;
sendConnectOKResponse(votes: boolean): void; sendConnectOKResponse(votes: boolean): void;
@@ -111,7 +129,11 @@ export interface IProtocol {
sendAddUser(users: ProtocolAddUser[]): void; sendAddUser(users: ProtocolAddUser[]): void;
sendRemUser(users: string[]): void; sendRemUser(users: string[]): void;
sendFlag(flag: ProtocolFlag[]): void;
sendSelfRename(status: ProtocolRenameStatus, newUsername: string, rank: Rank): void;
sendRename(oldUsername: string, newUsername: string, rank: Rank): void;
sendListResponse(list: ListEntry[]): void; sendListResponse(list: ListEntry[]): void;
sendVoteStarted(): void; sendVoteStarted(): void;
@@ -125,7 +147,7 @@ export interface IProtocol {
sendScreenUpdate(rect: ScreenRect): void; sendScreenUpdate(rect: ScreenRect): void;
} }
// base mixin for all protocols to use // Base mixin for all concrete protocols to use. Inherit from this!
export class ProtocolBase { export class ProtocolBase {
protected handlers: IProtocolHandlers | null = null; protected handlers: IProtocolHandlers | null = null;
protected user: User | null = null; protected user: User | null = null;
@@ -144,7 +166,9 @@ export class ProtocolBase {
} }
} }
// Holds protocol factories. // The protocol manager. Holds protocol factories, and provides the ability
// to create a protocol by name. Avoids direct dependency on a given list of protocols,
// and allows (relatively simple) expansion.
export class ProtocolManager { export class ProtocolManager {
private protocols = new Map<String, () => IProtocol>(); private protocols = new Map<String, () => IProtocol>();