diff --git a/server/models/session.js b/server/models/session.js index 54ca925..caeaa5e 100755 --- a/server/models/session.js +++ b/server/models/session.js @@ -1,9 +1,9 @@ import WebSocket from "ws"; import { Player } from "./player.js"; -import * as msg from "../utils/messages.js"; import { mustBeString, mustBe } from "../utils/mustbe.js"; import { Scene } from "../scenes/scene.js"; import { gGame } from "./globals.js"; +import { formatMessage, MessageType } from "../utils/messages.js"; export class Session { /** @type {WebSocket} */ @@ -71,7 +71,7 @@ export class Session { /** * Send a message via our websocket. * - * @param {string|number} messageType + * @param {MessageType} messageType The message "header" (the first arg in the array sent to the client) holds the message type. * @param {...any} args */ send(messageType, ...args) { @@ -79,7 +79,7 @@ export class Session { console.error("Trying to send a message without a valid websocket", messageType, args); return; } - this._websocket.send(JSON.stringify([messageType, ...args])); + this._websocket.send(formatMessage(messageType, ...args)); } /** @@ -100,7 +100,7 @@ export class Session { } this.send( - msg.PROMPT, // message type + MessageType.PROMPT, // message type text, // TODO: prompt text must be string or an array of strings mustBe(options, "object"), ); @@ -113,12 +113,12 @@ export class Session { * @param {object?} options message options for the client. */ sendText(text, options = {}) { - this.send(msg.TEXT, text, options); + this.send(MessageType.TEXT, text, options); } /** @param {string|string[]} errorMessage */ - sendError(errorMessage) { - this.send(msg.ERROR, mustBeString(errorMessage)); + sendError(errorMessage, options = { verbatim: true }) { + this.send(MessageType.ERROR, mustBeString(errorMessage), options); } /** @@ -128,15 +128,15 @@ export class Session { calamity(errorMessage) { // // The client should know not to format calamaties anyway, but we add โ€œpreformattedโ€ anyway - this.send(msg.CALAMITY, errorMessage, { preformatted: true }); + this.send(MessageType.CALAMITY, errorMessage, { preformatted: true }); this.close(); } /** - * @param {string} systemMessageType + * @param {MessageType} systemMessageType * @param {any?} value */ sendSystemMessage(systemMessageType, value = undefined) { - this.send(msg.SYSTEM, mustBeString(systemMessageType), value); + this.send(MessageType.SYSTEM, mustBeString(systemMessageType), value); } } diff --git a/server/package-lock.json b/server/package-lock.json index ccd4571..dd369ec 100755 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,446 +1,453 @@ { - "name": "websocket-mud", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "websocket-mud", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "figlet": "^1.8.2", - "ws": "^8.14.2" - }, - "devDependencies": { - "nodemon": "^3.0.1", - "prettier": "3.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/figlet": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.2.tgz", - "integrity": "sha512-iPCpE9B/rOcjewIzDnagP9F2eySzGeHReX8WlrZQJkqFBk2wvq8gY0c6U6Hd2y9HnX1LQcYSeP7aEHoPt6sVKQ==", - "license": "MIT", - "bin": { - "figlet": "bin/index.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true + "name": "websocket-mud", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "websocket-mud", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "figlet": "^1.8.2", + "sprintf-js": "^1.1.3", + "ws": "^8.14.2" + }, + "devDependencies": { + "nodemon": "^3.0.1", + "prettier": "3.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "utf-8-validate": { - "optional": true + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/figlet": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.2.tgz", + "integrity": "sha512-iPCpE9B/rOcjewIzDnagP9F2eySzGeHReX8WlrZQJkqFBk2wvq8gY0c6U6Hd2y9HnX1LQcYSeP7aEHoPt6sVKQ==", + "license": "MIT", + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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 + } + } } - } } - } } diff --git a/server/package.json b/server/package.json index 62b496c..0403aec 100755 --- a/server/package.json +++ b/server/package.json @@ -18,6 +18,7 @@ "license": "MIT", "dependencies": { "figlet": "^1.8.2", + "sprintf-js": "^1.1.3", "ws": "^8.14.2" }, "devDependencies": { @@ -33,7 +34,6 @@ "quoteProps": "consistent", "singleQuote": false, "trailingComma": "all", - "tabWidth": 4, "bracketSpacing": true, "objectWrap": "preserve", "arrowParens": "always" diff --git a/server/public/android-chrome-192x192.png b/server/public/android-chrome-192x192.png new file mode 100644 index 0000000..fffa8a7 Binary files /dev/null and b/server/public/android-chrome-192x192.png differ diff --git a/server/public/android-chrome-512x512.png b/server/public/android-chrome-512x512.png new file mode 100644 index 0000000..4c97596 Binary files /dev/null and b/server/public/android-chrome-512x512.png differ diff --git a/server/public/apple-touch-icon.png b/server/public/apple-touch-icon.png new file mode 100644 index 0000000..8c9dd2f Binary files /dev/null and b/server/public/apple-touch-icon.png differ diff --git a/server/public/client.js b/server/public/client.js index b434d4a..a30aebf 100755 --- a/server/public/client.js +++ b/server/public/client.js @@ -1,11 +1,16 @@ import { crackdown } from "./crackdown.js"; +import { parseArgs } from "./parseArgs.js"; +import { MessageType } from "./messages.js"; -const MsgContext.REPLY = "R"; -const QUIT = "QUIT"; -const HELP = "HELP"; -const COLON = ":"; +/** Regex to validate if a :help [topic] command i entered correctly */ const helpRegex = /^:help(?:\s+(.*))?$/; -const colonRegex = /^:([a-z0-9_]+)(?:\s+(.*))?$/; + +/** Regex to validate if a : [args] was entered correctly */ +const colonRegex = /^:([a-z0-9_]+)(?:\s+(.*?)\s*)?$/; + +/** + * The client that talks to the MUD Sever + */ class MUDClient { // // Constructor @@ -14,7 +19,7 @@ class MUDClient { this.websocket = null; /** @type {boolean} Are we in development mode (decided by the server); */ - this.dev = false; + this.isDev = false; this.promptOptions = {}; this.shouldReply = false; @@ -176,8 +181,8 @@ class MUDClient { // // The quit command has its own message type if (inputText === ":quit") { - this.send(QUIT); - this.writeToOutput("> " + inputText, { class: "input" }); + this.send(MessageType.QUIT); + this.writeToOutput("> " + inputText, { verbatim: true, class: "input" }); return; } @@ -193,8 +198,8 @@ class MUDClient { let help = helpRegex.exec(inputText); if (help) { console.log("here"); - help[1] ? this.send(HELP, help[1].trim()) : this.send(HELP); - this.writeToOutput("> " + inputText, { class: "input" }); + help[1] ? this.send(MshType.HELP, help[1].trim()) : this.send(MshType.HELP); + this.writeToOutput("> " + inputText, { verbatim: true, class: "input" }); return; } @@ -204,19 +209,14 @@ class MUDClient { // _ | (_| (_) | | | | | | | | | | | (_| | | | | (_| | // (_) \___\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_| //------------------------------------------------------ - let colonCommand = colonRegex.exec(inputText); - if (colonCommand) { - this.send(COLON, colonCommand[1], colonCommand[2]); - this.writeToOutput("> " + inputText, { class: "input" }); + const colon = colonRegex.exec(inputText); + if (colon) { + const args = typeof colon[2] === "string" ? parseArgs(colon[2]) : []; + this.send(MessageType.COLON, colon[1], args); + this.writeToOutput("> " + inputText, { verbatim: true, class: "input colon" }); return; } - // - // The server doesn't want any input from us, so we just ignore this input - if (!this.shouldReply) { - // the server is not ready for data! - return; - } // _ // _ __ ___ _ __ | |_ _ // | '__/ _ \ '_ \| | | | | @@ -227,6 +227,12 @@ class MUDClient { // We handle replies below //------------------------- + // + if (!this.shouldReply) { + // the server is not ready for data! + return; + } + // The server wants a password, let's hash it before sending it. if (this.promptOptions.password) { inputText = await this.hashPassword(inputText); @@ -238,14 +244,14 @@ class MUDClient { this.username = inputText; } - this.send(REPLY, inputText); + this.send(MessageType.REPLY, inputText); this.shouldReply = false; this.promptOptions = {}; // // We add our own command to the output stream so the // player can see what they typed. - this.writeToOutput("> " + inputText, { class: "input" }); + this.writeToOutput("> " + inputText, { verbatim: true, class: "input" }); return; } @@ -257,7 +263,7 @@ class MUDClient { // /** @param {any[]} data*/ onMessageReceived(data) { - if (this.dev) { + if (this.isDev) { console.debug(data); } const messageType = data.shift(); @@ -291,7 +297,7 @@ class MUDClient { return this.handleDebugMessages(data); } - if (this.dev) { + if (this.isDev) { this.writeToOutput(`unknown message type: ${messageType}: ${JSON.stringify(data)}`, { class: "debug", verbatim: true, @@ -314,7 +320,7 @@ class MUDClient { // Debug messages let the server send data to be displayed on the player's screen // and also logged to the players browser's log. handleDebugMessages(data) { - if (!this.dev) { + if (!this.isDev) { return; // debug messages are thrown away if we're not in dev mode. } this.writeToOutput(data, { class: "debug", verbatim: true }); @@ -332,16 +338,16 @@ class MUDClient { console.debug("Incoming system message", data); /** @type {string} */ - const messageType = data.shift(); + const systemMessageType = data.shift(); - switch (messageType) { + switch (systemMessageType) { case "username": this.username = data[0]; break; case "dev": // This is a message that tells us that the server is in // "dev" mode, and that we should do the same. - this.dev = data[0]; + this.isDev = !!data[0]; this.status.textContent = "[DEV] " + this.status.textContent; break; case "salt": @@ -349,12 +355,12 @@ class MUDClient { console.debug("updating crypto salt", data[0]); break; default: - console.debug("unknown system message", messageType, data); + console.debug("unknown system message", systemMessageType, data); } // If we're in dev mode, we should output all system messages (in a shaded/faint fashion). - if (this.dev) { - this.writeToOutput(`system message: ${messageType} = ${JSON.stringify(data)}`, { class: "debug" }); + if (this.isDev) { + this.writeToOutput(`system message: ${systemMessageType} = ${JSON.stringify(data)}`, { class: "debug" }); } return; } @@ -436,7 +442,7 @@ class MUDClient { * @param {string} className */ updateStatus(message, className) { - this.status.textContent = this.dev ? `[DEV] Status: ${message}` : `Status: ${message}`; + this.status.textContent = this.isDev ? `[DEV] Status: ${message}` : `Status: ${message}`; this.status.className = className; } } diff --git a/server/public/favicon-16x16.png b/server/public/favicon-16x16.png new file mode 100644 index 0000000..9e13675 Binary files /dev/null and b/server/public/favicon-16x16.png differ diff --git a/server/public/favicon-32x32.png b/server/public/favicon-32x32.png new file mode 100644 index 0000000..05b442d Binary files /dev/null and b/server/public/favicon-32x32.png differ diff --git a/server/public/favicon.ico b/server/public/favicon.ico new file mode 100644 index 0000000..791af1a Binary files /dev/null and b/server/public/favicon.ico differ diff --git a/server/public/messages.js b/server/public/messages.js new file mode 120000 index 0000000..ee4b7cb --- /dev/null +++ b/server/public/messages.js @@ -0,0 +1 @@ +../utils/messages.js \ No newline at end of file diff --git a/server/public/mustbe.js b/server/public/mustbe.js new file mode 120000 index 0000000..5847d59 --- /dev/null +++ b/server/public/mustbe.js @@ -0,0 +1 @@ +../utils/mustbe.js \ No newline at end of file diff --git a/server/utils/parseArgs.js b/server/public/parseArgs.js similarity index 91% rename from server/utils/parseArgs.js rename to server/public/parseArgs.js index 328193e..03c612a 100644 --- a/server/utils/parseArgs.js +++ b/server/public/parseArgs.js @@ -1,5 +1,3 @@ -import { mustBeString } from "./mustbe.js"; - /** * Parse a command string into arguments. For use with colon-commands. * @@ -7,7 +5,9 @@ import { mustBeString } from "./mustbe.js"; * @returns {(string|number)[]} Command arguments */ export function parseArgs(cmdString) { - mustBeString(cmdString); + if (typeof cmdString === "string") { + throw new Error("Expected string. GoT a finger in the eye instead"); + } const args = []; const quoteChars = ["'", '"', "`"]; const backslash = "\\"; @@ -16,7 +16,8 @@ export function parseArgs(cmdString) { let inQuotes = false; // are we inside quotes of some kind? let currentQuoteChar = ""; // if were in quotes, which are they? - const push = (value) => { + // helper function + const pushVal = (value) => { const n = Number(value); if (Number.isSafeInteger(n)) { args.push(n); @@ -39,7 +40,7 @@ export function parseArgs(cmdString) { } else if (char === " " || char === "\t") { // Whitespace - end current arg if it exists if (currentArg) { - push(currentArg); + pushVal(currentArg); currentArg = ""; } // Skip multiple whitespace @@ -68,7 +69,7 @@ export function parseArgs(cmdString) { // Add final argument if exists if (currentArg) { - push(currentArg); + pushVal(currentArg); } if (currentQuoteChar) { @@ -79,5 +80,3 @@ export function parseArgs(cmdString) { return args; } - -console.log(parseArgs("\"k1m er '-9 ' `anus pikke`")); diff --git a/server/public/site.webmanifest b/server/public/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/server/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/server/scenes/authentication/authenticationScene.js b/server/scenes/authentication/authenticationScene.js index 1181023..fab2af6 100755 --- a/server/scenes/authentication/authenticationScene.js +++ b/server/scenes/authentication/authenticationScene.js @@ -2,10 +2,10 @@ import { PasswordPrompt } from "./passwordPrompt.js"; import { Player } from "../../models/player.js"; import { Scene } from "../scene.js"; import { UsernamePrompt } from "./usernamePrompt.js"; +import { CreateUsernamePrompt } from "../playerCreation/createUsernamePrompt.js"; /** @property {Session} session */ export class AuthenticationScene extends Scene { - introText = [ "= Welcome", // ]; @@ -15,13 +15,13 @@ export class AuthenticationScene extends Scene { onReady() { // current prompt - this.doPrompt(new UsernamePrompt(this)); + this.show(UsernamePrompt); } /** @param {Player} player */ usernameAccepted(player) { this.player = player; - this.doPrompt(new PasswordPrompt(this)); + this.show(PasswordPrompt); } passwordAccepted() { @@ -34,4 +34,8 @@ export class AuthenticationScene extends Scene { this.session.setScene("new JustLoggedInScene"); } } + + createPlayer() { + scene.session.setScene(new PlayerCreationScene(this.scene)); + } } diff --git a/server/scenes/authentication/passwordPrompt.js b/server/scenes/authentication/passwordPrompt.js index 1d3491e..377d082 100755 --- a/server/scenes/authentication/passwordPrompt.js +++ b/server/scenes/authentication/passwordPrompt.js @@ -1,7 +1,6 @@ import { Prompt } from "../prompt.js"; import * as security from "../../utils/security.js"; import { Config } from "../../config.js"; -import { context } from "../../utils/messages.js"; export class PasswordPrompt extends Prompt { // diff --git a/server/scenes/authentication/usernamePrompt.js b/server/scenes/authentication/usernamePrompt.js index bcbe9f8..aebc096 100755 --- a/server/scenes/authentication/usernamePrompt.js +++ b/server/scenes/authentication/usernamePrompt.js @@ -1,11 +1,16 @@ import { Player } from "../../models/player.js"; import { Prompt } from "../prompt.js"; import * as security from "../../utils/security.js"; -import { context } from "../../utils/messages.js"; import { gGame } from "../../models/globals.js"; import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js"; import { Config } from "../../config.js"; +import { AuthenticationScene } from "./authenticationScene.js"; +/** + * @class + * + * @property {AuthenticationScene} scene + */ export class UsernamePrompt extends Prompt { // promptText = [ @@ -24,17 +29,14 @@ export class UsernamePrompt extends Prompt { // Let the client know that we're asking for a username promptOptions = { username: true }; - // - // User entered ":create" - onColon_create() { - // User creation scene. - this.scene.session.setScene(new PlayerCreationScene(this.scene)); + /** @returns {AuthenticationScene} */ + get scene() { + return this._scene; } // // User replied to our prompt onReply(text) { - // // do basic syntax checks on usernames if (!security.isUsernameSane(text)) { diff --git a/server/scenes/playerCreation/createUasswprdPrompt.js b/server/scenes/playerCreation/createUasswprdPrompt.js index 400055a..d72ab6e 100644 --- a/server/scenes/playerCreation/createUasswprdPrompt.js +++ b/server/scenes/playerCreation/createUasswprdPrompt.js @@ -1,7 +1,6 @@ import { Prompt } from "../prompt.js"; import * as security from "../../utils/security.js"; import { Config } from "../../config.js"; -import { context } from "../../utils/messages.js"; export class CreatePasswordPrompt extends Prompt { // diff --git a/server/scenes/playerCreation/playerCreationSene.js b/server/scenes/playerCreation/playerCreationSene.js index 229af78..99fd6e8 100755 --- a/server/scenes/playerCreation/playerCreationSene.js +++ b/server/scenes/playerCreation/playerCreationSene.js @@ -19,7 +19,7 @@ export class PlayerCreationScene extends Scene { this.session.calamity("Server is full, no more players can be created"); } - this.doPrompt(new CreateUsernamePrompt(this)); + this.showPrompt(new CreateUsernamePrompt(this)); } /** @@ -27,13 +27,13 @@ export class PlayerCreationScene extends Scene { * * @param {string} username */ - onUsernameAccepted(username) { + usernameAccepted(username) { const player = gGame.createPlayer(username); this.player = player; this.session.sendSystemMessage("salt", player.salt); this.session.sendText(`Username _*${username}*_ is available, and I've reserved it for you :)`); - this.doPrompt("new passwordprompt"); + this.showPrompt("new passwordprompt"); } /** @@ -42,9 +42,15 @@ export class PlayerCreationScene extends Scene { * * @param {string} password */ - onPasswordAccepted(password) { + passwordAccepted(password) { this.password = password; this.session.sendText("*_Success_* โœ… You will now be asked to log in again, sorry for that ;)"); this.player.setPasswordHash(security.generateHash(this.password)); } + + // + // User entered ":create" + onColon__create() { + this.scene.createPlayer(); + } } diff --git a/server/scenes/prompt.js b/server/scenes/prompt.js index cdcfb26..cfde0aa 100755 --- a/server/scenes/prompt.js +++ b/server/scenes/prompt.js @@ -1,10 +1,10 @@ import figlet from "figlet"; import { gGame } from "../models/globals.js"; -import * as msg from "../utils/messages.js"; import { Session } from "../models/session.js"; import { Scene } from "./scene.js"; -import { WebsocketMessage } from "../utils/messages.js"; +import { MessageType, WebsocketMessage } from "../utils/messages.js"; import { mustBe, mustBeString } from "../utils/mustbe.js"; +import { sprintf } from "sprintf-js"; /** * @typedef {object} PromptMethods @@ -19,8 +19,13 @@ import { mustBe, mustBeString } from "../utils/mustbe.js"; * - onColon(...) */ export class Prompt { - /** @protected @readonly @constant @type {Scene} */ - scene; + /** @private @readonly @type {Scene} */ + _scene; + + /** @type {Scene} */ + get scene() { + return this._scene; + } // // Extra info about the prompt we send to the client. @@ -58,7 +63,7 @@ export class Prompt { if (!(scene instanceof Scene)) { throw new Error("Expected an instance of >>Scene<< but got " + typeof scene); } - this.scene = scene; + this._scene = scene; } /** @@ -92,24 +97,42 @@ export class Prompt { * Triggered when a user types a :command that begins with a colon * * @param {string} command - * @param {string} argLine + * @param {any[]} args */ - onColon(command, argLine) { - const methodName = "onColon_" + command; - const method = this[methodName]; - if (typeof method === "function") { - method.call(this, argLine); + + onColon(command, args) { + const methodName = "onColon__" + command; + const property = this[methodName]; + + // + // Default: we have no handler for the Foo command, + // So let's see if daddy can handle it. + if (property === undefined) { + return this.scene.onColon(command, args); + } + + // + // If the prompt has a method called onColon_foo() => + if (typeof property === "function") { + property.call(this, args); return; } // - // For static "denial of commands" such as :inv ==> "you cannot access your inventory right now" - if (typeof method === "string") { - this.sendText(method); + // If the prompt has a _string_ called onColon_foo => + if (typeof property === "string") { + this.sendText(property); + return; } - // :inv ==> you cannot INV right now - this.sendError(`You cannot ${command.toUpperCase()} right now`); + // + // We found a property that has the right name but the wrong type. + throw new Error( + [ + `Logic error. Prompt has a handler for a command called ${command}`, + `but it is neither a function or a string, but a ${typeof property}`, + ].join(" "), + ); } /** @@ -167,7 +190,7 @@ export class Prompt { } /** - * @param {string} systemMessageType + * @param {string} systemMessageType The subtype of the system message (dev, salt, username, etc.) * @param {any?} value */ sendSystemMessage(...args) { @@ -184,13 +207,6 @@ export class Prompt { // // Easter รฆgg - onColon_pull_out_wand = "You cannot pull out your wand right now! But thanks for trying ๐Ÿ˜˜๐ŸŒ๐Ÿ†"; - - // - // Easter รฆgg2 - onColon_imperial(argLine) { - const n = Number(argLine); - - this.sendText(`${n} centimeters is only ${n / 2.54} inches. This is why americans have such small wands`); - } + // Example of having a string as a colon-handler + onColon__pull_out_wand = "You cannot pull out your wand right now! But thanks for trying ๐Ÿ˜˜๐ŸŒ๐Ÿ†"; } diff --git a/server/scenes/scene.js b/server/scenes/scene.js index a5aceff..c5dfd90 100755 --- a/server/scenes/scene.js +++ b/server/scenes/scene.js @@ -1,11 +1,16 @@ import { Session } from "../models/session.js"; import { Prompt } from "./prompt.js"; -const MsgContext.ERROR_INSANE_PASSWORD = "Invalid password."; -const MsgContext.ERROR_INCORRECT_PASSWOD = "Incorrect password."; /** + * Scene - a class for showing one or more prompts in a row. + * + * Scenes are mostly there to keep track of which prompt to show, + * and to store data for subsequent prompts to access. + * + * The prompts themselves are responsible for data validation and + * interpretation. + * * @abstract - * @method onReady */ export class Scene { /** @@ -31,8 +36,7 @@ export class Scene { return this._prompt; } - constructor() { - } + constructor() {} /** @param {Session} session */ execute(session) { @@ -51,8 +55,73 @@ export class Scene { /** * @param {Prompt} prompt */ - doPrompt(prompt) { + showPrompt(prompt) { this._prompt = prompt; prompt.execute(); } + + /** @param {new (scene: Scene) => Prompt} promptClassReference */ + show(promptClassReference) { + this.showPrompt(new promptClassReference(this)); + } + + /** + * Triggered when a user types a :command that begins with a colon + * and the current Prompt cannot handle that command. + * + * @param {string} command + * @param {any[]} args + */ + onColon(command, args) { + const propertyName = "onColon__" + command; + const property = this[propertyName]; + + // + // Default: we have no handler for the Foo command + if (property === undefined) { + this.session.sendError(`You cannot ${command.toUpperCase()} right now`, { verbatim: true }); // :foo ==> you cannot FOO right now + return; + } + + // + // If the prompt has a method called onColon_foo() => + if (typeof property === "function") { + property.call(this, args); + return; + } + + // + // If the prompt has a _string_ called onColon_foo => + if (typeof property === "string") { + this.session.sendText(property); + return; + } + + // + // We found a property that has the right name but the wrong type. + throw new Error( + [ + `Logic error. Scene has a handler for a command called ${command}`, + `but it is neither a function or a string, but a ${typeof property}`, + ].join(" "), + ); + } + + // + // Easter รฆgg + // Example dynamic colon handler + /** @param {any[]} args */ + onColon__imperial(args) { + if (args.length === 0) { + this.session.sendText("The imperial system is the freeest system ever. Also the least good"); + } + + const n = Number(args[0]); + + this.session.sendText( + sprintf("%.2f centimeters is only %.2f inches. This is american wands are so short!", n, n / 2.54), + ); + } + + onColon__hi = "Hoe"; } diff --git a/server/server.js b/server/server.js index 9e03550..d39dec6 100755 --- a/server/server.js +++ b/server/server.js @@ -2,12 +2,12 @@ import WebSocket, { WebSocketServer } from "ws"; import http from "http"; import path from "path"; import fs from "fs"; -import * as msg from "./utils/messages.js"; import { Session } from "./models/session.js"; import { GameSeeder } from "./seeders/gameSeeder.js"; import { Config } from "./config.js"; import { gGame } from "./models/globals.js"; import { AuthenticationScene } from "./scenes/authentication/authenticationScene.js"; +import { MessageType, WebsocketMessage, formatMessage } from "./utils/messages.js"; // __ __ _ _ ____ ____ // | \/ | | | | _ \ / ___| ___ _ ____ _____ _ __ @@ -32,7 +32,7 @@ class MudServer { console.info("New connection established"); const session = new Session(websocket, gGame); if (Config.dev) { - websocket.send(msg.prepareToSend(msg.SYSTEM, "dev", true)); + websocket.send(formatMessage(MessageType.SYSTEM, "dev", true)); } // ____ _ ___ ____ _____ @@ -56,7 +56,7 @@ class MudServer { this.onMessage(session, data); } catch (error) { console.error(error, data.toString(), data); - websocket.send(msg.prepareToSend(msg.CALAMITY, error)); + websocket.send(formatMessage(MessageType.CALAMITY, error)); session.close(); } }); @@ -159,7 +159,7 @@ class MudServer { return; } - const msgObj = new msg.WebsocketMessage(data.toString()); + const msgObj = new WebsocketMessage(data.toString()); // // Handle replies to prompts. The main workhorse of the game. @@ -174,7 +174,7 @@ class MudServer { } // - // Handle QUIT messages. When the player types :quit + // Handle MessageType.QUIT messages. When the player types :quit if (msgObj.isQuit()) { session.scene.onQuit(); session.close(0, "Closing the socket, graceful goodbye!"); @@ -184,7 +184,7 @@ class MudServer { // // Handle any text that starts with ":" that isn't :help or :quit if (msgObj.isColon()) { - return session.scene.prompt.onColon(msgObj.command, msgObj.argLine); + return session.scene.prompt.onColon(msgObj.command, msgObj.args); } // diff --git a/server/utils/messages.js b/server/utils/messages.js index 3e29736..75f2b3b 100755 --- a/server/utils/messages.js +++ b/server/utils/messages.js @@ -1,19 +1,6 @@ -import { mustBe, mustBeInteger, mustBeString, mustMatch } from "./mustbe.js"; +import { mustBe, mustBeString, mustMatch } from "./mustbe.js"; -const colonCommandRegex = /^:([a-z0-9_]+)(:?\s*(.*))?$/; - -/** - * Enum-like object holding placeholder tokens. - * - * @readonly - * @enum {string} - */ -export const MsgContext = Object.freeze({ - PASSWORD: ":password", - USERNAME: ":username", -}); - -export const MsgTtype = Object.freeze({ +export const MessageType = Object.freeze({ /** * Very bad logic error. Player must quit game, refresh page, and log in again. * @@ -28,7 +15,7 @@ export const MsgTtype = Object.freeze({ * * Server-->Client-->Player */ - MsgContext.ERROR: "E", + ERROR: "E", /** * Message to be displayed. @@ -42,7 +29,7 @@ export const MsgTtype = Object.freeze({ * * Player-->Client-->Server */ - MsgContext.REPLY: "R", + REPLY: "R", /** * Player wants to quit. @@ -93,7 +80,7 @@ export const MsgTtype = Object.freeze({ * Represents a message sent to/from client * * @property {string?} command - * @property {string?} argLine + * @property {any[]} args */ export class WebsocketMessage { /** @protected @type {any[]} _arr The array that contains the message data */ @@ -136,22 +123,22 @@ export class WebsocketMessage { this.type = mustBeString(data[0]); switch (this.type) { - case MsgContext.REPLY: // player ==> client ==> server + case MessageType.REPLY: // player ==> client ==> server this.text = mustBeString(data[1]); break; - case HELP: // player ==> client ==> server + case MessageType.HELP: // player ==> client ==> server this.text = data[1] === undefined ? "" : mustBeString(data[1]).trim(); break; - case COLON: // player ==> client ==> server + case MessageType.COLON: // player ==> client ==> server this.command = mustMatch(data[1], /^[a-z0-9_]+$/); - this.argLine = data[2]; // parse?? + this.args = mustBe(data[2], "any[]"); break; - case DEBUG: // server ==> client - case MsgContext.ERROR: // server ==> client ==> player - case QUIT: // player ==> client ==> server - case SYSTEM: // client <==> server - case PROMPT: // server ==> client ==> player - case TEXT: // server ==> client ==> player + case MessageType.DEBUG: // server ==> client + case MessageType.ERROR: // server ==> client ==> player + case MessageType.QUIT: // player ==> client ==> server + case MessageType.SYSTEM: // client <==> server + case MessageType.PROMPT: // server ==> client ==> player + case MessageType.TEXT: // server ==> client ==> player break; default: throw new Error(`Unknown message type: >>${typeof this.type}<<`); @@ -159,27 +146,27 @@ export class WebsocketMessage { } isQuit() { - return this.type === QUIT; + return this.type === MessageType.QUIT; } isHelp() { - return this.type === HELP; + return this.type === MessageType.HELP; } isColon() { - return this.type === COLON; + return this.type === MessageType.COLON; } isReply() { - return this.type === MsgContext.REPLY; + return this.type === MessageType.REPLY; } isSysMessage() { - return this.type === SYSTEM; + return this.type === MessageType.SYSTEM; } isDebug() { - return this.type === DEBUG; + return this.type === MessageType.DEBUG; } } @@ -189,6 +176,6 @@ export class WebsocketMessage { * @param {string} messageType * @param {...any} args */ -export function prepareToSend(messageType, ...args) { +export function formatMessage(messageType, ...args) { return JSON.stringify([messageType, ...args]); } diff --git a/server/utils/mustbe.js b/server/utils/mustbe.js index 9be8dab..5ad26e5 100755 --- a/server/utils/mustbe.js +++ b/server/utils/mustbe.js @@ -12,8 +12,14 @@ export function mustBe(value, ...types) { return value; } + const isArray = Array.isArray(value); + + if (isArray && (types.includes("any[]") || types.includes("array"))) { + return value; + } + // NOTE: only checks first element of array if it's a string. - if (types.includes("strings[]") && Array.isArray(value) && (value.length === 0 || typeof value[0] === "string")) { + if (isArray && types.includes("strings[]") && (value.length === 0 || typeof value[0] === "string")) { return value; } @@ -24,12 +30,6 @@ export function mustBeString(value) { return mustBe(value, "string"); } -export function mustBeInteger(value) { - if (typeof value === "number" && Number.isSafeInteger(value)) { - return value; - } -} - /** * * @param {string} str diff --git a/server/utils/tui.js b/server/utils/tui.js index 22e79f5..4bd72a7 100644 --- a/server/utils/tui.js +++ b/server/utils/tui.js @@ -222,7 +222,7 @@ export function frameText(text, options) { let output = ""; // - // GENERATE THE MARGIN SPACE ABOVE THE FRAMED TEXT + // GENERATE THE MARGIN SPACE ABOVE THE FRAMED MsgType.TEXT // // ( we insert space characters even though ) // ( they wouldn't normally be visible. But ) @@ -263,7 +263,7 @@ export function frameText(text, options) { ).repeat(options.vPadding); // - // GENERATE FRAMED TEXT SEGMENT + // GENERATE FRAMED MsgType.TEXT SEGMENT // // โ•‘ My pretty โ•‘ // โ•‘ text here โ•‘ @@ -318,7 +318,7 @@ export function frameText(text, options) { "\n"; // - // GENERATE THE MARGIN SPACE BELOW THE FRAMED TEXT + // GENERATE THE MARGIN SPACE BELOW THE FRAMED MsgType.TEXT // // ( we insert space characters even though ) // ( they wouldn't normally be visible. But )