everything
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
config.toml
|
||||||
48
config.example.toml
Normal file
48
config.example.toml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
[http]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 6004
|
||||||
|
# Whether the server is behind a reverse proxy, like NGINX
|
||||||
|
proxying = true
|
||||||
|
# IPs allowed to access the server in proxy mode.
|
||||||
|
# 99% of the time this will only be 127.0.0.1
|
||||||
|
proxyAllowedIps = ["127.0.0.1"]
|
||||||
|
|
||||||
|
[vm]
|
||||||
|
qemuArgs = "qemu-system-x86_64"
|
||||||
|
vncPort = 5900
|
||||||
|
snapshots = true
|
||||||
|
qmpSockDir = "/tmp/"
|
||||||
|
|
||||||
|
[collabvm]
|
||||||
|
node = "acoolvm"
|
||||||
|
displayname = "A <b>Really</b> Cool CollabVM Instance"
|
||||||
|
motd = "welcome!"
|
||||||
|
# Command used to ban an IP.
|
||||||
|
# Use $IP to specify an ip and (optionally) use $NAME to specify a username
|
||||||
|
bancmd = "iptables -A INPUT -s $IP -j REJECT"
|
||||||
|
moderatorEnabled = true
|
||||||
|
usernameblacklist = ["jjjj"]
|
||||||
|
maxChatLength = 100
|
||||||
|
# Temporarily mute a user if they send more than x messages in n seconds
|
||||||
|
automute = {enabled = true, seconds = 5, messages = 5}
|
||||||
|
# How long a temporary mute lasts, in seconds
|
||||||
|
tempMuteTime = 30
|
||||||
|
# How long a turn lasts, in seconds
|
||||||
|
turnTime = 20
|
||||||
|
# SHA256 sum of the admin and mod passwords. This can be generated with the following command:
|
||||||
|
# printf "<password>" | sha256sum -
|
||||||
|
# Example hash is hunter2 and hunter3
|
||||||
|
adminpass = "f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7"
|
||||||
|
modpass = "fb8c2e2b85ca81eb4350199faddd983cb26af3064614e737ea9f479621cfa57a"
|
||||||
|
[collabvm.moderatorPermissions]
|
||||||
|
# What a moderator can and can't do
|
||||||
|
restore = true
|
||||||
|
reboot = true
|
||||||
|
ban = true
|
||||||
|
forcevote = true
|
||||||
|
mute = true
|
||||||
|
kick = true
|
||||||
|
bypassturn = true
|
||||||
|
rename = true
|
||||||
|
grabip = true
|
||||||
|
xss = true
|
||||||
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "collabvm1.ts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "replacement for collabvm 1.2.11 because the old one :boom:",
|
||||||
|
"main": "build/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"serve": "node build/index.js"
|
||||||
|
},
|
||||||
|
"author": "Elijah R",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/ws": "^8.5.4",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
|
"mnemonist": "^0.39.5",
|
||||||
|
"rfb2": "^0.2.2",
|
||||||
|
"toml": "^3.0.0",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"ws": "^8.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/IConfig.ts
Normal file
46
src/IConfig.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export default interface IConfig {
|
||||||
|
http : {
|
||||||
|
host : string;
|
||||||
|
port : number;
|
||||||
|
proxying : boolean;
|
||||||
|
proxyAllowedIps : string[];
|
||||||
|
};
|
||||||
|
vm : {
|
||||||
|
qemuArgs : string;
|
||||||
|
vncPort : number;
|
||||||
|
snapshots : boolean;
|
||||||
|
qmpSockDir : string;
|
||||||
|
};
|
||||||
|
collabvm : {
|
||||||
|
node : string;
|
||||||
|
displayname : string;
|
||||||
|
motd : string;
|
||||||
|
bancmd : string;
|
||||||
|
moderatorEnabled : boolean;
|
||||||
|
usernameblacklist : string[];
|
||||||
|
maxChatLength : number;
|
||||||
|
automute : {
|
||||||
|
enabled: boolean;
|
||||||
|
seconds: number;
|
||||||
|
messages: number;
|
||||||
|
};
|
||||||
|
tempMuteTime : number;
|
||||||
|
turnTime : number;
|
||||||
|
adminpass : string;
|
||||||
|
modpass : string;
|
||||||
|
moderatorPermissions : Permissions;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Permissions {
|
||||||
|
restore : boolean;
|
||||||
|
reboot : boolean;
|
||||||
|
ban : boolean;
|
||||||
|
forcevote : boolean;
|
||||||
|
mute : boolean;
|
||||||
|
kick : boolean;
|
||||||
|
bypassturn : boolean;
|
||||||
|
rename : boolean;
|
||||||
|
grabip : boolean;
|
||||||
|
xss : boolean;
|
||||||
|
}
|
||||||
70
src/QEMUVM.ts
Normal file
70
src/QEMUVM.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
import IConfig from "./IConfig";
|
||||||
|
import * as rfb from 'rfb2';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { spawn, ChildProcess } from "child_process";
|
||||||
|
import QMPClient from "./QMPClient";
|
||||||
|
|
||||||
|
export default class QEMUVM extends EventEmitter {
|
||||||
|
vnc? : rfb.RfbClient;
|
||||||
|
vncPort : number;
|
||||||
|
qmpSock : string;
|
||||||
|
qmpClient : QMPClient;
|
||||||
|
qemuCmd : string;
|
||||||
|
qemuProcess? : ChildProcess;
|
||||||
|
qmpErrorLevel : number;
|
||||||
|
vncErrorLevel : number;
|
||||||
|
constructor(Config : IConfig) {
|
||||||
|
super();
|
||||||
|
if (Config.vm.vncPort < 5900) {
|
||||||
|
console.error("[FATAL] VNC Port must be 5900 or higher");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
this.qmpSock = `${Config.vm.qmpSockDir}collab-vm-qmp-${Config.collabvm.node}.sock`;
|
||||||
|
this.vncPort = Config.vm.vncPort;
|
||||||
|
this.qemuCmd = `${Config.vm.qemuArgs} -snapshot -no-shutdown -vnc 127.0.0.1:${this.vncPort - 5900} -qmp unix:${this.qmpSock},server`;
|
||||||
|
this.qmpErrorLevel = 0;
|
||||||
|
this.vncErrorLevel = 0;
|
||||||
|
this.qmpClient = new QMPClient(this.qmpSock);
|
||||||
|
this.qmpClient.on('connected', () => this.qmpConnected());
|
||||||
|
}
|
||||||
|
|
||||||
|
Start() {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
if (fs.existsSync(this.qmpSock))
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(this.qmpSock);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[FATAL] Could not remove existing QMP socket: " + e);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
var qemuArr = this.qemuCmd.split(" ");
|
||||||
|
this.qemuProcess = spawn(qemuArr[0], qemuArr.slice(1));
|
||||||
|
process.on("beforeExit", () => {
|
||||||
|
this.qemuProcess?.kill(9);
|
||||||
|
});
|
||||||
|
this.qemuProcess.stderr?.on('data', (d) => console.log(d.toString()));
|
||||||
|
this.qemuProcess.on('spawn', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.qmpClient.connect();
|
||||||
|
}, 1000)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private qmpConnected() {
|
||||||
|
console.log("QMP Connected");
|
||||||
|
setTimeout(() => this.startVNC(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startVNC() {
|
||||||
|
this.vnc = rfb.createConnection({
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: this.vncPort,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private qmpClosed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/QMPClient.ts
Normal file
45
src/QMPClient.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import EventEmitter from "events";
|
||||||
|
import { Socket } from "net";
|
||||||
|
|
||||||
|
export default class QMPClient extends EventEmitter {
|
||||||
|
socketfile : string;
|
||||||
|
socket : Socket;
|
||||||
|
connected : boolean;
|
||||||
|
sentConnected : boolean;
|
||||||
|
constructor(socketfile : string) {
|
||||||
|
super();
|
||||||
|
this.socketfile = socketfile;
|
||||||
|
this.socket = new Socket();
|
||||||
|
this.connected = false;
|
||||||
|
this.sentConnected = false;
|
||||||
|
}
|
||||||
|
connect() {
|
||||||
|
if (this.connected) return;
|
||||||
|
try {
|
||||||
|
this.socket.connect(this.socketfile);
|
||||||
|
} catch (e) {
|
||||||
|
this.emit("")
|
||||||
|
}
|
||||||
|
this.connected = true;
|
||||||
|
this.socket.on('data', (data) => this.onData(data));
|
||||||
|
this.socket.on('close', () => this.onClose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onData(data : Buffer) {
|
||||||
|
var msgraw = data.toString();
|
||||||
|
var msg = JSON.parse(msgraw);
|
||||||
|
console.log(msg);
|
||||||
|
if (msg.QMP) {
|
||||||
|
if (this.sentConnected) return;
|
||||||
|
this.socket.write(JSON.stringify({ execute: "qmp_capabilities" }));
|
||||||
|
this.emit('connected');
|
||||||
|
this.sentConnected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClose() {
|
||||||
|
this.connected = false;
|
||||||
|
this.sentConnected = false;
|
||||||
|
this.emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/RateLimiter.ts
Normal file
36
src/RateLimiter.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { EventEmitter } from "stream";
|
||||||
|
|
||||||
|
// Class to ratelimit a resource (chatting, logging in, etc)
|
||||||
|
export default class RateLimiter extends EventEmitter {
|
||||||
|
private limit : number;
|
||||||
|
private interval : number;
|
||||||
|
private requestCount : number;
|
||||||
|
private limiter? : NodeJS.Timer;
|
||||||
|
private limiterSet : boolean;
|
||||||
|
constructor(limit : number, interval : number) {
|
||||||
|
super();
|
||||||
|
this.limit = limit;
|
||||||
|
this.interval = interval;
|
||||||
|
this.requestCount = 0;
|
||||||
|
this.limiterSet = false;
|
||||||
|
}
|
||||||
|
// Return value is whether or not the action should be continued
|
||||||
|
request() : boolean {
|
||||||
|
this.requestCount++;
|
||||||
|
if (this.requestCount === this.limit) {
|
||||||
|
this.emit('limit');
|
||||||
|
clearTimeout(this.limiter);
|
||||||
|
this.limiterSet = false;
|
||||||
|
this.requestCount = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.limiterSet) {
|
||||||
|
this.limiter = setTimeout(() => {
|
||||||
|
this.limiterSet = false;
|
||||||
|
this.requestCount = 0;
|
||||||
|
}, this.interval * 1000);
|
||||||
|
this.limiterSet = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/User.ts
Normal file
104
src/User.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import * as Utilities from './Utilities';
|
||||||
|
import * as guacutils from './guacutils';
|
||||||
|
import {WebSocket} from 'ws';
|
||||||
|
import IConfig from './IConfig';
|
||||||
|
import RateLimiter from './RateLimiter';
|
||||||
|
export class User {
|
||||||
|
socket : WebSocket;
|
||||||
|
nopSendInterval : NodeJS.Timer;
|
||||||
|
msgRecieveInterval : NodeJS.Timer;
|
||||||
|
nopRecieveTimeout? : NodeJS.Timer;
|
||||||
|
username? : string;
|
||||||
|
connectedToNode : boolean;
|
||||||
|
rank : Rank;
|
||||||
|
muted : Boolean;
|
||||||
|
tempMuteExpireTimeout? : NodeJS.Timer;
|
||||||
|
msgsSent : number;
|
||||||
|
Config : IConfig;
|
||||||
|
IP : string;
|
||||||
|
// Rate limiters
|
||||||
|
ChatRateLimit : RateLimiter;
|
||||||
|
LoginRateLimit : RateLimiter;
|
||||||
|
RenameRateLimit : RateLimiter;
|
||||||
|
TurnRateLimit : RateLimiter;
|
||||||
|
constructor(ws : WebSocket, ip : string, config : IConfig, username? : string, node? : string) {
|
||||||
|
this.IP = ip;
|
||||||
|
this.connectedToNode = false;
|
||||||
|
this.Config = config;
|
||||||
|
this.socket = ws;
|
||||||
|
this.muted = false;
|
||||||
|
this.msgsSent = 0;
|
||||||
|
this.socket.on('close', () => {
|
||||||
|
clearInterval(this.nopSendInterval);
|
||||||
|
});
|
||||||
|
this.socket.on('message', (e) => {
|
||||||
|
clearTimeout(this.nopRecieveTimeout);
|
||||||
|
clearInterval(this.msgRecieveInterval);
|
||||||
|
this.msgRecieveInterval = setInterval(() => this.onNoMsg(), 10000);
|
||||||
|
})
|
||||||
|
this.nopSendInterval = setInterval(() => this.sendNop(), 5000);
|
||||||
|
this.msgRecieveInterval = setInterval(() => this.onNoMsg(), 10000);
|
||||||
|
this.sendNop();
|
||||||
|
if (username) this.username = username;
|
||||||
|
this.rank = 0;
|
||||||
|
this.ChatRateLimit = new RateLimiter(this.Config.collabvm.automute.messages, this.Config.collabvm.automute.seconds);
|
||||||
|
this.ChatRateLimit.on('limit', () => this.mute(false));
|
||||||
|
this.RenameRateLimit = new RateLimiter(4, 3);
|
||||||
|
this.RenameRateLimit.on('limit', () => this.closeConnection());
|
||||||
|
this.LoginRateLimit = new RateLimiter(4, 3);
|
||||||
|
this.LoginRateLimit.on('limit', () => this.closeConnection());
|
||||||
|
this.TurnRateLimit = new RateLimiter(5, 3);
|
||||||
|
this.TurnRateLimit.on('limit', () => this.closeConnection());
|
||||||
|
}
|
||||||
|
assignGuestName(existingUsers : string[]) : string {
|
||||||
|
var username;
|
||||||
|
do {
|
||||||
|
username = "guest" + Utilities.Randint(10000, 99999);
|
||||||
|
} while (existingUsers.indexOf(username) !== -1);
|
||||||
|
this.username = username;
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
sendNop() {
|
||||||
|
this.socket.send("3.nop;");
|
||||||
|
}
|
||||||
|
sendMsg(msg : string | Buffer) {
|
||||||
|
if (this.socket.readyState !== this.socket.OPEN) return;
|
||||||
|
clearInterval(this.nopSendInterval);
|
||||||
|
this.nopSendInterval = setInterval(() => this.sendNop(), 5000);
|
||||||
|
this.socket.send(msg);
|
||||||
|
}
|
||||||
|
private onNoMsg() {
|
||||||
|
this.sendNop();
|
||||||
|
this.nopRecieveTimeout = setTimeout(() => {
|
||||||
|
this.closeConnection();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
closeConnection() {
|
||||||
|
this.socket.send(guacutils.encode("disconnect"));
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
onMsgSent() {
|
||||||
|
if (!this.Config.collabvm.automute.enabled) return;
|
||||||
|
if (this.rank !== 0) return;
|
||||||
|
this.ChatRateLimit.request();
|
||||||
|
}
|
||||||
|
mute(permanent : boolean) {
|
||||||
|
this.muted = true;
|
||||||
|
this.sendMsg(guacutils.encode("chat", "", `You have been muted${permanent ? "" : ` for ${this.Config.collabvm.tempMuteTime} seconds`}.`));
|
||||||
|
if (!permanent) {
|
||||||
|
clearTimeout(this.tempMuteExpireTimeout);
|
||||||
|
this.tempMuteExpireTimeout = setTimeout(() => this.unmute(), this.Config.collabvm.tempMuteTime * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unmute() {
|
||||||
|
clearTimeout(this.tempMuteExpireTimeout);
|
||||||
|
this.muted = false;
|
||||||
|
this.sendMsg(guacutils.encode("chat", "", "You are no longer muted."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Rank {
|
||||||
|
Unregistered = 0,
|
||||||
|
Admin = 2,
|
||||||
|
Moderator = 3,
|
||||||
|
}
|
||||||
54
src/Utilities.ts
Normal file
54
src/Utilities.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Permissions } from "./IConfig";
|
||||||
|
|
||||||
|
export function Randint(min : number, max : number) {
|
||||||
|
return Math.floor((Math.random() * (max - min)) + min);
|
||||||
|
}
|
||||||
|
export function HTMLSanitize(input : string) : string {
|
||||||
|
var output = "";
|
||||||
|
for (var i = 0; i < input.length; i++) {
|
||||||
|
switch (input[i]) {
|
||||||
|
case "<":
|
||||||
|
output += "<"
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
output += ">"
|
||||||
|
break;
|
||||||
|
case "&":
|
||||||
|
output += "&"
|
||||||
|
break;
|
||||||
|
case "\"":
|
||||||
|
output += """
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
output += "'";
|
||||||
|
break;
|
||||||
|
case "/":
|
||||||
|
output += "/";
|
||||||
|
break;
|
||||||
|
case "\n":
|
||||||
|
output += " ";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
var charcode : number = input.charCodeAt(i);
|
||||||
|
if (charcode >= 32 && charcode <= 126)
|
||||||
|
output += input[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MakeModPerms(modperms : Permissions) : number {
|
||||||
|
var perms = 0;
|
||||||
|
if (modperms.restore) perms |= 1;
|
||||||
|
if (modperms.reboot) perms |= 2;
|
||||||
|
if (modperms.ban) perms |= 4;
|
||||||
|
if (modperms.forcevote) perms |= 8;
|
||||||
|
if (modperms.mute) perms |= 16;
|
||||||
|
if (modperms.kick) perms |= 32;
|
||||||
|
if (modperms.bypassturn) perms |= 64;
|
||||||
|
if (modperms.rename) perms |= 128;
|
||||||
|
if (modperms.grabip) perms |= 256;
|
||||||
|
if (modperms.xss) perms |= 512;
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
315
src/WSServer.ts
Normal file
315
src/WSServer.ts
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import {WebSocketServer, WebSocket} from 'ws';
|
||||||
|
import * as http from 'http';
|
||||||
|
import IConfig from './IConfig';
|
||||||
|
import internal from 'stream';
|
||||||
|
import * as Utilities from './Utilities';
|
||||||
|
import { User, Rank } from './User';
|
||||||
|
import * as guacutils from './guacutils';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { CircularBuffer, Queue } from 'mnemonist';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
import { isIP } from 'net';
|
||||||
|
|
||||||
|
export default class WSServer {
|
||||||
|
private Config : IConfig;
|
||||||
|
private server : http.Server;
|
||||||
|
private socket : WebSocketServer;
|
||||||
|
private clients : User[];
|
||||||
|
private ChatHistory : CircularBuffer<{user:string,msg:string}>
|
||||||
|
private TurnQueue : Queue<User>;
|
||||||
|
private TurnTime : number;
|
||||||
|
private TurnInterval? : NodeJS.Timer;
|
||||||
|
private TurnIntervalRunning : boolean;
|
||||||
|
private ModPerms : number;
|
||||||
|
constructor(config : IConfig) {
|
||||||
|
this.ChatHistory = new CircularBuffer<{user:string,msg:string}>(Array, 5);
|
||||||
|
this.TurnQueue = new Queue<User>();
|
||||||
|
this.TurnTime = 0;
|
||||||
|
this.TurnIntervalRunning = false;
|
||||||
|
this.clients = [];
|
||||||
|
this.Config = config;
|
||||||
|
this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions);
|
||||||
|
this.server = http.createServer();
|
||||||
|
this.socket = new WebSocketServer({noServer: true});
|
||||||
|
this.server.on('upgrade', (req : http.IncomingMessage, socket : internal.Duplex, head : Buffer) => this.httpOnUpgrade(req, socket, head));
|
||||||
|
this.socket.on('connection', (ws : WebSocket, req : http.IncomingMessage) => this.onConnection(ws, req));
|
||||||
|
}
|
||||||
|
|
||||||
|
listen() {
|
||||||
|
this.server.listen(this.Config.http.port, this.Config.http.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private httpOnUpgrade(req : http.IncomingMessage, socket : internal.Duplex, head : Buffer) {
|
||||||
|
var killConnection = () => {
|
||||||
|
socket.write("HTTP/1.1 400 Bad Request\n\n400 Bad Request");
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
req.headers['sec-websocket-protocol'] !== "guacamole"
|
||||||
|
// || req.headers['origin']?.toLocaleLowerCase() !== "https://computernewb.com"
|
||||||
|
) {
|
||||||
|
killConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.Config.http.proxying) {
|
||||||
|
// If the requesting IP isn't allowed to proxy, kill it
|
||||||
|
//@ts-ignore
|
||||||
|
if (this.Config.http.proxyAllowedIps.indexOf(req.socket.remoteAddress) === -1) {
|
||||||
|
killConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var _ip;
|
||||||
|
try {
|
||||||
|
// Get the first IP from the X-Forwarded-For variable
|
||||||
|
_ip = req.headers["x-forwarded-for"]?.toString().replace(/\ /g, "").split(",")[0];
|
||||||
|
} catch {
|
||||||
|
// If we can't get the ip, kill the connection
|
||||||
|
killConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If for some reason the IP isn't defined, kill it
|
||||||
|
if (!_ip) {
|
||||||
|
killConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make sure the ip is valid. If not, kill the connection.
|
||||||
|
if (!isIP(_ip)) {
|
||||||
|
killConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
req.proxiedIP = _ip;
|
||||||
|
}
|
||||||
|
this.socket.handleUpgrade(req, socket, head, (ws) => this.socket.emit('connection', ws, req));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onConnection(ws : WebSocket, req : http.IncomingMessage) {
|
||||||
|
var ip;
|
||||||
|
if (this.Config.http.proxying) {
|
||||||
|
//@ts-ignore
|
||||||
|
if (!req.proxiedIP) return;
|
||||||
|
//@ts-ignore
|
||||||
|
ip = req.proxiedIP;
|
||||||
|
} else {
|
||||||
|
if (!req.socket.remoteAddress) return;
|
||||||
|
ip = req.socket.remoteAddress;
|
||||||
|
}
|
||||||
|
var user = new User(ws, ip, this.Config);
|
||||||
|
this.clients.push(user);
|
||||||
|
ws.on('close', () => this.connectionClosed(user));
|
||||||
|
ws.on('message', (e) => {
|
||||||
|
var msg;
|
||||||
|
try {msg = e.toString()}
|
||||||
|
catch {
|
||||||
|
// Fuck the user off if they send a non-string message
|
||||||
|
user.closeConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onMessage(user, msg);
|
||||||
|
});
|
||||||
|
user.sendMsg(this.getAdduserMsg());
|
||||||
|
console.log(`[Connect] From ${user.IP}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
private connectionClosed(user : User) {
|
||||||
|
this.clients.splice(this.clients.indexOf(user), 1);
|
||||||
|
console.log(`[DISCONNECT] From ${user.IP}${user.username ? ` with username ${user.username}` : ""}`);
|
||||||
|
if (!user.username) return;
|
||||||
|
if (this.TurnQueue.toArray().indexOf(user) !== -1) {
|
||||||
|
var hadturn = (this.TurnQueue.peek() === user);
|
||||||
|
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(u => u !== user));
|
||||||
|
if (hadturn) this.nextTurn();
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
this.clients.forEach((c) => c.sendMsg(guacutils.encode("remuser", "1", user.username)));
|
||||||
|
}
|
||||||
|
fuck = fs.readFileSync("/home/elijah/Pictures/thumb.txt").toString();
|
||||||
|
private onMessage(client : User, message : string) {
|
||||||
|
var msgArr = guacutils.decode(message);
|
||||||
|
if (msgArr.length < 1) return;
|
||||||
|
switch (msgArr[0]) {
|
||||||
|
case "list":
|
||||||
|
client.sendMsg(guacutils.encode("list", this.Config.collabvm.node, this.Config.collabvm.displayname, this.fuck))
|
||||||
|
break;
|
||||||
|
case "connect":
|
||||||
|
if (!client.username || msgArr.length !== 2 || msgArr[1] !== this.Config.collabvm.node) {
|
||||||
|
client.sendMsg(guacutils.encode("connect", "0"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.connectedToNode = true;
|
||||||
|
client.sendMsg(guacutils.encode("connect", "1", "1", "1", "0"));
|
||||||
|
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode("chat", "", this.Config.collabvm.motd));
|
||||||
|
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
|
||||||
|
client.sendMsg(guacutils.encode("size", "0", "400", "300"));
|
||||||
|
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", this.fuck));
|
||||||
|
break;
|
||||||
|
case "rename":
|
||||||
|
if (!client.RenameRateLimit.request()) return;
|
||||||
|
// This shouldn't need a ternary but it does for some reason
|
||||||
|
var hadName : boolean = client.username ? true : false;
|
||||||
|
var oldname : any;
|
||||||
|
if (hadName) oldname = client.username;
|
||||||
|
if (msgArr.length === 1) {
|
||||||
|
client.assignGuestName(this.getUsernameList());
|
||||||
|
} else {
|
||||||
|
var newName = msgArr[1];
|
||||||
|
if (hadName && newName === oldname) {
|
||||||
|
//@ts-ignore
|
||||||
|
client.sendMsg(guacutils.encode("rename", "0", "0", client.username));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.getUsernameList().indexOf(newName) !== -1) {
|
||||||
|
client.sendMsg(guacutils.encode("rename", "0", "1"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName)) {
|
||||||
|
client.sendMsg(guacutils.encode("rename", "0", "2"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
|
||||||
|
client.sendMsg(guacutils.encode("rename", "0", "3"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.username = newName;
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
client.sendMsg(guacutils.encode("rename", "0", "0", client.username));
|
||||||
|
if (hadName) {
|
||||||
|
console.log(`[RENAME] ${client.IP} from ${oldname} to ${client.username}`);
|
||||||
|
this.clients.filter(c => c.username !== client.username).forEach((c) =>
|
||||||
|
//@ts-ignore
|
||||||
|
c.sendMsg(guacutils.encode("rename", "1", oldname, client.username)));
|
||||||
|
} else {
|
||||||
|
console.log(`[RENAME] ${client.IP} to ${client.username}`);
|
||||||
|
this.clients.forEach((c) =>
|
||||||
|
//@ts-ignore
|
||||||
|
c.sendMsg(guacutils.encode("adduser", "1", client.username, client.rank)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "chat":
|
||||||
|
if (!client.username) return;
|
||||||
|
if (client.muted) return;
|
||||||
|
if (msgArr.length !== 2) return;
|
||||||
|
var msg = Utilities.HTMLSanitize(msgArr[1]);
|
||||||
|
// One of the things I hated most about the old server is it completely discarded your message if it was too long
|
||||||
|
if (msg.length > this.Config.collabvm.maxChatLength) msg = msg.substring(0, this.Config.collabvm.maxChatLength);
|
||||||
|
if (msg.length < 1) return;
|
||||||
|
//@ts-ignore
|
||||||
|
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", client.username, msg)));
|
||||||
|
this.ChatHistory.push({user: client.username, msg: msg});
|
||||||
|
client.onMsgSent();
|
||||||
|
break;
|
||||||
|
case "turn":
|
||||||
|
if (!client.TurnRateLimit.request()) return;
|
||||||
|
if (!client.connectedToNode) return;
|
||||||
|
if (msgArr.length > 2) return;
|
||||||
|
var takingTurn : boolean;
|
||||||
|
if (msgArr.length === 1) takingTurn = true;
|
||||||
|
else switch (msgArr[1]) {
|
||||||
|
case "0":
|
||||||
|
takingTurn = false;
|
||||||
|
break;
|
||||||
|
case "1":
|
||||||
|
takingTurn = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (takingTurn) {
|
||||||
|
// If the user is already in the queue, fuck them off
|
||||||
|
if (this.TurnQueue.toArray().indexOf(client) !== -1) return;
|
||||||
|
// If they're muted, also fuck them off.
|
||||||
|
// Send them the turn queue to prevent client glitches
|
||||||
|
if (client.muted) return;
|
||||||
|
this.TurnQueue.enqueue(client);
|
||||||
|
if (this.TurnQueue.size === 1) this.nextTurn();
|
||||||
|
} else {
|
||||||
|
var hadturn = (this.TurnQueue.peek() === client);
|
||||||
|
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(u => u !== client));
|
||||||
|
if (hadturn) this.nextTurn();
|
||||||
|
}
|
||||||
|
this.sendTurnUpdate();
|
||||||
|
break;
|
||||||
|
case "admin":
|
||||||
|
if (msgArr.length < 2) return;
|
||||||
|
switch (msgArr[1]) {
|
||||||
|
case "2":
|
||||||
|
if (!client.LoginRateLimit.request()) return;
|
||||||
|
if (msgArr.length !== 3) return;
|
||||||
|
var sha256 = createHash("sha256");
|
||||||
|
sha256.update(msgArr[2]);
|
||||||
|
var pwdHash = sha256.digest('hex');
|
||||||
|
sha256.destroy();
|
||||||
|
if (pwdHash === this.Config.collabvm.adminpass) {
|
||||||
|
client.rank = Rank.Admin;
|
||||||
|
client.sendMsg(guacutils.encode("admin", "0", "1"));
|
||||||
|
} else if (this.Config.collabvm.moderatorEnabled && pwdHash === this.Config.collabvm.modpass) {
|
||||||
|
client.rank = Rank.Moderator;
|
||||||
|
client.sendMsg(guacutils.encode("admin", "0", "3", this.ModPerms.toString()));
|
||||||
|
} else {
|
||||||
|
client.sendMsg(guacutils.encode("admin", "0", "0"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
this.clients.forEach((c) => c.sendMsg(guacutils.encode("adduser", "1", client.username, client.rank)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsernameList() : string[] {
|
||||||
|
var arr : string[] = [];
|
||||||
|
//@ts-ignore
|
||||||
|
this.clients.filter(c => c.username).forEach((c) => arr.push(c.username));
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
getAdduserMsg() : string {
|
||||||
|
var arr : string[] = ["adduser", this.clients.length.toString()];
|
||||||
|
//@ts-ignore
|
||||||
|
this.clients.filter(c=>c.username).forEach((c) => arr.push(c.username, c.rank));
|
||||||
|
return guacutils.encode(...arr);
|
||||||
|
}
|
||||||
|
getChatHistoryMsg() : string {
|
||||||
|
var arr : string[] = ["chat"];
|
||||||
|
this.ChatHistory.forEach(c => arr.push(c.user, c.msg));
|
||||||
|
return guacutils.encode(...arr);
|
||||||
|
}
|
||||||
|
private sendTurnUpdate() {
|
||||||
|
var turnQueueArr = this.TurnQueue.toArray();
|
||||||
|
var arr = ["turn", (this.TurnTime * 1000).toString(), this.TurnQueue.size.toString()];
|
||||||
|
// @ts-ignore
|
||||||
|
this.TurnQueue.forEach((c) => arr.push(c.username));
|
||||||
|
var currentTurningUser = this.TurnQueue.peek();
|
||||||
|
this.clients.filter(c => (c !== currentTurningUser && c.connectedToNode)).forEach((c) => {
|
||||||
|
if (turnQueueArr.indexOf(c) !== -1) {
|
||||||
|
var time = ((this.TurnTime * 1000) + ((turnQueueArr.indexOf(c) - 1) * this.Config.collabvm.turnTime * 1000));
|
||||||
|
c.sendMsg(guacutils.encode(...arr, time.toString()));
|
||||||
|
} else {
|
||||||
|
c.sendMsg(guacutils.encode(...arr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentTurningUser)
|
||||||
|
currentTurningUser.sendMsg(guacutils.encode(...arr));
|
||||||
|
}
|
||||||
|
private nextTurn() {
|
||||||
|
clearInterval(this.TurnInterval);
|
||||||
|
if (this.TurnQueue.size === 0) {
|
||||||
|
this.TurnIntervalRunning = false;
|
||||||
|
} else {
|
||||||
|
this.TurnTime = this.Config.collabvm.turnTime;
|
||||||
|
this.TurnInterval = setInterval(() => this.turnInterval(), 1000);
|
||||||
|
}
|
||||||
|
this.sendTurnUpdate();
|
||||||
|
}
|
||||||
|
private turnInterval() {
|
||||||
|
this.TurnTime--;
|
||||||
|
if (this.TurnTime < 1) {
|
||||||
|
this.TurnQueue.dequeue();
|
||||||
|
this.nextTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/guacutils.ts
Normal file
43
src/guacutils.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export function decode(string : string) : string[] {
|
||||||
|
let pos = -1;
|
||||||
|
let sections = [];
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
let len = string.indexOf('.', pos + 1);
|
||||||
|
|
||||||
|
if(len === -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
pos = parseInt(string.slice(pos + 1, len)) + len + 1;
|
||||||
|
|
||||||
|
// don't allow funky protocol length
|
||||||
|
if(pos > string.length)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
sections.push(string.slice(len + 1, pos));
|
||||||
|
|
||||||
|
|
||||||
|
const sep = string.slice(pos, pos + 1);
|
||||||
|
|
||||||
|
if(sep === ',')
|
||||||
|
continue;
|
||||||
|
else if(sep === ';')
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
// Invalid data.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encode(...string : string[]) : string {
|
||||||
|
let command = '';
|
||||||
|
|
||||||
|
for(var i = 0; i < string.length; i++) {
|
||||||
|
let current = string[i];
|
||||||
|
command += current.toString().length + '.' + current;
|
||||||
|
command += ( i < string.length - 1 ? ',' : ';');
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
29
src/index.ts
Normal file
29
src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as toml from 'toml';
|
||||||
|
import IConfig from './IConfig';
|
||||||
|
import * as fs from "fs";
|
||||||
|
import WSServer from './WSServer';
|
||||||
|
import QEMUVM from './QEMUVM';
|
||||||
|
|
||||||
|
// Parse the config file
|
||||||
|
|
||||||
|
var Config : IConfig;
|
||||||
|
|
||||||
|
if (!fs.existsSync("config.toml")) {
|
||||||
|
console.error("config.toml not found. Please copy config.example.toml to config.toml and fill out fields.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var configRaw = fs.readFileSync("config.toml").toString();
|
||||||
|
Config = toml.parse(configRaw);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to read or parse the config file: ${e}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire up the VM
|
||||||
|
var VM = new QEMUVM(Config);
|
||||||
|
VM.Start();
|
||||||
|
|
||||||
|
// Start up the websocket server
|
||||||
|
var WS = new WSServer(Config);
|
||||||
|
WS.listen();
|
||||||
103
tsconfig.json
Normal file
103
tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./build", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user