diff --git a/eslint.config.js b/eslint.config.js index 6e9142b..acc9172 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,67 +1,66 @@ import { includeIgnoreFile } from '@eslint/compat' import js from '@eslint/js' import json from '@eslint/json' -import stylistic from '@stylistic/eslint-plugin' import restrictedGlobals from 'confusing-browser-globals' -import jsdoc from 'eslint-plugin-jsdoc' +import tsdoc from 'eslint-plugin-tsdoc' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' -import { defineConfig } from 'eslint/config' import globals from 'globals' +import tseslint from 'typescript-eslint' import path from 'node:path' -import { fileURLToPath } from 'node:url' -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const gitignore = path.resolve(__dirname, '.gitignore') +const gitignore = path.resolve(import.meta.dirname, '.gitignore') -export default defineConfig([ +export default tseslint.config( includeIgnoreFile(gitignore, '.gitignore'), - // javascript + // all files by default get shared globals { - files: ['**/*.{js,jsx}'], - plugins: { js, jsdoc, stylistic }, - extends: [ - 'js/recommended', - 'stylistic/recommended', - 'jsdoc/flat/recommended-typescript-flavor', - ], + name: 'javascript basics', + files: ['**/*.@(js|jsx|ts|tsx)'], + + plugins: {tsdoc}, + extends: [js.configs.recommended], + languageOptions: { - parserOptions: { - emcaFeatures: { - jsx: true, - }, - }, globals: { ...globals.es2024, ...globals['shared-node-browser'], }, }, + rules: { // eg, `open` is a global, but probably not really intended that way in our code 'no-restricted-globals': ['error', ...restrictedGlobals], + 'no-unused-vars': ['warn', { varsIgnorePattern: '(?:^_)' }], - // undef is handled by typecheck - // for typechecking we want to allow unused vars like `import * as preact_types from 'preact'` - 'no-undef': ['off'], - 'no-unused-vars': ['warn', { varsIgnorePattern: '(?:^_)|(?:_types$)' }], - - // preferences - 'max-len': ['warn', { code: 100 }], - 'no-multi-spaces': ['off'], - '@stylistic/dot-location': ['error', 'property'], - '@stylistic/padded-blocks': ['warn', { classes: 'always', blocks: 'never' }], + 'tsdoc/syntax': 'warn' + }, + }, - // enforce docs - 'jsdoc/check-indentation': ['warn'], - 'jsdoc/tag-lines': ['warn', 'always', { count: 0, startLines: 1 }], + { + name: 'typescript basics', + files: ['**/*.@(ts|tsx)'], + extends: [tseslint.configs.strictTypeChecked], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, + rules: { + '@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '(?:^_)' }], + '@typescript-eslint/no-unnecessary-condition': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + } }, - // server specific { - files: ['./src/server/*.js', './src/server/**/*.js', './src/cmd/*.js', './src/cmd/**/*.js'], + name: 'node files', + files: [ + 'src/server/**/*.@(js|jsx|ts|tsx)', + 'src/cmd/**/*.@(js|jsx|ts|tsx)' + ], languageOptions: { globals: { ...globals.es2024, @@ -70,26 +69,23 @@ export default defineConfig([ }, }, - // client specific - // mostly cribbed from preact's config, but that's not setup to handle eslint9 - // https://github.com/preactjs/eslint-config-preact/blob/master/index.js { - files: ['./src/client/*.{js,jsx}', './src/client/**/*.{js,jsx}'], - plugins: { - react, - reactHooks, - }, + // mostly cribbed from preact's config, but that's not setup to handle eslint9 + // https://github.com/preactjs/eslint-config-preact/blob/master/index.js + name: 'client files', + files: [ + 'src/client/**/*.@(js|jsx|ts|tsx)', + ], languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, globals: { ...globals.es2024, ...globals.browser, }, }, + plugins: { + react, + reactHooks, + }, settings: { react: { pragma: 'h', @@ -139,29 +135,22 @@ export default defineConfig([ // tests don't have jsdoc requirements { files: ['src/**/*.spec.{js,jsx}'], - rules: { - // Disable JSDoc requirements for test files - 'jsdoc/require-jsdoc': 'off', - 'jsdoc/require-returns': 'off', - 'jsdoc/require-param': 'off', - 'jsdoc/require-param-description': 'off', - 'jsdoc/require-param-type': 'off', - 'jsdoc/check-tag-names': 'off', - }, + rules: {}, }, // json (with comments in some files) { files: ['**/*.json'], ignores: ['package-lock.json'], + plugins: { json }, language: 'json/json', - extends: ['json/recommended'], + extends: [ + json.configs.recommended + ], }, { files: ['tsconfig.json'], - plugins: { json }, language: 'json/jsonc', - extends: ['json/recommended'], }, -]) +) diff --git a/jest.config.js b/jest.config.js index 5c487a6..1ede094 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,12 +4,6 @@ import { fileURLToPath } from 'node:url' import gitignore from 'parse-gitignore' import * as tsjest from 'ts-jest' -import * as jest_types from 'jest' - -/** - * @private - * @returns {Array} list of path regexp to ignore - */ function gitignorePatterns() { const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -20,7 +14,6 @@ function gitignorePatterns() { .map(p => globToRegexp(p, { globstar: true }).source) } -/** @type {jest_types.Config} */ export default { testMatch: ['/src/**/*.spec.{ts,tsx}'], testPathIgnorePatterns: [ diff --git a/package-lock.json b/package-lock.json index f34d07e..9c6f82f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "express": "^5.1.0", + "isomorphic-ws": "^5.0.0", "jose": "^6.0.11", "nanoid": "^5.1.5", "preact": "^10.26.9", @@ -26,7 +27,6 @@ "@faker-js/faker": "^9.8.0", "@jest/globals": "^30.0.0", "@preact/preset-vite": "^2.10.1", - "@stylistic/eslint-plugin": "^4.4.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/preact": "^3.2.4", "@types/confusing-browser-globals": "^1.0.3", @@ -37,21 +37,21 @@ "@types/ws": "^8.18.1", "confusing-browser-globals": "^1.0.11", "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "~51.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-tsdoc": "^0.4.0", "glob-to-regexp": "^0.4.1", "globals": "^16.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.0", "jest-fixed-jsdom": "^0.0.9", - "jsdoc": "^4.0.4", "jsdom": "^26.1.0", "parse-gitignore": "^2.0.0", "tidy-jsdoc-fork": "github:lygaret/tidy-jsdoc", "ts-jest": "^29.4.0", "typescript": "^5.8.3", + "typescript-eslint": "^8.34.1", "typescript-eslint-language-service": "^5.0.5", "vite": "^6.3.5", "vite-plugin-checker": "^0.9.3", @@ -807,23 +807,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.50.2", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", - "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6", - "@typescript-eslint/types": "^8.11.0", - "comment-parser": "1.4.1", - "esquery": "^1.6.0", - "jsdoc-type-pratt-parser": "~4.1.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -2212,17 +2195,69 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsdoc/salty": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", - "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", + "dev": true, + "license": "MIT", "dependencies": { - "lodash": "^4.17.21" + "@microsoft/tsdoc": "0.15.1", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=v12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/@napi-rs/wasm-runtime": { @@ -2761,39 +2796,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@stylistic/eslint-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", - "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^8.32.1", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=9.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@testing-library/dom": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", @@ -3361,6 +3363,7 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -3502,18 +3505,57 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", + "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/type-utils": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.34.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", + "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4" }, "engines": { @@ -3529,14 +3571,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", + "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.34.1", + "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "engines": { @@ -3551,14 +3593,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", + "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3569,9 +3611,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", + "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", "dev": true, "license": "MIT", "engines": { @@ -3585,10 +3627,34 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", + "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", + "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", "dev": true, "license": "MIT", "engines": { @@ -3600,16 +3666,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", + "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.34.1", + "@typescript-eslint/tsconfig-utils": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3642,16 +3708,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", + "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3666,14 +3732,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", + "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.34.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4126,16 +4192,6 @@ "node": ">= 8" } }, - "node_modules/are-docs-informative": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.1.1.tgz", - "integrity": "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5674,16 +5730,6 @@ "dev": true, "license": "MIT" }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/compact2string": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", @@ -7036,44 +7082,6 @@ } } }, - "node_modules/eslint-plugin-jsdoc": { - "version": "51.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-51.0.1.tgz", - "integrity": "sha512-nnH6O8uk0Wp5EvHlVEPESKdGWTlu5g1tfBUZmL/jMZLBpUtttxxW+9hPzTMCYmYsQ3HwDsJdHJAiaDRKsP6iUg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.50.2", - "are-docs-informative": "^0.1.1", - "comment-parser": "1.4.1", - "debug": "^4.4.1", - "escape-string-regexp": "^4.0.0", - "espree": "^10.3.0", - "esquery": "^1.6.0", - "parse-imports-exports": "^0.2.4", - "semver": "^7.7.2", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -7144,6 +7152,17 @@ "node": "*" } }, + "node_modules/eslint-plugin-tsdoc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.4.0.tgz", + "integrity": "sha512-MT/8b4aKLdDClnS8mP3R/JNjg29i0Oyqd/0ym6NnQf+gfKbJJ4ZcSh2Bs1H0YiUMTBwww5JwXGTWot/RwyJ7aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "@microsoft/tsdoc-config": "0.17.1" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -8102,6 +8121,13 @@ "dev": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -9090,6 +9116,15 @@ "node": ">=10" } }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -9905,6 +9940,13 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, "node_modules/join-async-iterator": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz", @@ -9958,56 +10000,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsdoc": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", - "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", - "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -10257,6 +10249,7 @@ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "uc.micro": "^2.0.0" } @@ -10506,6 +10499,7 @@ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -10831,7 +10825,8 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/media-typer": { "version": "1.1.0", @@ -12298,16 +12293,6 @@ "node": ">=14" } }, - "node_modules/parse-imports-exports": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-statements": "1.0.11" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -12327,13 +12312,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, "node_modules/parse-torrent": { "version": "11.0.18", "resolved": "https://registry.npmjs.org/parse-torrent/-/parse-torrent-11.0.18.tgz", @@ -12854,6 +12832,7 @@ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -13181,6 +13160,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", @@ -13941,31 +13930,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/speed-limiter": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/speed-limiter/-/speed-limiter-1.0.2.tgz", @@ -15193,6 +15157,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", + "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.34.1", + "@typescript-eslint/parser": "8.34.1", + "@typescript-eslint/utils": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/typescript-eslint-language-service": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/typescript-eslint-language-service/-/typescript-eslint-language-service-5.0.5.tgz", @@ -15210,7 +15197,8 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/uint8-util": { "version": "2.2.5", diff --git a/package.json b/package.json index 2c2ea27..23bb12c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "express": "^5.1.0", + "isomorphic-ws": "^5.0.0", "jose": "^6.0.11", "nanoid": "^5.1.5", "preact": "^10.26.9", @@ -38,7 +39,6 @@ "@faker-js/faker": "^9.8.0", "@jest/globals": "^30.0.0", "@preact/preset-vite": "^2.10.1", - "@stylistic/eslint-plugin": "^4.4.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/preact": "^3.2.4", "@types/confusing-browser-globals": "^1.0.3", @@ -49,21 +49,21 @@ "@types/ws": "^8.18.1", "confusing-browser-globals": "^1.0.11", "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "~51.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-tsdoc": "^0.4.0", "glob-to-regexp": "^0.4.1", "globals": "^16.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.0", "jest-fixed-jsdom": "^0.0.9", - "jsdoc": "^4.0.4", "jsdom": "^26.1.0", "parse-gitignore": "^2.0.0", "tidy-jsdoc-fork": "github:lygaret/tidy-jsdoc", "ts-jest": "^29.4.0", "typescript": "^5.8.3", + "typescript-eslint": "^8.34.1", "typescript-eslint-language-service": "^5.0.5", "vite": "^6.3.5", "vite-plugin-checker": "^0.9.3", diff --git a/src/client/components/messenger.tsx b/src/client/components/messenger.tsx index 1ae5c3a..6905881 100644 --- a/src/client/components/messenger.tsx +++ b/src/client/components/messenger.tsx @@ -1,4 +1,5 @@ import { RealmConnection } from '#client/realm/connection.js' +import { IdentID } from '#common/protocol' import { useState, useEffect, useCallback } from 'preact/hooks' export type MessengerProps = { @@ -7,10 +8,10 @@ export type MessengerProps = { export const Messenger: preact.FunctionComponent<{ webrtcManager: RealmConnection }> = (props) => { const { webrtcManager } = props - const [messages, setMessages] = useState([]) + const [messages, setMessages] = useState<[IdentID, string][]>([]) - const peerdata = useCallback((event: CustomEvent) => { - setMessages([...messages, [event.detail.remoteId, event.detail.data]]) + const peerdata = useCallback((event: CustomEvent<{ remoteId: IdentID, data: unknown }>) => { + setMessages([...messages, [event.detail.remoteId, `${event.detail.data}`]]) }, [messages]) const sendMessage = useCallback(() => { diff --git a/src/client/realm/connection.ts b/src/client/realm/connection.ts index 4c9643e..2a59ba9 100644 --- a/src/client/realm/connection.ts +++ b/src/client/realm/connection.ts @@ -1,11 +1,11 @@ -/** @module client/realm */ - import { nanoid } from 'nanoid' import SimplePeer from 'simple-peer' +import WebSocket from 'isomorphic-ws' +import { z } from 'zod/v4' import { generateSignableJwt, jwkExport } from '#common/crypto/jwks' import { normalizeError, normalizeProtocolError, ProtocolError } from '#common/errors' -import { IdentID, RealmBroadcastMessage, realmFromServerMessageSchema, RealmID, RealmRtcPeerWelcomeMessage, realmRtcPeerWelcomeMessageSchema, RealmRtcSignalMessage } from '#common/protocol' +import { IdentID, parseJson, PreauthRegisterMessage, RealmBroadcastMessage, realmFromServerMessageSchema, RealmID, RealmRtcPeerWelcomeMessage, realmRtcPeerWelcomeMessageSchema, RealmRtcSignalMessage } from '#common/protocol' import { sendSocket, streamSocketJson, takeSocketJson } from '#common/socket' /** the state of a specific peer */ @@ -66,10 +66,7 @@ export class RealmConnection extends EventTarget { const pubkey = await jwkExport.parseAsync(this.#identity.keypair.publicKey) this.#socket.send( - await this.#signJwt( - /** @type {protocol_types.PreauthRegisterMessage} */ - ({ msg: 'preauth.register', pubkey }), - ), + await this.#signJwt({ msg: 'preauth.register', pubkey } as PreauthRegisterMessage), ) // the next message should be a welcome message @@ -128,7 +125,7 @@ export class RealmConnection extends EventTarget { // may not have a connection yet if we're waiting for them to answer if (peer) { - peer.signal(JSON.parse(parse.data.payload)) + peer.signal(parse.data.payload) } continue @@ -148,13 +145,12 @@ export class RealmConnection extends EventTarget { } } - #handleSocketError: WebSocket['onerror'] = async (exc) => { + #handleSocketError: WebSocket['onerror'] = (exc) => { this.#dispatchCustomEvent('wserror', { error: normalizeProtocolError(exc) }) this.destroy() } - /** @type {WebSocket['onclose']} */ - #handleSocketClose: WebSocket['onclose'] = async () => { + #handleSocketClose: WebSocket['onclose'] = () => { this.#dispatchCustomEvent('wsclose') this.destroy() } @@ -224,7 +220,7 @@ export class RealmConnection extends EventTarget { this.#dispatchCustomEvent('peererror', { remoteid, error: err }) }) - peer.on('message', (data) => { + peer.on('message', (data: unknown) => { this.#dispatchCustomEvent('peerdata', { remoteid, data }) }) @@ -274,9 +270,7 @@ export class RealmConnection extends EventTarget { sendSocket(this.#socket, resp) } - /** - * @returns {Record} the current peer state mapping - */ + /** @returns the current peer state mapping */ getPeerStates(): Record { const states: Record = {} for (const [peerId, peer] of this.#peers) { @@ -292,6 +286,8 @@ export class RealmConnection extends EventTarget { } +const peerPingSchema = z.object({ type: z.literal('ping'), timestamp: z.number() }) + /** * a peer belonging to the connection manager */ @@ -340,19 +336,14 @@ export class RealmConnectionPeer extends SimplePeer { #handlePeerData = (chunk: string) => { try { - const parsed = JSON.parse(chunk) - switch (parsed.type) { - // there are some connection-manager internal messages - case 'pong': - this.send( - JSON.stringify({ type: 'pong', timestamp: parsed.timestamp }), - ) - break - - // dispatch others messages - default: - this.emit('message', parsed) - break + const ping = parseJson.pipe(peerPingSchema).safeParse(chunk) + if (ping.success) { + this.send( + JSON.stringify({ type: 'pong', timestamp: ping.data.timestamp }), + ) + } + else { + this.emit('message', chunk) } } catch (err) { diff --git a/src/client/webrtc-demo.tsx b/src/client/webrtc-demo.tsx index 468df80..977cea6 100644 --- a/src/client/webrtc-demo.tsx +++ b/src/client/webrtc-demo.tsx @@ -68,12 +68,18 @@ export const WebRTCDemo: preact.FunctionComponent = () => { } }) - const connect = useCallback(async () => { - const realmid = protocol.RealmBrand.parse('realm-n7-qM0rOzsJ8N-iF') // hard code for now - const identid = protocol.IdentBrand.generate() - const keypair = await generateSigningJwkPair() + const connect = useCallback(() => { + const go = async () => { + const realmid = protocol.RealmBrand.parse('realm-n7-qM0rOzsJ8N-iF') // hard code for now + const identid = protocol.IdentBrand.generate() + const keypair = await generateSigningJwkPair() - context.setIdentity({ realmid, identid, keypair }) + context.setIdentity({ realmid, identid, keypair }) + } + + go().catch((e: unknown) => { + console.error('couldnt create identity', e) + }) }, [context]) return ( diff --git a/src/common/async/aborts.ts b/src/common/async/aborts.ts index 767adc7..7d2272a 100644 --- a/src/common/async/aborts.ts +++ b/src/common/async/aborts.ts @@ -24,7 +24,7 @@ export function timeoutSignal(ms: number): TimeoutSignal { } /** - * @param signals the list of signals to combine + * @param signals - the list of signals to combine * @returns a combined signal, which will abort when any given signal does */ export function combineSignals(...signals: Array): AbortSignal { @@ -46,11 +46,11 @@ export function combineSignals(...signals: Array): Abor } signal.addEventListener('abort', handler) - cleanups.push(() => signal.removeEventListener('abort', handler)) + cleanups.push(() => { signal.removeEventListener('abort', handler); }) } controller.signal.addEventListener('abort', () => { - cleanups.forEach(cb => cb()) + cleanups.forEach(cb => { cb(); }) }) return controller.signal diff --git a/src/common/async/blocking-atom.ts b/src/common/async/blocking-atom.ts index a4f2cda..688dc8a 100644 --- a/src/common/async/blocking-atom.ts +++ b/src/common/async/blocking-atom.ts @@ -1,12 +1,8 @@ -/** @module common/async */ - import { Semaphore } from './semaphore.js' /** * simple blocking atom, for waiting for a value. * cribbed mostly from {@link https://github.com/ComFreek/async-playground} - * - * @template T - the type we're holding */ export class BlockingAtom { @@ -28,8 +24,10 @@ export class BlockingAtom { * tries to get the item from the atom, and blocks until available. * * @example - * if (await atom.take()) - * console.log('got it!') + * ``` + * if (await atom.take()) + * console.log('got it!') + * ``` * * @param signal - an abort signal to cancel the await * @returns a promise for the item, or undefined if something aborted. diff --git a/src/common/async/blocking-queue.ts b/src/common/async/blocking-queue.ts index cba49f5..5e2761d 100644 --- a/src/common/async/blocking-queue.ts +++ b/src/common/async/blocking-queue.ts @@ -1,5 +1,3 @@ -/** @module common/async */ - import { Semaphore } from './semaphore.js' /** @@ -18,7 +16,7 @@ export class BlockingQueue { this.#items = [] } - /** @returns {number} how deep is the queue? */ + /** @returns the depth of the queue */ get depth(): number { return this.#items.length } @@ -50,7 +48,7 @@ export class BlockingQueue { /** * block while waiting for an item off the queue. * - * @param [signal] a signal to use for aborting the block. + * @param signal - a signal to use for aborting the block. * @returns the item off the queue; rejects if aborted. */ async dequeue(signal?: AbortSignal): Promise { diff --git a/src/common/async/semaphore.ts b/src/common/async/semaphore.ts index 043e535..d8ec3de 100644 --- a/src/common/async/semaphore.ts +++ b/src/common/async/semaphore.ts @@ -1,5 +1,3 @@ -/** @module common/async */ - /** * Simple counting semaphore, for blocking async ops. * cribbed mostly from {@link https://github.com/ComFreek/async-playground} @@ -17,17 +15,17 @@ export class Semaphore { * try to take from the semaphore, reducing it's count * if the semaphore is empty, blocks until available, or the given signal aborts. * - * @param signal a signal to use to abort the block + * @param signal - a signal to use to abort the block * @returns true if the semaphore was successfully taken, false if aborted. */ take(signal?: AbortSignal): Promise { return new Promise((resolve) => { - if (signal?.aborted) return resolve(false) + if (signal?.aborted) { resolve(false); return; } // if there's resources available, use them this.#counter-- - if (this.#counter >= 0) return resolve(true) + if (this.#counter >= 0) { resolve(true); return; } // otherwise add to pending // and explicitly remove the resolver from the list on abort @@ -59,7 +57,8 @@ export class Semaphore { if (this.#resolvers.length > 0) { const resolver = this.#resolvers.shift() - resolver && queueMicrotask(() => resolver(true)) + if (resolver) + queueMicrotask(() => { resolver(true); }) } } diff --git a/src/common/async/sleep.ts b/src/common/async/sleep.ts index e6c4585..5eaa549 100644 --- a/src/common/async/sleep.ts +++ b/src/common/async/sleep.ts @@ -1,27 +1,26 @@ -/** @module common/async */ - /** - * @param ms the number of ms to sleep - * @param [signal] an aptional abort signal, to cancel the sleep + * @param ms - the number of ms to sleep + * @param signal - an aptional abort signal, to cancel the sleep * @returns a promise that resolves after given amount of time, and is interruptable with an abort signal. */ export function sleep(ms: number, signal?: AbortSignal): Promise { signal?.throwIfAborted() + // not sure why this error is coming up + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type const { resolve, reject, promise } = Promise.withResolvers() const timeout = setTimeout(resolve, ms) - if (signal) { - const abortHandler = () => { - clearTimeout(timeout) - reject(signal.reason) - } + if (!signal) + return promise - signal.addEventListener('abort', abortHandler) - promise.finally(() => { - signal.removeEventListener('abort', abortHandler) - }) + const abortHandler = () => { + clearTimeout(timeout) + reject(signal.reason) } - return promise + signal.addEventListener('abort', abortHandler) + return promise.finally(() => { + signal.removeEventListener('abort', abortHandler) + }) } diff --git a/src/common/breaker.ts b/src/common/breaker.ts index 7b2c2c7..672ccc1 100644 --- a/src/common/breaker.ts +++ b/src/common/breaker.ts @@ -1,5 +1,3 @@ -/** @module common/async */ - import { Callback } from "#common/types" /** @@ -7,6 +5,7 @@ import { Callback } from "#common/types" * the breaker is tripped. * * @example + * ``` * const breaker = makeBreaker() * * state.addEventHandler('finish', breaker.tripThen((e) => { @@ -23,6 +22,7 @@ import { Callback } from "#common/types" * // this will only be allowed to run many times * // but not *after* any of the _once_ wrappers has been called * }) + * ``` */ export class Breaker { @@ -30,8 +30,8 @@ export class Breaker { #onTripped?: () => void /** - * @param [onTripped] - * an optional callback, called when the breaker is tripped, /before/ any wrapped functions. + * @param onTripped - + * an optional callback, called when the breaker is tripped, /before/ any wrapped functions. */ constructor(onTripped?: () => void) { this.#tripped = false @@ -47,7 +47,7 @@ export class Breaker { * wrap the given callback in a function that will trip the breaker before it's called. * any subsequent calls to the wrapped function will be no-ops. * - * @param fn the function to be wrapped in the breaker + * @param fn - the function to be wrapped in the breaker * @returns a wrapped function, controlled by the breaker */ tripThen(fn: CB): CB { @@ -66,8 +66,8 @@ export class Breaker { * wrap the given callback in a function that check the breaker before it's called. * once the breaker has been tripped, calls to the wrapped function will be no-ops. * - * @param {common_types.Callback} fn the function to be wrapped in the breaker - * @returns {common_types.Callback} a wrapped function, controlled by the breaker + * @param fn - the function to be wrapped in the breaker + * @returns a wrapped function, controlled by the breaker */ untilTripped(fn: CB): CB { return ((...args: Parameters): void => { diff --git a/src/common/crypto/cipher.ts b/src/common/crypto/cipher.ts index d92e0d6..ef79f0b 100644 --- a/src/common/crypto/cipher.ts +++ b/src/common/crypto/cipher.ts @@ -1,5 +1,3 @@ -/** @module common/crypto */ - import { base64url } from 'jose' import { nanoid } from 'nanoid' @@ -25,12 +23,10 @@ function asUint8Array(s: string | Uint8Array): Uint8Array { * Derive a key given PBKDF inputs; so long as all of the inputs are stable, the key will * be the same across derivations. * - * @private - * - * @param passwordStr a password for derivation - * @param saltStr a salt for derivation - * @param nonceStr a nonce for derivation - * @param [iterations] number of iterations for pbkdf + * @param passwordStr - a password for derivation + * @param saltStr - a salt for derivation + * @param nonceStr - a nonce for derivation + * @param iterations - number of iterations for pbkdf * @returns the derived crypto key */ async function deriveKey(passwordStr: string, saltStr: string, nonceStr: string, iterations: number = 100000): Promise { @@ -68,9 +64,9 @@ export class Cipher { * any missing parameter (password/salt/nonce) is replaced with a random value, * but if a stable password/salt/nonce is given, the derived keys will be stable. * - * @param [passwordStr] a password for derivation - * @param [saltStr] a salt for derivation - * @param [nonceStr] a nonce for derivation + * @param passwordStr - a password for derivation + * @param saltStr - a salt for derivation + * @param nonceStr - a nonce for derivation * @returns the derived {@link Cipher} */ static async derive(passwordStr: string, saltStr: string, nonceStr: string): Promise { @@ -84,14 +80,14 @@ export class Cipher { * import a cipher from an aleady existing {@link CryptoKey}. * does _not_ ensure that the imported key will work with our preferred encryption * - * @param cryptokey the key to import into a Cipher + * @param cryptokey - the key to import into a Cipher */ constructor(cryptokey: CryptoKey) { this.#cryptokey = cryptokey } /** - * @param data the data to encrypte + * @param data - the data to encrypte * @returns a url-safe base64 encoded encrypted string. */ async encrypt(data: (string | Uint8Array)): Promise { @@ -109,7 +105,7 @@ export class Cipher { } /** - * @param encryptedData a base64 encoded string, previously encrypted with this cipher. + * @param encryptedData - a base64 encoded string, previously encrypted with this cipher. * @returns the decrypted output, decoded into utf-8 text. */ async decryptText(encryptedData: string): Promise { @@ -118,7 +114,7 @@ export class Cipher { } /** - * @param encryptedData a base64 encoded string, previously encrypted with this cipher. + * @param encryptedData - a base64 encoded string, previously encrypted with this cipher. * @returns the decrypted output, as an array buffer of bytes. */ async decryptBytes(encryptedData: string): Promise { diff --git a/src/common/crypto/jwks.ts b/src/common/crypto/jwks.ts index 11ec367..2d8e28f 100644 --- a/src/common/crypto/jwks.ts +++ b/src/common/crypto/jwks.ts @@ -1,11 +1,7 @@ -/** @module common/crypto */ - import * as jose from 'jose' import { z } from 'zod/v4' import { CryptoError } from './errors.js' -/** @typedef {jose.JWK} JWK */ - const subtleSignAlgo = { name: 'ECDSA', namedCurve: 'P-256' } const joseSignAlgo = { name: 'ES256' } @@ -105,7 +101,7 @@ export async function generateSigningJwkPair(): Promise { } /** - * @param payload the payload to sign + * @param payload - the payload to sign * @returns a properly configured jwt signer, with the payload provided */ export function generateSignableJwt(payload: jose.JWTPayload): jose.SignJWT { diff --git a/src/common/crypto/jwts.ts b/src/common/crypto/jwts.ts index 8db255d..4f08fa1 100644 --- a/src/common/crypto/jwts.ts +++ b/src/common/crypto/jwts.ts @@ -1,5 +1,3 @@ -/** @module common/crypto */ - import * as jose from 'jose' import { z } from 'zod/v4' import { JWTBadSignatureError } from '#common/crypto/errors' @@ -61,9 +59,9 @@ export const jwtPayload = (schema: z.ZodType): z.ZodType> /** - * @param jwt the (still encoded) token to verify - * @param pubkey the key with which to verify the token - * @param [options] the key with which to verify the token + * @param jwt - the (still encoded) token to verify + * @param pubkey - the key with which to verify the token + * @param options - the key with which to verify the token * @returns a verified payload * @throws if the signature is not valid */ @@ -85,7 +83,7 @@ export async function verifyJwtToken(jwt: string, pubkey: CryptoKey, options: Ve /** * generate a fingerprint for the given crypto key * - * @param key the key to fingerprint + * @param key - the key to fingerprint * @returns the sha256 fingerprint of the key */ export async function fingerprintKey(key: CryptoKey): Promise { diff --git a/src/common/errors.ts b/src/common/errors.ts index e3c8ced..f8d07e9 100644 --- a/src/common/errors.ts +++ b/src/common/errors.ts @@ -1,5 +1,3 @@ -/** @module common */ - import { prettifyError, ZodError } from 'zod/v4' const StatusCodes: Record = { diff --git a/src/common/protocol.ts b/src/common/protocol.ts index af2810d..0cc2339 100644 --- a/src/common/protocol.ts +++ b/src/common/protocol.ts @@ -1,5 +1,3 @@ -/** @module common/protocol */ - export * from './protocol/brands' export * from './protocol/messages' export * from './protocol/messages-preauth' @@ -11,7 +9,7 @@ import { z } from 'zod/v4' export const parseJson: z.ZodTransform = z.transform( (input, ctx) => { try { - return JSON.parse(input) + return JSON.parse(input) as unknown } catch { ctx.issues.push({ diff --git a/src/common/protocol/messages-preauth.ts b/src/common/protocol/messages-preauth.ts index 102dd4b..db3ed2b 100644 --- a/src/common/protocol/messages-preauth.ts +++ b/src/common/protocol/messages-preauth.ts @@ -1,5 +1,3 @@ -/** @module common/protocol */ - import { z } from 'zod/v4' import { jwkSchema } from '#common/crypto/jwks' diff --git a/src/common/schema/brand.ts b/src/common/schema/brand.ts index f2b4029..38b550c 100644 --- a/src/common/schema/brand.ts +++ b/src/common/schema/brand.ts @@ -1,5 +1,3 @@ -/** @module common/schema */ - import { nanoid } from 'nanoid' import { z } from 'zod/v4' @@ -37,7 +35,7 @@ export class Brand { return this.#schema.parse(input) as Branded } - /** @return a boolean if the string is valid */ + /** @returns a boolean if the string is valid */ validate(input: string): input is Branded { return input != null && typeof input === 'string' && this.#schema.safeParse(input).success } diff --git a/src/common/socket.ts b/src/common/socket.ts index 491bc88..5062c08 100644 --- a/src/common/socket.ts +++ b/src/common/socket.ts @@ -1,4 +1,4 @@ -/** @module common/socket */ +import WebSocket, { ErrorEvent, MessageEvent } from 'isomorphic-ws' import { combineSignals } from '#common/async/aborts' import { BlockingAtom } from '#common/async/blocking-atom' @@ -18,6 +18,7 @@ export function sendSocket(ws: WebSocket, data: unknown): void { * Given a websocket, wait and take a single message off and return it. * * @example + * ``` * const ws = new WebSocket("wss://example.com/stream") * const timeout = timeoutSignal(5000) * @@ -29,6 +30,7 @@ export function sendSocket(ws: WebSocket, data: unknown): void { * if (ws.readyState !== ws.CLOSED) * ws.close(); * } + * ``` */ export async function takeSocket(ws: WebSocket, signal?: AbortSignal): Promise { signal?.throwIfAborted() @@ -39,9 +41,9 @@ export async function takeSocket(ws: WebSocket, signal?: AbortSignal): Promise atom.set(m.data)) - const onError = breaker.tripThen(e => error.abort(e)) - const onClose = breaker.tripThen(() => error.abort('closed')) + const onMessage = breaker.tripThen((m: MessageEvent) => { atom.set(m.data); }) + const onError = breaker.tripThen((e: unknown) => { error.abort(e); }) + const onClose = breaker.tripThen(() => { error.abort('closed'); }) try { ws.addEventListener('message', onMessage) @@ -50,7 +52,8 @@ export async function takeSocket(ws: WebSocket, signal?: AbortSignal): Promise(ws: WebSocket, schema: z.ZodSchema, signal?: AbortSignal): Promise { +export async function takeSocketJson(ws: WebSocket, schema: z.ZodType, signal?: AbortSignal): Promise { const data = await takeSocket(ws, signal) return parseJson.pipe(schema).parseAsync(data) } @@ -101,6 +104,7 @@ type StreamYield = * Given a websocket, stream messages off the socket as an async generator. * * @example + * ```ts * const ws = new WebSocket("wss://example.com/stream") * const timeout = timeoutSignal(5000) * @@ -114,6 +118,7 @@ type StreamYield = * if (ws.readyState !== ws.CLOSED) * ws.close(); * } + * ``` */ export async function* streamSocket(ws: WebSocket, config_?: Partial) { const { signal, ...config } = { ...STREAM_CONFIG_DEFAULT, ...(config_ || {}) } @@ -123,7 +128,7 @@ export async function* streamSocket(ws: WebSocket, config_?: Partial(config.maxDepth) // if true, we're ignoring incoming messages until we drop the queue - let inBackoffMode = false + let inBackoffMode: boolean = false const backoffThresh = Math.floor(config.maxDepth * 0.9) // we don't want to keep processing after we've been closed @@ -145,8 +150,8 @@ export async function* streamSocket(ws: WebSocket, config_?: Partial { - queue.enqueue([error$, normalizeError(e)]) + const onError = breaker.tripThen((e: ErrorEvent) => { + queue.enqueue([error$, normalizeError(e.error)]) }) const onClose = breaker.tripThen(() => { @@ -190,7 +195,7 @@ export async function* streamSocket(ws: WebSocket, config_?: Partial): AsyncGenerator { +export async function* streamSocketJson(ws: WebSocket, config?: Partial): AsyncGenerator { for await (const message of streamSocket(ws, config)) { yield parseJson.parseAsync(message) } @@ -202,7 +207,7 @@ export async function* streamSocketJson(ws: WebSocket, config?: Partial( ws: WebSocket, - schema: z.ZodSchema, + schema: z.ZodType, config?: Partial, ): AsyncGenerator { const parser = parseJson.pipe(schema) diff --git a/src/common/strict-map.ts b/src/common/strict-map.ts index 281924b..a276177 100644 --- a/src/common/strict-map.ts +++ b/src/common/strict-map.ts @@ -1,15 +1,14 @@ -/** @module common */ - /** A map with methods to ensure key presence and safe update. */ export class StrictMap extends Map { /** * Get a value from the map, throwing if missing - * @throws {Error} if the key is not present in the map + * @throws Error if the key is not present in the map */ require(key: K): V { if (!this.has(key)) throw Error(`key is required but not in the map`) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const value = this.get(key)! return value } @@ -20,6 +19,7 @@ export class StrictMap extends Map { this.set(key, maker()) } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.get(key)! } diff --git a/src/common/types.ts b/src/common/types.ts index 332897a..38c3bc9 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,10 +1,7 @@ -/** @module common */ - import { NEVER } from 'zod/v4' -/** - * A callback function, with arbitrary arguments; use {Parameters} to extract them. - */ +/** A callback function, with arbitrary arguments; use `Parameters` to extract them. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type Callback = (...args: any[]) => void /** diff --git a/src/server/index.ts b/src/server/index.ts index 9d1dd33..959ca52 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,5 +1,6 @@ import express from 'express' import * as http from 'http' + import { WebSocketServer } from 'ws' import { apiRouter } from './routes-api/middleware' @@ -10,11 +11,14 @@ import { notFoundHandler } from './routes-error' /** * configures an http server which hosts the SPA and websocket endpoint * - * @param root the path to the root public/ directory + * @param root - the path to the root public/ directory * @returns a configured server */ -export function buildServer(root: string): http.Server { +export function buildServer(root: string): http.Server { const app = express() + + // not sure why this error is coming up + // eslint-disable-next-line @typescript-eslint/no-misused-promises const server = http.createServer(app) // API routes @@ -29,7 +33,11 @@ export function buildServer(root: string): http.Server { + socketHandler(ws) + .catch((e: unknown) => { console.error('uncaught error from websocket', e) }) + .finally(() => { console.log('socket handler complete') }) + }) return server } diff --git a/src/server/routes-socket/handler-preauth.ts b/src/server/routes-socket/handler-preauth.ts index bb1a4ce..2b4c553 100644 --- a/src/server/routes-socket/handler-preauth.ts +++ b/src/server/routes-socket/handler-preauth.ts @@ -1,3 +1,5 @@ +import WebSocket from 'isomorphic-ws' + import { combineSignals, timeoutSignal } from '#common/async/aborts' import { jwkImport } from '#common/crypto/jwks' import { jwtPayload, verifyJwtToken } from '#common/crypto/jwts' @@ -30,7 +32,7 @@ export async function preauthHandler(ws: WebSocket, signal?: AbortSignal): Promi realms.ensureRegisteredRealm(realmid, identid, registrantkey) } - return authenticatePreauth(realmid, identid, jwt.token) + return await authenticatePreauth(realmid, identid, jwt.token) } finally { timeout.cancel() diff --git a/src/server/routes-socket/handler-realm.ts b/src/server/routes-socket/handler-realm.ts index 28ddcbc..b3bda39 100644 --- a/src/server/routes-socket/handler-realm.ts +++ b/src/server/routes-socket/handler-realm.ts @@ -1,7 +1,8 @@ -import { normalizeProtocolError, ProtocolError } from '#common/errors' +import { WebSocket } from 'isomorphic-ws' -import * as protocol from '#common/protocol' +import { normalizeProtocolError, ProtocolError } from '#common/errors' import { sendSocket, streamSocket } from '#common/socket.js' +import * as protocol from '#common/protocol' import * as realm from '#server/routes-socket/state' /** @@ -29,7 +30,8 @@ export async function realmHandler(ws: WebSocket, auth: realm.AuthenticatedIdent continue default: - throw new ProtocolError(`unknown message type: ${msg}`, 400) + console.error('unknown message!', msg) + throw new ProtocolError(`unknown message type!`, 400) } } catch (exc) { @@ -82,13 +84,12 @@ function buildRealmError(error: ProtocolError): protocol.ErrorResponse { } /** - * @private - * @param {realm_types.AuthenticatedConnection} auth the current identity - * @param {unknown} payload the payload to send - * @param {protocol_types.IdentID[] | boolean} [recipients] - * when true, send to the whole realm, including self - * when false, send to the whole realm, excluding self - * when an array of recipients, send to those recipients explicitly + * @param auth - the current identity + * @param payload - the payload to send + * @param recipients - an optional list of recpipents + * when true, send to the whole realm, including self + * when false, send to the whole realm, excluding self + * when an array of recipients, send to those recipients explicitly */ function realmBroadcast(auth: realm.AuthenticatedIdentity, payload: unknown, recipients: protocol.IdentID[] | boolean = false) { const echo = recipients === true || Array.isArray(recipients) diff --git a/src/server/routes-socket/handler.ts b/src/server/routes-socket/handler.ts index 5706005..7460190 100644 --- a/src/server/routes-socket/handler.ts +++ b/src/server/routes-socket/handler.ts @@ -1,4 +1,5 @@ import { format } from 'node:util' +import WebSocket from 'isomorphic-ws' import { normalizeError, normalizeProtocolError } from '#common/errors' diff --git a/src/server/routes-socket/state.ts b/src/server/routes-socket/state.ts index a4c9bc6..06ef11b 100644 --- a/src/server/routes-socket/state.ts +++ b/src/server/routes-socket/state.ts @@ -1,3 +1,5 @@ +import WebSocket from 'isomorphic-ws' + import { IdentID, RealmID } from '#common/protocol.js' import { StrictMap } from '#common/strict-map' @@ -22,9 +24,9 @@ export const realmMap = new StrictMap() * as initial registrants in a newly created realm. If the realm already * exists, it's not changed. * - * @param realmid the realm id to ensure exists - * @param registrantid the identity id of the registrant - * @param registrantkey the public key of the registrant + * @param realmid - the realm id to ensure exists + * @param registrantid - the identity id of the registrant + * @param registrantkey - the public key of the registrant * @returns a registered realm, possibly newly created with the registrant */ export function ensureRegisteredRealm(realmid: RealmID, registrantid: IdentID, registrantkey: CryptoKey): Realm { diff --git a/src/server/routes-static.ts b/src/server/routes-static.ts index 5cebb43..6a9b19c 100644 --- a/src/server/routes-static.ts +++ b/src/server/routes-static.ts @@ -9,7 +9,7 @@ interface StaticOpts { /** * returns a configured static middleware * - * @param opts options for corfiguring the middleware + * @param opts - options for corfiguring the middleware * @returns a new middleware */ export function makeStaticMiddleware(opts: StaticOpts): express.RequestHandler { @@ -19,13 +19,13 @@ export function makeStaticMiddleware(opts: StaticOpts): express.RequestHandler { /** * returns the index file for any GET request for text/html it matches * - * @param opts options for configuring the middleware + * @param opts - options for configuring the middleware * @returns a new middleware */ export function makeSpaMiddleware(opts: StaticOpts): express.RequestHandler { return (req, res, next) => { if (req.method === 'GET' && req.accepts('text/html')) { - return res.sendFile(join(opts.root, opts.index)) + res.sendFile(join(opts.root, opts.index)); return; } next() // otherwise