mirror of https://github.com/Ale32bit/Soqet.git
Compare commits
2 Commits
8dccfa1c5e
...
7df6b9dc06
Author | SHA1 | Date |
---|---|---|
Alessandro | 7df6b9dc06 | |
Alessandro | 80c8a60dd5 |
|
@ -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/
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"Lua.diagnostics.disable": [
|
||||
"undefined-global"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
12
config.json
12
config.json
|
@ -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
589
index.ts
|
@ -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
376
legacy.js
|
@ -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
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
20
package.json
20
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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("");
|
||||
}
|
|
@ -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);
|
|
@ -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,
|
||||
}
|
|
@ -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",
|
||||
}))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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");
|
||||
})
|
||||
}
|
|
@ -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/**/*"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue