diff --git a/README.md b/README.md index 3cbce2a..46d7197 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,22 @@ The UDN project aims to provide decentralized and localhost-able infrastructure # Contents - [How it works](#how-it-works) - - [Channels](#channels) - - [Scalability](#scalability) - - [Example](#example) + - [Channels](#channels) + - [Scalability](#scalability) + - [Example](#example) - [User's guide](#users-guide) - - [Getting Connected](#getting-connected) - - [Using Apps](#using-apps) - - [Getting Apps](#getting-apps) + - [Getting Connected](#getting-connected) + - [Using Apps](#using-apps) + - [Getting Apps](#getting-apps) - [Administrator's Guide](#administrators-guide) - - [Getting Started](#getting-started) - - [Configuration](#configuration) - - [TLS](#tls) - - [Connecting Multiple Servers](#connecting-multiple-servers) + - [Getting Started](#getting-started) + - [Configuration](#configuration) + - [TLS](#tls) + - [Connecting Multiple Servers](#connecting-multiple-servers) - [App Developer's Guide](#app-developers-guide) - - [Guidelines](#guidelines) - - [The Recommended Way](#the-recommended-way) - - [The Manual Way](#the-manual-way) + - [Guidelines](#guidelines) + - [The Recommended Way](#the-recommended-way) + - [The Manual Way](#the-manual-way) # How it works @@ -40,14 +40,14 @@ Subscriptions and messages are handled by a server: ```md **Josh** --(subscribe to "my-channel")-> **Server** -**Josh** <-(subscription confirmed )-- **Server** +**Josh** <-(subscription confirmed )-- **Server** -**Bob** --(subscribe to "my-channel")-> **Server** -**Bob** <-(subscription confirmed )-- **Server** +**Bob** --(subscribe to "my-channel")-> **Server** +**Bob** <-(subscription confirmed )-- **Server** -**Josh** --("hello" on "my-channel" )-> **Server** -**Josh** <-("hello" on "my-channel" )-- **Server** -**Bob** <-("hello" on "my-channel" )-- **Server** +**Josh** --("hello" on "my-channel" )-> **Server** +**Josh** <-("hello" on "my-channel" )-- **Server** +**Bob** <-("hello" on "my-channel" )-- **Server** ``` ## Scalability @@ -79,36 +79,36 @@ Then, John will send his messages on both "s1-and-s2" and "car-warranty". As alw # John and Bob subscribe: **John** --(subscribe to "car-warranty")-> **S1** -**John** <-(subscription confirmed )-- **S1** +**John** <-(subscription confirmed )-- **S1** -**Bob** --(subscribe to "car-warranty")-> **S2** -**Bob** <-(subscription confirmed )-- **S2** +**Bob** --(subscribe to "car-warranty")-> **S2** +**Bob** <-(subscription confirmed )-- **S2** --- # The servers subscribe: -**S1** --(subscribe to "s1-and-s2" )-> **S2** -**S2** --(subscribe to "s1-and-s2" )-> **S1** +**S1** --(subscribe to "s1-and-s2" )-> **S2** +**S2** --(subscribe to "s1-and-s2" )-> **S1** --- # Sending the Message: John sends the message to his server -**John** --("hello" on: )-> **S1** - ( - "s1-and-s2" ) - ( - "car-warranty" ) +**John** --("hello" on: )-> **S1** +( - "s1-and-s2" ) +( - "car-warranty" ) S2 subscribed to "s1-and-s2", so S1 sends this message to S2 -**S1** --("hello" on: )-> **S2** - ( - "s1-and-s2" ) - ( - "car-warranty" ) +**S1** --("hello" on: )-> **S2** +( - "s1-and-s2" ) +( - "car-warranty" ) Bob subscribed to "car-warranty", so S2 sends this message to Bob -**S2** --("hello" on: )-> **Bob** - ( - "s1-and-s2" ) - ( - "car-warranty" ) +**S2** --("hello" on: )-> **Bob** +( - "s1-and-s2" ) +( - "car-warranty" ) ``` This requires John to know of "s1-and-s2" and, obviously, Bob to subscribe to "car-warranty". Jane and Alice could agree on their own primary channel and use "s1-and-s2" just like John did. @@ -139,16 +139,16 @@ As you can see, there are many ways to connect multiple servers and communicate - `http://` or `https://` (the secure variant) are protocols to request data and receive a single response - To send messages, apps use so-called WebSockets instead. Their protocol is `ws://` or `wss://` (the secure variant) - You will need to **replace `http` with `ws`**: - - `http://192.168.0.100:3000` -> `ws://192.168.0.100:3000` - - `https://192.168.0.200:3000` -> `wss://192.168.0.200:3000` + - `http://192.168.0.100:3000` -> `ws://192.168.0.100:3000` + - `https://192.168.0.200:3000` -> `wss://192.168.0.200:3000` ## Getting Apps Anyone can develop apps for the UDN project. Examples of such apps are listed below. If you have developed an app for the UDN project and you want your app to be listed here, create an issue. -| App Name | Description | Links | -| -- | -- | -- | -| Comms | Messenger for UDN | [more info](https://github.com/marlon-erler/udn-comms/) - [install](https://udn-comms.onrender.com/) +| App Name | Description | Links | +| -------- | ----------------- | ---------------------------------------------------------------------------------------------------- | +| Comms | Messenger for UDN | [more info](https://github.com/marlon-erler/udn-comms/) - [install](https://udn-comms.onrender.com/) | --- diff --git a/config-handler.ts b/config-handler.ts index fa197fc..d1289ec 100644 --- a/config-handler.ts +++ b/config-handler.ts @@ -2,30 +2,30 @@ const configPath = "config.json"; export const defaultConfig = { - port: 3000, - connectedServers: [], - subscribedChannels: [], + port: 3000, + connectedServers: [], + subscribedChannels: [], }; // METHODS export async function createConfig() { - const configString = JSON.stringify(defaultConfig, null, 4); - await Bun.write(configPath, configString); + const configString = JSON.stringify(defaultConfig, null, 4); + await Bun.write(configPath, configString); } export async function getConfig(): Promise { - try { - const configString = await Bun.file(configPath).text(); - const configObject = JSON.parse(configString); + try { + const configString = await Bun.file(configPath).text(); + const configObject = JSON.parse(configString); - // validate config file - Object.keys(defaultConfig).forEach((key) => { - if (!configObject[key]) throw null; - }); + // validate config file + Object.keys(defaultConfig).forEach((key) => { + if (!configObject[key]) throw null; + }); - return configObject; - } catch { - await createConfig(); - return defaultConfig; - } + return configObject; + } catch { + await createConfig(); + return defaultConfig; + } } diff --git a/index.ts b/index.ts index a617fb9..08cde9e 100755 --- a/index.ts +++ b/index.ts @@ -1,21 +1,21 @@ #!/usr/bin/env bun import { - connectServers, - forgetConnection, - getWebSocketStats, - processMessage, - reconnectServers, - removeExpiredMailboxes, - trackConnection, + connectServers, + forgetConnection, + getWebSocketStats, + processMessage, + reconnectServers, + removeExpiredMailboxes, + trackConnection, } from "./websocket-handler"; import { - createFileResponse, - getVersionNumber, - writeError, - writeStatNumber, - writeStatString, - writeSuccess, + createFileResponse, + getVersionNumber, + writeError, + writeStatNumber, + writeStatString, + writeSuccess, } from "./utility"; import Colors from "colors"; @@ -35,120 +35,121 @@ export type WS = ServerWebSocket | WebSocket; // MAIN async function main() { - // CONFIG - const config = await getConfig(); - const versionNumber = await getVersionNumber(); + // CONFIG + const config = await getConfig(); + const versionNumber = await getVersionNumber(); - // SERVER - const server = Bun.serve({ - port: config.port, + // SERVER + const server = Bun.serve({ + port: config.port, - fetch(request, server) { - // allow websockets - server.upgrade(request); + fetch(request, server) { + // allow websockets + server.upgrade(request); - // respond - const requestPath = new URL(request.url).pathname; - switch (requestPath) { - case "/": - return createFileResponse(DEFAULT_PAGE); - case "/stats": - return new Response(JSON.stringify(getAllStats())); - default: - return createFileResponse(requestPath); - } - }, + // respond + const requestPath = new URL(request.url).pathname; + switch (requestPath) { + case "/": + return createFileResponse(DEFAULT_PAGE); + case "/stats": + return new Response(JSON.stringify(getAllStats())); + default: + return createFileResponse(requestPath); + } + }, - websocket: { - open(ws) { - try { - trackConnection(ws); - } catch (error) { - writeError(error); - } - }, - message(ws, message) { - try { - processMessage(ws, message.toString(), config); - } catch (error) { - writeError(error); - } - }, - close(ws) { - try { - forgetConnection(ws); - } catch (error) { - writeError(error); - } - }, - }, + websocket: { + open(ws) { + try { + trackConnection(ws); + } catch (error) { + writeError(error); + } + }, + message(ws, message) { + try { + processMessage(ws, message.toString(), config); + } catch (error) { + writeError(error); + } + }, + close(ws) { + try { + forgetConnection(ws); + } catch (error) { + writeError(error); + } + }, + }, - tls: await (async () => { - const keyFile = Bun.file("./key.pem"); - const certFile = Bun.file("./cert.pem"); + tls: await (async () => { + const keyFile = Bun.file("./key.pem"); + const certFile = Bun.file("./cert.pem"); - if ( - (await keyFile.exists()) == false || - (await certFile.exists()) == false - ) - return undefined; + if ( + (await keyFile.exists()) == false || + (await certFile.exists()) == false + ) + return undefined; - return { - key: keyFile, - cert: certFile, - passphrase: process.env.TLS_PASSPHRASE, - }; - })(), - }); + return { + key: keyFile, + cert: certFile, + passphrase: process.env.TLS_PASSPHRASE, + }; + })(), + }); - // OTHER SERVERS - connectServers(config); + // OTHER SERVERS + connectServers(config); - // AUDIT - function getServerStats(): [string, string][] { - return [ - ["server url", server.url.toString()], - ["server ip address", IP.address()], - ["server version", versionNumber], - ]; - } - - function getAllStats(): [string, string | number][] { - return [...getServerStats(), ...getWebSocketStats()]; - } - - // CLI - function updateCLI() { - console.clear(); - - console.log(Colors.bold.bgWhite("UNIVERSAL DECENTRALIZED NETWORK")); - console.log( - Colors.yellow("relevant output is available in the 'logs' file") - ); - console.log(); - - console.log(new Date().toLocaleString()); - console.log(); - - getServerStats().forEach((entry) => writeStatString(...entry)); - console.log(); - - getWebSocketStats().forEach((entry) => writeStatNumber(...entry)); - console.log(); - } - - // LOOP - setInterval(() => { - if (process.env.DEV != "1") { - updateCLI(); - reconnectServers(config); + // AUDIT + function getServerStats(): [string, string][] { + return [ + ["server url", server.url.toString()], + ["server ip address", IP.address()], + ["server version", versionNumber], + ]; } - - removeExpiredMailboxes(); - }, 2000); - // FINISH - writeSuccess(`###\nstarted up ${new Date().toLocaleString()}`); + function getAllStats(): [string, string | number][] { + return [...getServerStats(), ...getWebSocketStats()]; + } + + // CLI + function updateCLI() { + return; + console.clear(); + + console.log(Colors.bold.bgWhite("UNIVERSAL DECENTRALIZED NETWORK")); + console.log( + Colors.yellow("relevant output is available in the 'logs' file"), + ); + console.log(); + + console.log(new Date().toLocaleString()); + console.log(); + + getServerStats().forEach((entry) => writeStatString(...entry)); + console.log(); + + getWebSocketStats().forEach((entry) => writeStatNumber(...entry)); + console.log(); + } + + // LOOP + setInterval(() => { + if (process.env.DEV != "1") { + updateCLI(); + reconnectServers(config); + } + + removeExpiredMailboxes(); + }, 2000); + + // FINISH + writeSuccess(`###\nstarted up ${new Date().toLocaleString()}`); } main(); diff --git a/package-lock.json b/package-lock.json index 23e8947..7ae8c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,754 +1,754 @@ { - "name": "udn", - "version": "1.0.0-beta13", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "udn", - "version": "1.0.0-beta13", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@types/bun": "^1.1.6", - "colors": "^1.4.0", - "express": "^4.19.2", - "ip": "^2.0.1" - }, - "bin": { - "start": "index.ts" - } - }, - "node_modules/@types/bun": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.6.tgz", - "integrity": "sha512-uJgKjTdX0GkWEHZzQzFsJkWp5+43ZS7HC8sZPFnOwnSo1AsNl2q9o2bFeS23disNDqbggEgyFkKCHl/w8iZsMA==", - "dependencies": { - "bun-types": "1.1.17" - } - }, - "node_modules/@types/node": { - "version": "20.12.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", - "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bun-types": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.17.tgz", - "integrity": "sha512-Z4+OplcSd/YZq7ZsrfD00DKJeCwuNY96a1IDJyR73+cTBaFIS7SC6LhpY/W3AMEXO9iYq5NJ58WAwnwL1p5vKg==", - "dependencies": { - "@types/node": "~20.12.8", - "@types/ws": "~8.5.10" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" - }, - "node_modules/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==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "name": "udn", + "version": "1.0.0-beta13", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "udn", + "version": "1.0.0-beta13", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@types/bun": "^1.1.6", + "colors": "^1.4.0", + "express": "^4.19.2", + "ip": "^2.0.1" + }, + "bin": { + "start": "index.ts" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "node_modules/@types/bun": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.6.tgz", + "integrity": "sha512-uJgKjTdX0GkWEHZzQzFsJkWp5+43ZS7HC8sZPFnOwnSo1AsNl2q9o2bFeS23disNDqbggEgyFkKCHl/w8iZsMA==", + "dependencies": { + "bun-types": "1.1.17" + } }, - { - "type": "consulting", - "url": "https://feross.org/support" + "node_modules/@types/node": { + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bun-types": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.17.tgz", + "integrity": "sha512-Z4+OplcSd/YZq7ZsrfD00DKJeCwuNY96a1IDJyR73+cTBaFIS7SC6LhpY/W3AMEXO9iYq5NJ58WAwnwL1p5vKg==", + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + }, + "node_modules/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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } } - } } diff --git a/package.json b/package.json index d9be478..18da94a 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,25 @@ { - "name": "udn", - "version": "1.0.0-beta14", - "description": "A websocket-based infrastructure for decentralized networks ", - "bin": { - "start": "index.ts" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/marlon-erler/universal-decentralized-network.git" - }, - "author": "marlon-erler", - "license": "SEE LICENSE IN LICENSE", - "bugs": { - "url": "https://github.com/marlon-erler/universal-decentralized-network/issues" - }, - "homepage": "https://github.com/marlon-erler/universal-decentralized-network#readme", - "dependencies": { - "@types/bun": "^1.1.6", - "@types/ip": "^1.1.3", - "colors": "^1.4.0", - "express": "^4.19.2", - "ip": "^2.0.1" - } + "name": "udn", + "version": "1.0.0-beta15", + "description": "A websocket-based infrastructure for decentralized networks ", + "bin": { + "start": "index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/marlon-erler/universal-decentralized-network.git" + }, + "author": "marlon-erler", + "license": "SEE LICENSE IN LICENSE", + "bugs": { + "url": "https://github.com/marlon-erler/universal-decentralized-network/issues" + }, + "homepage": "https://github.com/marlon-erler/universal-decentralized-network#readme", + "dependencies": { + "@types/bun": "^1.1.6", + "@types/ip": "^1.1.3", + "colors": "^1.4.0", + "express": "^4.19.2", + "ip": "^2.0.1" + } } diff --git a/static/dist/index.html b/static/dist/index.html index 9f84661..80a69ff 100644 --- a/static/dist/index.html +++ b/static/dist/index.html @@ -1,34 +1,34 @@ - - UDN Server + + UDN Server - - + + - - + + - - - - - + + + + + - - + + - -
- + +
+ diff --git a/static/dist/index.js b/static/dist/index.js index 0f16080..53bee8f 100644 --- a/static/dist/index.js +++ b/static/dist/index.js @@ -1,564 +1,814 @@ (() => { - // node_modules/bloatless-react/index.ts - var State = class { - _value; - _bindings = /* @__PURE__ */ new Set(); - // init - constructor(initialValue) { - this._value = initialValue; - } - // value - get value() { - return this._value; - } - set value(newValue) { - if (this._value == newValue) return; - this._value = newValue; - this.callSubscriptions(); - } - // subscriptions - callSubscriptions() { - this._bindings.forEach((fn) => fn(this._value)); - } - subscribe(fn) { - this._bindings.add(fn); - fn(this._value); - } - // stringify - toString() { - return JSON.stringify(this._value); - } - }; - var ListState = class extends State { - additionHandlers = /* @__PURE__ */ new Set(); - removalHandlers = /* @__PURE__ */ new Map(); - // init - constructor(initialItems) { - super(new Set(initialItems)); - } - // list - add(...items) { - items.forEach((item) => { - this.value.add(item); - this.additionHandlers.forEach((handler) => handler(item)); - }); - this.callSubscriptions(); - } - remove(...items) { - items.forEach((item) => { - this.value.delete(item); - if (!this.removalHandlers.has(item)) return; - this.removalHandlers.get(item)(item); - this.removalHandlers.delete(item); - }); - this.callSubscriptions(); - } - clear() { - this.remove(...this.value.values()); - } - // handlers - handleAddition(handler) { - this.additionHandlers.add(handler); - [...this.value.values()].forEach(handler); - } - handleRemoval(item, handler) { - this.removalHandlers.set(item, handler); - } - // stringification - toString() { - const array = [...this.value]; - const json = JSON.stringify(array); - return json; - } - }; - function createProxyState(statesToSubscibe, fn) { - const proxyState = new State(fn()); - statesToSubscibe.forEach( - (state) => state.subscribe(() => proxyState.value = fn()) - ); - return proxyState; - } - function persistState(localStorageKey, state) { - state.subscribe(() => { - const stringifiedValue = state.toString(); - localStorage.setItem(localStorageKey, stringifiedValue); - }); - } - function restoreState(localStorageKey, initialStateValue) { - const storedString = localStorage.getItem(localStorageKey) ?? JSON.stringify(initialStateValue); - const convertedValue = JSON.parse(storedString); - const state = new State(convertedValue); - persistState(localStorageKey, state); - return state; - } - function createElement(tagName, attributes = {}, ...children) { - const element = document.createElement(tagName); - if (attributes != null) - Object.entries(attributes).forEach((entry) => { - const [attributename, value] = entry; - const [directiveKey, directiveValue] = attributename.split(":"); - switch (directiveKey) { - case "on": { - switch (directiveValue) { - case "enter": { - element.addEventListener("keydown", (e) => { - if (e.key != "Enter") return; - value(); - }); - break; - } - default: { - element.addEventListener(directiveValue, value); - } - } - break; - } - case "subscribe": { - if (directiveValue == "children") { - element.style.scrollBehavior = "smooth"; - try { - const [listState, toElement] = value; - listState.handleAddition((newItem) => { - const child = toElement(newItem); - listState.handleRemoval( - newItem, - () => child.remove() - ); - element.append(child); - element.scrollTop = element.scrollHeight; - }); - } catch { - throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; - } - } else { - const state = value; - state.subscribe( - (newValue) => element[directiveValue] = newValue - ); - } - break; - } - case "bind": { - const state = value; - state.subscribe( - (newValue) => element[directiveValue] = newValue - ); - element.addEventListener( - "input", - () => state.value = element[directiveValue] - ); - break; - } - case "toggle": { - if (value.subscribe) { - const state = value; - state.subscribe( - (newValue) => element.toggleAttribute(directiveValue, newValue) - ); - } else { - element.toggleAttribute(directiveValue, value); - } - break; - } - case "set": { - const state = value; - state.subscribe( - (newValue) => element.setAttribute(directiveValue, newValue) - ); - break; - } - default: - element.setAttribute(attributename, value); + // node_modules/bloatless-react/index.ts + var State = class { + _value; + _bindings = /* @__PURE__ */ new Set(); + // init + constructor(initialValue) { + this._value = initialValue; + } + // value + get value() { + return this._value; + } + set value(newValue) { + if (this._value == newValue) return; + this._value = newValue; + this.callSubscriptions(); + } + // subscriptions + callSubscriptions() { + this._bindings.forEach((fn) => fn(this._value)); + } + subscribe(fn) { + this._bindings.add(fn); + fn(this._value); + } + // stringify + toString() { + return JSON.stringify(this._value); } - }); - children.filter((x) => x).forEach((child) => element.append(child)); - return element; - } - - // node_modules/udn-frontend/index.ts - var UDNFrontend = class { - ws; - // HANDLERS - connectionHandler = () => { }; - disconnectionHandler = () => { + var ListState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); + // init + constructor(initialItems) { + super(new Set(initialItems)); + } + // list + add(...items) { + items.forEach((item) => { + this.value.add(item); + this.additionHandlers.forEach((handler) => handler(item)); + }); + this.callSubscriptions(); + } + remove(...items) { + items.forEach((item) => { + this.value.delete(item); + if (!this.removalHandlers.has(item)) return; + this.removalHandlers.get(item)(item); + this.removalHandlers.delete(item); + }); + this.callSubscriptions(); + } + clear() { + this.remove(...this.value.values()); + } + // handlers + handleAddition(handler) { + this.additionHandlers.add(handler); + [...this.value.values()].forEach(handler); + } + handleRemoval(item, handler) { + this.removalHandlers.set(item, handler); + } + // stringification + toString() { + const array = [...this.value]; + const json = JSON.stringify(array); + return json; + } }; - messageHandler = (data) => { - }; - mailboxHandler = (mailboxId2) => { - }; - mailboxConnectionHandler = (mailboxId2) => { - }; - mailboxDeleteHandler = (mailboxId2) => { - }; - // INIT - set onconnect(handler) { - this.connectionHandler = handler; + function createProxyState(statesToSubscibe, fn) { + const proxyState = new State(fn()); + statesToSubscibe.forEach((state) => + state.subscribe(() => (proxyState.value = fn())), + ); + return proxyState; } - set ondisconnect(handler) { - this.disconnectionHandler = handler; - } - set onmessage(handler) { - this.messageHandler = handler; - } - set onmailboxcreate(handler) { - this.mailboxHandler = handler; - } - set onmailboxconnect(handler) { - this.mailboxConnectionHandler = handler; - } - set onmailboxdelete(handler) { - this.mailboxDeleteHandler = handler; - } - // UTILITY METHODS - send(messageObject) { - if (this.ws == void 0) return false; - const messageString = JSON.stringify(messageObject); - this.ws.send(messageString); - return true; - } - // PUBLIC METHODS - // connection - connect(address) { - try { - this.disconnect(); - this.ws = new WebSocket(address); - this.ws.addEventListener("open", this.connectionHandler); - this.ws.addEventListener("close", this.disconnectionHandler); - this.ws.addEventListener("message", (message) => { - const dataString = message.data.toString(); - const data = JSON.parse(dataString); - if (data.assignedMailboxId) { - return this.mailboxHandler(data.assignedMailboxId); - } else if (data.connectedMailboxId) { - return this.mailboxConnectionHandler(data.connectedMailboxId); - } else if (data.deletedMailbox) { - return this.mailboxDeleteHandler(data.deletedMailbox); - } else { - this.messageHandler(data); - } + function persistState(localStorageKey, state) { + state.subscribe(() => { + const stringifiedValue = state.toString(); + localStorage.setItem(localStorageKey, stringifiedValue); }); - } catch (error) { - console.error(error); - } } - disconnect() { - this.ws?.close(); + function restoreState(localStorageKey, initialStateValue) { + const storedString = + localStorage.getItem(localStorageKey) ?? + JSON.stringify(initialStateValue); + const convertedValue = JSON.parse(storedString); + const state = new State(convertedValue); + persistState(localStorageKey, state); + return state; } - // message - sendMessage(channel, body) { - const messageObject = { - messageChannel: channel, - messageBody: body - }; - return this.send(messageObject); + function createElement(tagName, attributes = {}, ...children) { + const element = document.createElement(tagName); + if (attributes != null) + Object.entries(attributes).forEach((entry) => { + const [attributename, value] = entry; + const [directiveKey, directiveValue] = attributename.split(":"); + switch (directiveKey) { + case "on": { + switch (directiveValue) { + case "enter": { + element.addEventListener("keydown", (e) => { + if (e.key != "Enter") return; + value(); + }); + break; + } + default: { + element.addEventListener(directiveValue, value); + } + } + break; + } + case "subscribe": { + if (directiveValue == "children") { + element.style.scrollBehavior = "smooth"; + try { + const [listState, toElement] = value; + listState.handleAddition((newItem) => { + const child = toElement(newItem); + listState.handleRemoval(newItem, () => + child.remove(), + ); + element.append(child); + element.scrollTop = element.scrollHeight; + }); + } catch { + throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; + } + } else { + const state = value; + state.subscribe( + (newValue) => + (element[directiveValue] = newValue), + ); + } + break; + } + case "bind": { + const state = value; + state.subscribe( + (newValue) => (element[directiveValue] = newValue), + ); + element.addEventListener( + "input", + () => (state.value = element[directiveValue]), + ); + break; + } + case "toggle": { + if (value.subscribe) { + const state = value; + state.subscribe((newValue) => + element.toggleAttribute( + directiveValue, + newValue, + ), + ); + } else { + element.toggleAttribute(directiveValue, value); + } + break; + } + case "set": { + const state = value; + state.subscribe((newValue) => + element.setAttribute(directiveValue, newValue), + ); + break; + } + default: + element.setAttribute(attributename, value); + } + }); + children.filter((x) => x).forEach((child) => element.append(child)); + return element; } - // subscription - subscribe(channel) { - const messageObject = { - subscribeChannel: channel - }; - return this.send(messageObject); - } - unsubscribe(channel) { - const messageObject = { - unsubscribeChannel: channel - }; - return this.send(messageObject); - } - // mailbox - requestMailbox() { - const messageObject = { - requestingMailboxSetup: true - }; - return this.send(messageObject); - } - connectMailbox(mailboxId2) { - const messageObject = { - requestedMailbox: mailboxId2 - }; - return this.send(messageObject); - } - deleteMailbox(mailboxId2) { - const messageObject = { - deletingMailbox: mailboxId2 - }; - return this.send(messageObject); - } - }; - // src/translations.tsx - var staticTextEnglish = { - mailbox: "Mailbox", - requestMailbox: "Request", - deleteMailbox: "Delete", - mailboxConnected: "Mailbox active", - channel: "Channel", - channel_placeholder: "my-channel", - disconnected: "Server disconnected", - reconnecting: "Reconnecting...", - info: "Info", - message: "Message", - messages: "Messages", - message_placeholder: "Hello, world!", - message_placeholder_generic: "Type a message...", - noMessagesReceived: "No messages received", - messageLastReceived: "Last received message", - messagesReceived: "Received Messages", - send: "Send", - serverInfo: "Server Info", - settings: "Settings", - subscribeToChannel: "Subscribe to channel", - subscribe: "Subscribe", - unsubscribe: "Unubscribe" - }; - var allTranslations = { - en: staticTextEnglish, - es: { - mailbox: "Buz\xF3n", - requestMailbox: "Solicitar", - deleteMailbox: "Eliminar", - mailboxConnected: "Buz\xF3n activado", - channel: "Canal", - channel_placeholder: "mi-canal", - disconnected: "Sin coneci\xF3n", - reconnecting: "Conectando...", - info: "Info", - message: "Mensaje", - messages: "Mensajes", - message_placeholder: "Hola!", - message_placeholder_generic: "Nuevo mensaje", - noMessagesReceived: "Sin mensajes", - messageLastReceived: "\xDAltimo mensaje", - messagesReceived: "Todos los Mensajes", - send: "Enviar", - serverInfo: "Informaci\xF3n", - settings: "Configuraci\xF3n", - subscribeToChannel: "Suscribirse a un canal", - subscribe: "Suscribirse", - unsubscribe: "Cancelar" - }, - de: { - mailbox: "Briefkasten", - requestMailbox: "Beantragen", - deleteMailbox: "L\xF6schen", - mailboxConnected: "Briefkasten aktiv", - channel: "Kanal", - channel_placeholder: "mein-kanal", - disconnected: "Verbindung getrennt", - reconnecting: "Verbindung wird hergestellt...", - info: "Daten", - message: "Nachricht", - messages: "Nachrichten", - message_placeholder: "Hallo!", - message_placeholder_generic: "Neue Nachricht", - noMessagesReceived: "Keine Nachrichten", - messageLastReceived: "Letzte Nachricht", - messagesReceived: "Empfangene Nachrichten", - send: "Senden", - serverInfo: "Serverdaten", - settings: "Einstellungen", - subscribeToChannel: "Kanal beitreten", - subscribe: "Beitreten", - unsubscribe: "Verlassen" - } - }; - var language = navigator.language.substring(0, 2); - var translations = allTranslations[language]; + // node_modules/udn-frontend/index.ts + var UDNFrontend = class { + ws; + // HANDLERS + connectionHandler = () => {}; + disconnectionHandler = () => {}; + messageHandler = (data) => {}; + mailboxHandler = (mailboxId2) => {}; + mailboxConnectionHandler = (mailboxId2) => {}; + mailboxDeleteHandler = (mailboxId2) => {}; + // INIT + set onconnect(handler) { + this.connectionHandler = handler; + } + set ondisconnect(handler) { + this.disconnectionHandler = handler; + } + set onmessage(handler) { + this.messageHandler = handler; + } + set onmailboxcreate(handler) { + this.mailboxHandler = handler; + } + set onmailboxconnect(handler) { + this.mailboxConnectionHandler = handler; + } + set onmailboxdelete(handler) { + this.mailboxDeleteHandler = handler; + } + // UTILITY METHODS + send(messageObject) { + if (this.ws == void 0) return false; + const messageString = JSON.stringify(messageObject); + this.ws.send(messageString); + return true; + } + // PUBLIC METHODS + // connection + connect(address) { + try { + this.disconnect(); + this.ws = new WebSocket(address); + this.ws.addEventListener("open", this.connectionHandler); + this.ws.addEventListener("close", this.disconnectionHandler); + this.ws.addEventListener("message", (message) => { + const dataString = message.data.toString(); + const data = JSON.parse(dataString); + if (data.assignedMailboxId) { + return this.mailboxHandler(data.assignedMailboxId); + } else if (data.connectedMailboxId) { + return this.mailboxConnectionHandler( + data.connectedMailboxId, + ); + } else if (data.deletedMailbox) { + return this.mailboxDeleteHandler(data.deletedMailbox); + } else { + this.messageHandler(data); + } + }); + } catch (error) { + console.error(error); + } + } + disconnect() { + this.ws?.close(); + } + // message + sendMessage(channel, body) { + const messageObject = { + messageChannel: channel, + messageBody: body, + }; + return this.send(messageObject); + } + // subscription + subscribe(channel) { + const messageObject = { + subscribeChannel: channel, + }; + return this.send(messageObject); + } + unsubscribe(channel) { + const messageObject = { + unsubscribeChannel: channel, + }; + return this.send(messageObject); + } + // mailbox + requestMailbox() { + const messageObject = { + requestingMailboxSetup: true, + }; + return this.send(messageObject); + } + connectMailbox(mailboxId2) { + const messageObject = { + requestedMailbox: mailboxId2, + }; + return this.send(messageObject); + } + deleteMailbox(mailboxId2) { + const messageObject = { + deletingMailbox: mailboxId2, + }; + return this.send(messageObject); + } + }; - // src/model.tsx - var Message = class { - constructor(channel, body) { - this.channel = channel; - this.body = body; + // src/translations.tsx + var staticTextEnglish = { + mailbox: "Mailbox", + requestMailbox: "Request", + deleteMailbox: "Delete", + mailboxConnected: "Mailbox active", + channel: "Channel", + channel_placeholder: "my-channel", + disconnected: "Server disconnected", + reconnecting: "Reconnecting...", + info: "Info", + message: "Message", + messages: "Messages", + message_placeholder: "Hello, world!", + message_placeholder_generic: "Type a message...", + noMessagesReceived: "No messages received", + messageLastReceived: "Last received message", + messagesReceived: "Received Messages", + send: "Send", + serverInfo: "Server Info", + settings: "Settings", + subscribeToChannel: "Subscribe to channel", + subscribe: "Subscribe", + unsubscribe: "Unubscribe", + }; + var allTranslations = { + en: staticTextEnglish, + es: { + mailbox: "Buz\xF3n", + requestMailbox: "Solicitar", + deleteMailbox: "Eliminar", + mailboxConnected: "Buz\xF3n activado", + channel: "Canal", + channel_placeholder: "mi-canal", + disconnected: "Sin coneci\xF3n", + reconnecting: "Conectando...", + info: "Info", + message: "Mensaje", + messages: "Mensajes", + message_placeholder: "Hola!", + message_placeholder_generic: "Nuevo mensaje", + noMessagesReceived: "Sin mensajes", + messageLastReceived: "\xDAltimo mensaje", + messagesReceived: "Todos los Mensajes", + send: "Enviar", + serverInfo: "Informaci\xF3n", + settings: "Configuraci\xF3n", + subscribeToChannel: "Suscribirse a un canal", + subscribe: "Suscribirse", + unsubscribe: "Cancelar", + }, + de: { + mailbox: "Briefkasten", + requestMailbox: "Beantragen", + deleteMailbox: "L\xF6schen", + mailboxConnected: "Briefkasten aktiv", + channel: "Kanal", + channel_placeholder: "mein-kanal", + disconnected: "Verbindung getrennt", + reconnecting: "Verbindung wird hergestellt...", + info: "Daten", + message: "Nachricht", + messages: "Nachrichten", + message_placeholder: "Hallo!", + message_placeholder_generic: "Neue Nachricht", + noMessagesReceived: "Keine Nachrichten", + messageLastReceived: "Letzte Nachricht", + messagesReceived: "Empfangene Nachrichten", + send: "Senden", + serverInfo: "Serverdaten", + settings: "Einstellungen", + subscribeToChannel: "Kanal beitreten", + subscribe: "Beitreten", + unsubscribe: "Verlassen", + }, + }; + var language = navigator.language.substring(0, 2); + var translations = allTranslations[language]; + + // src/model.tsx + var Message = class { + constructor(channel, body) { + this.channel = channel; + this.body = body; + } + }; + var isDisconnected = new State(true); + var mailboxId = restoreState("mailbox-id", ""); + var isMailboxDisonnected = new State(true); + var messages = new ListState(); + var lastReceivedMessage = new State(translations.noMessagesReceived); + var statHTMLString = new State(""); + async function updateStats() { + const response = await fetch( + `${window.location.protocol}//${window.location.host}/stats`, + ); + const data = await response.json(); + let html = ""; + data.forEach((item) => { + const tile = /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "secondary flex width-100" }, + item[0].toString(), + ), + /* @__PURE__ */ createElement("b", null, item[1].toString()), + ); + html += tile.outerHTML; + }); + statHTMLString.value = html; } - }; - var isDisconnected = new State(true); - var mailboxId = restoreState("mailbox-id", ""); - var isMailboxDisonnected = new State(true); - var messages = new ListState(); - var lastReceivedMessage = new State( - translations.noMessagesReceived - ); - var statHTMLString = new State(""); - async function updateStats() { - const response = await fetch( - `${window.location.protocol}//${window.location.host}/stats` + var subscriptionChannel = new State(""); + var newMessageChannel = new State(""); + var newMessageBody = new State(""); + var isMessageEmpty = createProxyState( + [newMessageChannel, newMessageBody], + () => newMessageChannel.value == "" || newMessageBody.value == "", ); - const data = await response.json(); - let html = ""; - data.forEach((item) => { - const tile = /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "secondary flex width-100" }, item[0].toString()), /* @__PURE__ */ createElement("b", null, item[1].toString())); - html += tile.outerHTML; - }); - statHTMLString.value = html; - } - var subscriptionChannel = new State(""); - var newMessageChannel = new State(""); - var newMessageBody = new State(""); - var isMessageEmpty = createProxyState( - [newMessageChannel, newMessageBody], - () => newMessageChannel.value == "" || newMessageBody.value == "" - ); - var UDN = new UDNFrontend(); - UDN.onconnect = () => { - isDisconnected.value = false; - updateStats(); - if (mailboxId.value != "") { - UDN.connectMailbox(mailboxId.value); + var UDN = new UDNFrontend(); + UDN.onconnect = () => { + isDisconnected.value = false; + updateStats(); + if (mailboxId.value != "") { + UDN.connectMailbox(mailboxId.value); + } + }; + UDN.onmessage = (data) => { + lastReceivedMessage.value = JSON.stringify(data, null, 4); + const { messageChannel, messageBody } = data; + if (messageChannel && messageBody) { + const messageObject = new Message(messageChannel, messageBody); + if (messageBody) messages.add(messageObject); + } + }; + UDN.onmailboxcreate = (id) => { + console.log(id); + mailboxId.value = id; + UDN.connectMailbox(id); + }; + UDN.onmailboxconnect = () => { + isMailboxDisonnected.value = false; + }; + UDN.onmailboxdelete = () => { + isMailboxDisonnected.value = true; + }; + UDN.ondisconnect = () => { + isDisconnected.value = true; + isMailboxDisonnected.value = true; + setTimeout(connect, 1e3); + }; + function connect() { + UDN.connect( + `${window.location.protocol.replace("http", "ws")}//${window.location.host}`, + ); } - }; - UDN.onmessage = (data) => { - lastReceivedMessage.value = JSON.stringify(data, null, 4); - const { messageChannel, messageBody } = data; - if (messageChannel && messageBody) { - const messageObject = new Message(messageChannel, messageBody); - if (messageBody) messages.add(messageObject); + connect(); + function subscribe() { + UDN.subscribe(subscriptionChannel.value); } - }; - UDN.onmailboxcreate = (id) => { - console.log(id); - mailboxId.value = id; - UDN.connectMailbox(id); - }; - UDN.onmailboxconnect = () => { - isMailboxDisonnected.value = false; - }; - UDN.onmailboxdelete = () => { - isMailboxDisonnected.value = true; - }; - UDN.ondisconnect = () => { - isDisconnected.value = true; - isMailboxDisonnected.value = true; - setTimeout(connect, 1e3); - }; - function connect() { - UDN.connect( - `${window.location.protocol.replace("http", "ws")}//${window.location.host}` + function unsubscribe() { + UDN.unsubscribe(subscriptionChannel.value); + } + function sendMessage() { + if (isMessageEmpty.value == true) return; + UDN.sendMessage(newMessageChannel.value, newMessageBody.value); + newMessageBody.value = ""; + } + function requestMailbox() { + UDN.requestMailbox(); + } + function deleteMailbox() { + console.log("test"); + UDN.deleteMailbox(mailboxId.value); + } + + // src/infoScreen.tsx + function InfoScreen() { + return /* @__PURE__ */ createElement( + "article", + { id: "info-screen" }, + /* @__PURE__ */ createElement( + "header", + null, + translations.serverInfo, + /* @__PURE__ */ createElement( + "span", + null, + /* @__PURE__ */ createElement( + "button", + { "on:click": updateStats }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "refresh", + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement("div", { + class: "flex-column gap", + "subscribe:innerHTML": statHTMLString, + }), + ), + ); + } + + // src/mainScreen.tsx + function MainScreen() { + return /* @__PURE__ */ createElement( + "article", + { id: "main-screen" }, + /* @__PURE__ */ createElement( + "header", + null, + translations.settings, + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "div", + { class: "tile width-input" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "b", + null, + translations.messageLastReceived, + ), + /* @__PURE__ */ createElement("p", { + class: "secondary", + "subscribe:innerText": lastReceivedMessage, + }), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "tile width-input" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "b", + null, + translations.mailbox, + ), + /* @__PURE__ */ createElement( + "p", + { + class: "secondary", + "toggle:hidden": isMailboxDisonnected, + }, + translations.mailboxConnected, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input justify-end" }, + /* @__PURE__ */ createElement( + "button", + { + class: "danger width-50", + "on:click": deleteMailbox, + "toggle:disabled": isMailboxDisonnected, + }, + translations.deleteMailbox, + ), + /* @__PURE__ */ createElement( + "button", + { + class: "primary width-50", + "on:click": requestMailbox, + "toggle:disabled": isDisconnected, + }, + translations.requestMailbox, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "inbox", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "label", + { class: "tile" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.subscribeToChannel, + ), + /* @__PURE__ */ createElement("input", { + placeholder: translations.channel_placeholder, + "bind:value": subscriptionChannel, + "on:enter": subscribe, + }), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input justify-end" }, + /* @__PURE__ */ createElement( + "button", + { class: "danger width-50", "on:click": unsubscribe }, + translations.unsubscribe, + ), + /* @__PURE__ */ createElement( + "button", + { class: "primary width-50", "on:click": subscribe }, + translations.subscribe, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "label", + { class: "tile" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.channel, + ), + /* @__PURE__ */ createElement("input", { + placeholder: translations.channel_placeholder, + "bind:value": newMessageChannel, + "on:enter": sendMessage, + }), + ), + ), + /* @__PURE__ */ createElement( + "label", + { class: "tile" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.message, + ), + /* @__PURE__ */ createElement("input", { + placeholder: translations.message_placeholder, + "bind:value": newMessageBody, + "on:enter": sendMessage, + }), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input justify-end" }, + /* @__PURE__ */ createElement( + "button", + { + class: "primary width-50", + "on:click": sendMessage, + "toggle:disabled": isMessageEmpty, + }, + translations.send, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "send", + ), + ), + ), + ), + ); + } + + // src/messageScreen.tsx + var convertMessageToElement = (message) => { + return /* @__PURE__ */ createElement( + "div", + { class: "tile", style: "flex: 0" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + message.channel, + ), + /* @__PURE__ */ createElement("b", null, message.body), + ), + ); + }; + function MessageScreen() { + return /* @__PURE__ */ createElement( + "article", + { id: "message-screen" }, + /* @__PURE__ */ createElement( + "header", + null, + translations.messagesReceived, + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement("div", { + class: "flex-column gap", + "subscribe:children": [messages, convertMessageToElement], + }), + ), + /* @__PURE__ */ createElement( + "footer", + null, + /* @__PURE__ */ createElement("input", { + style: "max-width: unset", + placeholder: translations.message_placeholder_generic, + "bind:value": newMessageBody, + "on:enter": sendMessage, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": sendMessage, + "toggle:disabled": isMessageEmpty, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "send", + ), + ), + ), + ); + } + + // src/index.tsx + document.body.prepend( + /* @__PURE__ */ createElement( + "menu", + null, + /* @__PURE__ */ createElement( + "a", + { class: "tab-link", href: "#info-screen", active: true }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "info", + ), + translations.info, + ), + /* @__PURE__ */ createElement( + "a", + { class: "tab-link", href: "#main-screen" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "settings", + ), + translations.settings, + ), + /* @__PURE__ */ createElement( + "a", + { class: "tab-link", href: "#message-screen" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "chat", + ), + translations.messages, + ), + ), + ); + document + .querySelector("main") + .append(InfoScreen(), MainScreen(), MessageScreen()); + document.body.append( + /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isDisconnected }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "div", + { + class: "flex-column align-center justify-center width-100 height-100", + style: "gap: 1rem", + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon error", style: "font-size: 3rem" }, + "signal_disconnected", + ), + /* @__PURE__ */ createElement( + "h1", + { class: "error" }, + translations.disconnected, + ), + /* @__PURE__ */ createElement( + "p", + { class: "secondary" }, + translations.reconnecting, + ), + ), + ), + ), + ), ); - } - connect(); - function subscribe() { - UDN.subscribe(subscriptionChannel.value); - } - function unsubscribe() { - UDN.unsubscribe(subscriptionChannel.value); - } - function sendMessage() { - if (isMessageEmpty.value == true) return; - UDN.sendMessage(newMessageChannel.value, newMessageBody.value); - newMessageBody.value = ""; - } - function requestMailbox() { - UDN.requestMailbox(); - } - function deleteMailbox() { - console.log("test"); - UDN.deleteMailbox(mailboxId.value); - } - - // src/infoScreen.tsx - function InfoScreen() { - return /* @__PURE__ */ createElement("article", { id: "info-screen" }, /* @__PURE__ */ createElement("header", null, translations.serverInfo, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement("button", { "on:click": updateStats }, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "flex-column gap", "subscribe:innerHTML": statHTMLString }))); - } - - // src/mainScreen.tsx - function MainScreen() { - return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, translations.settings), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.messageLastReceived), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": lastReceivedMessage }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.mailbox), /* @__PURE__ */ createElement("p", { class: "secondary", "toggle:hidden": isMailboxDisonnected }, translations.mailboxConnected))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement( - "button", - { - class: "danger width-50", - "on:click": deleteMailbox, - "toggle:disabled": isMailboxDisonnected - }, - translations.deleteMailbox - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary width-50", - "on:click": requestMailbox, - "toggle:disabled": isDisconnected - }, - translations.requestMailbox, - /* @__PURE__ */ createElement("span", { class: "icon" }, "inbox") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.subscribeToChannel), /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.channel_placeholder, - "bind:value": subscriptionChannel, - "on:enter": subscribe - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement("button", { class: "danger width-50", "on:click": unsubscribe }, translations.unsubscribe), /* @__PURE__ */ createElement("button", { class: "primary width-50", "on:click": subscribe }, translations.subscribe, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.channel), /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.channel_placeholder, - "bind:value": newMessageChannel, - "on:enter": sendMessage - } - ))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.message), /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.message_placeholder, - "bind:value": newMessageBody, - "on:enter": sendMessage - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement( - "button", - { - class: "primary width-50", - "on:click": sendMessage, - "toggle:disabled": isMessageEmpty - }, - translations.send, - /* @__PURE__ */ createElement("span", { class: "icon" }, "send") - )))); - } - - // src/messageScreen.tsx - var convertMessageToElement = (message) => { - return /* @__PURE__ */ createElement("div", { class: "tile", style: "flex: 0" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, message.channel), /* @__PURE__ */ createElement("b", null, message.body))); - }; - function MessageScreen() { - return /* @__PURE__ */ createElement("article", { id: "message-screen" }, /* @__PURE__ */ createElement("header", null, translations.messagesReceived), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "subscribe:children": [messages, convertMessageToElement] - } - )), /* @__PURE__ */ createElement("footer", null, /* @__PURE__ */ createElement( - "input", - { - style: "max-width: unset", - placeholder: translations.message_placeholder_generic, - "bind:value": newMessageBody, - "on:enter": sendMessage - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": sendMessage, - "toggle:disabled": isMessageEmpty - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "send") - ))); - } - - // src/index.tsx - document.body.prepend( - /* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#info-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info"), translations.info), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), translations.settings), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#message-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "chat"), translations.messages)) - ); - document.querySelector("main").append(InfoScreen(), MainScreen(), MessageScreen()); - document.body.append( - /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isDisconnected }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement( - "div", - { - class: "flex-column align-center justify-center width-100 height-100", - style: "gap: 1rem" - }, - /* @__PURE__ */ createElement("span", { class: "icon error", style: "font-size: 3rem" }, "signal_disconnected"), - /* @__PURE__ */ createElement("h1", { class: "error" }, translations.disconnected), - /* @__PURE__ */ createElement("p", { class: "secondary" }, translations.reconnecting) - )))) - ); })(); diff --git a/static/dist/manifest.json b/static/dist/manifest.json index 1ed9dea..8b2c8ac 100644 --- a/static/dist/manifest.json +++ b/static/dist/manifest.json @@ -1,10 +1,10 @@ { - "name": "UDN Server", - "display": "standalone", - "icons": [ - { - "src": "icon.png", - "sizes": "any" - } - ] + "name": "UDN Server", + "display": "standalone", + "icons": [ + { + "src": "icon.png", + "sizes": "any" + } + ] } diff --git a/static/dist/styles.css b/static/dist/styles.css index 453235d..449ca8a 100644 --- a/static/dist/styles.css +++ b/static/dist/styles.css @@ -2,12 +2,12 @@ BASIC */ * { - box-sizing: border-box; - text-align: left; - border: none; - outline: none; - margin: 0px; - padding: 0px; + box-sizing: border-box; + text-align: left; + border: none; + outline: none; + margin: 0px; + padding: 0px; } /* @@ -15,116 +15,116 @@ COLORS */ /* definition */ :root { - --background: hsla(0, 0%, 100%, 100%); - --background-transparent: hsla(0, 0%, 100%, 60%); - --background-hover: hsla(0, 0%, 55%, 12%); - --background-active: hsla(0, 0%, 55%, 12%); + --background: hsla(0, 0%, 100%, 100%); + --background-transparent: hsla(0, 0%, 100%, 60%); + --background-hover: hsla(0, 0%, 55%, 12%); + --background-active: hsla(0, 0%, 55%, 12%); - --layer: hsla(0, 0%, 96%, 60%); + --layer: hsla(0, 0%, 96%, 60%); - --surface: #f4f4f4; - --surface-hover: #e8e8e8; - --surface-active: #c6c6c6; + --surface: #f4f4f4; + --surface-hover: #e8e8e8; + --surface-active: #c6c6c6; - --border: hsla(0, 0%, 0%, 10%); - --surface-border: #8d8d8d; + --border: hsla(0, 0%, 0%, 10%); + --surface-border: #8d8d8d; - --text-normal: #161616; - --text-on-color: #ffffff; + --text-normal: #161616; + --text-on-color: #ffffff; - --text-success: #24a148; - --text-warn: #f1c21b; - --text-error: #da1e28; - --text-info: #0043ce; + --text-success: #24a148; + --text-warn: #f1c21b; + --text-error: #da1e28; + --text-info: #0043ce; - --link: #0f62fe; - --link-hover: #0043ce; + --link: #0f62fe; + --link-hover: #0043ce; - --primary: #0f62fe; - --primary-hover: #0353e9; - --primary-active: #002d9c; + --primary: #0f62fe; + --primary-hover: #0353e9; + --primary-active: #002d9c; - --danger: #da1e28; - --danger-hover: #ba1b23; - --danger-active: #750e13; + --danger: #da1e28; + --danger-hover: #ba1b23; + --danger-active: #750e13; } @media (prefers-color-scheme: dark) { - :root { - --background: hsla(0, 0%, 9%, 100%); - --background-transparent: hsla(0, 0%, 9%, 60%); - --background-hover: hsla(0, 0%, 55%, 16%); - --background-active: hsla(0, 0%, 55%, 40%); - - --layer: hsla(0, 0%, 15%, 60%); - - --surface: #262626; - --surface-hover: #333333; - --surface-active: #6f6f6f; - - --border: hsla(0, 0%, 100%, 10%); - --surface-border: #6f6f6f; - - --text-normal: #f4f4f4; - - --text-success: #42be65; - --text-warning: #f1c21b; - --text-error: #fa4d56; - --text-info: #4589ff; - - --link: #78a9ff; - --link-hover: #a6c8ff; - } - - @media (display-mode: standalone) { :root { - --background: hsla(0, 0%, 0%, 100%); - --background-transparent: hsla(0, 0%, 0%, 60%); - --layer: hsla(0, 0%, 0%, 60%); + --background: hsla(0, 0%, 9%, 100%); + --background-transparent: hsla(0, 0%, 9%, 60%); + --background-hover: hsla(0, 0%, 55%, 16%); + --background-active: hsla(0, 0%, 55%, 40%); + + --layer: hsla(0, 0%, 15%, 60%); + + --surface: #262626; + --surface-hover: #333333; + --surface-active: #6f6f6f; + + --border: hsla(0, 0%, 100%, 10%); + --surface-border: #6f6f6f; + + --text-normal: #f4f4f4; + + --text-success: #42be65; + --text-warning: #f1c21b; + --text-error: #fa4d56; + --text-info: #4589ff; + + --link: #78a9ff; + --link-hover: #a6c8ff; + } + + @media (display-mode: standalone) { + :root { + --background: hsla(0, 0%, 0%, 100%); + --background-transparent: hsla(0, 0%, 0%, 60%); + --layer: hsla(0, 0%, 0%, 60%); + } } - } } /* basic */ html { - background-color: var(--background); - color: var(--text-normal); + background-color: var(--background); + color: var(--text-normal); } .modal > div { - background-color: var(--background); + background-color: var(--background); } menu, header { - background-color: var(--background-transparent); + background-color: var(--background-transparent); } * { - color: inherit; - accent-color: var(--primary); + color: inherit; + accent-color: var(--primary); } a { - color: var(--link); + color: var(--link); } /* text */ .success { - color: var(--text-success); + color: var(--text-success); } .warning { - color: var(--text-warning); + color: var(--text-warning); } .error { - color: var(--text-error); + color: var(--text-error); } .info { - color: var(--text-info); + color: var(--text-info); } /* controls */ @@ -135,117 +135,120 @@ textarea, select, .surface, .tile { - background-color: var(--surface); + background-color: var(--surface); } @media (hover: hover) { - button:hover, - select:hover, - button.tile.ghost:hover, - [selected]:active { - background-color: var(--surface-hover); - } + button:hover, + select:hover, + button.tile.ghost:hover, + [selected]:active { + background-color: var(--surface-hover); + } } button:active, select:active, button.tile.ghost:active, [selected] { - background-color: var(--surface-active); + background-color: var(--surface-active); } header button { - background-color: transparent; + background-color: transparent; } input:checked { - background-color: var(--primary); + background-color: var(--primary); } /* primary button */ button.primary { - background-color: var(--primary); - color: var(--text-on-color); + background-color: var(--primary); + color: var(--text-on-color); } @media (hover: hover) { - button.primary:hover { - background-color: var(--primary-hover); - } + button.primary:hover { + background-color: var(--primary-hover); + } } button.primary:active { - background-color: var(--primary-active); + background-color: var(--primary-active); } /* ghost button */ button.ghost:not(.tile) { - background-color: transparent; + background-color: transparent; } button.ghost { - color: var(--link); + color: var(--link); } .tab-link[active] { - color: var(--primary); + color: var(--primary); } @media (hover: hover) { - button.ghost:hover { - background-color: var(--background-hover); - } + button.ghost:hover { + background-color: var(--background-hover); + } } button.ghost:active { - background-color: var(--background-active); + background-color: var(--background-active); } /* danger button */ button.danger { - background-color: var(--danger); - color: var(--text-on-color); + background-color: var(--danger); + color: var(--text-on-color); } @media (hover: hover) { - button.danger:hover { - background-color: var(--danger-hover); - } + button.danger:hover { + background-color: var(--danger-hover); + } } button.danger:active { - background-color: var(--danger-active); + background-color: var(--danger-active); } /* SIZES */ :root { - /* gap */ - --gap-between-tiles: 4px; - --gap-in-tab-links: 1rem; - --gap-in-controls: 1.2rem; + /* gap */ + --gap-between-tiles: 4px; + --gap-in-tab-links: 1rem; + --gap-in-controls: 1.2rem; - /* margin */ - --label-top-padding: 0.25rem; - --label-tile-top-padding: 0.5rem; - --label-margin-bottom: 1rem; - --content-margin: 0.75rem; + /* margin */ + --label-top-padding: 0.25rem; + --label-tile-top-padding: 0.5rem; + --label-margin-bottom: 1rem; + --content-margin: 0.75rem; - /* padding */ - --control-padding: 1rem; - --control-marker-padding: 0.7rem; - --content-padding: 1rem; - --modal-padding: 1.25rem; - --horizontal-tab-link-padding: 0.8rem; + /* padding */ + --control-padding: 1rem; + --control-marker-padding: 0.7rem; + --content-padding: 1rem; + --modal-padding: 1.25rem; + --horizontal-tab-link-padding: 0.8rem; - /* size */ - --checkbox-size: 16px; + /* size */ + --checkbox-size: 16px; - --menu-height-mobile: calc(var(--header-height) + var(--safe-area-bottom)); - --menu-width-desktop: 200px; - --split-pane-width-desktop: 300px; + --menu-height-mobile: calc(var(--header-height) + var(--safe-area-bottom)); + --menu-width-desktop: 200px; + --split-pane-width-desktop: 300px; - --control-height: 42px; - --header-height: 48px; + --control-height: 42px; + --header-height: 48px; - --max-input-width: 24rem; - --max-content-width: 38rem; + --max-input-width: 24rem; + --max-content-width: 38rem; - /* safe */ - --safe-area-top: env(safe-area-inset-top); - --safe-area-left: env(safe-area-inset-left); - --safe-area-right: env(safe-area-inset-right); - --safe-area-bottom: max(env(safe-area-inset-bottom), var(--control-padding)); + /* safe */ + --safe-area-top: env(safe-area-inset-top); + --safe-area-left: env(safe-area-inset-left); + --safe-area-right: env(safe-area-inset-right); + --safe-area-bottom: max( + env(safe-area-inset-bottom), + var(--control-padding) + ); } /* @@ -253,24 +256,24 @@ TYPE */ @font-face { - font-family: IBM Plex Sans; - src: url("Fonts/IBMPlexSans-Regular.ttf"); + font-family: IBM Plex Sans; + src: url("Fonts/IBMPlexSans-Regular.ttf"); } @font-face { - font-family: IBM Plex Sans; - src: url("Fonts/IBMPlexSans-Bold.ttf"); - font-weight: bold; + font-family: IBM Plex Sans; + src: url("Fonts/IBMPlexSans-Bold.ttf"); + font-weight: bold; } @font-face { - font-family: IBM Plex Mono; - src: url("Fonts/IBMPlexMono-Regular.ttf"); + font-family: IBM Plex Mono; + src: url("Fonts/IBMPlexMono-Regular.ttf"); } @font-face { - font-family: IBM Plex Serif; - src: url("Fonts/IBMPlexSerif-Regular.ttf"); + font-family: IBM Plex Serif; + src: url("Fonts/IBMPlexSerif-Regular.ttf"); } /* @@ -280,16 +283,16 @@ MAIN input[type="checkbox"], input[type="radio"], select { - appearance: none; - --webkit-appearance: none; - --mpz-appearance: none; + appearance: none; + --webkit-appearance: none; + --mpz-appearance: none; } /* backdrop-filter */ header, menu { - backdrop-filter: blur(70px); - -webkit-backdrop-filter: blur(70px); + backdrop-filter: blur(70px); + -webkit-backdrop-filter: blur(70px); } /* border, outline, etc */ @@ -297,54 +300,54 @@ button, input, select, textarea { - border: none; + border: none; } footer { - border-top: 1px solid var(--surface-border); + border-top: 1px solid var(--surface-border); } input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not( - label.tile * - ), + label.tile * + ), label.tile, select:not(label.tile *), textarea:not(label.tile *) { - border-bottom: 1px solid var(--surface-border); + border-bottom: 1px solid var(--surface-border); } hr { - border-bottom: 1px solid var(--border); + border-bottom: 1px solid var(--border); } input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):focus:not( - label.tile * - ), + label.tile * + ), label.tile:has(input:focus), label.tile:has(textarea:focus), textarea:focus:not(label.tile *), [selected] { - outline: var(--gap-between-tiles) solid var(--primary); + outline: var(--gap-between-tiles) solid var(--primary); } /* border-radius */ * { - border-radius: 0px; + border-radius: 0px; } input[type="radio"] { - border-radius: 100%; + border-radius: 100%; } /* cursor */ a, .tab-link { - cursor: pointer; + cursor: pointer; } /* display etc */ .align-start { - align-items: start !important; + align-items: start !important; } button:not(.tile), @@ -356,20 +359,20 @@ label.inline, select, textarea, .tile:not(label) { - align-items: center; + align-items: center; } .align-center { - align-items: center !important; + align-items: center !important; } .align-end { - align-items: end !important; + align-items: end !important; } label, label * { - display: block; + display: block; } footer, @@ -382,87 +385,87 @@ main, .modal > div, .tile, .tile > div { - display: flex; + display: flex; } .flex-column, .flex-row { - display: flex; + display: flex; } .grid { - display: grid !important; + display: grid !important; } button:not(.tile), input, select, textarea { - display: inline-flex; + display: inline-flex; } .modal, [hidden] { - display: none; + display: none; } main, .tile, .tile > div { - flex: 1; + flex: 1; } .flex { - flex: 1 !important; + flex: 1 !important; } .flex-no { - flex: 0 !important; + flex: 0 !important; } .modal > div, .tile > div { - flex-direction: column; + flex-direction: column; } .flex-column { - flex-direction: column !important; + flex-direction: column !important; } .flex-row { - flex-direction: row !important; + flex-direction: row !important; } .flex-wrap { - flex-wrap: wrap !important; + flex-wrap: wrap !important; } button, label.inline, .tile, .control-gap { - gap: var(--gap-in-controls); + gap: var(--gap-in-controls); } .gap { - gap: var(--gap-between-tiles) !important; + gap: var(--gap-between-tiles) !important; } header, button { - justify-content: space-between; + justify-content: space-between; } .justify-apart { - justify-content: space-between !important; + justify-content: space-between !important; } .tile { - justify-content: start; + justify-content: start; } .justify-start { - justify-content: start !important; + justify-content: start !important; } footer, @@ -470,144 +473,145 @@ header button, input[type="checkbox"], input[type="radio"], .modal { - justify-content: center; + justify-content: center; } .justify-center { - justify-content: center !important; + justify-content: center !important; } .justify-end { - justify-content: end !important; + justify-content: end !important; } /* opacity */ [disabled] { - opacity: 0.5; + opacity: 0.5; } label span, .helper-text, .secondary, .tab-link:not([active]) { - opacity: 0.6; + opacity: 0.6; } /* padding, margin, etc */ h1, h2, h3 { - margin: var(--control-padding) 0px; + margin: var(--control-padding) 0px; } main, p { - margin: 0px; + margin: 0px; } .margin-0 { - margin: 0px !important; + margin: 0px !important; } hr { - margin: var(--content-margin) 0px; + margin: var(--content-margin) 0px; } :is(code, p):not(:last-child) { - margin-bottom: var(--content-margin); + margin-bottom: var(--content-margin); } label:not(.tile) { - margin-bottom: var(--label-margin-bottom); + margin-bottom: var(--label-margin-bottom); } .margin-bottom { - margin-bottom: var(--gap-between-tiles) !important; + margin-bottom: var(--gap-between-tiles) !important; } label.tile { - margin-bottom: var(--gap-between-tiles); + margin-bottom: var(--gap-between-tiles); } label:not(.tile) span:first-child { - margin-top: var(--label-top-padding); + margin-top: var(--label-top-padding); } label.tile span:first-child { - margin-top: var(--label-tile-top-padding); + margin-top: var(--label-tile-top-padding); } input[type="color"], input[type="checkbox"], input[type="radio"] { - padding: 0px; + padding: 0px; } .padding-0 { - padding: 0 !important; + padding: 0 !important; } article > div { - padding: var(--content-padding); + padding: var(--content-padding); } button:not(.tile):not(header *), input, select { - padding: 0px var(--control-padding); + padding: 0px var(--control-padding); } .padding-h { - padding: 0px var(--control-padding) !important; + padding: 0px var(--control-padding) !important; } label.inline, textarea, .tile:not(label) { - padding: var(--control-padding); + padding: var(--control-padding); } .padding { - padding: var(--control-padding) !important; + padding: var(--control-padding) !important; } .modal main { - padding: var(--modal-padding); + padding: var(--modal-padding); } article > div { - padding-top: calc(var(--header-height) + var(--content-margin)); + padding-top: calc(var(--header-height) + var(--content-margin)); } header { - padding-left: var(--content-padding); + padding-left: var(--content-padding); } label.tile:has(> span.icon) { - padding-left: var(--control-padding); + padding-left: var(--control-padding); } label.tile:has(> span.icon) input { - padding-left: 0px; + padding-left: 0px; } article > div { - padding-bottom: calc( - var(--menu-height-mobile) + var(--control-height) + var(--content-padding) - ); + padding-bottom: calc( + var(--menu-height-mobile) + var(--control-height) + + var(--content-padding) + ); } label:not(.inline) { - text-indent: var(--control-padding); + text-indent: var(--control-padding); } label.tile:has(> span.icon) { - text-indent: 0px; + text-indent: 0px; } /* pointer-events */ [disabled] { - pointer-events: none; + pointer-events: none; } /* position etc */ @@ -615,88 +619,88 @@ article > div, footer, header, select + .icon { - position: absolute; + position: absolute; } .absolute { - position: absolute !important; + position: absolute !important; } .modal { - position: fixed; + position: fixed; } .fixed { - position: fixed !important; + position: fixed !important; } article, main, input, label { - position: relative; + position: relative; } .relative { - position: relative !important; + position: relative !important; } .sticky { - position: sticky !important; + position: sticky !important; } article > div, header, .modal { - top: 0; + top: 0; } .top-0 { - top: 0 !important; + top: 0 !important; } article > div, footer, header, .modal { - left: 0px; + left: 0px; } .left-0 { - left: 0px !important; + left: 0px !important; } article > div, footer, header, .modal { - right: 0px; + right: 0px; } .right-0 { - right: 0px !important; + right: 0px !important; } select + .icon { - right: var(--control-marker-padding); + right: var(--control-marker-padding); } footer { - bottom: var(--safe-area-bottom); + bottom: var(--safe-area-bottom); } body:has(> menu) footer { - bottom: var(--menu-height-mobile); + bottom: var(--menu-height-mobile); } article > div, select + .icon, .modal { - bottom: 0px; + bottom: 0px; } .bottom-0 { - bottom: 0px !important; + bottom: 0px !important; } /* size */ @@ -705,11 +709,11 @@ main, select + .icon, .modal > div, .modal main { - height: 100%; + height: 100%; } .height-100 { - height: 100% !important; + height: 100% !important; } button:not(.tile):not(header *), @@ -718,20 +722,20 @@ input:not([type="checkbox"]):not([type="radio"]), select, select + .icon, textarea { - height: var(--control-height); + height: var(--control-height); } header, header button { - height: var(--header-height); + height: var(--header-height); } article { - min-width: 100%; + min-width: 100%; } .width-50 { - width: 50% !important; + width: 50% !important; } article, @@ -744,47 +748,47 @@ textarea, .modal > div, .tile, .tile > div { - width: 100%; + width: 100%; } .width-content, .width-input, .width-100 { - width: 100% !important; + width: 100% !important; } header button { - width: var(--header-height); + width: var(--header-height); } input[type="checkbox"], input[type="radio"] { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } /* text */ * { - font-family: IBM Plex Sans; - font-size: 1rem; + font-family: IBM Plex Sans; + font-size: 1rem; } h1 { - font-size: 2rem; + font-size: 2rem; } h2 { - font-size: 1.6rem; + font-size: 1.6rem; } h3 { - font-size: 1.4rem; + font-size: 1.4rem; } code { - font-family: IBM Plex Mono; + font-family: IBM Plex Mono; } .secondary, @@ -792,63 +796,63 @@ code { .helper-text, .error-text, label { - font-size: 0.9rem; + font-size: 0.9rem; } /* text-decoration */ a { - text-decoration: none; + text-decoration: none; } /* z-index */ footer, header, menu { - z-index: 1; + z-index: 1; } .modal { - z-index: 2; + z-index: 2; } .modal > div { - z-index: 2; + z-index: 2; } /* PSEUDO */ input[type="checkbox"]:checked::before { - content: "check"; - font-size: 70% !important; - color: var(--text-on-color); + content: "check"; + font-size: 70% !important; + color: var(--text-on-color); } input[type="radio"]:checked::before { - content: ""; + content: ""; - background-color: var(--text-on-color); - border-radius: 100%; + background-color: var(--text-on-color); + border-radius: 100%; - --inset: 4px; - position: absolute; - top: var(--inset); - left: var(--inset); - right: var(--inset); - bottom: var(--inset); + --inset: 4px; + position: absolute; + top: var(--inset); + left: var(--inset); + right: var(--inset); + bottom: var(--inset); } .modal::before { - content: ""; + content: ""; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; - background-color: var(--background); - opacity: 0.6; + background-color: var(--background); + opacity: 0.6; } /* @@ -857,193 +861,194 @@ LAYOUT /* basic */ body, html { - display: flex; - flex-direction: column-reverse; + display: flex; + flex-direction: column-reverse; - margin: 0px; - width: 100%; - height: 100%; + margin: 0px; + width: 100%; + height: 100%; } body, .modal { - padding-left: var(--safe-area-left); - padding-right: var(--safe-area-right); - padding-top: var(--safe-area-top); + padding-left: var(--safe-area-left); + padding-right: var(--safe-area-right); + padding-top: var(--safe-area-top); } .modal { - padding-bottom: var(--safe-area-bottom); + padding-bottom: var(--safe-area-bottom); } menu { - width: 100%; - height: var(--menu-height-mobile); + width: 100%; + height: var(--menu-height-mobile); - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; - display: flex; - flex-direction: row; - justify-content: space-around; + display: flex; + flex-direction: row; + justify-content: space-around; - padding-top: 0px; - padding-bottom: var(--safe-area-bottom); + padding-top: 0px; + padding-bottom: var(--safe-area-bottom); } .tab-link { - width: 100%; - height: var(--header-height); + width: 100%; + height: var(--header-height); - padding: var(--horizontal-tab-link-padding) var(--control-padding); + padding: var(--horizontal-tab-link-padding) var(--control-padding); - display: flex; - flex-direction: column; - align-items: center; - gap: 0px; + display: flex; + flex-direction: column; + align-items: center; + gap: 0px; - font-size: 0.5rem; + font-size: 0.5rem; } article > div { - padding-bottom: calc( - var(--menu-height-mobile) + var(--header-height) + var(--content-padding) - ); + padding-bottom: calc( + var(--menu-height-mobile) + var(--header-height) + + var(--content-padding) + ); } /* overflow */ menu { - overflow: auto; + overflow: auto; } article, body, main { - overflow: hidden; + overflow: hidden; } article > div { - overflow: scroll; + overflow: scroll; } main { - overflow-x: scroll; + overflow-x: scroll; } .tile { - word-break: break-all; + word-break: break-all; } /* scrolling */ main { - scrollbar-width: none; + scrollbar-width: none; } main::-webkit-scrollbar { - display: none; + display: none; } main { - scroll-behavior: smooth; + scroll-behavior: smooth; } article { - scroll-snap-align: start; + scroll-snap-align: start; } main { - scroll-snap-type: x mandatory; + scroll-snap-type: x mandatory; } /* tablet & up */ @media (min-width: 640px) { - /* basic */ - .mobile-only { - display: none; - } + /* basic */ + .mobile-only { + display: none; + } - /* border, outline, etc */ - .modal > div { - border: 1px solid var(--surface-border); - } + /* border, outline, etc */ + .modal > div { + border: 1px solid var(--surface-border); + } - menu, - main.split > article:first-of-type { - border-right: 1px solid var(--border); - } + menu, + main.split > article:first-of-type { + border-right: 1px solid var(--border); + } - /* size */ - .modal > div { - height: auto; - } + /* size */ + .modal > div { + height: auto; + } - p, - .modal > div { - max-width: var(--max-content-width); - } + p, + .modal > div { + max-width: var(--max-content-width); + } - .width-content { - max-width: var(--max-content-width) !important; - } + .width-content { + max-width: var(--max-content-width) !important; + } - input, - label, - select, - textarea { - max-width: var(--max-input-width); - } + input, + label, + select, + textarea { + max-width: var(--max-input-width); + } - .width-input { - max-width: var(--max-input-width) !important; - } + .width-input { + max-width: var(--max-input-width) !important; + } - /* layout */ - body { - flex-direction: row; - } + /* layout */ + body { + flex-direction: row; + } - main.split { - display: grid; - grid-template-columns: var(--split-pane-width-desktop) 1fr; - } + main.split { + display: grid; + grid-template-columns: var(--split-pane-width-desktop) 1fr; + } - footer { - bottom: 0px !important; - } + footer { + bottom: 0px !important; + } - article > div { - padding-bottom: calc(var(--content-padding) + var(--control-height)); - } + article > div { + padding-bottom: calc(var(--content-padding) + var(--control-height)); + } - menu { - position: static; + menu { + position: static; - width: var(--menu-width-desktop); - height: 100%; + width: var(--menu-width-desktop); + height: 100%; - flex-direction: column; - justify-content: start; - padding-top: 20px; - } + flex-direction: column; + justify-content: start; + padding-top: 20px; + } - .tab-link { - width: 100%; - height: var(--control-height); + .tab-link { + width: 100%; + height: var(--control-height); - flex-direction: row; - gap: var(--gap-in-tab-links); + flex-direction: row; + gap: var(--gap-in-tab-links); - font-size: 1rem; - } + font-size: 1rem; + } } /* desktop */ @media (min-width: 1024px) { - :root { - --split-pane-width-desktop: 400px; - } + :root { + --split-pane-width-desktop: 400px; + } } /* @@ -1052,26 +1057,26 @@ ICONS input[type="checkbox"], .tile.arrow-right::after, .icon { - font-family: "Material Symbols Sharp"; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; + font-family: "Material Symbols Sharp"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; } @font-face { - font-family: "Material Symbols Sharp"; - font-style: normal; - font-weight: 300; - src: url("Icons/material-icons.woff") format("woff"); + font-family: "Material Symbols Sharp"; + font-style: normal; + font-weight: 300; + src: url("Icons/material-icons.woff") format("woff"); } diff --git a/static/dist/support.js b/static/dist/support.js index 2a9bce5..2ca5f1b 100644 --- a/static/dist/support.js +++ b/static/dist/support.js @@ -1,31 +1,34 @@ // TABS document.querySelector("main").addEventListener("scroll", () => { - const links = document.querySelectorAll(".tab-link"); - links.forEach((element) => element.removeAttribute("active")); + const links = document.querySelectorAll(".tab-link"); + links.forEach((element) => element.removeAttribute("active")); - const pageWidth = document.querySelector("main > article").offsetWidth; - const scroll = document.querySelector("main").scrollLeft; - const currentPage = Math.round(scroll / pageWidth); + const pageWidth = document.querySelector("main > article").offsetWidth; + const scroll = document.querySelector("main").scrollLeft; + const currentPage = Math.round(scroll / pageWidth); - if (links[currentPage]) links[currentPage].setAttribute("active", ""); + if (links[currentPage]) links[currentPage].setAttribute("active", ""); }); // SERVICE WORKER async function registerServiceWorker() { - if ("serviceWorker" in navigator) { - try { - const registration = await navigator.serviceWorker.register("/sw.js", { - scope: "/", - }); - if (registration.installing) { - setTimeout(() => window.location.reload(), 500); - } else if (registration.active) { - console.log("Service worker active"); - } - } catch (error) { - console.error(`Could not install service worker: ${error}`); + if ("serviceWorker" in navigator) { + try { + const registration = await navigator.serviceWorker.register( + "/sw.js", + { + scope: "/", + }, + ); + if (registration.installing) { + setTimeout(() => window.location.reload(), 500); + } else if (registration.active) { + console.log("Service worker active"); + } + } catch (error) { + console.error(`Could not install service worker: ${error}`); + } } - } } registerServiceWorker(); diff --git a/static/dist/sw.js b/static/dist/sw.js index 5768bc6..8f2e283 100644 --- a/static/dist/sw.js +++ b/static/dist/sw.js @@ -1,38 +1,38 @@ const CACHE_VERSION = "v1"; async function handleRequest(event) { - const responsePromise = getResponse(event); - event.respondWith(responsePromise); + const responsePromise = getResponse(event); + event.respondWith(responsePromise); } async function getResponse(event) { - try { - return await fetchAndCache(event); - } catch { - return await getFromCache(event); - } + try { + return await fetchAndCache(event); + } catch { + return await getFromCache(event); + } } async function fetchAndCache(event) { - const request = event.request; + const request = event.request; - const response = await fetch(request); + const response = await fetch(request); - const cache = await caches.open(CACHE_VERSION); - await cache.put(request, response.clone()); + const cache = await caches.open(CACHE_VERSION); + await cache.put(request, response.clone()); - return response; + return response; } async function getFromCache(event) { - const request = event.request; + const request = event.request; - const cache = await caches.open(CACHE_VERSION); - const response = await cache.match(request); + const cache = await caches.open(CACHE_VERSION); + const response = await cache.match(request); - return response; + return response; } self.addEventListener("fetch", (event) => { - handleRequest(event); -}); \ No newline at end of file + handleRequest(event); +}); diff --git a/static/package.json b/static/package.json index eb23422..ff4daef 100644 --- a/static/package.json +++ b/static/package.json @@ -1,10 +1,10 @@ { - "dependencies": { - "bloatless-react": "^1.2.6", - "esbuild": "latest", - "udn-frontend": "^1.0.6" - }, - "scripts": { - "build": "esbuild src/index.tsx --bundle --outdir=dist" - } + "dependencies": { + "bloatless-react": "^1.2.6", + "esbuild": "latest", + "udn-frontend": "^1.0.6" + }, + "scripts": { + "build": "esbuild src/index.tsx --bundle --outdir=dist" + } } diff --git a/static/src/index.tsx b/static/src/index.tsx index 4377802..384ff20 100644 --- a/static/src/index.tsx +++ b/static/src/index.tsx @@ -7,41 +7,41 @@ import { isDisconnected } from "./model"; import { translations } from "./translations"; document.body.prepend( - - - info - {translations.info} - - - settings - {translations.settings} - - - chat - {translations.messages} - - + + + info + {translations.info} + + + settings + {translations.settings} + + + chat + {translations.messages} + + , ); document - .querySelector("main")! - .append(InfoScreen(), MainScreen(), MessageScreen()); + .querySelector("main")! + .append(InfoScreen(), MainScreen(), MessageScreen()); document.body.append( - + , ); diff --git a/static/src/infoScreen.tsx b/static/src/infoScreen.tsx index 995b4a5..327ae7d 100644 --- a/static/src/infoScreen.tsx +++ b/static/src/infoScreen.tsx @@ -5,19 +5,22 @@ import { statHTMLString, updateStats } from "./model"; import { translations } from "./translations"; export function InfoScreen() { - return ( -
-
- {translations.serverInfo} - - - -
-
-
-
-
- ); + return ( +
+
+ {translations.serverInfo} + + + +
+
+
+
+
+ ); } diff --git a/static/src/mainScreen.tsx b/static/src/mainScreen.tsx index b07bb22..fca07cd 100644 --- a/static/src/mainScreen.tsx +++ b/static/src/mainScreen.tsx @@ -1,122 +1,128 @@ import * as React from "bloatless-react"; import { - deleteMailbox, - isDisconnected, - isMailboxDisonnected, - isMessageEmpty, - lastReceivedMessage, - newMessageBody, - newMessageChannel, - requestMailbox, - sendMessage, - subscribe, - subscriptionChannel, - unsubscribe, + deleteMailbox, + isDisconnected, + isMailboxDisonnected, + isMessageEmpty, + lastReceivedMessage, + newMessageBody, + newMessageChannel, + requestMailbox, + sendMessage, + subscribe, + subscriptionChannel, + unsubscribe, } from "./model"; import { translations } from "./translations"; export function MainScreen() { - // UI - return ( -
-
{translations.settings}
-
-
-
- {translations.messageLastReceived} -

-
-
+ // UI + return ( +
+
{translations.settings}
+
+
+
+ {translations.messageLastReceived} +

+
+
-
+
-
-
- {translations.mailbox} -

- {translations.mailboxConnected} -

-
-
+
+
+ {translations.mailbox} +

+ {translations.mailboxConnected} +

+
+
-
- - -
+
+ + +
-
+
- + -
- - -
+
+ + +
-
+
- + - + -
- -
-
-
- ); +
+ +
+
+
+ ); } diff --git a/static/src/messageScreen.tsx b/static/src/messageScreen.tsx index cdf5cca..a835efa 100644 --- a/static/src/messageScreen.tsx +++ b/static/src/messageScreen.tsx @@ -1,51 +1,51 @@ import * as React from "bloatless-react"; import { - Message, - isMessageEmpty, - messages, - newMessageBody, - sendMessage, + Message, + isMessageEmpty, + messages, + newMessageBody, + sendMessage, } from "./model"; import { translations } from "./translations"; const convertMessageToElement: React.ListItemConverter = (message) => { - return ( -
-
- {message.channel} - {message.body} -
-
- ); + return ( +
+
+ {message.channel} + {message.body} +
+
+ ); }; export function MessageScreen() { - return ( -
-
{translations.messagesReceived}
-
-
-
-
- - -
-
- ); + return ( +
+
{translations.messagesReceived}
+
+
+
+
+ + +
+
+ ); } diff --git a/static/src/model.tsx b/static/src/model.tsx index b6340e6..2368e83 100644 --- a/static/src/model.tsx +++ b/static/src/model.tsx @@ -4,7 +4,10 @@ import UDNFrontend from "udn-frontend"; import { translations } from "./translations"; export class Message { - constructor(public channel: string, public body: string) {} + constructor( + public channel: string, + public body: string, + ) {} } // STATE @@ -18,28 +21,30 @@ export const isMailboxDisonnected = new React.State(true); // messages export const messages = new React.ListState(); export const lastReceivedMessage = new React.State( - translations.noMessagesReceived + translations.noMessagesReceived, ); // stats export const statHTMLString = new React.State(""); export async function updateStats() { - const response = await fetch( - `${window.location.protocol}//${window.location.host}/stats` - ); - const data: [string, string][] = await response.json(); - - let html = ""; - data.forEach((item) => { - const tile = ( -
- {item[0].toString()} - {item[1].toString()} -
+ const response = await fetch( + `${window.location.protocol}//${window.location.host}/stats`, ); - html += tile.outerHTML; - }); - statHTMLString.value = html; + const data: [string, string][] = await response.json(); + + let html = ""; + data.forEach((item) => { + const tile = ( +
+ + {item[0].toString()} + + {item[1].toString()} +
+ ); + html += tile.outerHTML; + }); + statHTMLString.value = html; } // channels @@ -49,79 +54,79 @@ export const newMessageChannel = new React.State(""); export const newMessageBody = new React.State(""); export const isMessageEmpty = React.createProxyState( - [newMessageChannel, newMessageBody], - () => newMessageChannel.value == "" || newMessageBody.value == "" + [newMessageChannel, newMessageBody], + () => newMessageChannel.value == "" || newMessageBody.value == "", ); // UDN Frontend const UDN = new UDNFrontend(); UDN.onconnect = () => { - isDisconnected.value = false; - updateStats(); + isDisconnected.value = false; + updateStats(); - if (mailboxId.value != "") { - UDN.connectMailbox(mailboxId.value); - } + if (mailboxId.value != "") { + UDN.connectMailbox(mailboxId.value); + } }; UDN.onmessage = (data) => { - lastReceivedMessage.value = JSON.stringify(data, null, 4); + lastReceivedMessage.value = JSON.stringify(data, null, 4); - const { messageChannel, messageBody } = data; - if (messageChannel && messageBody) { - const messageObject = new Message(messageChannel, messageBody); - if (messageBody) messages.add(messageObject); - } + const { messageChannel, messageBody } = data; + if (messageChannel && messageBody) { + const messageObject = new Message(messageChannel, messageBody); + if (messageBody) messages.add(messageObject); + } }; UDN.onmailboxcreate = (id) => { - console.log(id); - mailboxId.value = id; - UDN.connectMailbox(id); + console.log(id); + mailboxId.value = id; + UDN.connectMailbox(id); }; UDN.onmailboxconnect = () => { - isMailboxDisonnected.value = false; + isMailboxDisonnected.value = false; }; UDN.onmailboxdelete = () => { - isMailboxDisonnected.value = true; -} + isMailboxDisonnected.value = true; +}; UDN.ondisconnect = () => { - isDisconnected.value = true; - isMailboxDisonnected.value = true; - setTimeout(connect, 1000); + isDisconnected.value = true; + isMailboxDisonnected.value = true; + setTimeout(connect, 1000); }; function connect() { - UDN.connect( - `${window.location.protocol.replace("http", "ws")}//${window.location.host}` - ); + UDN.connect( + `${window.location.protocol.replace("http", "ws")}//${window.location.host}`, + ); } connect(); // METHODS export function subscribe() { - UDN.subscribe(subscriptionChannel.value); + UDN.subscribe(subscriptionChannel.value); } export function unsubscribe() { - UDN.unsubscribe(subscriptionChannel.value); + UDN.unsubscribe(subscriptionChannel.value); } export function sendMessage() { - if (isMessageEmpty.value == true) return; - UDN.sendMessage(newMessageChannel.value, newMessageBody.value); - newMessageBody.value = ""; + if (isMessageEmpty.value == true) return; + UDN.sendMessage(newMessageChannel.value, newMessageBody.value); + newMessageBody.value = ""; } export function requestMailbox() { - UDN.requestMailbox(); + UDN.requestMailbox(); } export function deleteMailbox() { - console.log("test"); - UDN.deleteMailbox(mailboxId.value); + console.log("test"); + UDN.deleteMailbox(mailboxId.value); } diff --git a/static/src/translations.tsx b/static/src/translations.tsx index f1aaccb..7a8c2c1 100644 --- a/static/src/translations.tsx +++ b/static/src/translations.tsx @@ -1,79 +1,79 @@ // STATIC const staticTextEnglish = { - mailbox: "Mailbox", - requestMailbox: "Request", - deleteMailbox: "Delete", - mailboxConnected: "Mailbox active", - channel: "Channel", - channel_placeholder: "my-channel", - disconnected: "Server disconnected", - reconnecting: "Reconnecting...", - info: "Info", - message: "Message", - messages: "Messages", - message_placeholder: "Hello, world!", - message_placeholder_generic: "Type a message...", - noMessagesReceived: "No messages received", - messageLastReceived: "Last received message", - messagesReceived: "Received Messages", - send: "Send", - serverInfo: "Server Info", - settings: "Settings", - subscribeToChannel: "Subscribe to channel", - subscribe: "Subscribe", - unsubscribe: "Unubscribe", + mailbox: "Mailbox", + requestMailbox: "Request", + deleteMailbox: "Delete", + mailboxConnected: "Mailbox active", + channel: "Channel", + channel_placeholder: "my-channel", + disconnected: "Server disconnected", + reconnecting: "Reconnecting...", + info: "Info", + message: "Message", + messages: "Messages", + message_placeholder: "Hello, world!", + message_placeholder_generic: "Type a message...", + noMessagesReceived: "No messages received", + messageLastReceived: "Last received message", + messagesReceived: "Received Messages", + send: "Send", + serverInfo: "Server Info", + settings: "Settings", + subscribeToChannel: "Subscribe to channel", + subscribe: "Subscribe", + unsubscribe: "Unubscribe", }; const allTranslations: { [key: string]: typeof staticTextEnglish } = { - en: staticTextEnglish, - es: { - mailbox: "Buzón", - requestMailbox: "Solicitar", - deleteMailbox: "Eliminar", - mailboxConnected: "Buzón activado", - channel: "Canal", - channel_placeholder: "mi-canal", - disconnected: "Sin coneción", - reconnecting: "Conectando...", - info: "Info", - message: "Mensaje", - messages: "Mensajes", - message_placeholder: "Hola!", - message_placeholder_generic: "Nuevo mensaje", - noMessagesReceived: "Sin mensajes", - messageLastReceived: "Último mensaje", - messagesReceived: "Todos los Mensajes", - send: "Enviar", - serverInfo: "Información", - settings: "Configuración", - subscribeToChannel: "Suscribirse a un canal", - subscribe: "Suscribirse", - unsubscribe: "Cancelar", - }, - de: { - mailbox: "Briefkasten", - requestMailbox: "Beantragen", - deleteMailbox: "Löschen", - mailboxConnected: "Briefkasten aktiv", - channel: "Kanal", - channel_placeholder: "mein-kanal", - disconnected: "Verbindung getrennt", - reconnecting: "Verbindung wird hergestellt...", - info: "Daten", - message: "Nachricht", - messages: "Nachrichten", - message_placeholder: "Hallo!", - message_placeholder_generic: "Neue Nachricht", - noMessagesReceived: "Keine Nachrichten", - messageLastReceived: "Letzte Nachricht", - messagesReceived: "Empfangene Nachrichten", - send: "Senden", - serverInfo: "Serverdaten", - settings: "Einstellungen", - subscribeToChannel: "Kanal beitreten", - subscribe: "Beitreten", - unsubscribe: "Verlassen", - }, + en: staticTextEnglish, + es: { + mailbox: "Buzón", + requestMailbox: "Solicitar", + deleteMailbox: "Eliminar", + mailboxConnected: "Buzón activado", + channel: "Canal", + channel_placeholder: "mi-canal", + disconnected: "Sin coneción", + reconnecting: "Conectando...", + info: "Info", + message: "Mensaje", + messages: "Mensajes", + message_placeholder: "Hola!", + message_placeholder_generic: "Nuevo mensaje", + noMessagesReceived: "Sin mensajes", + messageLastReceived: "Último mensaje", + messagesReceived: "Todos los Mensajes", + send: "Enviar", + serverInfo: "Información", + settings: "Configuración", + subscribeToChannel: "Suscribirse a un canal", + subscribe: "Suscribirse", + unsubscribe: "Cancelar", + }, + de: { + mailbox: "Briefkasten", + requestMailbox: "Beantragen", + deleteMailbox: "Löschen", + mailboxConnected: "Briefkasten aktiv", + channel: "Kanal", + channel_placeholder: "mein-kanal", + disconnected: "Verbindung getrennt", + reconnecting: "Verbindung wird hergestellt...", + info: "Daten", + message: "Nachricht", + messages: "Nachrichten", + message_placeholder: "Hallo!", + message_placeholder_generic: "Neue Nachricht", + noMessagesReceived: "Keine Nachrichten", + messageLastReceived: "Letzte Nachricht", + messagesReceived: "Empfangene Nachrichten", + send: "Senden", + serverInfo: "Serverdaten", + settings: "Einstellungen", + subscribeToChannel: "Kanal beitreten", + subscribe: "Beitreten", + unsubscribe: "Verlassen", + }, }; const language = navigator.language.substring(0, 2); diff --git a/subscriptionHandler.ts b/subscriptionHandler.ts index 58bf7ce..feb1911 100644 --- a/subscriptionHandler.ts +++ b/subscriptionHandler.ts @@ -8,7 +8,7 @@ import Crypto from "crypto"; import { WS } from "./index"; export interface Subscriber { - send: (message: string) => void; + send: (message: string, compress?: boolean, id?: string) => void; } export class Mailbox implements Subscriber { @@ -35,7 +35,7 @@ export class Mailbox implements Subscriber { { assignedMailboxId: this.id, }, - ws + ws, ); } @@ -46,7 +46,7 @@ export class Mailbox implements Subscriber { { connectedMailboxId: this.id, }, - this.ws + this.ws, ); } @@ -57,7 +57,7 @@ export class Mailbox implements Subscriber { } // storage - messages = new Set(); + messages = new Map(); // websocket private _ws: WS | undefined; @@ -90,13 +90,13 @@ export class Mailbox implements Subscriber { } // sending - send(message: string): void { - if (this._ws?.readyState == 1) { - this._ws.send(message); - this.dateLastUsed = new Date(); - } else { - this.messages.add(message); - } + send(message: string, _?: boolean, uuid?: string): void { + this._ws?.send(message); + + console.log(this.id, uuid, message, "sending"); + + if (!uuid) return; + this.messages.set(uuid, message); } sendUnreadMessages(): void { @@ -107,6 +107,12 @@ export class Mailbox implements Subscriber { this.messages.delete(message); }); } + + checkMessageReceivedConfirmation(uuid: string): void { + if (!this.messages.has(uuid)) return; + this.dateLastUsed = new Date(); + this.messages.delete(uuid); + } } export class SubscriptionMap { diff --git a/utility.ts b/utility.ts index 1bb936e..a1f0fa9 100644 --- a/utility.ts +++ b/utility.ts @@ -3,67 +3,67 @@ import { PACKAGE_FILE, STATIC_DIR, WS } from "."; import Colors from "colors"; import Fs from "fs/promises"; import Path from "path"; -import { - WebSocketMessage, -} from "./websocket-handler"; +import { WebSocketMessage } from "./websocket-handler"; // CONFIG const STAT_INDENT = 25; // MISC export function doIfIsString(data: any, cb: (data: string) => void): void { - if (typeof data != "string" || data == "") return; - cb(data); + if (typeof data != "string" || data == "") return; + cb(data); } export async function getVersionNumber(): Promise { - try { - const packageString = await Bun.file(PACKAGE_FILE).text(); - const packageObject = JSON.parse(packageString); - return packageObject.version; - } catch { - return "unknown"; - } + try { + const packageString = await Bun.file(PACKAGE_FILE).text(); + const packageObject = JSON.parse(packageString); + return packageObject.version; + } catch { + return "unknown"; + } } // SERVER export function createFileResponse(requestPath: string): Response { - const fullPath = Path.join(STATIC_DIR, requestPath); - return new Response(Bun.file(fullPath)); + const fullPath = Path.join(STATIC_DIR, requestPath); + return new Response(Bun.file(fullPath)); } // OUTPUT async function writeToLogFile(message: string): Promise { - await Fs.appendFile("logs", `${message}\n`); + await Fs.appendFile("logs", `${message}\n`); } // stats export function writeStatString(label: string, value: string): void { - console.log(`${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.green(value)}`); + console.log( + `${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.green(value)}`, + ); } export function writeStatNumber(label: string, value: number): void { - console.log( - `${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.blue(value.toString())}` - ); + console.log( + `${label.padEnd(STAT_INDENT, " ")} ${Colors.bold.blue(value.toString())}`, + ); } // logging export function writeError(message: any): void { - console.log(Colors.bold.red(message)); - writeToLogFile(message); + console.log(Colors.bold.red(message)); + writeToLogFile(message); } export function writeSuccess(message: string): void { - console.log(Colors.bold.green(message)); - writeToLogFile(message); + console.log(Colors.bold.green(message)); + writeToLogFile(message); } // MESSAGES export function stringifyMessage(messageObject: WebSocketMessage): string { - return JSON.stringify(messageObject); + return JSON.stringify(messageObject); } export function parseMessage(messageString: string): Object { - return JSON.parse(messageString); -} \ No newline at end of file + return JSON.parse(messageString); +} diff --git a/websocket-handler.ts b/websocket-handler.ts index a38bcd5..6a3ff0e 100644 --- a/websocket-handler.ts +++ b/websocket-handler.ts @@ -1,10 +1,10 @@ import { Mailbox, Subscriber, SubscriptionMap } from "./subscriptionHandler"; import { - doIfIsString, - parseMessage, - stringifyMessage, - writeError, - writeSuccess, + doIfIsString, + parseMessage, + stringifyMessage, + writeError, + writeSuccess, } from "./utility"; import Crypto from "crypto"; @@ -13,27 +13,30 @@ import { defaultConfig } from "./config-handler"; // requests export interface WebSocketMessage { - uuid?: string; + uuid?: string; - // connecting to server - requestingServerConnection?: boolean; + // connecting to server + requestingServerConnection?: boolean; - // mailbox - requestingMailboxSetup?: boolean; - requestedMailbox?: string; - deletingMailbox?: string; - assignedMailboxId?: string; // sent by server - connectedMailboxId?: string; // sent by server - deletedMailbox?: string; // sent by server + // mailbox + requestingMailboxSetup?: boolean; + requestedMailbox?: string; + deletingMailbox?: string; + assignedMailboxId?: string; // sent by server + connectedMailboxId?: string; // sent by server + deletedMailbox?: string; // sent by server - // subscribing to channel - subscribeChannel?: string; - unsubscribeChannel?: string; - subscribed?: boolean; // sent by server + // subscribing to channel + subscribeChannel?: string; + unsubscribeChannel?: string; + subscribed?: boolean; // sent by server - //sending message - messageChannel?: string; - messageBody?: string; + //sending message + messageChannel?: string; + messageBody?: string; + + // confirming message + confirmingMessageReceived?: boolean; } // TRACKING @@ -49,219 +52,233 @@ export const serversConnected = new Set(); export const serversDisconnected = new Set(); export function getServerCount() { - return serversConnected.size + serversConnectedAsClient.size; + return serversConnected.size + serversConnectedAsClient.size; } export function trackConnection(ws: WS) { - clientsConnected.add(ws); + clientsConnected.add(ws); } export function forgetConnection(ws: WS): void { - clientsConnected.delete(ws); - serversConnectedAsClient.delete(ws); - subscriptionMap.deleteSubscriber(ws); + clientsConnected.delete(ws); + serversConnectedAsClient.delete(ws); + subscriptionMap.deleteSubscriber(ws); } export function trackSubscription( - subscriber: Subscriber, - channel: string + subscriber: Subscriber, + channel: string, ): void { - subscriptionMap.set(subscriber, channel); - confirmSubscription(subscriber, channel); + subscriptionMap.set(subscriber, channel); + confirmSubscription(subscriber, channel); } export function forgetSubscription( - subscriber: Subscriber, - channel: string + subscriber: Subscriber, + channel: string, ): void { - subscriptionMap.delete(subscriber, channel); - confirmSubscription(subscriber, channel); + subscriptionMap.delete(subscriber, channel); + confirmSubscription(subscriber, channel); } // AUDIT export function getWebSocketStats(): [string, number][] { - return [ - ["channels", subscriptionMap.subscribersPerChannel.size], - ["clients connected", clientsConnected.size], - ["mailboxes", mailboxes.size], - ["servers connected", getServerCount()], - ["servers disconnected", serversDisconnected.size], - ]; + return [ + ["channels", subscriptionMap.subscribersPerChannel.size], + ["clients connected", clientsConnected.size], + ["mailboxes", mailboxes.size], + ["servers connected", getServerCount()], + ["servers disconnected", serversDisconnected.size], + ]; } // OTHER SERVERS export function connectServers(config: typeof defaultConfig): void { - config.connectedServers.forEach((serverAddress) => { - connectServer(serverAddress, config); - }); + config.connectedServers.forEach((serverAddress) => { + connectServer(serverAddress, config); + }); } export function reconnectServers(config: typeof defaultConfig): void { - [...serversDisconnected.values()].forEach((address) => { - connectServer(address, config); - }); + [...serversDisconnected.values()].forEach((address) => { + connectServer(address, config); + }); } export function connectServer( - address: string, - config: typeof defaultConfig + address: string, + config: typeof defaultConfig, ): void { - try { - // connect - const ws = new WebSocket(address); - serversConnected.add(ws); + try { + // connect + const ws = new WebSocket(address); + serversConnected.add(ws); - // listen - ws.addEventListener("open", () => { - serversDisconnected.delete(address); + // listen + ws.addEventListener("open", () => { + serversDisconnected.delete(address); - writeSuccess(`connected to server at "${address}".`); + writeSuccess(`connected to server at "${address}".`); - subscribeChannels(ws, config); - requestServerConnection(ws); - }); + subscribeChannels(ws, config); + requestServerConnection(ws); + }); - ws.addEventListener("message", (message) => { - processMessage(ws, message.data.toString(), config); - }); + ws.addEventListener("message", (message) => { + processMessage(ws, message.data.toString(), config); + }); - ws.addEventListener("close", () => { - serversConnected.delete(ws); - serversDisconnected.add(address); - writeError(`server at "${address}" disconnected`); - }); - } catch (error) { - writeError(`failed to connect to server at "${address}": ${error}`); - } + ws.addEventListener("close", () => { + serversConnected.delete(ws); + serversDisconnected.add(address); + writeError(`server at "${address}" disconnected`); + }); + } catch (error) { + writeError(`failed to connect to server at "${address}": ${error}`); + } } function subscribeChannels(ws: WS, config: typeof defaultConfig): void { - config.subscribedChannels.forEach((channel) => { - subscribeChannel(ws, channel); - }); + config.subscribedChannels.forEach((channel) => { + subscribeChannel(ws, channel); + }); } export function subscribeChannel(server: WS, channel: string): void { - const message: WebSocketMessage = { - subscribeChannel: channel, - }; - const messageString = stringifyMessage(message); - server.send(messageString); + const message: WebSocketMessage = { + subscribeChannel: channel, + }; + const messageString = stringifyMessage(message); + server.send(messageString); } // PROCESS MESSAGE export function processMessage( - ws: WS, - messageString: string, - config: typeof defaultConfig + ws: WS, + messageString: string, + config: typeof defaultConfig, ): void { - // MESSAGE ID - // check id - const messageObject: WebSocketMessage = parseMessage(messageString); - if (!messageObject.uuid) { - // assign if no id - messageObject.uuid = Crypto.randomUUID(); - } else { - // ignore if already seen - if (knownMessageIds.has(messageObject.uuid)) return; - } - // remember message - knownMessageIds.add(messageObject.uuid); + // MESSAGE ID + // check id + const messageObject: WebSocketMessage = parseMessage(messageString); + if (!messageObject.uuid) { + // assign if no id + messageObject.uuid = Crypto.randomUUID(); + } else if (messageObject.confirmingMessageReceived == true) { + // received confirmation + mailboxes.forEach((mailbox) => { + if (mailbox.ws != ws) return; + return mailbox.checkMessageReceivedConfirmation( + messageObject.uuid!, + ); + }); + } else { + // ignore if already seen + if (knownMessageIds.has(messageObject.uuid)) return; + } + // remember message + knownMessageIds.add(messageObject.uuid); - // SUBSCRIPTIONS - doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => { - trackSubscription(ws, subscribeChannel); - }); - doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => { - forgetSubscription(ws, unsubscribeChannel); - }); + // SUBSCRIPTIONS + doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => { + trackSubscription(ws, subscribeChannel); + }); + doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => { + forgetSubscription(ws, unsubscribeChannel); + }); - // MESSAGE - doIfIsString(messageObject.messageChannel, (messageChannel) => { - if (messageObject.messageBody == undefined) return; - sendMessage(messageChannel, messageObject.messageBody, messageObject.uuid!); - }); + // MESSAGE + doIfIsString(messageObject.messageChannel, (messageChannel) => { + if (messageObject.messageBody == undefined) return; + sendMessage( + messageChannel, + messageObject.messageBody, + messageObject.uuid!, + ); + }); - // MAILBOX - if (messageObject.requestingMailboxSetup == true) { - const mailbox = new Mailbox(ws); - mailboxes.set(mailbox.id, mailbox); - } - doIfIsString(messageObject.requestedMailbox, (requestedMailbox) => { - const mailbox = mailboxes.get(requestedMailbox); - if (!mailbox) - return sendWebSocketMessage( - { deletedMailbox: requestedMailbox}, - ws - ); - mailbox.ws = ws; - }); - doIfIsString(messageObject.deletingMailbox, (deletingMailbox) => { - const mailbox = mailboxes.get(deletingMailbox); - if (!mailbox) return; - removeMailbox(mailbox); - }); + // MAILBOX + if (messageObject.requestingMailboxSetup == true) { + const mailbox = new Mailbox(ws); + mailboxes.set(mailbox.id, mailbox); + } + doIfIsString(messageObject.requestedMailbox, (requestedMailbox) => { + const mailbox = mailboxes.get(requestedMailbox); + if (!mailbox) + return sendWebSocketMessage( + { deletedMailbox: requestedMailbox }, + ws, + ); + mailbox.ws = ws; + }); + doIfIsString(messageObject.deletingMailbox, (deletingMailbox) => { + const mailbox = mailboxes.get(deletingMailbox); + if (!mailbox) return; + removeMailbox(mailbox); + }); - // OTHER SERVER - if (messageObject.requestingServerConnection == true) { - clientsConnected.delete(ws); - serversConnectedAsClient.add(ws); - subscribeChannels(ws, config); - } + // OTHER SERVER + if (messageObject.requestingServerConnection == true) { + clientsConnected.delete(ws); + serversConnectedAsClient.add(ws); + subscribeChannels(ws, config); + } } // MAILBOX export function removeExpiredMailboxes() { - mailboxes.forEach((mailbox) => { - if (mailbox.isExpired) removeMailbox(mailbox); - }); + mailboxes.forEach((mailbox) => { + if (mailbox.isExpired) removeMailbox(mailbox); + }); } export function removeMailbox(mailbox: Mailbox) { - mailbox.delete(); - mailboxes.delete(mailbox.id); + mailbox.delete(); + mailboxes.delete(mailbox.id); } // MESSAGING export function sendWebSocketMessage( - messageObject: WebSocketMessage, - destination: Subscriber + messageObject: WebSocketMessage, + destination: Subscriber, ): void { - const messageString = JSON.stringify(messageObject); - destination.send(messageString); + const messageString = JSON.stringify(messageObject); + destination.send(messageString, false, messageObject.uuid); } export function sendMessage( - messageChannel: string, - messageBody: string, - uuid: string + messageChannel: string, + messageBody: string, + uuid: string, ): void { - const channels = messageChannel.split("/"); - subscriptionMap.forwardMessage(channels, { - uuid, - messageChannel, - messageBody, - }); + const channels = messageChannel.split("/"); + subscriptionMap.forwardMessage(channels, { + uuid, + messageChannel, + messageBody, + }); } function requestServerConnection(ws: WS): void { - const messageObject = { - requestingServerConnection: true, - }; - const messageString = stringifyMessage(messageObject); - ws.send(messageString); + const messageObject = { + requestingServerConnection: true, + }; + const messageString = stringifyMessage(messageObject); + ws.send(messageString); } function confirmSubscription( - subscriber: Subscriber, - messageChannel: string + subscriber: Subscriber, + messageChannel: string, ): void { - const messageObject: WebSocketMessage = { - messageChannel, - subscribed: subscriptionMap.getChannelList(subscriber)?.has(messageChannel) - ? true - : false, - }; - const messageString = stringifyMessage(messageObject); - subscriber.send(messageString); + const messageObject: WebSocketMessage = { + messageChannel, + subscribed: subscriptionMap + .getChannelList(subscriber) + ?.has(messageChannel) + ? true + : false, + }; + const messageString = stringifyMessage(messageObject); + subscriber.send(messageString); }