diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07d2f05..87c2e4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,4 +17,3 @@ jobs: - run: npm run build - run: npm test - run: npm run lint - - run: npm run compile-schemas && git diff --exit-code diff --git a/package-lock.json b/package-lock.json index ba26ec4..4650066 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "ajv": "^8.16.0", "ajv-formats": "^3.0.1", "recoil-tdf": "^1.0.0", + "tachyon-protocol": "1.9.0", "tiny-typed-emitter": "^2.1.0", "ws": "^8.17.1" }, @@ -28,7 +29,6 @@ "eslint": "8.57.0", "eslint-config-prettier": "^9.1.0", "fastify": "^4.28.0", - "json-schema-to-typescript": "^14.0.5", "pino-pretty": "^11.2.1", "prettier": "3.3.2", "typescript": "^5.4.5" @@ -40,24 +40,6 @@ "bufferutil": "^4.0.8" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.6.4", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.4.tgz", - "integrity": "sha512-9K6xOqeevacvweLGik6LnZCb1fBtCOSIWQs8d096XGeqoLKC33UVMGz9+77Gw44KvbH4pKcQPWo4ZpxkXYj05w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, "node_modules/@babel/runtime": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", @@ -344,60 +326,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true, - "license": "MIT" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -436,17 +364,6 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@tsconfig/node20": { "version": "20.1.4", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", @@ -454,20 +371,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "20.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", @@ -790,13 +693,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -952,23 +848,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-color": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", - "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.64", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.15", - "timers-ext": "^0.1.7" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1028,30 +907,6 @@ "node": ">= 8" } }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "dev": true, - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -1126,20 +981,6 @@ "stream-shift": "^1.0.2" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1150,62 +991,6 @@ "once": "^1.4.0" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1366,22 +1151,6 @@ "node": "*" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1446,17 +1215,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -1477,16 +1235,6 @@ "node": ">=0.8.x" } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, "node_modules/fast-content-type-parse": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", @@ -1660,30 +1408,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1764,36 +1488,6 @@ "dev": true, "license": "ISC" }, - "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1811,30 +1505,6 @@ "dev": true, "license": "ISC" }, - "node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2006,16 +1676,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2049,13 +1709,6 @@ "node": ">=8" } }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2063,25 +1716,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -2137,34 +1771,6 @@ "node": ">=16" } }, - "node_modules/json-schema-to-typescript": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-14.0.5.tgz", - "integrity": "sha512-JmHsbgY0KKo8Pw0HRXpGzAlZYxlu+M5kFhSzhNkUSrVJ4sCXPdAGIdSpzva5ev2/Kybz10S6AfnNdF4o3Pzt3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.5", - "@types/json-schema": "^7.0.15", - "@types/lodash": "^4.17.0", - "cli-color": "^2.0.4", - "glob": "^10.3.12", - "is-glob": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "mkdirp": "^3.0.1", - "mz": "^2.7.0", - "node-fetch": "^3.3.2", - "prettier": "^3.2.5" - }, - "bin": { - "json2ts": "dist/src/cli.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2230,13 +1836,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2244,46 +1843,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/memoizee": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", - "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "es5-ext": "^0.10.64", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2334,32 +1893,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2367,18 +1900,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2386,52 +1907,6 @@ "dev": true, "license": "MIT" }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-gyp-build": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", @@ -2444,16 +1919,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -2524,13 +1989,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2574,23 +2032,6 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3087,19 +2528,6 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3147,76 +2575,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3230,20 +2588,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3270,6 +2614,16 @@ "node": ">=8" } }, + "node_modules/tachyon-protocol": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tachyon-protocol/-/tachyon-protocol-1.9.0.tgz", + "integrity": "sha512-AuPYu5hmk25ADHnza0ZVeE7f3k8N0MiffM/SOfp4Mmt12idDV3m8KDIyJpG0zrrekYW3qTEUDRSCodJ64Fe3Vw==", + "license": "ISC", + "dependencies": { + "ajv": "^8.16.0", + "ajv-formats": "^3.0.1" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3277,29 +2631,6 @@ "dev": true, "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -3310,20 +2641,6 @@ "real-require": "^0.2.0" } }, - "node_modules/timers-ext": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", - "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", - "dev": true, - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/tiny-typed-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", @@ -3374,13 +2691,6 @@ "typescript": ">=4.2.0" } }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "dev": true, - "license": "ISC" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3444,16 +2754,6 @@ "dev": true, "license": "MIT" }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3480,107 +2780,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 1639651..c771d72 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "description": "Simple script to start recoil dedicated instances.", "scripts": { - "compile-schemas": "json2ts -i src/schemas -o src/types --style.useTabs --style.singleQuote --style.printWidth 100", "start": "tsc && node --enable-source-maps dist/main.js", "build": "tsc", "build:prod": "tsc -p tsconfig.prod.json", @@ -25,6 +24,7 @@ "ajv": "^8.16.0", "ajv-formats": "^3.0.1", "recoil-tdf": "^1.0.0", + "tachyon-protocol": "1.9.0", "tiny-typed-emitter": "^2.1.0", "ws": "^8.17.1" }, @@ -38,10 +38,9 @@ "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/parser": "^7.11.0", - "eslint-config-prettier": "^9.1.0", "eslint": "8.57.0", + "eslint-config-prettier": "^9.1.0", "fastify": "^4.28.0", - "json-schema-to-typescript": "^14.0.5", "pino-pretty": "^11.2.1", "prettier": "3.3.2", "typescript": "^5.4.5" diff --git a/src/engineRunner.test.ts b/src/engineRunner.test.ts index b935ac6..4fb3e91 100644 --- a/src/engineRunner.test.ts +++ b/src/engineRunner.test.ts @@ -5,7 +5,7 @@ import events from 'node:events'; import { mkdtemp, mkdir, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import type { StartRequest } from './types/startRequest.js'; +import type { AutohostStartRequestData } from 'tachyon-protocol/types'; import { setImmediate as asyncSetImmediate } from 'timers/promises'; import { runEngine, EngineRunner } from './engineRunner.js'; @@ -21,13 +21,28 @@ console.log('testPort', testPort); // The contents of this except for the gameUUID doesn't matter much // unit tests don't execute the real engine. -const demoStartRequest: StartRequest = { +const demoStartRequest: AutohostStartRequestData = { battleId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', engineVersion: 'test', mapName: 'map v1', gameName: 'mod v1', startPosType: 'fixed', - allyTeams: [{ teams: [] }], + allyTeams: [ + { + teams: [ + { + players: [ + { + userId: '441a8dde-4a7a-4baf-9a3f-f51015fa61c4', + name: 'Player X', + password: 'X', + countryCode: 'DE', + }, + ], + }, + ], + }, + ], }; const optsBase = { diff --git a/src/engineRunner.ts b/src/engineRunner.ts index 8f3c4cf..4120b1f 100644 --- a/src/engineRunner.ts +++ b/src/engineRunner.ts @@ -9,7 +9,7 @@ import * as tdf from 'recoil-tdf'; import { TypedEmitter } from 'tiny-typed-emitter'; import { parsePacket, type Event, EventType } from './autohostInterface.js'; import { scriptGameFromStartRequest } from './startScriptGen.js'; -import type { StartRequest } from './types/startRequest.js'; +import type { AutohostStartRequestData } from 'tachyon-protocol/types'; function serializeEngineSettings(obj: { [k: string]: string }): string { return Object.entries(obj) @@ -60,7 +60,7 @@ enum State { * Options for the engine runner */ interface Opts { - startRequest: StartRequest; + startRequest: AutohostStartRequestData; autohostPort: number; hostIP: string; hostPort: number; @@ -236,7 +236,7 @@ export class EngineRunner extends TypedEmitter { private async startEngine( instanceDir: string, - startRequest: StartRequest, + startRequest: AutohostStartRequestData, spawnFunc: typeof spawn, ): Promise { const engineDir = path.resolve('engines', startRequest.engineVersion); diff --git a/src/games.ts b/src/games.ts index e9263f6..0824277 100644 --- a/src/games.ts +++ b/src/games.ts @@ -1,4 +1,4 @@ -import type { StartRequest } from './types/startRequest.js'; +import type { AutohostStartRequestData } from 'tachyon-protocol/types'; import { runEngine, type EngineRunner } from './engineRunner.js'; import { EventType } from './autohostInterface.js'; import events from 'node:events'; @@ -33,7 +33,7 @@ export class GamesManager { throw new Error('no free port offsets'); } - async start(req: StartRequest): Promise<{ ip: string; port: number }> { + async start(req: AutohostStartRequestData): Promise<{ ip: string; port: number }> { if (this.usedUUIDs.has(req.battleId)) { throw new Error(`game ${req.battleId} already used`); } diff --git a/src/main.ts b/src/main.ts index 6ec8b10..2c0546a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,70 +6,69 @@ import { GamesManager } from './games.js'; import { callTachyonAutohost, createTachyonEvent, - KillRequest, - PlayerAddRequest, - PlayerKickRequest, - PlayerMuteRequest, - PlayersSpecRequest, - precompileSchemas, - SendCommandRequest, - SendMessageRequest, - StartRequest, - StartResponse, - SubscribeUpdatesRequest, TachyonAutohost, TachyonError, TachyonServer, } from './tachyonTypes.js'; +import { + AutohostAddPlayerRequestData, + AutohostKickPlayerRequestData, + AutohostKillRequestData, + AutohostMutePlayerRequestData, + AutohostSendCommandRequestData, + AutohostSendMessageRequestData, + AutohostSpecPlayersRequestData, + AutohostStartOkResponseData, + AutohostStartRequestData, + AutohostSubscribeUpdatesRequestData, +} from 'tachyon-protocol/types'; import { TachyonClient, TachyonClientOpts } from './tachyonClient.js'; -precompileSchemas(); - class Autohost implements TachyonAutohost { private server?: TachyonServer; constructor(private manager: GamesManager) {} - async start(req: StartRequest): Promise { + async start(req: AutohostStartRequestData): Promise { const { ip, port } = await this.manager.start(req); return { ips: [ip], port }; } - async kill(_req: KillRequest): Promise { + async kill(_req: AutohostKillRequestData): Promise { throw new TachyonError('command_unimplemented', 'kill not implemented'); } - async playerAdd(_req: PlayerAddRequest): Promise { + async addPlayer(_req: AutohostAddPlayerRequestData): Promise { throw new TachyonError('command_unimplemented', 'playerAdd not implemented'); } - async playerKick(_req: PlayerKickRequest): Promise { + async kickPlayer(_req: AutohostKickPlayerRequestData): Promise { throw new TachyonError('command_unimplemented', 'playerKick not implemented'); } - async playerMute(_req: PlayerMuteRequest): Promise { + async mutePlayer(_req: AutohostMutePlayerRequestData): Promise { throw new TachyonError('command_unimplemented', 'playerMute not implemented'); } - async playersSpec(_req: PlayersSpecRequest): Promise { + async specPlayers(_req: AutohostSpecPlayersRequestData): Promise { throw new TachyonError('command_unimplemented', 'playersSpec not implemented'); } - async sendCommand(_req: SendCommandRequest): Promise { + async sendCommand(_req: AutohostSendCommandRequestData): Promise { throw new TachyonError('command_unimplemented', 'sendCommand not implemented'); } - async sendMessage(_req: SendMessageRequest): Promise { + async sendMessage(_req: AutohostSendMessageRequestData): Promise { throw new TachyonError('command_unimplemented', 'sendMessage not implemented'); } - async subscribeUpdates(_req: SubscribeUpdatesRequest): Promise { + async subscribeUpdates(_req: AutohostSubscribeUpdatesRequestData): Promise { throw new TachyonError('command_unimplemented', 'subscribeUpdates not implemented'); } connected(server: TachyonServer): void { this.server = server; - server.status({ currentGames: 0, maxGames: 10 }); + server.status({ currentBattles: 0, maxBattles: 10 }); } disconnected(): void { diff --git a/src/schemas/killRequest.json b/src/schemas/killRequest.json deleted file mode 100644 index e256610..0000000 --- a/src/schemas/killRequest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/kill/request", - "title": "KillRequest", - "description": "Request to kill a battle.", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - } - }, - "required": ["battleId"], - "additionalProperties": false -} diff --git a/src/schemas/playerAddRequest.json b/src/schemas/playerAddRequest.json deleted file mode 100644 index fe97afc..0000000 --- a/src/schemas/playerAddRequest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/playerAdd/request", - "title": "PlayerAddRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "userId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "required": ["battleId", "userId", "name", "password"], - "additionalProperties": false -} diff --git a/src/schemas/playerKickRequest.json b/src/schemas/playerKickRequest.json deleted file mode 100644 index 9d48c48..0000000 --- a/src/schemas/playerKickRequest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/playerKick/request", - "title": "PlayerKickRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "userId": { - "type": "string" - } - }, - "required": ["battleId", "userId"], - "additionalProperties": false -} diff --git a/src/schemas/playerMuteRequest.json b/src/schemas/playerMuteRequest.json deleted file mode 100644 index 84b2638..0000000 --- a/src/schemas/playerMuteRequest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/playerMute/request", - "title": "PlayerMuteRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "userId": { - "type": "string" - }, - "chat": { - "type": "boolean" - }, - "draw": { - "type": "boolean" - } - }, - "required": ["battleId", "userId", "chat", "draw"], - "additionalProperties": false -} diff --git a/src/schemas/playersSpecRequest.json b/src/schemas/playersSpecRequest.json deleted file mode 100644 index 249d19f..0000000 --- a/src/schemas/playersSpecRequest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/playersSpec/request", - "title": "PlayersSpecRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "userIds": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["battleId", "userIds"], - "additionalProperties": false -} diff --git a/src/schemas/sendCommandRequest.json b/src/schemas/sendCommandRequest.json deleted file mode 100644 index 998b892..0000000 --- a/src/schemas/sendCommandRequest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/sendCommand/request", - "title": "SendCommandRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "command": { - "type": "string" - }, - "arguments": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["battleId", "command", "arguments"], - "additionalProperties": false -} diff --git a/src/schemas/sendMessageRequest.json b/src/schemas/sendMessageRequest.json deleted file mode 100644 index 4a0754d..0000000 --- a/src/schemas/sendMessageRequest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/sendMessage/request", - "title": "SendMessageRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "message": { - "type": "string", - "maxLength": 127 - } - }, - "required": ["battleId", "message"], - "additionalProperties": false -} diff --git a/src/schemas/startRequest.json b/src/schemas/startRequest.json deleted file mode 100644 index 501d32d..0000000 --- a/src/schemas/startRequest.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/start/request", - "title": "StartRequest", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "engineVersion": { - "type": "string", - "pattern": "^[0-9a-zA-Z .+-]+$" - }, - "gameName": { "type": "string" }, - "mapName": { "type": "string" }, - "gameArchiveHash": { - "type": "string", - "pattern": "^[a-fA-F0-9]{128}$" - }, - "mapArchiveHash": { - "type": "string", - "pattern": "^[a-fA-F0-9]{128}$" - }, - "mapOptions": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "gameOptions": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "startDelay": { "type": "integer" }, - "startPosType": { - "$comment": "The order here matters, it maps to 0-3", - "enum": ["fixed", "random", "ingame", "beforegame"] - }, - "allyTeams": { - "type": "array", - "items": { "$ref": "#/$defs/allyTeam" }, - "minItems": 1 - }, - "spectators": { - "type": "array", - "items": { "$ref": "#/$defs/player" } - }, - "restrictions": { - "type": "object", - "description": "Unit restrictions", - "additionalProperties": { "type": "integer" } - } - }, - "required": ["engineVersion", "battleId", "gameName", "mapName", "allyTeams", "startPosType"], - "additionalProperties": false, - "$defs": { - "zero2one": { - "type": "number", - "description": "Floating value between 0 and 1", - "minimum": 0, - "maximum": 1 - }, - "allyTeam": { - "type": "object", - "title": "AllyTeam", - "properties": { - "teams": { - "type": "array", - "items": { "$ref": "#/$defs/team" } - }, - "startbox": { - "type": "object", - "properties": { - "top": { "$ref": "#/$defs/zero2one" }, - "bottom": { "$ref": "#/$defs/zero2one" }, - "left": { "$ref": "#/$defs/zero2one" }, - "right": { "$ref": "#/$defs/zero2one" } - }, - "required": ["top", "bottom", "left", "right"], - "additionalProperties": false - }, - "allies": { - "type": "array", - "description": "Indexes into of the other allyteams to ally with", - "items": { "type": "integer" } - }, - "customOpts": { - "type": "object", - "additionalProperties": { "type": "string" } - } - }, - "required": ["teams"], - "additionalProperties": false - }, - "team": { - "type": "object", - "title": "Team", - "properties": { - "players": { - "type": "array", - "items": { "$ref": "#/$defs/player" } - }, - "ais": { - "type": "array", - "items": { "$ref": "#/$defs/ai" } - }, - "advantage": { - "type": "number", - "minimum": -1 - }, - "incomeMultiplier": { - "type": "number", - "minimum": 0 - }, - "side": { "type": "string" }, - "color": { - "type": "object", - "properties": { - "r": { "$ref": "#/$defs/zero2one" }, - "g": { "$ref": "#/$defs/zero2one" }, - "b": { "$ref": "#/$defs/zero2one" } - }, - "required": ["r", "g", "b"], - "additionalProperties": false - }, - "startPos": { - "type": "object", - "properties": { - "x": { "type": "integer" }, - "z": { "type": "integer" } - }, - "required": ["x", "z"], - "additionalProperties": false - }, - "customOpts": { - "type": "object", - "additionalProperties": { "type": "string" } - } - }, - "additionalProperties": false - }, - "player": { - "type": "object", - "title": "Player", - "properties": { - "userId": { - "type": "string" - }, - "name": { - "type": "string", - "description": "Name of the player, must be unique just like userId" - }, - "password": { "type": "string" }, - "rank": { "type": "integer" }, - "countryCode": { "type": "string" }, - "customOpts": { - "type": "object", - "additionalProperties": { "type": "string" } - } - }, - "required": ["userId", "name", "password"], - "additionalProperties": false - }, - "ai": { - "type": "object", - "title": "AI", - "properties": { - "hostUserId": { - "type": "string", - "format": "uuid", - "description": "UserId of the player hosting this AI" - }, - "shortName": { "type": "string" }, - "version": { "type": "string" }, - "name": { "type": "string" }, - "options": { - "type": "object", - "additionalProperties": { "type": "string" } - } - }, - "required": ["hostUserId", "shortName"], - "additionalProperties": false - } - } -} diff --git a/src/schemas/startResponse.json b/src/schemas/startResponse.json deleted file mode 100644 index 1b12230..0000000 --- a/src/schemas/startResponse.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/start/response", - "title": "StartResponse", - "type": "object", - "properties": { - "ips": { - "type": "array", - "minItems": 1, - "items": { - "oneOf": [ - { - "type": "string", - "format": "ipv4" - }, - { - "type": "string", - "format": "ipv6" - } - ] - } - }, - "port": { - "type": "integer", - "minimum": 1024, - "maximum": 65535 - } - }, - "required": ["ips", "port"], - "additionalProperties": true -} diff --git a/src/schemas/statusEvent.json b/src/schemas/statusEvent.json deleted file mode 100644 index 91ec125..0000000 --- a/src/schemas/statusEvent.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/status/event", - "title": "StatusEvent", - "type": "object", - "properties": { - "maxGames": { - "type": "integer", - "minimum": 0 - }, - "currentGames": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["maxGames", "currentGames"], - "additionalProperties": true -} diff --git a/src/schemas/subscribeUpdatesRequest.json b/src/schemas/subscribeUpdatesRequest.json deleted file mode 100644 index 4abc471..0000000 --- a/src/schemas/subscribeUpdatesRequest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/subscribeUpdates/request", - "title": "SubscribeUpdatesRequest", - "type": "object", - "properties": { - "since": { - "type": "integer", - "description": "Unix timestamp in microseconds, it needs to be simply what was received in the last update" - } - }, - "additionalProperties": true -} diff --git a/src/schemas/tachyonMessage.json b/src/schemas/tachyonMessage.json deleted file mode 100644 index acd3b72..0000000 --- a/src/schemas/tachyonMessage.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/message", - "title": "TachyonMessage", - "oneOf": [ - { "$ref": "#/$defs/request" }, - { "$ref": "#/$defs/responseOk" }, - { "$ref": "#/$defs/responseFail" }, - { "$ref": "#/$defs/event" } - ], - "$defs": { - "base": { - "title": "TachyonBaseMessage", - "type": "object", - "properties": { - "type": { - "enum": ["request", "response", "event"] - }, - "messageId": { - "type": "string" - }, - "commandId": { - "type": "string" - } - }, - "required": ["type", "messageId", "commandId"], - "additionalProperties": true - }, - "request": { - "title": "TachyonRequest", - "allOf": [ - { "$ref": "#/$defs/base" }, - { - "type": "object", - "properties": { - "type": { - "const": "request" - }, - "data": { - "type": "object", - "tsType": "unknown" - } - }, - "required": ["type"] - } - ] - }, - "event": { - "title": "TachyonEvent", - "allOf": [ - { "$ref": "#/$defs/base" }, - { - "type": "object", - "properties": { - "type": { - "const": "event" - }, - "data": { - "type": "object", - "tsType": "unknown" - } - }, - "required": ["type"] - } - ] - }, - "responseOk": { - "title": "TachyonResponseOk", - "allOf": [ - { "$ref": "#/$defs/base" }, - { - "type": "object", - "properties": { - "type": { - "const": "response" - }, - "status": { - "const": "success" - }, - "data": { - "type": "object", - "tsType": "unknown" - } - }, - "required": ["type", "status"] - } - ] - }, - "responseFail": { - "title": "TachyonResponseFail", - "allOf": [ - { "$ref": "#/$defs/base" }, - { - "type": "object", - "properties": { - "type": { - "const": "response" - }, - "status": { - "const": "failed" - }, - "reason": { - "enum": [ - "command_unimplemented", - "internal_error", - "invalid_request", - "unknown_command" - ] - }, - "details": { - "type": "string", - "description": "Additional details about the failure for developers" - } - }, - "required": ["type", "status", "reason"] - } - ] - } - } -} diff --git a/src/schemas/updateEvent.json b/src/schemas/updateEvent.json deleted file mode 100644 index 48656f8..0000000 --- a/src/schemas/updateEvent.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://beyondallreason.dev/schema/tachyon/autohost/update/event", - "title": "UpdateEvent", - "type": "object", - "properties": { - "battleId": { - "type": "string", - "format": "uuid" - }, - "time": { - "type": "integer", - "description": "Unix timestamp in microseconds" - }, - "update": { - "oneOf": [ - { "$ref": "#/$defs/start" }, - { "$ref": "#/$defs/finished" }, - { "$ref": "#/$defs/engineMessage" }, - { "$ref": "#/$defs/engineWarning" }, - { "$ref": "#/$defs/engineQuit" }, - { "$ref": "#/$defs/engineCrash" }, - { "$ref": "#/$defs/playerJoined" }, - { "$ref": "#/$defs/playerLeft" }, - { "$ref": "#/$defs/playerChat" }, - { "$ref": "#/$defs/playerDefeated" }, - { "$ref": "#/$defs/luamsg" } - ] - } - }, - "required": ["battleId", "time", "update"], - "additionalProperties": false, - "$defs": { - "start": { - "type": "object", - "title": "StartUpdate", - "description": "The battle started.", - "properties": { - "type": { - "const": "start" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "finished": { - "type": "object", - "title": "FinishedUpdate", - "description": "The battle finished, generated once per every single player reporting who won.", - "properties": { - "type": { - "const": "finished" - }, - "userId": { - "type": "string" - }, - "winningAllyTeams": { - "type": "array", - "minItems": 1, - "items": { - "type": "integer", - "description": "Ally team IDs" - } - } - }, - "required": ["type", "userId", "winningAllyTeams"], - "additionalProperties": false - }, - "engineMessage": { - "type": "object", - "title": "EngineMessageUpdate", - "description": "A message from the engine, e.g. some ip is trying to connect.", - "properties": { - "type": { - "const": "engine_message" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false - }, - "engineWarning": { - "type": "object", - "title": "EngineWarningUpdate", - "properties": { - "type": { - "const": "engine_warning" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false - }, - "engineQuit": { - "type": "object", - "title": "EngineQuitUpdate", - "description": "The engine process for battle has quit cleanly, no more updates will be sent for this battle.", - "properties": { - "type": { - "const": "engine_quit" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "engineCrash": { - "type": "object", - "title": "EngineCrashUpdate", - "description": "The engine process for battle has crashed, no more updates will be sent for this battle.", - "properties": { - "type": { - "const": "engine_crash" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "playerJoined": { - "type": "object", - "title": "PlayerJoinedUpdate", - "properties": { - "type": { - "const": "player_joined" - }, - "userId": { - "type": "string" - }, - "playerNumber": { - "type": "integer", - "description": "Player number in the game, can be useful for custom commands" - } - }, - "required": ["type", "userId", "playerNumber"], - "additionalProperties": false - }, - "playerLeft": { - "type": "object", - "title": "PlayerLeftUpdate", - "properties": { - "type": { - "const": "player_left" - }, - "userId": { - "type": "string" - }, - "reason": { - "enum": ["lost_connection", "left", "kicked"] - } - }, - "required": ["type", "userId", "reason"], - "additionalProperties": false - }, - "playerChat": { - "type": "object", - "title": "PlayerChatUpdate", - "properties": { - "type": { - "const": "player_chat" - }, - "userId": { - "type": "string" - }, - "message": { - "type": "string" - }, - "destination": { - "enum": ["allies", "all", "spectators", "player"] - }, - "toUserId": { - "type": "string", - "$comment": "Only if destination is 'player'" - } - }, - "required": ["type", "userId", "message", "destination"], - "additionalProperties": false - }, - "playerDefeated": { - "type": "object", - "title": "PlayerDefeatedUpdate", - "properties": { - "type": { - "const": "player_defeated" - }, - "userId": { - "type": "string" - } - }, - "required": ["type", "userId"], - "additionalProperties": false - }, - "luamsg": { - "type": "object", - "title": "LuaMsgUpdate", - "properties": { - "type": { - "const": "luamsg" - }, - "userId": { - "type": "string" - }, - "script": { - "enum": ["ui", "gaia", "rules"] - }, - "uiMode": { - "enum": ["all", "allies", "spectators"], - "$comment": "Only if script is 'ui'" - }, - "data": { - "type": "string", - "contentEncoding": "base64", - "contentMediaType": "application/octet-stream" - } - }, - "required": ["type", "userId", "script", "data"], - "additionalProperties": false - } - } -} diff --git a/src/startScriptGen.test.ts b/src/startScriptGen.test.ts index 828be92..05ec572 100644 --- a/src/startScriptGen.test.ts +++ b/src/startScriptGen.test.ts @@ -1,10 +1,10 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { StartRequest } from './types/startRequest.js'; +import { AutohostStartRequestData } from 'tachyon-protocol/types'; import { scriptGameFromStartRequest } from './startScriptGen.js'; test('simple full example', () => { - const startReq: StartRequest = { + const startReq: AutohostStartRequestData = { battleId: 'e4f9f751-3626-48eb-bb8b-1ff8f25e12f9', engineVersion: 'recoil 2024.08.15-gdefse23', gameName: 'Game 22', @@ -27,18 +27,18 @@ test('simple full example', () => { countryCode: 'NA', }, ], - side: 'ARM', + faction: 'ARM', color: { r: 1, g: 0, b: 0.5, }, - customOpts: { + customProperties: { 'specialModOption': 'asd', }, }, ], - startbox: { + startBox: { top: 0, left: 0, bottom: 0, @@ -48,27 +48,30 @@ test('simple full example', () => { { teams: [ { - side: 'CORE', + faction: 'CORE', advantage: 0.5, incomeMultiplier: 1.2, startPos: { x: 100, - z: 100, + y: 100, }, - ais: [ + bots: [ { hostUserId: '730c874d-e5a3-4c24-a053-fcb2cfb23b32', - shortName: 'BARb', - version: '3.2', + aiShortName: 'BARb', + aiVersion: '3.2', name: 'AI 1', - options: { + aiOptions: { 'difficulty': 'op', }, + customProperties: { + 'x': 'y', + }, }, ], }, ], - startbox: { + startBox: { top: 0, left: 0.8, bottom: 0, @@ -77,7 +80,18 @@ test('simple full example', () => { }, { allies: [0], - teams: [], + teams: [ + { + players: [ + { + userId: '441a8dde-4a7a-4baf-9a3f-f51015fa61c4', + name: 'Player X', + password: 'X', + countryCode: 'DE', + }, + ], + }, + ], }, ], spectators: [ @@ -87,7 +101,7 @@ test('simple full example', () => { password: 'asd', countryCode: 'PL', rank: 1, - customOpts: { + customProperties: { 'key': 'value', }, }, @@ -171,12 +185,24 @@ test('simple full example', () => { 'OPTIONS': { 'difficulty': 'op', }, + 'x': 'y', }, 'ALLYTEAM2': { 'NumAllies': 1, 'Ally0': 0, }, + 'TEAM2': { + 'AllyTeam': 2, + 'TeamLeader': 1, + }, 'PLAYER1': { + 'Name': 'Player X', + 'UserID': '441a8dde-4a7a-4baf-9a3f-f51015fa61c4', + 'Password': 'X', + 'Team': 2, + 'CountryCode': 'DE', + }, + 'PLAYER2': { 'Name': 'Player 2', 'UserID': '7cd7fbda-44c8-4986-afc9-1f5d8b68e59e', 'Password': 'asd', @@ -186,8 +212,8 @@ test('simple full example', () => { 'key': 'value', }, 'NumAllyTeams': 3, - 'NumTeams': 2, - 'NumPlayers': 2, + 'NumTeams': 3, + 'NumPlayers': 3, }; const actual = scriptGameFromStartRequest(startReq); @@ -195,18 +221,33 @@ test('simple full example', () => { assert.deepStrictEqual(actual, expected); }); -const throwStartReqBase: StartRequest = { +const throwStartReqBase: AutohostStartRequestData = { battleId: 'e4f9f751-3626-48eb-bb8b-1ff8f25e12f9', engineVersion: 'recoil 2024.08.15-gdefse23', gameName: 'Game 22', mapName: 'de_duck 1.2', startPosType: 'ingame', - allyTeams: [{ teams: [] }], + allyTeams: [ + { + teams: [ + { + players: [ + { + userId: '441a8dde-4a7a-4baf-9a3f-f51015fa61c4', + name: 'Player X', + password: 'X', + countryCode: 'DE', + }, + ], + }, + ], + }, + ], }; test('throw on non-unique players', () => { // Players in different teams. - const startReq1: StartRequest = { + const startReq1: AutohostStartRequestData = { ...throwStartReqBase, allyTeams: [ { @@ -240,7 +281,7 @@ test('throw on non-unique players', () => { assert.throws(() => scriptGameFromStartRequest(startReq1)); // Also in spectators. - const startReq2: StartRequest = { + const startReq2: AutohostStartRequestData = { ...throwStartReqBase, allyTeams: [ { @@ -269,7 +310,7 @@ test('throw on non-unique players', () => { }); test('at least one ai/player is required', () => { - const startReq: StartRequest = { + const startReq: AutohostStartRequestData = { ...throwStartReqBase, allyTeams: [ { @@ -281,7 +322,7 @@ test('at least one ai/player is required', () => { }); test("custom opts can't override built-in fields", () => { - const startReq: StartRequest = { + const startReq: AutohostStartRequestData = { ...throwStartReqBase, allyTeams: [ { @@ -294,7 +335,7 @@ test("custom opts can't override built-in fields", () => { password: '87dw9cnqr86437w', }, ], - customOpts: { + customProperties: { 'AllyTeam': '1', }, }, @@ -306,7 +347,7 @@ test("custom opts can't override built-in fields", () => { }); test('ai must reference existing player', () => { - const startReq: StartRequest = { + const startReq: AutohostStartRequestData = { ...throwStartReqBase, allyTeams: [ { @@ -319,10 +360,10 @@ test('ai must reference existing player', () => { password: '87dw9cnqr86437w', }, ], - ais: [ + bots: [ { hostUserId: '7cd7fbda-44c8-4986-afc9-1f5d8b68e59e', - shortName: 'BARb', + aiShortName: 'BARb', }, ], }, diff --git a/src/startScriptGen.ts b/src/startScriptGen.ts index c1adcfe..eea4717 100644 --- a/src/startScriptGen.ts +++ b/src/startScriptGen.ts @@ -1,5 +1,4 @@ -import { StartRequest, Player, AllyTeam, Team, AI } from './types/startRequest.js'; -import StartRequestSchema from './schemas/startRequest.json' with { type: 'json' }; +import { AutohostStartRequestData, Player, AllyTeam, Team, Bot } from 'tachyon-protocol/types'; import * as tdf from 'recoil-tdf'; function shareKey(a: object, b: object): boolean { @@ -8,25 +7,25 @@ function shareKey(a: object, b: object): boolean { function mergeCustomOpts( o: tdf.TDFSerializable, - p: { customOpts?: { [k: string]: string } }, + p: { customProperties?: { [k: string]: string } }, ): tdf.TDFSerializable { - if (p.customOpts) { - if (shareKey(o, p.customOpts)) { - throw new Error('customOpts must have different keys then outer object'); + if (p.customProperties) { + if (shareKey(o, p.customProperties)) { + throw new Error('customProperties must have different keys then outer object'); } - o = { ...o, ...p.customOpts }; + o = { ...o, ...p.customProperties }; } return o; } function buildAllyTeam(numAllyTeams: number, at: AllyTeam): tdf.TDFSerializable { let o: tdf.TDFSerializable = {}; - if (at.startbox) { + if (at.startBox) { o = { - 'StartRectTop': at.startbox.top, - 'StartRectLeft': at.startbox.left, - 'StartRectBottom': at.startbox.bottom, - 'StartRectRight': at.startbox.right, + 'StartRectTop': at.startBox.top, + 'StartRectLeft': at.startBox.left, + 'StartRectBottom': at.startBox.bottom, + 'StartRectRight': at.startBox.right, }; } if (at.allies) { @@ -46,20 +45,20 @@ function buildTeam( playersMap: Map, team: Team, ): tdf.TDFSerializable { - if ((!team.players || team.players.length == 0) && (!team.ais || team.ais.length == 0)) { + if ((!team.players || team.players.length == 0) && (!team.bots || team.bots.length == 0)) { throw new Error('There must be at least one player or AI in each team'); } const o: tdf.TDFSerializable = { 'AllyTeam': allyTeamIdx, - 'TeamLeader': playersMap.get(team.players?.[0]?.userId ?? team.ais![0].hostUserId!)!, + 'TeamLeader': playersMap.get(team.players?.[0]?.userId ?? team.bots![0].hostUserId!)!, }; if (team.advantage !== undefined) o['Advantage'] = team.advantage; if (team.incomeMultiplier !== undefined) o['IncomeMultiplier'] = team.incomeMultiplier; - if (team.side) o['Side'] = team.side; + if (team.faction) o['Side'] = team.faction; if (team.color) o['RgbColor'] = `${team.color.r} ${team.color.g} ${team.color.b}`; if (team.startPos) { o['StartPosX'] = team.startPos.x; - o['StartPosZ'] = team.startPos.z; + o['StartPosZ'] = team.startPos.y; } return mergeCustomOpts(o, team); } @@ -80,33 +79,48 @@ function buildPlayer(teamIdx: number | null, p: Player): tdf.TDFSerializable { return mergeCustomOpts(o, p); } -function buildAI(teamIdx: number, playersMap: Map, ai: AI): tdf.TDFSerializable { +function buildAI(teamIdx: number, playersMap: Map, ai: Bot): tdf.TDFSerializable { if (!playersMap.has(ai.hostUserId)) { throw new Error('AI hosted by not existing player'); } const o: tdf.TDFSerializable = { - 'ShortName': ai.shortName, + 'ShortName': ai.aiShortName, 'Host': playersMap.get(ai.hostUserId)!, 'Team': teamIdx, }; - if (ai.version) o['Version'] = ai.version; + if (ai.aiVersion) o['Version'] = ai.aiVersion; if (ai.name) o['Name'] = ai.name; - if (ai.options) o['OPTIONS'] = ai.options; - return o; + if (ai.aiOptions) o['OPTIONS'] = ai.aiOptions; + return mergeCustomOpts(o, ai); } -export function scriptGameFromStartRequest(req: StartRequest): { +export function scriptGameFromStartRequest(req: AutohostStartRequestData): { [k: string]: tdf.TDFSerializable | string | number | boolean; } { + let startPosType: number; + switch (req.startPosType) { + case 'fixed': + startPosType = 0; + break; + case 'random': + startPosType = 1; + break; + case 'ingame': + startPosType = 2; + break; + case 'beforegame': + startPosType = 3; + break; + default: + throw new Error('Invalid startPosType'); + } const g: tdf.TDFSerializable = { 'GameID': req.battleId, 'GameType': req.gameName, 'MapName': req.mapName, 'ModHash': req.gameArchiveHash ?? '1', 'MapHash': req.mapArchiveHash ?? '1', - 'StartPosType': StartRequestSchema.properties.startPosType.enum.findIndex( - (v) => v == req.startPosType, - ), + 'StartPosType': startPosType, 'MODOPTIONS': req.gameOptions ?? {}, 'MAPOPTIONS': req.mapOptions ?? {}, }; @@ -150,7 +164,7 @@ export function scriptGameFromStartRequest(req: StartRequest): { for (const p of team.players ?? []) { g[`PLAYER${playerIdx++}`] = buildPlayer(teamIdx, p); } - for (const ai of team.ais ?? []) { + for (const ai of team.bots ?? []) { g[`AI${aiIdx++}`] = buildAI(teamIdx, playersMap, ai); } ++teamIdx; diff --git a/src/tachyonClient.test.ts b/src/tachyonClient.test.ts index f87485e..95f27d3 100644 --- a/src/tachyonClient.test.ts +++ b/src/tachyonClient.test.ts @@ -5,7 +5,7 @@ import { once } from 'node:events'; import { TachyonClient } from './tachyonClient.js'; import { createTachyonServer } from './tachyonServer.fake.js'; import { deepEqual } from 'node:assert'; -import { TachyonMessage, TachyonResponseOk } from './tachyonTypes.js'; +import { TachyonMessage } from './tachyonTypes.js'; // Let's reuse the same server for all tests to make them quicker. const server = await createTachyonServer({ clientId: 'c', clientSecret: 's' }); @@ -25,8 +25,11 @@ test('simple full example', async () => { server.on('connection', (conn) => { conn.on('message', (msg) => { equal(msg.type, 'request'); - equal(msg.commandId, 'test/command'); - deepEqual(msg.data, { test: 'test' }); + equal(msg.commandId, 'autohost/sendMessage'); + deepEqual((msg as unknown as { data: string }).data, { + battleId: 'id', + message: 'msg', + }); conn.send({ type: 'response', commandId: msg.commandId, @@ -40,14 +43,14 @@ test('simple full example', async () => { await once(client, 'connected'); client.send({ type: 'request', - commandId: 'test/command', + commandId: 'autohost/sendMessage', messageId: 'test-message1', - data: { test: 'test' }, + data: { battleId: 'id', message: 'msg' }, }); const msg = (await once(client, 'message')) as [TachyonMessage]; deepEqual(msg[0], { type: 'response', - commandId: 'test/command', + commandId: 'autohost/sendMessage', messageId: 'test-message1', status: 'success', }); @@ -59,16 +62,16 @@ test("doesn't emit bad tachyon messages", async () => { conn.on('message', () => { conn.send({ type: 'asdasdasd', - } as unknown as TachyonResponseOk); + }); }); }); const client = new TachyonClient(connectionParams); await once(client, 'connected'); client.send({ type: 'request', - commandId: 'test/command', + commandId: 'autohost/sendMessage', messageId: 'test-message1', - data: { test: 'test' }, + data: { battleId: 'id', message: 'msg' }, }); let gotMessages = 0; client.on('message', () => { diff --git a/src/tachyonClient.ts b/src/tachyonClient.ts index 67ab8da..5889fc6 100644 --- a/src/tachyonClient.ts +++ b/src/tachyonClient.ts @@ -6,7 +6,8 @@ * and the client can send messages to the server. */ import { TypedEmitter } from 'tiny-typed-emitter'; -import { parseTachyonMessage, TachyonMessage, TACHYON_PROTOCOL_VERSION } from './tachyonTypes.js'; +import { parseTachyonMessage, TACHYON_PROTOCOL_VERSION, TachyonMessage } from './tachyonTypes.js'; +import { TachyonCommand } from 'tachyon-protocol/types'; import { getAccessToken } from './oauth2Client.js'; import WebSocket from 'ws'; @@ -113,7 +114,7 @@ export class TachyonClient extends TypedEmitter<{ this.close(); } - public send(msg: TachyonMessage): void { + public send(msg: TachyonCommand): void { if (this.state !== ClientState.CONNECTED) { throw new Error('Client is not connected'); } diff --git a/src/tachyonServer.fake.ts b/src/tachyonServer.fake.ts index de4a022..2dbd2f4 100644 --- a/src/tachyonServer.fake.ts +++ b/src/tachyonServer.fake.ts @@ -25,12 +25,7 @@ import FastifyFormBody from '@fastify/formbody'; import FastifyBasicAuth from '@fastify/basic-auth'; import FastifyWebSocket from '@fastify/websocket'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'; -import { - parseTachyonMessage, - TachyonMessage, - TachyonRequest, - TACHYON_PROTOCOL_VERSION, -} from './tachyonTypes.js'; +import { parseTachyonMessage, TachyonMessage, TACHYON_PROTOCOL_VERSION } from './tachyonTypes.js'; import { TypedEmitter } from 'tiny-typed-emitter'; import { WebSocket } from 'ws'; @@ -75,7 +70,8 @@ export class TachyonClientConnection extends TypedEmitter<{ }); } - send(msg: TachyonMessage): void { + // Allow sending any object whatsoever as that's useful for testing. + send(msg: object): void { const buf = JSON.stringify(msg); this.ws.send(buf); } @@ -311,7 +307,7 @@ if (import.meta.filename == process.argv[1]) { return `Couldn't find the open connection ${req.params.connIdx}`; } const conn = connections.get(req.params.connIdx)!; - const tachyonMsg: TachyonRequest = { + const tachyonMsg = { type: 'request', commandId: `autohost/${req.params.autohostCommand}`, messageId: randomUUID(), diff --git a/src/tachyonTypes.test.ts b/src/tachyonTypes.test.ts index f29be07..18d7d40 100644 --- a/src/tachyonTypes.test.ts +++ b/src/tachyonTypes.test.ts @@ -1,17 +1,13 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { + callTachyonAutohost, createTachyonEvent, - precompileSchemas, parseTachyonMessage, - callTachyonAutohost, - TachyonRequest, - KillRequest, TachyonAutohost, + TachyonMessage, } from './tachyonTypes.js'; - -// Let's force all schemas to be compiled to catch errors early -precompileSchemas(); +import { AutohostKillRequestData } from 'tachyon-protocol/types'; test('parsing correct tachyon message succeeds', () => { const message = { @@ -37,28 +33,28 @@ test('parsing incorrect tachyon message fails', () => { }); test('creating a tachyon event succeeds', () => { - const event = createTachyonEvent('autohost/status', { currentGames: 2, maxGames: 4 }); + const event = createTachyonEvent('autohost/status', { currentBattles: 2, maxBattles: 4 }); assert.deepStrictEqual(event, { type: 'event', messageId: event.messageId, commandId: 'autohost/status', - data: { currentGames: 2, maxGames: 4 }, + data: { currentBattles: 2, maxBattles: 4 }, }); }); test('calling tachyon autohost succeeds', async () => { - const killData: KillRequest = { + const killData: AutohostKillRequestData = { battleId: '873bf189-d659-4527-befd-e9d63b308955', }; - const req: TachyonRequest = { + const req = { messageId: 'some-message-id', commandId: 'autohost/kill', type: 'request', data: killData, - }; + } as TachyonMessage; let called = 0; const autohost = { - kill: async (data: KillRequest) => { + kill: async (data: AutohostKillRequestData) => { assert.deepStrictEqual(data, killData); ++called; }, @@ -75,34 +71,36 @@ test('calling tachyon autohost succeeds', async () => { }); test('calling tachyon autohost catches bad commands', async () => { - const req: TachyonRequest = { + const req = { messageId: 'some-message-id', commandId: 'autohost/killss', type: 'request', data: {}, - }; + } as TachyonMessage; const response = await callTachyonAutohost(req, {} as TachyonAutohost); + assert(response.status === 'failed'); assert.deepStrictEqual(response, { type: 'response', status: 'failed', messageId: req.messageId, commandId: req.commandId, - reason: 'unknown_command', + reason: 'command_unimplemented', details: response.details, }); }); test('calling tachyon autohost validates data', async () => { - const killData: KillRequest = { + const killData: AutohostKillRequestData = { battleId: '873bf189-d659-4527-befd-e9d63b308', // invalid uuid }; - const req: TachyonRequest = { + const req = { messageId: 'some-message-id', commandId: 'autohost/kill', type: 'request', data: killData, - }; + } as TachyonMessage; const response = await callTachyonAutohost(req, {} as TachyonAutohost); + assert(response.status === 'failed'); assert.deepStrictEqual(response, { type: 'response', status: 'failed', diff --git a/src/tachyonTypes.ts b/src/tachyonTypes.ts index dd21d72..c533724 100644 --- a/src/tachyonTypes.ts +++ b/src/tachyonTypes.ts @@ -5,110 +5,74 @@ * schemas and the actual implementation of the Tachyon protocol. Most of this code could be * automatically generated, but it's small enough that it's not worth the effort at the moment. */ +import { type TachyonMeta, tachyonMeta } from 'tachyon-protocol'; +import { validator } from 'tachyon-protocol/validators'; import { randomUUID } from 'node:crypto'; -import { Ajv } from 'ajv'; -import ajvFormatsModule from 'ajv-formats'; -const addFormats = ajvFormatsModule.default; +import { Ajv, type JSONSchemaType } from 'ajv'; +import type { + AutohostAddPlayerRequestData, + AutohostKickPlayerRequestData, + AutohostKillRequestData, + AutohostMutePlayerRequestData, + AutohostSendCommandRequestData, + AutohostSendMessageRequestData, + AutohostSpecPlayersRequestData, + AutohostStartOkResponseData, + AutohostStartRequestData, + AutohostStatusEventData, + AutohostSubscribeUpdatesRequestData, + AutohostUpdateEventData, + TachyonCommand, +} from 'tachyon-protocol/types'; export const TACHYON_PROTOCOL_VERSION = 'v0.tachyon'; -// Import all the schemas -import KillRequestSchema from './schemas/killRequest.json' with { type: 'json' }; -import PlayerAddRequestSchema from './schemas/playerAddRequest.json' with { type: 'json' }; -import PlayerKickRequestSchema from './schemas/playerKickRequest.json' with { type: 'json' }; -import PlayerMuteRequestSchema from './schemas/playerMuteRequest.json' with { type: 'json' }; -import PlayersSpecRequestSchema from './schemas/playersSpecRequest.json' with { type: 'json' }; -import SendCommandRequestSchema from './schemas/sendCommandRequest.json' with { type: 'json' }; -import SendMessageRequestSchema from './schemas/sendMessageRequest.json' with { type: 'json' }; -import StartRequestSchema from './schemas/startRequest.json' with { type: 'json' }; -import StartResponseSchema from './schemas/startResponse.json' with { type: 'json' }; -import StatusEventSchema from './schemas/statusEvent.json' with { type: 'json' }; -import SubscribeUpdatesRequestSchema from './schemas/subscribeUpdatesRequest.json' with { type: 'json' }; -import TachyonMessageSchema from './schemas/tachyonMessage.json' with { type: 'json' }; -import UpdateEventSchema from './schemas/updateEvent.json' with { type: 'json' }; +// Top level message schema for Tachyon messages as they appear in the WebSocket. +export interface TachyonMessage { + type: 'request' | 'response' | 'event'; + messageId: string; + commandId: string; +} + +const TachyonMessageSchema: JSONSchemaType = { + type: 'object', + properties: { + type: { type: 'string', enum: ['request', 'response', 'event'] }, + messageId: { type: 'string' }, + commandId: { type: 'string' }, + }, + required: ['messageId', 'commandId', 'type'], + additionalProperties: true, +}; -const schemas = [ - StartRequestSchema, - KillRequestSchema, - PlayerAddRequestSchema, - PlayerKickRequestSchema, - PlayerMuteRequestSchema, - PlayersSpecRequestSchema, - SendCommandRequestSchema, - SendMessageRequestSchema, - StartResponseSchema, - StatusEventSchema, - SubscribeUpdatesRequestSchema, - TachyonMessageSchema, - UpdateEventSchema, -]; -const ajv = new Ajv({ schemas, strict: true }); -ajv.addVocabulary(['tsType']); -addFormats(ajv); +const ajv = new Ajv({ strict: true }); +const validateTachyonMessage = ajv.compile(TachyonMessageSchema); /** - * Precompiles all the schemas so they are ready to be used for validation. + * Parses a Tachyon message from a string. * - * It's refactored into a function so it can be called explicitly and not just when the module is - * imported even in unrelated unit tests. + * It won't validate the command nor command data, and only the top level tachyon message + * structure. */ -export function precompileSchemas() { - for (const schema of schemas) { - ajv.getSchema(schema.$id); +export function parseTachyonMessage(message: string): TachyonMessage { + const parsed = JSON.parse(message); + if (!validateTachyonMessage(parsed)) { + throw new Error( + `Failed to validate the root request: ${ajv.errorsText(validateTachyonMessage.errors)}`, + ); } + return parsed; } -// Now let's import all the generated types -import type { KillRequest } from './types/killRequest.js'; -import type { PlayerAddRequest } from './types/playerAddRequest.js'; -import type { PlayerKickRequest } from './types/playerKickRequest.js'; -import type { PlayerMuteRequest } from './types/playerMuteRequest.js'; -import type { PlayersSpecRequest } from './types/playersSpecRequest.js'; -import type { SendCommandRequest } from './types/sendCommandRequest.js'; -import type { SendMessageRequest } from './types/sendMessageRequest.js'; -import type { StartRequest } from './types/startRequest.js'; -import type { StartResponse } from './types/startResponse.js'; -import type { StatusEvent } from './types/statusEvent.js'; -import type { SubscribeUpdatesRequest } from './types/subscribeUpdatesRequest.js'; -import type { UpdateEvent } from './types/updateEvent.js'; -import type { - TachyonEvent, - TachyonMessage, - TachyonRequest, - TachyonResponseFail, - TachyonResponseOk, -} from './types/tachyonMessage.js'; - -// Export all the message types for convenience -export { - KillRequest, - PlayerAddRequest, - PlayerKickRequest, - PlayerMuteRequest, - PlayersSpecRequest, - SendCommandRequest, - SendMessageRequest, - StartRequest, - StartResponse, - StatusEvent, - SubscribeUpdatesRequest, - UpdateEvent, - TachyonEvent, - TachyonMessage, - TachyonRequest, - TachyonResponseFail, - TachyonResponseOk, -}; - /** * A custom error class to represent errors that can be returned by the Tachyon protocol. * * To be used primarily by the autohost interface implementation. All generic errors * otherwise will be caught and transformed into a generic internal error. */ -export class TachyonError extends Error { +export class TachyonError extends Error { constructor( - public readonly reason: TachyonResponseFail['reason'], + public readonly reason: TachyonMeta['failedReasons'][T][number], public readonly details: string, ) { super(`failed with reason ${reason}: ${details}`); @@ -121,15 +85,15 @@ export class TachyonError extends Error { * for the autohost protocol commands. */ export interface TachyonAutohost { - start(request: StartRequest): Promise; - kill(request: KillRequest): Promise; - playerAdd(request: PlayerAddRequest): Promise; - playerKick(request: PlayerKickRequest): Promise; - playerMute(request: PlayerMuteRequest): Promise; - playersSpec(request: PlayersSpecRequest): Promise; - sendCommand(request: SendCommandRequest): Promise; - sendMessage(request: SendMessageRequest): Promise; - subscribeUpdates(request: SubscribeUpdatesRequest): Promise; + start(request: AutohostStartRequestData): Promise; + kill(request: AutohostKillRequestData): Promise; + addPlayer(request: AutohostAddPlayerRequestData): Promise; + kickPlayer(request: AutohostKickPlayerRequestData): Promise; + mutePlayer(request: AutohostMutePlayerRequestData): Promise; + specPlayers(request: AutohostSpecPlayersRequestData): Promise; + sendCommand(request: AutohostSendCommandRequestData): Promise; + sendMessage(request: AutohostSendMessageRequestData): Promise; + subscribeUpdates(request: AutohostSubscribeUpdatesRequestData): Promise; connected(server: TachyonServer): void; disconnected(): void; } @@ -138,43 +102,18 @@ export interface TachyonAutohost { * The interface that represents the functionality that the autohost can call on the server. */ export interface TachyonServer { - status(event: StatusEvent): void; - update(event: UpdateEvent): void; + status(event: AutohostStatusEventData): void; + update(event: AutohostUpdateEventData): void; } -function dispatchTachyonAutohostCall( - req: TachyonRequest, - autohost: TachyonAutohost, -): Promise { - // - // !!! WARNING !!! - // - // This switch is the most dangerous one in this file, because we do literal resolution of the - // commandId to type. So if we make typo or cast to wrong type static analysis won't catch it. - switch (req.commandId) { - case 'autohost/start': - return autohost.start(req.data as StartRequest); - case 'autohost/kill': - return autohost.kill(req.data as KillRequest); - case 'autohost/playerAdd': - return autohost.playerAdd(req.data as PlayerAddRequest); - case 'autohost/playerKick': - return autohost.playerKick(req.data as PlayerKickRequest); - case 'autohost/playerMute': - return autohost.playerMute(req.data as PlayerMuteRequest); - case 'autohost/playersSpec': - return autohost.playersSpec(req.data as PlayersSpecRequest); - case 'autohost/sendCommand': - return autohost.sendCommand(req.data as SendCommandRequest); - case 'autohost/sendMessage': - return autohost.sendMessage(req.data as SendMessageRequest); - case 'autohost/subscribeUpdates': - return autohost.subscribeUpdates(req.data as SubscribeUpdatesRequest); - default: - throw new Error( - `Unknown command ${req.commandId}, that should never happen, it should have been caught by the validator`, - ); - } +// Helper that works as a type guard to check if an element is included in an array. +function includes(coll: ReadonlyArray, el: U): el is T { + return coll.includes(el as T); +} + +// Helper to assert that a switch statement is exhaustive. +function assertUnreachable(_x: never): never { + throw new Error('Unreachable autohost command reached!'); } /** @@ -184,85 +123,129 @@ function dispatchTachyonAutohostCall( * a proper Tachyon response object ready to be serialized. */ export async function callTachyonAutohost( - req: TachyonRequest, + req: TachyonMessage, autohost: TachyonAutohost, -): Promise { - try { - const validator = ajv.getSchema( - `https://beyondallreason.dev/schema/tachyon/${req.commandId}/request`, +): Promise> { + if (req.type !== 'request') { + throw new Error('Only requests are allowed'); + } + if (!includes(tachyonMeta.schema.actors.autohost.request.receive, req.commandId)) { + return createTachyonResponseFail( + req, + 'command_unimplemented', + `${req.commandId} of type request not recognized by autohost`, ); - if (!validator) { - throw new TachyonError( - 'unknown_command', - `${req.commandId} of type request not recognized by autohost`, - ); - } - const valid = validator(req.data); + } + try { + const reqValidator = validator[req.commandId]['request']; + const valid = reqValidator(req); if (!valid) { throw new TachyonError( 'invalid_request', - `Failed to validate command ${req.commandId} data: ${ajv.errorsText(validator.errors)}`, + `Failed to validate command ${req.commandId} data: ${ajv.errorsText(reqValidator.errors)}`, ); } - const respData = await dispatchTachyonAutohostCall(req, autohost); - return { - type: 'response', - status: 'success', - messageId: req.messageId, - commandId: req.commandId, - data: respData, - }; + switch (req.commandId) { + case 'autohost/start': + return createTachyonResponseOk(req, await autohost.start(req.data)); + case 'autohost/kill': + return createTachyonResponseOk(req, await autohost.kill(req.data)); + case 'autohost/addPlayer': + return createTachyonResponseOk(req, await autohost.addPlayer(req.data)); + case 'autohost/kickPlayer': + return createTachyonResponseOk(req, await autohost.kickPlayer(req.data)); + case 'autohost/mutePlayer': + return createTachyonResponseOk(req, await autohost.mutePlayer(req.data)); + case 'autohost/specPlayers': + return createTachyonResponseOk(req, await autohost.specPlayers(req.data)); + case 'autohost/sendCommand': + return createTachyonResponseOk(req, await autohost.sendCommand(req.data)); + case 'autohost/sendMessage': + return createTachyonResponseOk(req, await autohost.sendMessage(req.data)); + case 'autohost/subscribeUpdates': + return createTachyonResponseOk(req, await autohost.subscribeUpdates(req.data)); + default: + assertUnreachable(req); + } } catch (error) { - const tachyonErr: TachyonResponseFail = { - type: 'response', - status: 'failed', - messageId: req.messageId, - commandId: req.commandId, - reason: 'internal_error', - }; + const failedReasons = tachyonMeta.schema.failedReasons; if (error instanceof TachyonError) { - tachyonErr.reason = error.reason; - tachyonErr.details = error.details; + if ( + req.commandId in failedReasons && + includes(failedReasons[req.commandId as keyof typeof failedReasons], error.reason) + ) { + return createTachyonResponseFail(req, error.reason, error.details); + } + console.error( + `Invalid TachyonError reason: ${error.reason} for command ${req.commandId}. Details: ${error.details}`, + ); + } else { + console.error(`Error processing command ${req.commandId}: ${error}`); } - return tachyonErr; + return createTachyonResponseFail(req, 'internal_error'); } } +type AutohostEvent = Extract< + TachyonCommand, + { type: 'event'; commandId: TachyonMeta['actors']['autohost']['event']['send'][number] } +>; + /** - * Parses a Tachyon message from a string. - * - * It won't validate the command nor command data, and only the top level tachyon message - * structure. + * Creates a event message given the event type and the event data. */ -export function parseTachyonMessage(message: string): TachyonMessage { - const parsed = JSON.parse(message); - const valid = ajv.getSchema('https://beyondallreason.dev/schema/tachyon/message'); - if (!valid) { - throw new Error('Failed to find the root request schema, this should never happen'); - } - if (!valid(parsed)) { - throw new Error(`Failed to validate the root request: ${ajv.errorsText(valid.errors)}`); - } - return parsed as TachyonMessage; +export function createTachyonEvent< + CommandId extends AutohostEvent['commandId'], + Event extends Extract, +>(status: CommandId, event: Event['data']): Event { + return { + type: 'event', + messageId: randomUUID(), + commandId: status, + data: event, + } as Event; } -// Another, last one, dangerous mapping that can't be statically checked. -interface _EventTypeHelper { - 'autohost/status': StatusEvent; - 'autohost/update': UpdateEvent; +/** + * Creates a successful response object given the request object and the data + * for the response matching the request. + */ +export function createTachyonResponseOk< + Req extends Extract, + Res extends Extract< + TachyonCommand, + { type: 'response'; status: 'success'; commandId: Req['commandId'] } + >, +>(req: Req, data: Res extends { data: unknown } ? Res['data'] : void): Res { + return { + type: 'response', + status: 'success', + commandId: req.commandId, + messageId: req.messageId, + data, + } as Res; } /** - * Creates a proper tachyon message given the event type and the event data. + * Creates a failed response object given the request object and reason for the failure. + * + * If the commandId is constant, then it's fully typed, otherwise any response + * that is a valid reason for any of the commands will be accepted. */ -export function createTachyonEvent( - status: S, - event: _EventTypeHelper[S], -): TachyonEvent { +export function createTachyonResponseFail< + CommandId, + Req extends { commandId: CommandId; messageId: string }, + Res extends Extract< + TachyonCommand, + { type: 'response'; commandId: Req['commandId']; status: 'failed' } + >, +>(req: Req, reason: Res['reason'], details?: string): Res { return { - type: 'event', - messageId: randomUUID(), - commandId: status, - data: event, - }; + type: 'response', + status: 'failed', + commandId: req.commandId, + messageId: req.messageId, + reason, + details, + } as Res; } diff --git a/src/types/killRequest.d.ts b/src/types/killRequest.d.ts deleted file mode 100644 index 38e9c64..0000000 --- a/src/types/killRequest.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -/** - * Request to kill a battle. - */ -export interface KillRequest { - battleId: string; -} diff --git a/src/types/playerAddRequest.d.ts b/src/types/playerAddRequest.d.ts deleted file mode 100644 index 449e7a1..0000000 --- a/src/types/playerAddRequest.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface PlayerAddRequest { - battleId: string; - userId: string; - name: string; - password: string; -} diff --git a/src/types/playerKickRequest.d.ts b/src/types/playerKickRequest.d.ts deleted file mode 100644 index 4523d3a..0000000 --- a/src/types/playerKickRequest.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface PlayerKickRequest { - battleId: string; - userId: string; -} diff --git a/src/types/playerMuteRequest.d.ts b/src/types/playerMuteRequest.d.ts deleted file mode 100644 index ea8941b..0000000 --- a/src/types/playerMuteRequest.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface PlayerMuteRequest { - battleId: string; - userId: string; - chat: boolean; - draw: boolean; -} diff --git a/src/types/playersSpecRequest.d.ts b/src/types/playersSpecRequest.d.ts deleted file mode 100644 index 8dcb0de..0000000 --- a/src/types/playersSpecRequest.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface PlayersSpecRequest { - battleId: string; - userIds: string[]; -} diff --git a/src/types/sendCommandRequest.d.ts b/src/types/sendCommandRequest.d.ts deleted file mode 100644 index 3a148e0..0000000 --- a/src/types/sendCommandRequest.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface SendCommandRequest { - battleId: string; - command: string; - arguments: string[]; -} diff --git a/src/types/sendMessageRequest.d.ts b/src/types/sendMessageRequest.d.ts deleted file mode 100644 index 7c2cf94..0000000 --- a/src/types/sendMessageRequest.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface SendMessageRequest { - battleId: string; - message: string; -} diff --git a/src/types/startRequest.d.ts b/src/types/startRequest.d.ts deleted file mode 100644 index 5c45739..0000000 --- a/src/types/startRequest.d.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -/** - * Floating value between 0 and 1 - */ -export type Zero2One = number; - -export interface StartRequest { - battleId: string; - engineVersion: string; - gameName: string; - mapName: string; - gameArchiveHash?: string; - mapArchiveHash?: string; - mapOptions?: { - [k: string]: string; - }; - gameOptions?: { - [k: string]: string; - }; - startDelay?: number; - startPosType: 'fixed' | 'random' | 'ingame' | 'beforegame'; - /** - * @minItems 1 - */ - allyTeams: [AllyTeam, ...AllyTeam[]]; - spectators?: Player[]; - /** - * Unit restrictions - */ - restrictions?: { - [k: string]: number; - }; -} -export interface AllyTeam { - teams: Team[]; - startbox?: { - top: Zero2One; - bottom: Zero2One; - left: Zero2One; - right: Zero2One; - }; - /** - * Indexes into of the other allyteams to ally with - */ - allies?: number[]; - customOpts?: { - [k: string]: string; - }; -} -export interface Team { - players?: Player[]; - ais?: AI[]; - advantage?: number; - incomeMultiplier?: number; - side?: string; - color?: { - r: Zero2One; - g: Zero2One; - b: Zero2One; - }; - startPos?: { - x: number; - z: number; - }; - customOpts?: { - [k: string]: string; - }; -} -export interface Player { - userId: string; - /** - * Name of the player, must be unique just like userId - */ - name: string; - password: string; - rank?: number; - countryCode?: string; - customOpts?: { - [k: string]: string; - }; -} -export interface AI { - /** - * UserId of the player hosting this AI - */ - hostUserId: string; - shortName: string; - version?: string; - name?: string; - options?: { - [k: string]: string; - }; -} diff --git a/src/types/startResponse.d.ts b/src/types/startResponse.d.ts deleted file mode 100644 index 90c2142..0000000 --- a/src/types/startResponse.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface StartResponse { - /** - * @minItems 1 - */ - ips: [string | string, ...(string | string)[]]; - port: number; - [k: string]: unknown; -} diff --git a/src/types/statusEvent.d.ts b/src/types/statusEvent.d.ts deleted file mode 100644 index 15fba60..0000000 --- a/src/types/statusEvent.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface StatusEvent { - maxGames: number; - currentGames: number; - [k: string]: unknown; -} diff --git a/src/types/subscribeUpdatesRequest.d.ts b/src/types/subscribeUpdatesRequest.d.ts deleted file mode 100644 index 8756ba3..0000000 --- a/src/types/subscribeUpdatesRequest.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface SubscribeUpdatesRequest { - /** - * Unix timestamp in microseconds, it needs to be simply what was received in the last update - */ - since?: number; - [k: string]: unknown; -} diff --git a/src/types/tachyonMessage.d.ts b/src/types/tachyonMessage.d.ts deleted file mode 100644 index 9034798..0000000 --- a/src/types/tachyonMessage.d.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export type TachyonMessage = - | TachyonRequest - | TachyonResponseOk - | TachyonResponseFail - | TachyonEvent; -export type TachyonRequest = TachyonBaseMessage & { - type: 'request'; - data?: unknown; - [k: string]: unknown; -}; -export type TachyonResponseOk = TachyonBaseMessage & { - type: 'response'; - status: 'success'; - data?: unknown; - [k: string]: unknown; -}; -export type TachyonResponseFail = TachyonBaseMessage & { - type: 'response'; - status: 'failed'; - reason: 'command_unimplemented' | 'internal_error' | 'invalid_request' | 'unknown_command'; - /** - * Additional details about the failure for developers - */ - details?: string; - [k: string]: unknown; -}; -export type TachyonEvent = TachyonBaseMessage & { - type: 'event'; - data?: unknown; - [k: string]: unknown; -}; - -export interface TachyonBaseMessage { - type: 'request' | 'response' | 'event'; - messageId: string; - commandId: string; - [k: string]: unknown; -} diff --git a/src/types/updateEvent.d.ts b/src/types/updateEvent.d.ts deleted file mode 100644 index acb3749..0000000 --- a/src/types/updateEvent.d.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface UpdateEvent { - battleId: string; - /** - * Unix timestamp in microseconds - */ - time: number; - update: - | StartUpdate - | FinishedUpdate - | EngineMessageUpdate - | EngineWarningUpdate - | EngineQuitUpdate - | EngineCrashUpdate - | PlayerJoinedUpdate - | PlayerLeftUpdate - | PlayerChatUpdate - | PlayerDefeatedUpdate - | LuaMsgUpdate; -} -/** - * The battle started. - */ -export interface StartUpdate { - type: 'start'; -} -/** - * The battle finished, generated once per every single player reporting who won. - */ -export interface FinishedUpdate { - type: 'finished'; - userId: string; - /** - * @minItems 1 - */ - winningAllyTeams: [number, ...number[]]; -} -/** - * A message from the engine, e.g. some ip is trying to connect. - */ -export interface EngineMessageUpdate { - type: 'engine_message'; - message: string; -} -export interface EngineWarningUpdate { - type: 'engine_warning'; - message: string; -} -/** - * The engine process for battle has quit cleanly, no more updates will be sent for this battle. - */ -export interface EngineQuitUpdate { - type: 'engine_quit'; -} -/** - * The engine process for battle has crashed, no more updates will be sent for this battle. - */ -export interface EngineCrashUpdate { - type: 'engine_crash'; -} -export interface PlayerJoinedUpdate { - type: 'player_joined'; - userId: string; - /** - * Player number in the game, can be useful for custom commands - */ - playerNumber: number; -} -export interface PlayerLeftUpdate { - type: 'player_left'; - userId: string; - reason: 'lost_connection' | 'left' | 'kicked'; -} -export interface PlayerChatUpdate { - type: 'player_chat'; - userId: string; - message: string; - destination: 'allies' | 'all' | 'spectators' | 'player'; - toUserId?: string; -} -export interface PlayerDefeatedUpdate { - type: 'player_defeated'; - userId: string; -} -export interface LuaMsgUpdate { - type: 'luamsg'; - userId: string; - script: 'ui' | 'gaia' | 'rules'; - uiMode?: 'all' | 'allies' | 'spectators'; - data: string; -}