From 29fa54ce711138a8efc4a9235b6f31d23279b807 Mon Sep 17 00:00:00 2001 From: Ann Date: Sun, 9 Mar 2025 20:02:58 +0300 Subject: [PATCH] Initial project structure and beta backend, with half finished frontend --- index.js | 66 +++++ lib/connectionManager.js | 44 +++ lib/messageRouter.js | 78 +++++ package.json | 6 + pnpm-lock.yaml | 602 +++++++++++++++++++++++++++++++++++++++ public/components.js | 183 ++++++++++++ public/favicon.ico | Bin 0 -> 7849 bytes public/favicon.png | Bin 0 -> 7861 bytes public/index.html | 31 ++ public/mmdy-theme.css | 59 ++++ 10 files changed, 1069 insertions(+) create mode 100644 index.js create mode 100644 lib/connectionManager.js create mode 100644 lib/messageRouter.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 public/components.js create mode 100644 public/favicon.ico create mode 100644 public/favicon.png create mode 100644 public/index.html create mode 100644 public/mmdy-theme.css diff --git a/index.js b/index.js new file mode 100644 index 0000000..71f3572 --- /dev/null +++ b/index.js @@ -0,0 +1,66 @@ +const express = require('express'); +const { createServer } = require('http'); +const WebSocket = require('ws'); +const path = require('path'); +const MessageRouter = require('./lib/messageRouter'); +const ConnectionManager = require('./lib/connectionManager'); + +const app = express(); + +app.use(express.static(path.join(__dirname, 'public'))); + +const server = createServer(app); +const wss = new WebSocket.Server({ server }); + +const connectionManager = new ConnectionManager(); +const messageRouter = new MessageRouter(connectionManager); + +wss.on('connection', (ws, req) => { + const query = req.url?.split('?')[1]; + const urlParams = new URLSearchParams(query); + const username = urlParams.get('u'); + + if (!username) { + ws.send(JSON.stringify({ + error: true, + message: "Use /?u=... to set a username" + })); + ws.close(); + return; + } + + if (!connectionManager.addUser(username, ws)) { + ws.send(JSON.stringify({ + error: true, + message: "This user is currently online, pick another username" + })); + ws.close(); + return; + } + + console.log(`[Connection] User connected: ${username}`); + + ws.on('message', (data) => { + try { + const parsedData = JSON.parse(data.toString()); + messageRouter.routeMessage(username, parsedData); + } catch (err) { + const errorMsg = { + error: true, + message: `Invalid JSON format: ${err.message}` + }; + ws.send(JSON.stringify(errorMsg)); + } + }); + + ws.on('close', () => { + connectionManager.removeUser(username); + console.log(`[Connection] User disconnected: ${username}`); + }); + + ws.send(JSON.stringify({ error: false, message: `Connected as ${username}` })); +}); + +server.listen(65010, 'localhost', () => { + console.log(`[callback] started`); +}); diff --git a/lib/connectionManager.js b/lib/connectionManager.js new file mode 100644 index 0000000..b7c62d2 --- /dev/null +++ b/lib/connectionManager.js @@ -0,0 +1,44 @@ +class ConnectionManager { + constructor() { + this.users = new Map(); + } + + /** + * Adds a new user to the active connections. + * Returns false if the user already exists. + */ + addUser(username, ws) { + if (this.users.has(username)) { + return false; + } + this.users.set(username, ws); + return true; + } + + /** + * Remove a user by username. + */ + removeUser(username) { + this.users.delete(username); + } + + /** + * Get the socket connection for a given username. + */ + getUserSocket(username) { + return this.users.get(username); + } + + /** + * Broadcasts a message to all connected users except the supplied username. + */ + broadcast(message, excludeUser = null) { + for (const [username, ws] of this.users.entries()) { + if (username !== excludeUser && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify(message)); + } + } + } +} + +module.exports = ConnectionManager; diff --git a/lib/messageRouter.js b/lib/messageRouter.js new file mode 100644 index 0000000..0cd2a90 --- /dev/null +++ b/lib/messageRouter.js @@ -0,0 +1,78 @@ +class MessageRouter { + /** + * @param {ConnectionManager} connectionManager An instance of ConnectionManager. + */ + constructor(connectionManager) { + this.connectionManager = connectionManager; + } + + /** + * Routes the incoming message based on its content. + * + * The expected JSON structure: + * { + * type: "private" | "broadcast" | "other", + * payload: { + * u: "recipient username", // for private messages + * body: "...", // message body + * // additional fields for other types can be added too + * } + * } + * + * If 'type' is omitted, it defaults to "private". + */ + routeMessage(fromUser, data) { + const type = data.type || 'private'; + switch (type) { + case 'private': + this.handlePrivateMessage(fromUser, data.payload); + break; + case 'broadcast': + this.handleBroadcastMessage(fromUser, data.payload); + break; + default: + this.sendError(fromUser, `Unknown message type: ${type}`); + } + } + + handlePrivateMessage(fromUser, payload) { + if (!payload || !payload.u || !payload.body) { + return this.sendError(fromUser, "Invalid payload for a private message. Required: { u, body }"); + } + const recipientSocket = this.connectionManager.getUserSocket(payload.u); + if (recipientSocket && recipientSocket.readyState === recipientSocket.OPEN) { + recipientSocket.send(JSON.stringify({ + error: false, + message: { + from: fromUser, + body: payload.body + } + })); + } else { + this.sendError(fromUser, `User ${payload.u} is not online`); + } + } + + handleBroadcastMessage(fromUser, payload) { + if (!payload || !payload.body) { + return this.sendError(fromUser, "Invalid payload for broadcast message. Required: { body }"); + } + const msg = { + error: false, + message: { + from: fromUser, + body: payload.body + } + }; + this.connectionManager.broadcast(msg, fromUser); + } + + sendError(toUser, errorMessage) { + const ws = this.connectionManager.getUserSocket(toUser); + if (ws && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ error: true, message: errorMessage })); + } + } +} + +module.exports = MessageRouter; diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd3f940 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "express": "^4.21.2", + "ws": "^8.18.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c1de89e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,602 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + express: + specifier: ^4.21.2 + version: 4.21.2 + ws: + specifier: ^8.18.1 + version: 8.18.1 + +packages: + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + array-flatten@1.1.1: {} + + body-parser@1.20.3: + 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.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@0.6.3: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + parseurl@1.3.3: {} + + path-to-regexp@0.1.12: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + send@0.19.0: + 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 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.1: {} + + toidentifier@1.0.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + unpipe@1.0.0: {} + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + ws@8.18.1: {} diff --git a/public/components.js b/public/components.js new file mode 100644 index 0000000..b9bcde8 --- /dev/null +++ b/public/components.js @@ -0,0 +1,183 @@ +nr.defineComponent({ + name: "login", + template: ` +
+
+
+ +
+
Welcome to callback.
+
+ +
+ Username + +
+ + +
+
+
+ `, + beforeCreate: () => { + return { useShadowRoot: false } + }, + afterCreate: (shadowRoot, config = {}, env) => { + const compName = nr.components[nr.mountedComponents[env.mountingTo]].name; + const ctx = nr.findComponentById(env.cid); + const form = { + loginButton: ctx.querySelector('button'), + progressBar: ctx.querySelector('.linear-progress'), + usernameField: ctx.querySelector('.textfield input'), + errorContainer: ctx.querySelector('.alert') + } + + if (localStorage.getItem('username')) { + form.usernameField.value = localStorage.getItem('username'); + handleLogin(); + } + + form.loginButton.addEventListener('click', handleLogin); + + function showError(error) { + form.progressBar.style.display = 'none'; + form.loginButton.disabled = false; + form.usernameField.disabled = false; + form.errorContainer.style.display = 'block'; + form.errorContainer.textContent = error; + form.usernameField.focus(); + } + + function handleLogin() { + try { + form.errorContainer.style.display = 'none'; + form.progressBar.style.display = 'block'; + form.loginButton.disabled = true; + form.usernameField.disabled = true; + const username = form.usernameField.value; + + if (!username) throw new Error('Username cannot be empty'); + console.log(`[${compName}] Trying to connect as ${username}`); + let protocol + switch (window.location.protocol) { + case 'http:': + protocol = 'ws:'; + break; + case 'https:': + protocol = 'wss:'; + break; + default: + throw new Error('Unknown page protocol'); + } + window.callback = { + socket: new WebSocket(`${protocol}//${window.location.host}?u=${encodeURIComponent(username)}`) + } + + function handleWSLogin(message) { + try { + const res = JSON.parse(message.data); + if (res.error) throw new Error(res.message); + localStorage.setItem('username', username); + console.log(`[${compName}] Logged in as ${username}`) + nr.unmount(env.mountingTo); + nr.mount(config?.appComponent || 'callback', env.mountingTo) + } catch (e) { + showError(e.message); + console.error(e); + } + } + + window.callback.socket.addEventListener('open', console.log(`[${compName}] Connected to websocket`)); + window.callback.socket.addEventListener('message', handleWSLogin); + } catch (e) { + showError(e.message); + console.error(e); + } + } + } +}); + +nr.defineComponent({ + name: 'callback', + template: ` +
+ + +
+
+ menu + Person +
+
+ CHATDIV +
+
+
+ Message + +
+ +
+
+
+ `, + beforeCreate: () => { + return { useShadowRoot: false } + }, + afterCreate: () => { + const sidebar = document.querySelector('#sidebar'); + const sidebarBody = document.querySelector('#sidebarbody'); + const menutoggle = document.querySelector('#menutoggle'); + menutoggle.addEventListener('click', () => { + sidebar.style.left = "0"; + }); + sidebar.addEventListener('click', (event) => { + if (!sidebarBody.contains(event.target)) { + sidebar.style.left = "-100%"; + } + }); + + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); + const fortyRemInPixels = 65 * rootFontSize; + + if (entry.contentRect.width > fortyRemInPixels) { + sidebar.style.position = 'static'; + sidebarBody.classList.remove('modal'); + sidebarBody.classList.add('standart'); + menutoggle.style.display = 'none'; + } else { + sidebar.style.position = 'fixed'; + sidebarBody.classList.remove('standart'); + sidebarBody.classList.add('modal'); + menutoggle.style.display = 'block'; + } + } + }); + + resizeObserver.observe(document.body); + } +}) \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e71d75e7ff7636d83a6dd37227cf5cfd0f4d4513 GIT binary patch literal 7849 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 literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..40df1f0 --- /dev/null +++ b/public/index.html @@ -0,0 +1,31 @@ + + + + + + callback + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/public/mmdy-theme.css b/public/mmdy-theme.css new file mode 100644 index 0000000..961b19e --- /dev/null +++ b/public/mmdy-theme.css @@ -0,0 +1,59 @@ +:root { + --md-sys-color-primary: rgb(219 198 110); + --md-sys-color-surface-tint: rgb(219 198 110); + --md-sys-color-on-primary: rgb(58 48 0); + --md-sys-color-primary-container: rgb(83 70 0); + --md-sys-color-on-primary-container: rgb(248 226 135); + --md-sys-color-secondary: rgb(209 198 161); + --md-sys-color-on-secondary: rgb(54 48 22); + --md-sys-color-secondary-container: rgb(78 71 42); + --md-sys-color-on-secondary-container: rgb(238 226 188); + --md-sys-color-tertiary: rgb(157 212 158); + --md-sys-color-on-tertiary: rgb(1 57 19); + --md-sys-color-tertiary-container: rgb(30 81 40); + --md-sys-color-on-tertiary-container: rgb(184 241 185); + --md-sys-color-error: rgb(255 180 171); + --md-sys-color-on-error: rgb(105 0 5); + --md-sys-color-error-container: rgb(147 0 10); + --md-sys-color-on-error-container: rgb(255 218 214); + --md-sys-color-background: rgb(21 19 11); + --md-sys-color-on-background: rgb(232 226 212); + --md-sys-color-surface: rgb(21 19 11); + --md-sys-color-on-surface: rgb(232 226 212); + --md-sys-color-surface-variant: rgb(75 71 57); + --md-sys-color-on-surface-variant: rgb(205 198 180); + --md-sys-color-outline: rgb(150 144 128); + --md-sys-color-outline-variant: rgb(75 71 57); + --md-sys-color-shadow: rgb(0 0 0); + --md-sys-color-scrim: rgb(0 0 0); + --md-sys-color-inverse-surface: rgb(232 226 212); + --md-sys-color-inverse-on-surface: rgb(51 48 39); + --md-sys-color-inverse-primary: rgb(109 94 15); + --md-sys-color-primary-fixed: rgb(248 226 135); + --md-sys-color-on-primary-fixed: rgb(34 27 0); + --md-sys-color-primary-fixed-dim: rgb(219 198 110); + --md-sys-color-on-primary-fixed-variant: rgb(83 70 0); + --md-sys-color-secondary-fixed: rgb(238 226 188); + --md-sys-color-on-secondary-fixed: rgb(33 27 4); + --md-sys-color-secondary-fixed-dim: rgb(209 198 161); + --md-sys-color-on-secondary-fixed-variant: rgb(78 71 42); + --md-sys-color-tertiary-fixed: rgb(184 241 185); + --md-sys-color-on-tertiary-fixed: rgb(0 33 8); + --md-sys-color-tertiary-fixed-dim: rgb(157 212 158); + --md-sys-color-on-tertiary-fixed-variant: rgb(30 81 40); + --md-sys-color-surface-dim: rgb(21 19 11); + --md-sys-color-surface-bright: rgb(60 57 48); + --md-sys-color-surface-container-lowest: rgb(16 14 7); + --md-sys-color-surface-container-low: rgb(30 27 19); + --md-sys-color-surface-container: rgb(34 32 23); + --md-sys-color-surface-container-high: rgb(45 42 33); + --md-sys-color-surface-container-highest: rgb(56 53 43); +} + +#sidebar:has(.navdrawer.standart) { + width: 40% !important; +} + +.navdrawer.standart { + width: 100% !important; +} \ No newline at end of file