Compare commits

...

2 Commits

Author SHA1 Message Date
Alessandro 7df6b9dc06 let's just bury v1 2021-09-25 20:04:11 +02:00
Alessandro 80c8a60dd5 Upgraded to V2 2021-09-25 20:02:51 +02:00
21 changed files with 1143 additions and 1569 deletions

122
.gitignore vendored
View File

@ -1,2 +1,120 @@
# Project exclude paths
/node_modules/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
dist/

View File

@ -1,5 +0,0 @@
{
"Lua.diagnostics.disable": [
"undefined-global"
]
}

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# Soqet
This is the source code powering the Soqet Network.
## Libraries
#### For ComputerCraft
`wget https://raw.githubusercontent.com/Ale32bit/Soqet/master/libs/cc_soqet.lua soqet.lua`
#### For OpenComputers
`wget https://raw.githubusercontent.com/Ale32bit/Soqet/master/libs/oc_soqet.lua soqet.lua`
## API Documentation
Documentation can be found here: https://soqet.alexdevs.pw or check the `public/index.html`.
## Self hosting
### Build
Run `npm install` to install all packages and then `npm run build` to build the server.
### Run
Run `npm run start` to run the server

View File

@ -1,6 +1,10 @@
{
"port": 3004,
"tcp_port": 25555,
"wildcard_channel": "*",
"motd": "Welcome to the Soqet Network"
"motd": "Welcome to Soqet v2.0",
"httpPort": 3004,
"tcpPort": 25555,
"enableWebsocket": true,
"enableTCPSocket": true,
"enablePolling": true,
"wildcardChannel": "*",
"pollingInterval": 3600
}

589
index.ts
View File

@ -1,589 +0,0 @@
//
// Soqet V3 by AlexDevs
// (c) 2020 Alessandro
//
// MIT License
// https://github.com/Ale32bit/Soqet/blob/master/LICENSE
//
// https://github.com/Ale32bit/Soqet
// https://soqet.alexdevs.pw/
//
import express from "express";
import expressWs from "express-ws";
import * as net from "net";
import * as crypto from "crypto";
const { app } = expressWs(express());
const config = require("./config.json")
interface Client {
send: (data: string | object) => void,
uuid: string,
id: string,
auth: boolean,
type: string,
client: any,
}
interface PollingClient extends Client {
lastPing: number,
queue: Array<any>,
token: string,
}
interface Channels {
[key: string]: Array<string>,
}
interface Clients {
[key: string]: PollingClient | Client,
}
interface PollingTokens {
[key: string]: string,
}
const channels: Channels = {};
const clients: Clients = {};
const pollingTokens: PollingTokens = {};
function sha256(str: string): string {
return crypto.createHash("sha256").update(str).digest("hex");
}
function sha256_raw(str: string): Buffer {
return crypto.createHash("sha256").update(str).digest();
}
function toBase36(input: number): string { // hexadecimal number to base 36
const byte = 48 + Math.floor(input / 7);
return String.fromCharCode(byte + 39 > 122 ? 101 : byte > 57 ? byte + 39 : byte);
}
function random(len: number = 16, prefix: string = "g"): string { // Generate a random secure string in hex
let bytes = crypto.randomBytes(len);
for (let i = 0; i < len; i++) {
prefix += toBase36(bytes[i]);
}
return prefix;
}
function createID(token: string, prefix: string = "a"): string { // New create ID algorithm
let buff: Buffer = sha256_raw(token);
let len = 32;
let id: Array<string> = [];
for (let i = 0; i < len; i++) {
buff = sha256_raw(buff.toString("hex"));
let chunk = buff[0];
id[i] = toBase36(chunk);
}
return prefix + id.join("");
}
function disconnect(id: string) {
for (let name in channels) {
let ch: Array<string> = channels[name];
let index: number = ch.indexOf(id);
if (index > -1) {
ch.splice(index, 1);
}
if (ch.length === 0) {
delete channels[name];
}
}
}
function transmit(id: string, channel: string | number, message: unknown, meta: object): void {
try {
if (channel === config.wildcard_channel) return;
if (channels[channel]) {
channels[channel].forEach(cid => {
if (id === cid) return;
let client = clients[cid];
if (client) {
client.send({
type: "message",
channel: channel,
message: message,
meta: meta,
})
}
})
}
if (channels[config.wildcard_channel]) {
channels[config.wildcard_channel].forEach(cid => {
let client = clients[cid];
if (client) {
client.send({
type: "message",
channel: config.wildcard_channel,
message: message,
meta: meta,
})
}
})
}
} catch (e) {
console.error(e);
}
}
function send(client: Client | PollingClient, channel: string | number, message: any, meta: any) {
meta = typeof meta === "object" && !Array.isArray(meta) ? meta : {};
meta.uuid = client.uuid; // sender uuid
meta.time = Date.now(); // time of sending
meta.channel = channel; // channel
meta.guest = !client.auth; // if not authenticated
transmit(client.id, channel, message, meta);
}
function openChannel(id: string, channel: string | number): void {
if (!channels[channel]) channels[channel] = [];
channels[channel].push(id);
}
function closeChannel(id: string, channel: string | number) {
if (channels[channel]) { // remove uuid from the channel
let index = channels[channel].indexOf(id);
if (index >= 0) {
channels[channel].splice(index, 1);
}
}
}
function onMessage(client: Client, message: any): void {
let data;
if (typeof message !== "string") message = message.toString("utf-8");
try {
data = JSON.parse(message);
} catch (e) {
return client.send({
ok: false,
error: "Invalid data format",
uuid: client.uuid,
})
}
data.id = Number.parseInt(data.id) || 1;
if (!data.type) {
return client.send({
ok: false,
error: "Invalid request",
uuid: client.uuid,
id: data.id,
})
}
switch (data.type) {
case "send": // Send a message to a channel
if (!data.channel) {
return client.send({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
id: data.id,
})
}
// proceed to send the message
send(client, data.channel, data.message, data.meta || {});
client.send({
ok: true,
id: data.id,
uuid: client.uuid,
});
break;
case "open": // Open channel
if (!data.channel) {
return client.send({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
id: data.id,
})
}
// limit of channel name:
// Must be either a string long max 256 chars or a number
if ((typeof data.channel === "string" && data.channel.length <= 256) || typeof data.channel === "number") {
openChannel(client.id, data.channel)
client.send({
ok: true,
id: data.id,
uuid: client.uuid,
});
} else {
return client.send({
ok: false,
error: "Invalid channel field",
uuid: client.uuid,
id: data.id,
})
}
break;
case "close": // close a channel
if (!data.channel) {
return client.send({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
id: data.id,
})
}
closeChannel(client.id, data.channel);
client.send({
ok: true,
id: data.id,
uuid: client.uuid,
});
break;
case "ping": // allow clients to ping the server if they want to
client.send({
ok: true,
id: data.id,
uuid: client.uuid,
});
break;
case "auth": // authentication
if (!data.token) {
return client.send({
ok: false,
error: "Missing token field",
uuid: client.uuid,
id: data.id,
})
}
let authid = createID(data.token); // create the UUID from the token
let olduuid = client.uuid;
client.uuid = authid; // set new UUID
client.auth = true; // set as authenticated
console.log(`AUTH: ${olduuid} is now ${client.uuid}`);
return client.send({
ok: true,
uuid: client.uuid,
id: data.id,
});
break;
default: // if no request type is found send this as error
client.send({
ok: false,
error: "Invalid request",
uuid: client.uuid,
id: data.id,
})
}
}
const netServer = net.createServer();
app.ws("*", ws => {
let client = {} as Client;
client.uuid = random();
client.id = random(undefined, "S");
client.auth = false;
client.type = "websocket";
client.client = ws;
client.send = function (data: string | object) {
if (typeof data === "object") data = JSON.stringify(data);
if(client.client.readyState === ws.OPEN) {
client.client.send(data)
}
}
clients[client.id] = client;
let pingInterval = setInterval(function () {
client.send({
type: "ping",
uuid: client.uuid,
ping: Date.now(),
})
}, 10000);
client.send({
type: "motd",
motd: config.motd || "soqet",
uuid: client.uuid,
});
console.log("[WS]", `Client connected: ${client.id}`);
ws.on("message", data => {
onMessage(client, data);
})
ws.on("close", (code, reason) => {
console.log("[WS]", `Client disconnected ${client.id} (${code} ${reason})`);
clearInterval(pingInterval);
disconnect(client.id);
})
ws.on("error", err => {
console.error("[TCP]", client.id, err) // it can happen
})
})
netServer.on("connection", socket => {
let client = {} as Client;
client.uuid = random();
client.id = random(undefined, "S");
client.auth = false;
client.type = "socket";
client.client = socket as net.Socket;
client.send = function (data: string | object) {
if (typeof data === "object") data = JSON.stringify(data);
client.client.write(data)
}
clients[client.id] = client;
let pingInterval = setInterval(function () {
client.send({
type: "ping",
uuid: client.uuid,
ping: Date.now(),
})
}, 10000);
client.send({
type: "motd",
motd: config.motd || "soqet",
uuid: client.uuid,
});
console.log("[TCP]", `Client connected: ${client.id}`);
socket.on("data", data => {
onMessage(client, data);
})
socket.on("close", (code: any, reason: any) => { // WS Client disconnects
console.log("[TCP]", `Client disconnected ${client.id} (${code} ${reason})`);
clearInterval(pingInterval); // Clear Ping interval
// remove uuid from all channels
disconnect(client.id);
});
socket.on("error", (err) => {
console.error("[TCP]", client.id, err) // it can happen
});
})
app.use(express.json({
limit: "10mb"
}));
let pollingRouter = express.Router();
// Create a polling connection
pollingRouter.get("/connect", (req, res, next) => {
let query = req.query;
let uuid = random();
if (query.token) {
uuid = createID(query.token as string);
}
let sessionToken = random(127, "$"); // will be used as ID
let client = {} as PollingClient;
client.uuid = uuid;
client.id = random();
client.auth = false;
client.type = "polling";
client.client = req;
client.queue = [];
client.token = sessionToken;
client.send = function (data: string | object) {
if (typeof data === "string") data = JSON.parse(data);
client.queue.push(data);
}
pollingTokens[sessionToken] = client.id;
clients[client.id] = client;
console.log("[POL]", `Client connected: ${client.id}`);
return res.json({
ok: true,
motd: config.motd || "soqet",
uuid: client.uuid,
token: sessionToken,
})
});
pollingRouter.use("*", function (req, res, next) {
let sessionToken = req.body.token as string;
if (!sessionToken || !pollingTokens[sessionToken]) {
return res.status(401).json({
ok: false,
error: "Invalid token",
})
}
next();
})
// Open a channel
pollingRouter.post("/open", (req, res, next) => {
let client = clients[pollingTokens[req.body.token]];
let channel = req.body.channel as string | number;
if (!channel) {
return res.status(400).json({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
})
}
if ((typeof channel === "string" && channel.length <= 256) || typeof channel === "number") {
openChannel(client.id, channel);
res.json({
ok: true,
uuid: client.uuid,
});
} else {
return res.status(400).json({
ok: false,
error: "Invalid channel field",
uuid: client.uuid,
})
}
})
// Close a channel
pollingRouter.post("/close", (req, res, next) => {
let client = clients[pollingTokens[req.body.token]];
let channel = req.body.channel as string | number;
if (!channel) {
return res.status(400).json({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
})
}
closeChannel(client.id, channel);
res.json({
ok: true,
uuid: client.uuid,
});
})
// Transmit a message
pollingRouter.post("/send", (req, res, next) => {
let client = clients[pollingTokens[req.body.token]];
let channel = req.body.channel as string | number;
if (!channel) {
return res.status(400).json({
ok: false,
error: "Missing channel field",
uuid: client.uuid,
})
}
let message: any = req.body.message;
let meta: any = req.body.meta;
// create the message meta
send(client, channel, message, meta || {}); // proceed to send the message
res.json({
ok: true,
uuid: client.uuid,
});
})
// Request queue
pollingRouter.post("/update", (req, res, next) => {
let client = clients[pollingTokens[req.body.token]] as PollingClient;
res.json({
ok: true,
uuid: client.uuid,
queue: client.queue,
});
client.lastPing = Date.now();
client.queue = [];
})
app.use("/api/", pollingRouter);
app.use(express.static("public"));
netServer.on("listening", () => {
console.log("Listening on TCP port", config.tcp_port)
})
app.listen(config.port, () => {
console.log("Listening on HTTP port", config.port);
setInterval(function () {
let pClients = Object.keys(clients)
.filter(key => key.startsWith("$"))
.reduce((obj, key) => {
obj.push(key);
return obj;
}, [] as Array<string>);
pClients.forEach(id => {
let v = clients[id] as PollingClient;
if ((Date.now() - v.lastPing) > 60000) {
console.log("[POL]", `Client connected: ${id}`);
let clientToken = (clients[id] as PollingClient).token;
delete clients[id];
delete pollingTokens[clientToken]
}
})
}, 60000)
});
netServer.listen(config.tcp_port);

376
legacy.js
View File

@ -1,376 +0,0 @@
/*
Soqet Server by Ale32bit
MIT License
*/
const WebSocket = require("ws");
const net = require("net");
const crypto = require("crypto");
const config = require("./config.json");
const channels = {};
const clients = [];
const WILDCARD = '*';
// ---- FUNCTIONS ----
function sha256(str) { // Hash a string using SHA256
return crypto.createHash("sha256").update(str).digest("hex");
}
function random(len = 16, prefix = "g") { // Generate a random secure string in hex
return prefix + crypto.randomBytes(len).toString("hex");
}
function randomToken() { // Same with random but in base64 and non-alphanumerical chars removed -- Currently not used
return crypto.randomBytes(42).toString("base64").replace(/[^a-zA-Z0-9 -]/g, "")
}
function incRandom(min, max) { // random number inclusive
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min
}
function hexToBase36(input) { // hexadecimal number to base 36
const byte = 48 + Math.floor(input / 7);
return String.fromCharCode(byte + 39 > 122 ? 101 : byte > 57 ? byte + 39 : byte);
}
function createID(token) { // From krist-utils, generate an ID from the token, like an HASH
token = sha256(token);
let prefix = "a";
let len = 31;
let hash = sha256(sha256(token));
let chars = [];
for (let i = 0; i <= len; i++) {
chars[i] = hash.substring(0, 2);
hash = sha256(sha256(hash));
}
for (let i = 0; i <= len;) {
const index = parseInt(hash.substring(2 * i, 2 + (2 * i)), 16) % (len + 1);
if (!chars[index]) {
hash = sha256(hash);
} else {
prefix += hexToBase36(parseInt(chars[index], 16));
chars[index] = undefined;
i++;
}
}
return prefix;
}
function disconnect(sID) { // remove a client UUID from all channels.
for (let chn in channels) {
let ch = channels[chn];
for (let i = 0; i < ch.length; i++) {
if (ch[i] === sID) {
let index = ch.indexOf(sID);
ch.splice(index, 1)
}
}
if (ch.length === 0) {
delete channels[chn]
}
}
}
const server = new WebSocket.Server({ // create the websocket server, port is from config.json
port: config.port,
});
const netServer = net.createServer();
function getClient(sID) { // just a for loop to get the ws client from the session ID
for (let item of clients) {
if (item.sID === sID) {
return item;
}
}
}
function transmit(channel, message, meta, ignore = null) { // transmit a message to the channel. WILDCARD channel is read-only
try {
if (channel === WILDCARD) { // prevents from sending a message directly to WILDCARD channel
return;
}
if(channels[channel]) {
channels[channel].forEach(sID => {
if (ignore === sID) return;
let ws = getClient(sID);
if (ws) {
ws.send(JSON.stringify({
type: "message",
channel: channel,
message: message,
meta: meta,
}))
}
});
}
if (channels[WILDCARD]) { // send message to WILDCARD channel
channels[WILDCARD].forEach(sID => {
let ws = getClient(sID);
if (ws) {
ws.send(JSON.stringify({
type: "message",
channel: WILDCARD,
message: message,
meta: meta,
}))
}
})
}
} catch (e) { // this code kept crashing, no longer happens
console.error(e);
}
}
function onMessage(ws) {
return function(message) {
let data;
try {
data = JSON.parse(message);
} catch (e) {
return ws.send(JSON.stringify({
ok: false,
error: "Invalid data format",
uuid: ws.uuid,
}));
}
data.id = Number.parseInt(data.id) || 1; // if request ID is invalid or nonexistend, define it as 1
if (!data.type) { // requests require type field
return ws.send(JSON.stringify({
ok: false,
error: "Invalid request",
uuid: ws.uuid,
id: data.id,
}))
}
switch (data.type) {
case "send": // Send a message to a channel
if (!data.channel) {
return ws.send(JSON.stringify({
ok: false,
error: "Missing channel field",
uuid: ws.uuid,
id: data.id,
}))
}
// create the message meta
let meta = typeof data.meta === "object" && !Array.isArray(data.meta) ? data.meta : {};
meta.uuid = ws.uuid; // sender uuid
meta.time = Date.now(); // time of sending
meta.channel = data.channel; // channel
meta.guest = !ws.auth; // if not authenticated
transmit(data.channel, data.message, meta, ws.sID); // proceed to send the message
ws.send(JSON.stringify({
ok: true,
id: data.id,
uuid: ws.uuid,
}));
break;
case "open": // Open channel
if (!data.channel) {
return ws.send(JSON.stringify({
ok: false,
error: "Missing channel field",
uuid: ws.uuid,
id: data.id,
}))
}
// limit of channel name:
// Must be either a string long max 256 chars or a number
if ((typeof data.channel === "string" && data.channel.length <= 256) || typeof data.channel === "number") {
if (!channels[data.channel]) channels[data.channel] = [];
channels[data.channel].push(ws.sID);
ws.send(JSON.stringify({
ok: true,
id: data.id,
uuid: ws.uuid,
}));
} else {
return ws.send(JSON.stringify({
ok: false,
error: "Invalid channel field",
uuid: ws.uuid,
id: data.id,
}))
}
break;
case "close": // close a channel
if (!data.channel) {
return ws.send(JSON.stringify({
ok: false,
error: "Missing channel field",
uuid: ws.uuid,
id: data.id,
}))
}
if (channels[data.channel]) { // remove uuid from the channel
let index = channels[data.channel].indexOf(ws.sID);
if (index >= 0) {
channels[data.channel].splice(index, 1);
}
}
ws.send(JSON.stringify({
ok: true,
id: data.id,
uuid: ws.uuid,
}));
break;
case "ping": // allow clients to ping the server if they want to
ws.send(JSON.stringify({
ok: true,
id: data.id,
uuid: ws.uuid,
}));
break;
case "auth": // authentication
if (!data.token) {
return ws.send(JSON.stringify({
ok: false,
error: "Missing token field",
uuid: ws.uuid,
id: data.id,
}))
}
let authid = createID(data.token); // create the UUID from the token
let olduuid = ws.uuid;
ws.uuid = authid; // set new UUID
ws.auth = true; // set as authenticated
console.log(`AUTH: ${olduuid} is now ${ws.uuid}`);
return ws.send(JSON.stringify({
ok: true,
uuid: ws.uuid,
id: data.id,
}));
break;
default: // if no request type is found send this as error
ws.send(JSON.stringify({
ok: false,
error: "Invalid request",
uuid: ws.uuid,
id: data.id,
}))
}
}
}
server.on("connection", ws => { // Listen to clients connecting to the websocket server
ws.uuid = random(); // assign a random UUID as guest
ws.sID = random(undefined, "S"); // Session ID
ws.auth = false; // not authenticated by default
ws.type = "websocket";
clients.push(ws);
console.log("Connect:", ws.uuid, ws.sID);
let pingInterval = setInterval(function () { // Send a ping to the client every 10 seconds to keep the connection alive
ws.send(JSON.stringify({
type: "ping",
uuid: ws.uuid,
ping: Date.now(),
}))
}, 10000);
ws.send(JSON.stringify({ // A friendly message upon connection
type: "motd",
motd: "Welcome to the Soqet network",
uuid: ws.uuid,
}));
ws.on("message", onMessage(ws));
ws.on("close", (code, reason) => { // WS Client disconnects
console.log("Close:", ws.uuid, ws.sID, `(${code} ${reason})`);
clearInterval(pingInterval); // Clear Ping interval
// remove uuid from all channels
disconnect(ws.sID);
});
ws.on("error", (err) => {
console.error(ws.uuid, ws.sID, err) // it can happen
});
});
netServer.on("connection", socket => {
socket.send = function(data) {
return socket.write(data)
}
socket.uuid = random();
socket.sID = random(undefined, "S");
socket.auth = false;
socket.type = "socket"
clients.push(socket)
console.log("TCP Connect:", socket.uuid, socket.sID);
let pingInterval = setInterval(function () { // Send a ping to the client every 10 seconds to keep the connection alive
socket.send(JSON.stringify({
type: "ping",
uuid: socket.uuid,
ping: Date.now(),
}))
}, 10000);
socket.send(JSON.stringify({ // A friendly message upon connection
type: "motd",
motd: "Welcome to the Soqet network",
uuid: socket.uuid,
}));
socket.on("data", onMessage(socket));
socket.on("close", (code, reason) => { // WS Client disconnects
console.log("Close:", socket.uuid, socket.sID, `(${code} ${reason})`);
clearInterval(pingInterval); // Clear Ping interval
// remove uuid from all channels
disconnect(socket.sID);
});
socket.on("error", (err) => {
console.error(socket.uuid, socket.sID, err) // it can happen
});
})
netServer.listen(config.tcp_port);
// hopefully this service will help cc communities

618
package-lock.json generated
View File

@ -1,558 +1,98 @@
{
"name": "soqet",
"version": "3.0.0",
"lockfileVersion": 1,
"version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "soqet",
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"ws": "^7.5.5"
},
"devDependencies": {
"@types/node": "^14.17.19",
"@types/ws": "^7.4.7",
"typescript": "^4.4.3"
}
},
"node_modules/@types/node": {
"version": "14.17.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz",
"integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==",
"dev": true
},
"node_modules/@types/ws": {
"version": "7.4.7",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
"integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/typescript": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/ws": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz",
"integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"@types/body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.33",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.8",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz",
"integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz",
"integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*"
}
},
"@types/express-ws": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.0.tgz",
"integrity": "sha512-GxsWec7Vp6h7sJuK0PwnZHeXNZnOwQn8kHAbCfvii66it5jXHTWzSg5cgHVtESwJfBLOe9SJ5wmM7C6gsDoyQw==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/express-serve-static-core": "*",
"@types/ws": "*"
}
},
"@types/mime": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
"dev": true
},
"@types/node": {
"version": "14.6.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz",
"integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==",
"version": "14.17.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz",
"integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==",
"dev": true
},
"@types/qs": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz",
"integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/serve-static": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz",
"integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==",
"dev": true,
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
}
},
"@types/ws": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz",
"integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==",
"version": "7.4.7",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
"integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"express-ws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-4.0.0.tgz",
"integrity": "sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==",
"requires": {
"ws": "^5.2.0"
},
"dependencies": {
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
},
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"requires": {
"mime-db": "1.44.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"ts-node": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
"integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"typescript": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
"ws": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz",
"integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==",
"requires": {}
}
}
}

View File

@ -1,25 +1,21 @@
{
"name": "soqet",
"version": "3.0.0",
"version": "2.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"tsc": "tsc",
"start": "tsc && node index.js"
"build": "echo Building... && tsc",
"start": "echo Starting... && node ./dist/"
},
"keywords": [],
"author": "",
"author": "AlexDevs",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"express-ws": "^4.0.0"
"ws": "^7.5.5"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/express-ws": "^3.0.0",
"@types/node": "^14.6.4",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
"@types/node": "^14.17.19",
"@types/ws": "^7.4.7",
"typescript": "^4.4.3"
}
}

View File

@ -108,7 +108,8 @@
<li><a style="font-size: 32px; font-weight: 600;">Soqet Docs</a></li>
<li><a href="#polling">Polling</a></li>
<li><a href="/ui.html">Soqet Viewer</a></li>
<li><a href="https://raw.githubusercontent.com/Ale32bit/Soqet/master/soqet.lua">Soqet.lua</a></li>
<li><a href="https://raw.githubusercontent.com/Ale32bit/Soqet/master/libs/cc_soqet.lua">CC Soqet</a></li>
<li><a href="https://raw.githubusercontent.com/Ale32bit/Soqet/master/libs/oc_soqet.lua">OC Soqet</a></li>
<li><a href="https://github.com/Ale32bit/Soqet">Source code</a></li>
</ul>
</div>
@ -292,12 +293,15 @@
<h3>Connection</h3>
<div class="sub">
<p>Polling connection is possible via the GET URL
<code>https://soqet.alexdevs.pw/api/connect?token=[optional token here]</code>.</p>
<code>https://soqet.alexdevs.pw/api/connect?token=[optional token here]</code>.
</p>
<p>Response is in <code>application/json</code> type and contains the session token used for requests in the
<code>token</code> field.</p>
<code>token</code> field.
</p>
<p>Field <code>motd</code> is also supplied upon connection.</p>
<p><b>Once connected you need to request <code>/api/update</code> at least once every 60 seconds to keep the session token alive!</b>
<p><b>Once connected you need to request <code>/api/update</code> at least once every 60 seconds to keep the
session token alive!</b>
</p>
</div>
@ -384,7 +388,8 @@
<h2 id="events">Events</h2>
<div class="sub">
<p>The events are messages sent by the server automatically and do not directly require any request (unless client is using polling).</p>
<p>The events are messages sent by the server automatically and do not directly require any request (unless
client is using polling).</p>
<p>Currently the events are:</p>
<h4>Ping <code>ping</code></h4>
@ -487,7 +492,8 @@
<tr>
<td>motd</td>
<td>string</td>
<td><i>Inspiring quotes to help the developer and the user get over problems and easily achieve life goals.</i></td>
<td><i>Inspiring quotes to help the developer and the user get over problems and easily achieve life
goals.</i></td>
</tr>
</table>

View File

@ -3,16 +3,38 @@
<!-- This code kinda sucks. Pardon me -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Soqet Viewer</title>
<link rel="stylesheet" type="text/css" href="ui.css">
<noscript><a style="color:white;">JavaScript is required to run!</a></noscript>
</head>
<body>
<div style="width: 100%">
<a style="color: #8a8a8a; display:none;"></a>
<div id="escape" style="display: none;"></div>
<div class="displaychat">
<div id="container" style="display: block;">
<div class="chat" id="chat">
<noscript><a style="color:white;">JavaScript is required to run!</a></noscript>
</div>
<div class="textinput" id="textinput">
<form onsubmit="return sendMessage()">
<input id="channel" type="text" placeholder="Channel" autocomplete="true" style="width: 20%">
<input id="sendMsg" type="text" placeholder="Message" autocomplete="true" autofocus>
<input type="submit" value="Send" id="submitMsg">
</form>
</div>
</div>
</div>
</div>
<script>if (typeof module === 'object') {
window.module = module;
module = undefined;
}</script>
window.module = module;
module = undefined;
}</script>
<script type="text/javascript">
let channels = {};
@ -30,21 +52,21 @@
var message = document.getElementById("sendMsg").value;
var channel = document.getElementById("channel").value;
channel = Number.parseInt(channel) || channel;
message = (function(){
message = (function () {
try {
return JSON.parse(message);
} catch(e) {
} catch (e) {
return message;
}
})();
if(!channels[channel]) {
if (!channels[channel]) {
ws.send(JSON.stringify({
type: "open",
channel: channel,
}));
channels[channel] = true
}
addLine("> "+message);
addLine("> " + message);
if (message !== "") {
ws.send(JSON.stringify({
@ -67,10 +89,10 @@
ws.addEventListener("message", function (event) { // Listen to all messages from server
var data = JSON.parse(event.data); // Parse plain text to JSON
console.log(data);
if(data.type === "message") {
if (data.type === "message") {
addLine(`< [${data.channel}] <a style="color: #31D3FF">${JSON.stringify(data.message)}</a> <a style="color: #8a8a8a">${JSON.stringify(data.meta)}</a>`);
} else {
if(data.type !== "ping") {
if (data.type !== "ping") {
addLine(`< <a style="color: #8a8a8a">${JSON.stringify(data)}</a>`);
}
}
@ -81,26 +103,6 @@
startWS(); // Start WS for the first time
</script>
<script>if (window.module) module = window.module;</script>
</head>
<body>
<div style="width: 100%">
<a style="color: #8a8a8a; display:none;"></a>
<div id="escape" style="display: none;"></div>
<div class="displaychat">
<div id="container" style="display: block;">
<div class="chat" id="chat">
</div>
<div class="textinput" id="textinput">
<form onsubmit="return sendMessage()">
<input id="channel" type="text" placeholder="Channel" autocomplete="true" style="width: 20%">
<input id="sendMsg" type="text" placeholder="Message" autocomplete="true" autofocus>
<input type="submit" value="Send" id="submitMsg">
</form>
</div>
</div>
</div>
</div>
</body>
</html>
</html>

8
soqet-ts.iml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

98
src/common.ts Normal file
View File

@ -0,0 +1,98 @@
import http from "http";
import crypto from "crypto";
import Timeout = NodeJS.Timeout;
export interface Client {
uuid: string,
sessionId: string,
channels: Array<string | number>,
channelsAmount: number,
send: (data: any) => void,
guest: boolean,
socket?: any,
}
export interface PollingClient extends Client {
pollingTimeout: Timeout,
pollingQueue: any[],
pollingToken: string,
}
export interface Server {
clients: {
[key: string]: Client | PollingClient,
},
channels: {
[key: string]: Array<string>,
[key: number]: Array<string>,
}
httpServer: http.Server,
config: {
motd: string,
httpPort: number,
tcpPort: number,
enableWebsocket: boolean,
enableTCPSocket: boolean,
enablePolling: boolean,
wildcardChannel: string,
pollingInterval: number,
},
log: (...data: any) => void,
processData: (sessionId: string, data: { [key: string]: any }) => void,
buildClient: (send: (data: any) => void, token?: string) => Client | PollingClient;
destroyClient: (sessionId: string) => void,
[key: string]: any,
}
export interface MetaMessage {
uuid: string,
time: number,
channel: string | number,
guest: boolean,
[key: string]: any,
}
export function sha256(input: string): Buffer {
return crypto.createHash("sha256").update(input).digest();
}
export function randomString(length: number = 16): string {
let out = "";
let random = crypto.randomBytes(length);
for (let i = 0; i < random.length; i++) {
out += random[i].toString(36)
}
return out;
}
export function createUniqueUUID(clients: { [key: string]: Client }): string {
let uuid: string;
while (true) {
uuid = randomString(16);
if (!clients[uuid]) break;
}
return uuid;
}
export function resolveToken(token: string): string {
let buff = sha256(token);
let uuid: Array<string> = [];
for (let i = 0; i < 16; i++) {
buff = sha256(buff.toString("hex"));
uuid[i] = buff[0].toString(36);
}
return uuid.join("");
}

307
src/index.ts Normal file
View File

@ -0,0 +1,307 @@
/*
Soqet v2
*/
import * as http from "http";
import { Client, createUniqueUUID, MetaMessage, resolveToken, Server } from "./common";
import websocket from "./protocols/websocket";
import tcpsocket from "./protocols/tcpsocket";
import pollingsocket from "./protocols/pollingsocket";
const config = require("../config.json");
const pack = require("../package.json");
console.log(`Soqet ${pack.version}`);
function openChannel(sessionId: string, channel: string | number) {
if (!channel) {
return {
ok: false,
error: "Missing channel field",
};
}
if (!(["string", "number"].includes(typeof channel))) {
return {
ok: false,
error: "Channel must either be a string or a number",
}
}
// Create channel if it does not exist
if (!server.channels[channel]) server.channels[channel] = [];
// Add client to channel
let channelArray = server.channels[channel];
// DO not add it if it's already open
if (channelArray.indexOf(sessionId) !== -1) {
return {
ok: true
};
}
channelArray.push(sessionId);
// Add channel to client
let client = server.clients[sessionId];
client.channels.push(channel);
return {
ok: true,
};
}
function closeChannel(sessionId: string, channel: string | number) {
if (!channel) {
return {
ok: false,
error: "Missing channel field",
};
}
if (!(["string", "number"].includes(typeof channel))) {
return {
ok: false,
error: "Channel must either be a string or a number",
}
}
// Does not really matter if it does not exist
if (!server.channels[channel]) {
return {
ok: true,
}
}
let channelArray = server.channels[channel];
let clientIndex = channelArray.indexOf(sessionId);
if (clientIndex === -1) {
return {
ok: true,
}
}
channelArray.splice(clientIndex, 1);
if (channelArray.length === 0) {
delete server.channels[channel];
}
let client: Client = server.clients[sessionId];
let channelIndex = client.channels.indexOf(channel);
client.channels.splice(channelIndex, 1);
return {
ok: true,
};
}
function transmitMessage(sessionId: string, channel: string | number, message: any, rawMeta: unknown) {
if (!(["string", "number"].includes(typeof channel))) {
return {
ok: false,
error: "Channel must either be a string or a number",
}
}
if (channel === config.wildcardChannel) return {
ok: false,
error: config.wildcardChannel + " is read-only",
}
// Build the message meta
let meta: MetaMessage = (rawMeta || {}) as MetaMessage;
let client = server.clients[sessionId];
meta.uuid = client.uuid;
meta.time = Date.now();
meta.channel = channel;
meta.guest = client.guest;
// Send message to the channel
if (server.channels[channel]) {
let channelArray = server.channels[channel];
channelArray.forEach(recipientId => {
if (recipientId === sessionId) return;
server.clients[recipientId].send({
type: "message",
channel: channel,
message: message,
meta: meta,
})
})
}
// Send message to wildcard channel
let wildcardChannelArray = server.channels[config.wildcardChannel];
if (wildcardChannelArray) {
wildcardChannelArray.forEach(recipientId => {
server.clients[recipientId].send({
type: "message",
channel: config.wildcardChannel,
message: message,
meta: meta,
})
})
}
return {
ok: true,
};
}
function authenticateClient(sessionId: string, token: string) {
if (!token) {
return {
ok: false,
error: "Missing token field",
}
}
if (typeof token !== "string") {
return {
ok: false,
error: "Token must be a string",
}
}
server.clients[sessionId].uuid = resolveToken(token);
server.clients[sessionId].guest = true;
return {
ok: true
};
}
function destroyClient(sessionId: string) {
let channels = server.clients[sessionId].channels;
for (let channelName in channels) {
closeChannel(sessionId, channelName);
}
delete server.clients[sessionId];
}
function buildClient(send: (data: any) => void, token?: string): Client {
let client: Client = {
sessionId: Date.now().toString(36),
uuid: createUniqueUUID(server.clients),
channels: [],
channelsAmount: 0,
guest: true,
send,
};
server.clients[client.sessionId] = client;
if (token) {
authenticateClient(client.sessionId, token);
}
return client;
}
function processData(sessionId: string, data: {
[key: string]: any,
}): void {
let client = server.clients[sessionId];
let uuid = client.uuid;
let id = data.id || 1;
let response;
if (!data.type) {
return client.send({
ok: false,
error: "Invalid request",
uuid,
id,
});
}
switch (data.type) {
case "send":
response = transmitMessage(sessionId, data.channel, data.message, data.meta) as any;
response.uuid = client.uuid;
response.id = id;
client.send(response);
break;
case "open":
response = openChannel(sessionId, data.channel) as any;
response.uuid = client.uuid;
response.id = id;
client.send(response);
break;
case "close":
response = openChannel(sessionId, data.channel) as any;
response.uuid = client.uuid;
response.id = id;
client.send(response);
break;
case "auth":
response = authenticateClient(sessionId, data.token) as any;
response.uuid = client.uuid;
response.id = id;
client.send(response);
break;
case "ping":
client.send({
ok: true,
uuid,
id,
})
break;
default:
client.send({
ok: false,
error: "Invalid request",
uuid,
id,
})
break;
}
}
const server: Server = {
clients: {},
channels: {},
config: config,
httpServer: http.createServer(),
log: function (...data) {
console.log(...data);
},
processData: processData,
buildClient: buildClient,
destroyClient: destroyClient,
};
server.httpServer.on("listening", () => {
server.log("[HTTP] Ready");
})
websocket(server);
tcpsocket(server);
pollingsocket(server);
server.httpServer.listen(config.httpPort);

View File

@ -0,0 +1,12 @@
import http from "http";
export interface Request extends http.IncomingMessage {
query: { [key: string]: any },
body: string,
params: { [key: string]: any },
}
export interface Response extends http.ServerResponse {
json: (data: any) => Response,
status: (statusCode: number) => Response,
}

View File

@ -0,0 +1,160 @@
import * as common from "../../common";
import routers from "./polling_routers";
import http from "http";
import url from "url";
import {Request, Response} from "./common";
let server: common.Server;
let clientTokens: {
[key: string]: string,
} = {};
let routes: {
GET: { [key: string]: any },
POST: { [key: string]: any },
} = {
GET: {},
POST: {},
}
function resolveClient(token: string): common.PollingClient | null {
if (!token) return null;
if (clientTokens[token]) {
return server.clients[clientTokens[token]] as common.PollingClient;
}
return null;
}
function get(path: string, callback: (req: Request, res: Response) => void) {
routes.GET[path] = callback;
}
function post(path: string, callback: (req: Request, res: Response) => void) {
routes.POST[path] = callback;
}
function updateClient(sessionId: string) {
let client = server.clients[sessionId] as common.PollingClient;
if (client.pollingTimeout) {
clearTimeout(client.pollingTimeout);
}
client.pollingTimeout = setTimeout(() => {
server.destroyClient(client.sessionId);
}, server.config.pollingInterval * 1000);
}
function getClient(req: Request, res: Response): common.PollingClient | null {
if (!req.params.token) {
res.status(400).json({
ok: false,
error: "Missing token field",
})
return null
}
let client = resolveClient(req.params.token);
if (!client) {
res.status(400).json({
ok: false,
error: "Invalid token",
})
return null;
}
updateClient(client.sessionId);
return client;
}
let app = {
get,
post,
getClient,
clientTokens,
};
export default function run(srv: common.Server) {
server = srv;
if (!server.config.enablePolling) {
console.log("HTTP Long Polling is disabled!");
return;
}
routers(srv, app);
server.httpServer.on("request", (req: http.IncomingMessage, res: http.ServerResponse) => {
req.url = req.url || "/";
let reqUrl = url.parse(req.url, true);
let request: Request = req as Request;
request.query = reqUrl.query;
let response: Response = res as Response;
response.status = function (statusCode: number) {
res.statusCode = statusCode;
return response;
}
response.json = function (data: any) {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(data));
return response;
}
let routed = false;
let path = reqUrl.pathname;
if (request.method === "GET") {
if (routes.GET[path as string]) {
routed = true;
try {
return routes.GET[path as string](request, response)
} catch (e) {
return server.log(e);
}
}
} else if (request.method === "POST") {
if (routes.POST[path as string]) {
routed = true;
let body = "";
req.on("data", chunk => {
body += chunk.toString();
});
req.on("end", () => {
request.body = body;
request.params;
try {
request.params = JSON.parse(request.body);
} catch (e) {
request.params = {};
}
try {
return routes.POST[path as string](request, response)
} catch (e) {
return server.log(e);
}
})
}
}
if (!routed) {
res.writeHead(404, {
"Content-Type": "application/json",
})
res.end(JSON.stringify({
ok: false,
error: "File not found",
}))
}
})
}

View File

@ -0,0 +1,126 @@
import * as common from "../../common";
import {Request, Response} from "./common";
import * as fs from "fs";
import * as path from "path";
export default function routes(server: common.Server, app: any) {
app.get("/", function(req: Request, res: Response) {
res.writeHead(302, {
'Location': 'index.html'
});
res.end();
});
app.get("/api/connect", function (req: Request, res: Response) {
let query = req.query;
let queue: any[] = [];
function send(data: any) {
queue.push(data);
}
let client: common.PollingClient = server.buildClient(send, query.token) as common.PollingClient;
client.pollingQueue = queue;
client.pollingToken = "$" + common.randomString(63);
app.clientTokens[client.pollingToken] = client.sessionId;
res.json({
ok: true,
motd: server.config.motd,
uuid: client.uuid,
token: client.pollingToken,
})
});
// POST PATHS HERE
app.post("/api/send", function (req: Request, res: Response) {
let client = app.getClient(req, res);
if (!client) return;
let data = {
type: "send",
message: req.params.message,
channel: req.params.channel,
meta: req.params.meta,
id: req.params.id,
};
server.processData(client.sessionId, data);
let result = client.pollingQueue.pop();
//res.status(result.ok ? 200 : 400);
res.json(result);
})
app.post("/api/open", function (req: Request, res: Response) {
let client = app.getClient(req, res);
if (!client) return;
let data = {
type: "open",
channel: req.params.channel,
id: req.params.id,
}
server.processData(client.sessionId, data);
let result = client.pollingQueue.pop();
res.json(result);
});
app.post("/api/close", function (req: Request, res: Response) {
let client = app.getClient(req, res);
if (!client) return
let data = {
type: "close",
channel: req.params.channel,
id: req.params.id,
}
server.processData(client.sessionId, data);
let result = client.pollingQueue.pop();
res.json(result);
})
app.post("/api/auth", function (req: Request, res: Response) {
let client = app.getClient(req, res);
if (!client) return;
let data = {
type: "auth",
token: req.params.token,
id: req.params.id,
}
server.processData(client.sessionId, data);
let result = client.pollingQueue.pop();
res.json(result);
})
app.post("/api/update", function (req: Request, res: Response) {
let client = app.getClient(req, res);
if (!client)
return;
res.status(200).json({
ok: true,
queue: client.pollingQueue,
uuid: client.uuid,
id: req.params.id || 1,
})
client.pollingQueue.length = 0;
})
}
// END ROUTES

View File

@ -0,0 +1,66 @@
import * as common from "../common";
import net from "net";
export default function run(server: common.Server) {
if (!server.config.enableTCPSocket) {
console.log("TCP Socket is disabled!");
return;
}
const netServer = net.createServer();
netServer.on("connection", socket => {
let send = function (data: any) {
if (socket.writable) {
socket.write(JSON.stringify(data));
}
}
let client = server.buildClient(send);
client.socket = socket;
let pingInterval = setInterval(function () {
send({
type: "ping",
uuid: client.uuid,
})
}, 10000);
socket.on("data", message => {
console.log(message);
let data;
try {
data = JSON.parse(message.toString());
} catch (e) {
return send({
ok: false,
error: "Invalid data format",
uuid: client.uuid,
})
}
server.processData(client.sessionId, data);
})
socket.on("error", err => {
server.log("[TCP Error]", client.sessionId, err);
})
socket.on("close", () => {
server.log("[TCP Close]", client.sessionId);
clearInterval(pingInterval);
})
send({
type: "motd",
motd: server.config.motd,
uuid: client.uuid,
})
})
netServer.on("listening", () => {
server.log("[TCP]", "Ready");
})
netServer.listen(server.config.tcpPort);
}

View File

@ -0,0 +1,77 @@
import * as common from "../common";
import WebSocket from "ws";
export default function run(server: common.Server): void {
if (!server.config.enableWebsocket) {
console.log("WebSocket is disabled!");
return;
}
const wss = new WebSocket.Server({
noServer: true,
})
if (!server.httpServer) {
console.log("HTTP server not yet initialized?")
return;
}
server.httpServer.on("upgrade", (req: any, socket: any, head: any) => {
wss.handleUpgrade(req, socket, head, ws => {
wss.emit("connection", ws);
})
});
wss.on("connection", (ws) => {
function send (data: any) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
let client = server.buildClient(send);
client.socket = ws;
let pingInterval = setInterval(function () {
send({
type: "ping",
uuid: client.uuid,
})
}, 10000)
ws.on("message", message => {
let data;
try {
data = JSON.parse(message.toString());
} catch (e) {
return send({
ok: false,
error: "Invalid data format",
uuid: client.uuid,
});
}
server.processData(client.sessionId, data);
})
ws.on("error", err => {
server.log("[WS Error]", client.sessionId, err)
})
ws.on("close", (code, reason) => {
server.log("[WS Close]", client.sessionId, code, reason);
clearInterval(pingInterval);
})
send({
type: "motd",
motd: server.config.motd,
uuid: client.uuid,
})
});
wss.on("listening", () => {
server.log("[WS] Ready");
})
}

View File

@ -14,11 +14,11 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
"outDir": "./dist/", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
"removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
@ -41,7 +41,7 @@
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
@ -65,5 +65,9 @@
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
},
"include": [
"src/**/*"
]
}