From ec020e6358f638da51bd647db35d13c844831745 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Tue, 31 Mar 2026 22:02:13 +0000 Subject: [PATCH] change mailbox behaviour to use reception confirmations --- README.md | 76 +- config-handler.ts | 34 +- index.ts | 233 +++--- package-lock.json | 1496 +++++++++++++++++----------------- package.json | 46 +- static/dist/index.html | 54 +- static/dist/index.js | 1344 +++++++++++++++++------------- static/dist/manifest.json | 16 +- static/dist/styles.css | 797 +++++++++--------- static/dist/support.js | 41 +- static/dist/sw.js | 36 +- static/package.json | 16 +- static/src/index.tsx | 62 +- static/src/infoScreen.tsx | 33 +- static/src/mainScreen.tsx | 212 ++--- static/src/messageScreen.tsx | 78 +- static/src/model.tsx | 105 +-- static/src/translations.tsx | 142 ++-- subscriptionHandler.ts | 28 +- utility.ts | 52 +- websocket-handler.ts | 347 ++++---- 21 files changed, 2772 insertions(+), 2476 deletions(-) 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); }