let's just bury v1

This commit is contained in:
Alessandro 2021-09-25 20:04:11 +02:00
parent 80c8a60dd5
commit 7df6b9dc06
12 changed files with 0 additions and 3046 deletions

View File

@ -1,6 +0,0 @@
{
"port": 3004,
"tcp_port": 25555,
"wildcard_channel": "*",
"motd": "Welcome to the Soqet Network"
}

View File

@ -1,500 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Soqet Documentation</title>
<style>
body {
background-color: #004d40;
color: white;
font-family: sans-serif;
margin: 0;
}
.top {
border-bottom: white solid 2px;
width: 100%;
}
.top a {
color: white;
}
.top h1 {
padding-left: 10px;
}
.top ul {
list-style-type: none;
}
.top li {
display: inline;
}
.top li a {
padding: 14px 16px;
}
.top li a:link,
a:visited,
a:hover,
a:active {
color: white;
}
.container {
padding-left: 20px;
}
.container h3 {
margin-bottom: 0;
}
.container .sub {
padding-left: 20px;
margin: 5px;
}
.container table {
border-collapse: collapse;
}
.container th {
height: 20px;
text-align: center;
padding-right: 20px;
padding-left: 20px;
}
.container td {
text-align: left;
padding: 5px;
}
.container table,
th,
td {
background-color: #263238;
border: 1px solid #546e7a;
}
.container code {
background-color: #263238;
padding: 2px;
font-weight: 600;
font-size: 16px;
font-family: monospace;
}
.codeblock {
background-color: #263238;
padding: 3px;
font-weight: 600;
font-size: 16px;
font-family: monospace;
width: 450px;
border: 1px solid #546e7a;
}
</style>
</head>
<body>
<div class="top">
<ul>
<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://github.com/Ale32bit/Soqet">Source code</a></li>
</ul>
</div>
<div class="container">
<h2 id="sockets">WebSockets and TCP Sockets</h2>
<div class="sub">
<p>All communications with the server must be in JSON.</p>
<p>Responses of client requests will always contain the following fields:</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>ok</td>
<td><i>boolean</i></td>
<td>Successful request</td>
</tr>
<tr>
<td>error*</td>
<td><i>string</i></td>
<td>Error message (Only if <b>ok</b> is <i>false</i>)</td>
</tr>
<tr>
<td>id</td>
<td><i>number</i></td>
<td>ID of the request provided by the client (defaults to <i>1</i>)</td>
</tr>
<tr>
<td>uuid</td>
<td><i>string</i></td>
<td>UUID of the client session (random if not authenticated)</td>
</tr>
</table>
<p><i>* Not always included</i></p>
</div>
<h3>Connection</h3>
<div class="sub">
<p>Connection is possible via WebSocket from the endpoint <code>wss://soqet.alexdevs.pw/</code> and from the
TCP socket <code>soqet.alexdevs.pw:25555</code></p>
</div>
<h2>Requests</h2>
<p>Requests can optionally have the <code>id</code> field to identify requests, this is to help clients manage
better the server responses.</p>
<h3>Authentication</h3>
<div class="sub">
<p>It's possible to get unique and persistent UUIDs by authenticating with a token.</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>"token"</td>
<td>Required</td>
</tr>
<tr>
<td>token</td>
<td><i>string</i></td>
<td>Token for authentication</td>
</tr>
</table>
</div>
<h3>Opening a channel</h3>
<div class="sub">
<p>Opening channels allows the receiving of messages from other clients.</p>
<p>The <b>Wildcard</b> to listen to all channels is <code>*</code>.</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>"open"</td>
<td>Required</td>
</tr>
<tr>
<td>channel</td>
<td><i>string | number</i></td>
<td>Channel to open</td>
</tr>
</table>
</div>
<h3>Closing a channel</h3>
<div class="sub">
<p>Closing channels will stop getting messages from other clients.</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>"close"</td>
<td>Required</td>
</tr>
<tr>
<td>channel</td>
<td><i>stirng | number</i></td>
<td>Channel to close</td>
</tr>
</table>
</div>
<h3>Sending a message</h3>
<div class="sub">
<p>The main point of this server is to transmit messages remotely.</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>"send"</td>
<td>Required</td>
</tr>
<tr>
<td>channel</td>
<td><i>string | number</i></td>
<td>Channel to send to</td>
</tr>
<tr>
<td>message</td>
<td><i>any</i></td>
<td>The message itself, can be anything.</td>
</tr>
<tr>
<td>meta</td>
<td><i>object</i></td>
<td>The metadata object. See <a href="#message-metadata">metadata</a> for default values.</td>
</tr>
</table>
</div>
<h2 id="polling">Polling</h2>
<div class="sub">
<p>All communications with the server must be in JSON.</p>
<p>Responses of client requests will always contain the following fields:</p>
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td>ok</td>
<td><i>boolean</i></td>
<td>Successful request</td>
</tr>
<tr>
<td>error*</td>
<td><i>string</i></td>
<td>Error message (Only if <b>ok</b> is <i>false</i>)</td>
</tr>
<tr>
<td>uuid</td>
<td><i>string</i></td>
<td>UUID of the client session (random if not authenticated)</td>
</tr>
</table>
<p><i>* Not always included</i></p>
</div>
<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>
<p>Response is in <code>application/json</code> type and contains the session token used for requests in the
<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>
</div>
<h3>REST URLs</h3>
<div class="sub">
<p><b><code>token</code> must always be supplied for the following requests!</b></p>
<h4><b>POST</b> <code>/api/update</code></h4>
<p>Fetch latest updates from the queue</p>
<p><b>This API should be requested often</b></p>
<p>Fields</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>queue</td>
<td>array</td>
<td>The queue contains all events that happened since the last update request.<br>See <a
href="#events">events</a> for a list of possible values.</td>
</tr>
</table>
<h4><b>POST</b> <code>/api/send</code></h4>
<p>Transmit a message to a channel</p>
<p>Fields</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>channel</td>
<td>string | number</td>
<td>The channel name.</td>
</tr>
<tr>
<td>message</td>
<td>any</td>
<td>The message itself, can be anything.</td>
</tr>
<tr>
<td>meta</td>
<td>object</td>
<td>The metadata object. See <a href="#message-metadata">metadata</a> for default values.</td>
</tr>
</table>
<h4><b>POST</b> <code>/api/open</code></h4>
<p>Open a channel to listen to</p>
<p>Fields</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>channel</td>
<td>string | number</td>
<td>The channel name</td>
</tr>
</table>
<h4><b>POST</b> <code>/api/close</code></h4>
<p>Close a channel</p>
<p>Fields</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>channel</td>
<td>string | number</td>
<td>The channel name</td>
</tr>
</table>
</div>
<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>Currently the events are:</p>
<h4>Ping <code>ping</code></h4>
<p>This event is sent every 10 seconds.</p>
<p><i>Not available in polling clients.</i></p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>string</td>
<td>ping</td>
</tr>
<tr>
<td>time</td>
<td>number</td>
<td><i>Unix Epoch in milliseconds</i></td>
</tr>
</table>
<h4>Message <code>message</code></h4>
<p>Message events can only be received by opening channels.</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>string</td>
<td>message</td>
</tr>
<tr>
<td>channel</td>
<td>string / number</td>
<td><i>Channel name</i></td>
</tr>
<tr>
<td>message</td>
<td><i>any</i></td>
<td><i>Message sent by a client</i></td>
</tr>
<tr>
<td>meta</td>
<td>object</td>
<td>Contains metadata</td>
</tr>
</table>
<div class="sub">
<h4 id="message-metadata">Message metadata</h4>
<p>The message metadata contains additional information about the message, any client can add any value
to it.</p>
<p>The server always sends these metadata by default (and overrides client's):</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>uuid</td>
<td>string</td>
<td>UUID of the sender</td>
</tr>
<tr>
<td>channel</td>
<td>string / number</td>
<td><i>Channel name</i></td>
</tr>
<tr>
<td>time</td>
<td>number</td>
<td><i>Unix epoch of the date the message was sent</i></td>
</tr>
<tr>
<td>guest</td>
<td>boolean</td>
<td>If the sender was authenticated</td>
</tr>
</table>
</div>
<h4>Message of the day <code>motd</code></h4>
<p>This event is sent upon connection to the server</p>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>type</td>
<td>string</td>
<td>motd</td>
</tr>
<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>
</tr>
</table>
</div>
</div>
</body>
</html>

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);

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

View File

@ -1,290 +0,0 @@
--[[
-- OC_Soqet.lua --
https://github.com/Ale32bit/Soqet/
MIT License
Copyright (c) 2020 Alessandro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
--[[
-- json.lua --
https://github.com/rxi/json.lua
Copyright (c) 2019 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
local http = require("internet")
local fs = require("filesystem")
if not http then
error("Missing internet card", 2)
end
local function _request(...)
local handle = http.request(...)
handle.finishConnect()
local content = ""
for chunk in handle do
content = content .. chunk
end
return content
end
local function get(url)
return _request(url)
end
local function post(url, data, headers)
return _request(url, data, headers, "POST")
end
if not fs.exists("/lib/json.lua") then
local con = get("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua")
local f = io.open("/lib/json.lua", "w")
f:write(con)
f:close()
end
local function expect(index, value, ...)
local types = {...}
local valueType = type(value)
local valid = false
for _, v in ipairs(types) do
if valueType == v then
valid = true
break
end
end
if not valid then
error(("bad argument #%d (expected %s, got %s)"):format(index, table.concat(types, ", "), valueType), 3)
end
return value
end
local soqet = {
ENDPOINT = "soqet.alexdevs.pw",
ssl = false,
json = require("json"),
credits = "OC_Soqet.lua by AlexDevs"
}
local function postJson(url, data)
return _request(
url,
soqet.json.encode(data),
{
["Content-Type"] = "application/json"
}
)
end
function soqet.new()
error("WebSocket client is not supported. Use long polling instead.", 2)
end
function soqet.poll()
local client = {
channels = {},
uuid = nil,
sessionId = math.random(0xffffff),
sessionToken = nil,
ssl = soqet.ssl,
connected = false,
listening = true,
updateInterval = 1
}
if ssl then
client.ENDPOINT = "https://" .. soqet.ENDPOINT
else
client.ENDPOINT = "http://" .. soqet.ENDPOINT
end
local function send(path, body)
return postJson(client.ENDPOINT .. "/api/" .. path, body)
end
local function rawreceive()
if not client.connected then
client.connect()
end
while true do
local h =
send(
"update",
{
token = client.sessionToken
}
)
local data = soqet.json.decode(h)
local queue = {}
for i, v in ipairs(data.queue) do
if v.type == "message" then
table.insert(
queue,
{
channel = v.channel,
message = v.message,
meta = v.meta
}
)
end
end
if #queue > 0 then
return queue
else
os.sleep(client.updateInterval)
end
end
end
function client.connect(token)
if nil and type(token) ~= "string" then
error("bad argument #1", 2)
end
local h = get(client.ENDPOINT .. "/api/connect?token=" .. (token or ""))
local data = soqet.json.decode(h)
client.uuid = data.uuid
client.sessionToken = data.token
client.motd = data.motd
client.connected = true
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end
function client.open(channel)
expect(1, channel, "string", "number")
client.channels[#client.channels + 1] = channel
send(
"open",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.close(channel)
expect(1, channel, "string", "number")
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
send(
"close",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
meta = meta or {}
meta.library = meta.library or soqet.credits
send(
"send",
{
token = client.sessionToken,
channel = channel,
message = message,
meta = meta
}
)
end
function client.receive()
return rawreceive()
end
function client.listen()
client.listening = true
while client.listening do
local queue = rawreceive()
for i, v in ipairs(queue) do
computer.pushSignal("soqet_message", v.channel, v.message, v.meta, client.sessionId)
end
sleep(client.updateInterval)
end
end
function client.unlisten()
client.listening = false
return true
end
return client
end
return soqet

View File

@ -1,558 +0,0 @@
{
"name": "soqet",
"version": "3.0.0",
"lockfileVersion": 1,
"requires": 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==",
"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==",
"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==",
"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
}
}
}

View File

@ -1,25 +0,0 @@
{
"name": "soqet",
"version": "3.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"tsc": "tsc",
"start": "tsc && node index.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"express-ws": "^4.0.0"
},
"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"
}
}

View File

@ -1,410 +0,0 @@
--[[
-- Soqet.lua --
https://github.com/Ale32bit/Soqet/
MIT License
Copyright (c) 2020 Alessandro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
--[[
-- json.lua --
https://github.com/rxi/json.lua
Copyright (c) 2019 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
local expect = dofile("rom/modules/main/cc/expect.lua").expect
local soqet = {
ENDPOINT = "soqet.alexdevs.pw",
ssl = true,
json = json,
credits = "Soqet.lua v2 by AlexDevs"
}
if not soqet.json then
if not fs.exists("json.lua") then
local h = http.get("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua")
local f = fs.open("json.lua", "w")
f.write(h.readAll())
f.close()
h.close()
end
soqet.json = require("json")
end
function soqet.new()
if not http then
return false, "HTTP is not enabled!"
end
if not http.websocket then
return false, "HTTP WebSocket feature is not enabled!"
end
local client = {
channels = {},
uuid = nil,
socket = nil,
sessionId = math.random(0xffffff),
ssl = soqet.ssl
}
local function rawsend(data)
if not client.socket then
return false
end
client.socket.send(soqet.json.encode(data))
return true
end
local function rawreceive()
if not client.socket then
client.connect()
end
while true do
local data = client.socket.receive()
data = soqet.json.decode(data)
client.uuid = data.uuid
if data.type == "ping" then
rawsend(
{
type = "ping",
id = 99
}
)
elseif data.type == "motd" then
client.motd = data.motd
elseif data.type == "message" then
return data.channel, data.message, data.meta
end
end
end
if soqet.ssl then
client.ENDPOINT = "wss://" .. soqet.ENDPOINT .. "/" .. client.sessionId
else
client.ENDPOINT = "ws://" .. soqet.ENDPOINT .. "/" .. client.sessionId
end
function client.connect()
if client.socket then
pcall(client.socket.close)
end
local socket, err = http.websocket(client.ENDPOINT)
if not socket then
return false, err
end
client.socket = socket
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end
function client.open(channel)
expect(1, channel, "string", "number")
client.channels[#client.channels + 1] = channel
return rawsend(
{
type = "open",
channel = channel
}
)
end
function client.close(channel)
expect(1, channel, "string", "number")
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
return rawsend(
{
type = "close",
channel = channel
}
)
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
meta = meta or {}
meta.library = meta.library or soqet.credits
return rawsend(
{
type = "send",
channel = channel,
message = message,
meta = meta
}
)
end
function client.auth(token)
expect(1, token, "string")
return rawsend(
{
type = "auth",
token = token
}
)
end
function client.receive()
return rawreceive()
end
function client.listen()
client.listening = true
while client.listening do
local channel, message, meta = rawreceive()
os.queueEvent("soqet_message", channel, message, meta, client.sessionId)
end
return true
end
function client.unlisten()
client.listening = false
return true
end
return client, client.sessionId
end
function soqet.poll(token)
local client = {
channels = {},
uuid = nil,
sessionId = math.random(0xffffff),
sessionToken = nil,
ssl = soqet.ssl,
connected = false,
listening = true,
updateInterval = 1
}
if ssl then
client.ENDPOINT = "https://" .. soqet.ENDPOINT
else
client.ENDPOINT = "http://" .. soqet.ENDPOINT
end
local function postJson(path, body)
return http.post(
client.ENDPOINT .. "/api/" .. path,
soqet.json.encode(body),
{
["Content-Type"] = "application/json"
}
)
end
local function rawreceive()
if not client.connected then
client.connect()
end
while true do
local h, err =
postJson(
"update",
{
token = client.sessionToken
}
)
if not h then
error(err)
end
local data = soqet.json.decode(h.readAll())
h.close()
local queue = {}
for i, v in ipairs(data.queue) do
if v.type == "message" then
table.insert(
queue,
{
channel = v.channel,
message = v.message,
meta = v.meta
}
)
end
end
if #queue > 0 then
return queue
else
sleep(client.updateInterval)
end
end
end
function client.connect(token)
expect(1, token, "nil", "string")
local h, err, eh = http.get(client.ENDPOINT .. "/api/connect?token=" .. textutils.urlEncode(token or ""))
if not h then
return false, err, eh
end
local data = soqet.json.decode(h.readAll())
h.close()
client.uuid = data.uuid
client.sessionToken = data.token
client.motd = data.motd
client.connected = true
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end
function client.open(channel)
expect(1, channel, "string", "number")
client.channels[#client.channels + 1] = channel
postJson(
"open",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.close(channel)
expect(1, channel, "string", "number")
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
postJson(
"close",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
meta = meta or {}
meta.library = meta.library or soqet.credits
postJson(
"send",
{
token = client.sessionToken,
channel = channel,
message = message,
meta = meta
}
)
end
function client.receive()
return rawreceive()
end
function client.listen()
client.listening = true
while client.listening do
local queue = rawreceive()
for i, v in ipairs(queue) do
os.queueEvent("soqet_message", v.channel, v.message, v.meta, client.sessionId)
end
sleep(client.updateInterval)
end
end
function client.unlisten()
client.listening = false
return true
end
return client
end
return soqet

View File

@ -1,69 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "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. */
// "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. */
// "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'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "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). */
// "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. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -1,117 +0,0 @@
* { margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 13px BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
background-color: #2C2F33;
}
a:link {
color: #3f84c5;
}
a:visited {
color: #3f84c5;
}
.chat {
list-style-type: none;
margin: 0;
padding: 0;
word-wrap: break-word;
color: #bdbdbd;
}
.chat div {
padding: 5px 10px;
border-bottom: 1px solid #353535;
color: #bdbdbd;
}
/*.chat { margin-bottom: 40px; }*/
.displaychat {
margin-bottom: 25px;
padding-bottom: 8px;
color: #bdbdbd;
}
.button {
background-color: #484b52;
border: none;
color: #b9bbbe !important;
padding: 10px 27px;
margin-top: 6px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
outline:none;
border-radius: 5px;
}
.button:hover {
background-color: #3c3e44;
color: white;
}
.textinput {
bottom: 0px;
position: fixed;
padding: 3px;
width: 100vw;
background-color: #2c2f33;
border-top: 1px solid #353535;
/*display: inline-block;
z-index: 1;
overflow:hidden;*/
}
.textinput input[type=text] {
width: 40vw;
padding: 5px 5px;
margin: 0 0 0 3px;
box-sizing: border-box;
font-size: 15px;
color: white;
background-color: #484b52;
border: none;
border-radius: 5px;
outline: none;
/*cursor: not-allowed;*/
}
.textinput:focus {
outline: none;
}
.textinput input[type=submit] {
width: auto;
padding: 6px;
margin-bottom: 0;
margin-left: 2px;
color: #7f8186;
background-color: #484b52;
border: none;
border-radius: 5px;
outline: none;
position: absolute;
/*cursor: not-allowed;*/
}
.textinput input[type=submit]:hover {
background-color: #7f8186;
color: white;
}
/*.container {
display: grid;
grid-column-gap: 50px;
grid-row-gap: 50px;
grid-template-areas:
'main main main menu'
}*/
/*.chat { grid-area: main; overflow: scroll;}
.sidebar { grid-area: menu ; }*/

View File

@ -1,106 +0,0 @@
<!DOCTYPE html>
<!-- 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>
<script>if (typeof module === 'object') {
window.module = module;
module = undefined;
}</script>
<script type="text/javascript">
let channels = {};
function addLine(message, latest) {
var list = document.getElementById('chat'); // get list
var entry = document.createElement('div'); // create <p>
entry.innerHTML = message;
entry.class = "chat"; // give class chat to the <p>
list.appendChild(entry); // add <p> to list
window.scrollTo(0, document.body.scrollHeight); // auto scroll to bottom
}
function sendMessage() {
//return false;
var message = document.getElementById("sendMsg").value;
var channel = document.getElementById("channel").value;
channel = Number.parseInt(channel) || channel;
message = (function(){
try {
return JSON.parse(message);
} catch(e) {
return message;
}
})();
if(!channels[channel]) {
ws.send(JSON.stringify({
type: "open",
channel: channel,
}));
channels[channel] = true
}
addLine("> "+message);
if (message !== "") {
ws.send(JSON.stringify({
type: "send",
channel: channel,
message: message,
}));
}
document.getElementById("sendMsg").value = "";
return false;
}
</script>
<script>
var ws;
function startWS() {
ws = new WebSocket("wss://soqet.alexdevs.pw"); // Connect to WS server
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") {
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") {
addLine(`< <a style="color: #8a8a8a">${JSON.stringify(data)}</a>`);
}
}
});
}
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>

View File