From 819c089aa4462c1ec1d0f1d13b3c3cd0d8bb0b03 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 28 Nov 2024 15:04:01 +0000 Subject: [PATCH] Update minimum Node version to 22 (#990) * Update dependencies * Node 22 is now the new minimum version. * changelog. * Begin porting eslint to new config format. * Make linter happy. * Update reqwest to fix SSL issue? * Fix test types * quick check on ubuntu LTS 24.04 * Change cache key * update rust action * revert mocha due to esminess * Remove the only usage of pqueue * Use babel for TS transformations to get around ESM import bug. * Dependency bundle upgrade * Drop babel, not actually used. * lint * lint * update default config (mostly sections moving around) --- .eslintrc.js | 60 - .github/workflows/main.yml | 12 +- .gitignore | 4 + .node-version | 2 +- Cargo.lock | 306 +- Cargo.toml | 4 +- Dockerfile | 4 +- changelog.d/990.removal | 1 + config.sample.yml | 98 +- devenv.nix | 2 +- docs/setup.md | 2 +- eslint.config.mjs | 82 + jest.config.ts | 9 +- package.json | 60 +- spec/util/homerunner.ts | 4 +- src/AdminRoom.ts | 1 - src/AdminRoomCommandHandler.ts | 1 - src/Bridge.ts | 110 +- src/CommentProcessor.ts | 3 +- src/Connections/FigmaFileConnection.ts | 3 +- src/Connections/GithubIssue.ts | 1 - src/Connections/GithubProject.ts | 1 - src/Connections/GithubRepo.ts | 5 +- src/Connections/GithubUserSpace.ts | 1 - src/Gitlab/Types.ts | 1 - src/Gitlab/WebhookTypes.ts | 2 - src/MatrixEvent.ts | 6 +- src/Notifications/UserNotificationWatcher.ts | 2 +- src/NotificationsProcessor.ts | 6 - src/Webhooks.ts | 5 +- src/config/Config.ts | 19 +- src/config/Defaults.ts | 7 +- src/generic/types.ts | 2 - src/github/GithubInstance.ts | 19 +- src/github/Types.ts | 1 - src/jira/GrantChecker.ts | 2 +- src/jira/client/CloudClient.ts | 7 +- src/libRs.js | 9 +- tests/FeedReader.spec.ts | 7 + tests/FormatUtilTest.ts | 10 + tests/HookFilter.ts | 3 + tests/IntentUtilsTest.ts | 4 +- tests/MessageQueueTest.ts | 1 + tests/config/config.ts | 6 + tests/config/permissions.ts | 16 +- tests/connections/FeedTest.spec.ts | 7 +- tests/connections/GenericHookTest.ts | 17 +- tests/connections/GithubRepoTest.ts | 10 +- tests/connections/GitlabRepoTest.ts | 12 + tests/github/AdminCommands.ts | 3 + tests/grants/GrantChecker.spec.ts | 3 + tests/jira/Utils.ts | 4 +- tests/tokens/tokenencryption.spec.ts | 5 + tsconfig.json | 7 +- tsconfig.spec.json | 17 + vite.config.mjs | 7 + web/App.tsx | 5 - web/BridgeAPI.ts | 3 - web/components/AdminSettings.tsx | 22 +- web/components/GitHubState.tsx | 8 +- web/components/elements/ButtonSet.tsx | 2 +- web/components/elements/EventHookCheckbox.tsx | 2 +- .../roomConfig/GithubRepoConfig.tsx | 2 +- .../roomConfig/GitlabRepoConfig.tsx | 2 +- .../roomConfig/JiraProjectConfig.tsx | 2 +- yarn.lock | 5804 ++++++++++++----- 66 files changed, 4787 insertions(+), 2068 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 changelog.d/990.removal create mode 100644 eslint.config.mjs create mode 100644 tsconfig.spec.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index c4fad7c14..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint' - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - ], - // eslint-config-preact needs a Jest version to be happy, even if Jest isn't used. - // See https://github.com/preactjs/eslint-config-preact/issues/19#issuecomment-997924892 - settings: { - jest: { "version": 27 }, - }, - rules: { - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "warn", - "camelcase": ["error", { "properties": "never", "ignoreDestructuring": true }], - "no-console": "error" - }, - env: { - node: true, - es6: true, - }, - overrides: [ - { - files: ["test/**/*.ts"], - parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint', - 'mocha', - ], - }, - { - files: ["web/**/*.ts", "web/**/*.tsx"], - parser: '@typescript-eslint/parser', - env: { - browser: true, - node: false, - }, - extends: [ - 'plugin:@typescript-eslint/recommended', - 'preact', - ], - plugins: [ - '@typescript-eslint', - ], - rules: { - "no-console": "off", - "no-unused-vars": "off", - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": ["error"], - "no-useless-constructor": "off", - "@typescript-eslint/no-useless-constructor": ["error"], - }, - } - ] -}; \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b8528415..fadfacb89 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,10 +34,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - profile: minimal components: rustfmt - run: cargo fmt --all -- --check - run: cargo clippy -- -Dwarnings @@ -66,23 +65,22 @@ jobs: test: # Test on LTS-1 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - node_version: [20, 21] + node_version: [22, 23] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node_version }} - - uses: actions-rs/toolchain@v1 + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - profile: minimal - uses: Swatinem/rust-cache@v2 with: - shared-key: rust-cache + shared-key: ubuntu-2204-rust-cache - run: yarn - run: yarn test:cover diff --git a/.gitignore b/.gitignore index 64e3bfedd..0db7bb407 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ public/ # Generated during build /src/libRs.d.ts +# Generated during test run +/spec-lib +/hookshot-int-* + book *.cer *.pem diff --git a/.node-version b/.node-version index 209e3ef4b..2bd5a0a98 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20 +22 diff --git a/Cargo.lock b/Cargo.lock index 8f7b03712..1f735a7f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -51,6 +51,12 @@ dependencies = [ "quick-xml", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -78,6 +84,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -125,9 +137,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" @@ -456,15 +468,15 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.26" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -507,9 +519,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -518,12 +530,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", "pin-project-lite", ] @@ -533,47 +557,76 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "0.14.28" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -648,7 +701,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -717,7 +770,7 @@ dependencies = [ [[package]] name = "matrix-hookshot" -version = "5.3.0" +version = "5.4.1" dependencies = [ "atom_syndication", "base64ct", @@ -1265,11 +1318,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.24" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -1277,8 +1330,11 @@ dependencies = [ "h2", "http", "http-body", + "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1300,7 +1356,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -1312,6 +1368,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsa" version = "0.9.6" @@ -1366,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bca4c33c50e47b4cdceeac71bdef0c04153b0e29aa992d9030ec14a62323e85" dependencies = [ "as_variant", - "base64", + "base64 0.21.7", "bytes", "form_urlencoded", "indexmap", @@ -1466,13 +1537,43 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1659,6 +1760,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -1731,26 +1838,29 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1840,6 +1950,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -1964,6 +2085,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -2090,6 +2217,36 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "495ec47bf3c1345005f40724f0269362c8556cbc43aed0526ed44cae1d35fceb" +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2105,7 +2262,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2125,17 +2282,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2146,9 +2304,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2158,9 +2316,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2170,9 +2328,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2182,9 +2346,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2194,9 +2358,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2206,9 +2370,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2218,9 +2382,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -2231,16 +2395,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 344dfefa1..4ef9d6cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "matrix-hookshot" -version = "5.3.0" +version = "5.4.1" edition = "2021" [lib] @@ -20,7 +20,7 @@ hex = "0.4" rss = "2.0" atom_syndication = "0.12" ruma = { version = "0.9", features = ["events", "html"] } -reqwest = "0.11" +reqwest = "0.12.9" rand = "0.8.5" rsa = { version = "0.9.6", features = ["sha2"] } base64ct = { version = "1.6.0", features = ["alloc"] } diff --git a/Dockerfile b/Dockerfile index 598d5075a..ed423069c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Stage 0: Build the thing # Need debian based image to build the native rust module # as musl doesn't support cdylib -FROM node:20.12-slim AS builder +FROM node:22-slim AS builder # Needed in order to build rust FFI bindings. RUN apt-get update && apt-get install -y build-essential cmake curl pkg-config pkg-config libssl-dev @@ -29,7 +29,7 @@ RUN yarn build # Stage 1: The actual container -FROM node:20.12-slim +FROM node:22-slim WORKDIR /bin/matrix-hookshot diff --git a/changelog.d/990.removal b/changelog.d/990.removal new file mode 100644 index 000000000..9b90f6d40 --- /dev/null +++ b/changelog.d/990.removal @@ -0,0 +1 @@ +Drop support for Node 20 and start supporting Node 22, 23. \ No newline at end of file diff --git a/config.sample.yml b/config.sample.yml index 9a54339b1..c2fafe5c2 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -1,11 +1,5 @@ # This is an example configuration file -logging: - # Logging settings. You can have a severity debug,info,warn,error - level: info - colorize: true - json: false - timestampFormat: HH:mm:ss:SSS bridge: # Basic homeserver configuration domain: example.com @@ -13,6 +7,12 @@ bridge: mediaUrl: https://example.com port: 9993 bindAddress: 127.0.0.1 +logging: + # Logging settings. You can have a severity debug,info,warn,error + level: info + colorize: true + json: false + timestampFormat: HH:mm:ss:SSS passFile: # A passkey used to encrypt tokens stored inside the bridge. # Run openssl genpkey -out passkey.pem -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:4096 to generate @@ -37,6 +37,25 @@ listeners: resources: - widgets +#cache: +# # (Optional) Cache options for large scale deployments. +# # For encryption to work, this must be configured. +# redisUri: redis://localhost:6379 + +#encryption: +# # (Optional) Configuration for encryption support in the bridge. +# # If omitted, encryption support will be disabled. +# storagePath: +# # Path to the directory used to store encryption files. These files must be persist between restarts of the service. +# ./cryptostore + +#permissions: +# # (Optional) Permissions for using the bridge. See docs/setup.md#permissions for help +# - actor: example.com +# services: +# - service: "*" +# level: admin + #github: # # (Optional) Configure this to enable GitHub support # auth: @@ -75,15 +94,6 @@ listeners: # # (Optional) Aggregate comments by waiting this many miliseconds before posting them to Matrix. Defaults to 5000 (5 seconds) # 5000 -#figma: -# # (Optional) Configure this to enable Figma support -# publicUrl: https://example.com/hookshot/ -# instances: -# your-instance: -# teamId: your-team-id -# accessToken: your-personal-access-token -# passcode: your-webhook-passcode - #jira: # # (Optional) Configure this to enable Jira support. Only specify `url` if you are using a On Premise install (i.e. not atlassian.com) # webhook: @@ -101,25 +111,30 @@ listeners: # enabled: false # outbound: false -# enableHttpGet: false -# sendExpiryNotice: false -# requireExpiryTime: false # urlPrefix: https://example.com/webhook/ # userIdPrefix: _webhooks_ # allowJsTransformationFunctions: false # waitForComplete: false +# enableHttpGet: false +# sendExpiryNotice: false +# requireExpiryTime: false # maxExpiryTime: 30d +#figma: +# # (Optional) Configure this to enable Figma support +# publicUrl: https://example.com/hookshot/ +# instances: +# your-instance: +# teamId: your-team-id +# accessToken: your-personal-access-token +# passcode: your-webhook-passcode + #feeds: # # (Optional) Configure this to enable RSS/Atom feed support # enabled: false -# pollConcurrency: 4 # pollIntervalSeconds: 600 # pollTimeoutSeconds: 30 - -#provisioning: -# # (Optional) Provisioning API for integration managers -# secret: "!secretToken" +# pollConcurrency: 4 #bot: # # (Optional) Define profile information for the bot user @@ -134,25 +149,12 @@ listeners: # prefix: "!feeds" # service: feeds -#metrics: -# # (Optional) Prometheus metrics support -# enabled: true - -#cache: -# # (Optional) Cache options for large scale deployments. -# # For encryption to work, this must be configured. -# redisUri: redis://localhost:6379 - -#encryption: -# # (Optional) Configuration for encryption support in the bridge. -# # If omitted, encryption support will be disabled. -# storagePath: -# # Path to the directory used to store encryption files. These files must be persist between restarts of the service. -# ./cryptostore - #widgets: # # (Optional) EXPERIMENTAL support for complimentary widgets # addToAdminRooms: false +# publicUrl: https://example.com/widgetapi/v1/static/ +# roomSetupWidget: +# addOnInvite: false # disallowedIpRanges: # - 127.0.0.0/8 # - 10.0.0.0/8 @@ -173,23 +175,21 @@ listeners: # - 2001:db8::/32 # - ff00::/8 # - fec0::/10 -# roomSetupWidget: -# addOnInvite: false -# publicUrl: https://example.com/widgetapi/v1/static/ # branding: # widgetTitle: Hookshot Configuration +#provisioning: +# # (Optional) Provisioning API for integration managers +# secret: "!secretToken" + +#metrics: +# # (Optional) Prometheus metrics support +# enabled: true + #sentry: # # (Optional) Configure Sentry error reporting # dsn: https://examplePublicKey@o0.ingest.sentry.io/0 # environment: production -#permissions: -# # (Optional) Permissions for using the bridge. See docs/setup.md#permissions for help -# - actor: example.com -# services: -# - service: "*" -# level: admin - diff --git a/devenv.nix b/devenv.nix index ad3d1cf4a..ca0089cef 100644 --- a/devenv.nix +++ b/devenv.nix @@ -18,7 +18,7 @@ in languages.typescript.enable = true; languages.javascript.yarn.enable = true; languages.javascript.enable = true; - languages.javascript.package = pkgs-upstream.nodejs_20; + languages.javascript.package = pkgs-upstream.nodejs_22; languages.rust.enable = true; languages.rust.channel = "stable"; } diff --git a/docs/setup.md b/docs/setup.md index b8c66e494..15a0953cd 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -13,7 +13,7 @@ Hookshot requires the homeserver to be configured with its appservice registrati ## Local installation -This bridge requires at least Node 16 and Rust installed. +This bridge requires at least Node 22 and Rust installed. To install Node.JS, [nvm](https://github.com/nvm-sh/nvm) is a good option. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..c5d20da64 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,82 @@ +import mocha from "eslint-plugin-mocha"; +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import react from "eslint-plugin-react"; +import chai from "eslint-plugin-chai-expect"; + +export default [ + { + ignores: ["lib/**/*", "spec-lib/**/*", "contrib/**/*"], + }, + ...tseslint.config( + { + files: ["src/**/*.ts", "scripts/*.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + ], + rules: { + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn", + camelcase: ["error", { + properties: "never", + ignoreDestructuring: true, + }], + "no-console": "error", + }, + }, + ), + ...tseslint.config( + { + files: ["tests/**/*.ts", "tests/**/*.spec.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + mocha.configs.flat.recommended, + chai.configs["recommended-flat"], + ], + rules: { + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn", + // Chai assertions don't call functions + "@typescript-eslint/no-unused-expressions": "off", + camelcase: ["error", { + properties: "never", + ignoreDestructuring: true, + }], + "no-console": "error", + // Needs a refactor + "mocha/no-mocha-arrows": "off", + }, + }, + ), + ...tseslint.config( + { + settings: { + react: { + pragma: "Preact", + version: "17", + } + }, + files: ["web/**/*.ts", "web/**/*.tsx"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + react.configs.flat.recommended, + react.configs.flat['jsx-runtime'], + ], + rules: { + "no-console": "off", + "no-unused-vars": "off", + "no-useless-constructor": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-useless-constructor": "error", + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + }, + }, + ), +]; \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index 371b39f86..c6f38e155 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -7,15 +7,8 @@ import type {Config} from 'jest'; const config: Config = { // The root directory that Jest should scan for tests and modules within - rootDir: "spec", + rootDir: "spec-lib", testTimeout: 60000, - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - }, - ], - }, }; export default config; diff --git a/package.json b/package.json index 530f45113..539cf4ba2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "name": "matrix-hookshot-rs" }, "engines": { - "node": ">=20" + "node": ">=22" }, "scripts": { "build:web": "vite build", @@ -31,11 +31,11 @@ "start:webhooks": "node --require source-map-support/register lib/App/GithubWebhookApp.js", "start:matrixsender": "node --require source-map-support/register lib/App/MatrixSenderApp.js", "start:resetcrypto": "node --require source-map-support/register lib/App/ResetCryptoStore.js", - "test": "mocha -r ts-node/register tests/init.ts tests/*.ts tests/**/*.ts", - "test:e2e": "yarn node --experimental-vm-modules $(yarn bin jest)", + "test": "mocha -r ts-node/register tests/init.ts 'tests/*.ts' 'tests/**/*.ts'", + "test:e2e": "tsc --p tsconfig.spec.json && cp ./lib/libRs.js ./lib/matrix-hookshot-rs.node ./spec-lib/src && yarn node --experimental-vm-modules $(yarn bin jest)", "test:cover": "nyc --reporter=lcov --reporter=text yarn test", "lint": "yarn run lint:js && yarn run lint:rs", - "lint:js": "eslint -c .eslintrc.js 'src/**/*.ts' 'tests/**/*.ts' 'web/**/*.ts' 'web/**/*.tsx'", + "lint:js": "eslint", "lint:rs": "cargo fmt --all -- --check && cargo clippy -- -Dwarnings", "lint:rs:apply": "cargo clippy --fix && cargo fmt --all", "generate-default-config": "ts-node src/config/Defaults.ts --config > config.sample.yml", @@ -63,16 +63,15 @@ "jira-client": "^8.2.2", "markdown-it": "^14.0.0", "matrix-appservice-bridge": "^9.0.1", - "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@v0.7.1-element.6", - "matrix-widget-api": "^1.6.0", + "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@0.7.1-element.6", + "matrix-widget-api": "^1.10.0", "micromatch": "^4.0.8", - "mime": "^4.0.1", + "mime": "^4.0.4", "node-emoji": "^2.1.3", - "p-queue": "^6.6.2", "parse-duration": "^1.1.0", "preact-render-to-string": "^6.3.1", "prom-client": "^15.1.0", - "quickjs-emscripten": "^0.26.0", + "quickjs-emscripten": "^0.31.0", "reflect-metadata": "^0.2.1", "source-map-support": "^0.5.21", "string-argv": "^0.3.1", @@ -84,11 +83,14 @@ }, "devDependencies": { "@codemirror/lang-javascript": "^6.0.2", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.15.0", "@fontsource/inter": "^5.1.0", "@napi-rs/cli": "^2.13.2", - "@preact/preset-vite": "^2.2.0", + "@octokit/webhooks-types": "^7.6.1", + "@preact/preset-vite": "^2.9.1", "@rollup/plugin-alias": "^5.1.0", - "@tsconfig/node18": "^18.2.2", + "@tsconfig/node22": "^22", "@types/ajv": "^1.0.0", "@types/busboy": "^1.5.4", "@types/chai": "^4.2.22", @@ -100,28 +102,28 @@ "@types/micromatch": "^4.0.1", "@types/mime": "^3.0.4", "@types/mocha": "^10.0.6", - "@types/node": "20.10.6", + "@types/node": "^22", "@types/xml2js": "^0.4.11", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", "@uiw/react-codemirror": "^4.12.3", + "babel-cli": "^6.26.0", + "babel-jest": "^29.7.0", "busboy": "^1.6.0", - "chai": "^4.3.4", - "eslint": "^8.49.0", - "eslint-config-preact": "^1.3.0", - "eslint-plugin-mocha": "^10.1.0", - "homerunner-client": "^1.0.0", + "chai": "^4", + "eslint": "^9.15.0", + "eslint-plugin-chai-expect": "^3.1.0", + "eslint-plugin-mocha": "^10.5.0", + "eslint-plugin-react": "^7.37.2", + "homerunner-client": "^1.1.0", "jest": "^29.7.0", - "mini.css": "^3.0.1", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "preact": "^10.5.15", - "rimraf": "^5.0.5", - "sass": "^1.69.6", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.1", - "typescript": "^5.3.3", - "vite": "^5.1.8" + "mocha": "^10.8.2", + "nyc": "^17.1.0", + "preact": "^10.24.3", + "rimraf": "6.0.1", + "sass": "^1.81.0", + "ts-node": "10.9.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vite": "^5.4.11" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/spec/util/homerunner.ts b/spec/util/homerunner.ts index 16e4a1750..ac7ada3bb 100644 --- a/spec/util/homerunner.ts +++ b/spec/util/homerunner.ts @@ -61,10 +61,10 @@ export async function createHS(localparts: string[] = [], workerId: number, cryp URL: `http://${COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT}:${appPort}`, SenderLocalpart: 'hookshot', RateLimited: false, - ...{ASToken: asToken, + ASToken: asToken, HSToken: hsToken, SendEphemeral: true, - EnableEncryption: true}, + EnableEncryption: true, }] }], } diff --git a/src/AdminRoom.ts b/src/AdminRoom.ts index 691551635..9c9b85553 100644 --- a/src/AdminRoom.ts +++ b/src/AdminRoom.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import "reflect-metadata"; import { AdminAccountData, AdminRoomCommandHandler, Category } from "./AdminRoomCommandHandler"; import { botCommand, compileBotCommands, handleCommand, BotCommands, HelpFunction } from "./BotCommands"; diff --git a/src/AdminRoomCommandHandler.ts b/src/AdminRoomCommandHandler.ts index 6840729ef..e11a081f1 100644 --- a/src/AdminRoomCommandHandler.ts +++ b/src/AdminRoomCommandHandler.ts @@ -13,7 +13,6 @@ export enum Category { export interface AdminAccountData { - // eslint-disable-next-line camelcase admin_user: string; github?: { notifications?: { diff --git a/src/Bridge.ts b/src/Bridge.ts index 099e05fe0..36bd695e7 100644 --- a/src/Bridge.ts +++ b/src/Bridge.ts @@ -39,7 +39,6 @@ import { JiraOAuthRequestCloud, JiraOAuthRequestOnPrem, JiraOAuthRequestResult } import { GenericWebhookEvent, GenericWebhookEventResult } from "./generic/types"; import { SetupWidget } from "./Widgets/SetupWidget"; import { FeedEntry, FeedError, FeedReader, FeedSuccess } from "./feeds/FeedReader"; -import PQueue from "p-queue"; import * as Sentry from '@sentry/node'; import { HoundConnection, HoundPayload } from "./Connections/HoundConnection"; import { HoundReader } from "./hound/reader"; @@ -98,7 +97,7 @@ export class Bridge { while (!reached) { try { // Make a request to determine if we can reach the homeserver - await this.as.botIntent.getJoinedRooms(); + await this.as.botIntent.underlyingClient.getWhoAmI(); reached = true; } catch (e) { log.warn("Failed to connect to homeserver, retrying in 5s", e); @@ -692,67 +691,70 @@ export class Bridge { (c, data) => c.handleNewActivity(data.activity) ); - const queue = new PQueue({ - concurrency: 2, - }); - // Set up already joined rooms - await queue.addAll(this.botUsersManager.joinedRooms.map((roomId) => async () => { - log.debug("Fetching state for " + roomId); - - try { - await connManager.createConnectionsForRoomId(roomId, false); - } catch (ex) { - log.error(`Unable to create connection for ${roomId}`, ex); - return; - } + const allRooms = this.botUsersManager.joinedRooms; - const botUser = this.botUsersManager.getBotUserInRoom(roomId); - if (!botUser) { - log.error(`Failed to find a bot in room '${roomId}' when setting up admin room`); - return; - } + const processRooms = async () => { + for (let roomId = allRooms.pop(); roomId !== undefined; roomId = allRooms.pop()) { + log.debug("Fetching state for " + roomId); - // TODO: Refactor this to be a connection - try { - let accountData = await botUser.intent.underlyingClient.getSafeRoomAccountData( - BRIDGE_ROOM_TYPE, roomId, - ); - if (!accountData) { - accountData = await botUser.intent.underlyingClient.getSafeRoomAccountData( - LEGACY_BRIDGE_ROOM_TYPE, roomId, - ); - if (!accountData) { - log.debug(`Room ${roomId} has no connections and is not an admin room`); - return; - } else { - // Upgrade the room - await botUser.intent.underlyingClient.setRoomAccountData(BRIDGE_ROOM_TYPE, roomId, accountData); - } + try { + await connManager.createConnectionsForRoomId(roomId, false); + } catch (ex) { + log.error(`Unable to create connection for ${roomId}`, ex); + continue; } - - let notifContent; + + const botUser = this.botUsersManager.getBotUserInRoom(roomId); + if (!botUser) { + log.error(`Failed to find a bot in room '${roomId}' when setting up admin room`); + continue; + } + + // TODO: Refactor this to be a connection try { - notifContent = await botUser.intent.underlyingClient.getRoomStateEvent( - roomId, NotifFilter.StateType, "", + let accountData = await botUser.intent.underlyingClient.getSafeRoomAccountData( + BRIDGE_ROOM_TYPE, roomId, ); - } catch (ex) { + if (!accountData) { + accountData = await botUser.intent.underlyingClient.getSafeRoomAccountData( + LEGACY_BRIDGE_ROOM_TYPE, roomId, + ); + if (!accountData) { + log.debug(`Room ${roomId} has no connections and is not an admin room`); + continue; + } else { + // Upgrade the room + await botUser.intent.underlyingClient.setRoomAccountData(BRIDGE_ROOM_TYPE, roomId, accountData); + } + } + + let notifContent; try { notifContent = await botUser.intent.underlyingClient.getRoomStateEvent( - roomId, NotifFilter.LegacyStateType, "", + roomId, NotifFilter.StateType, "", ); + } catch (ex) { + try { + notifContent = await botUser.intent.underlyingClient.getRoomStateEvent( + roomId, NotifFilter.LegacyStateType, "", + ); + } + catch (ex) { + // No state yet + } } - catch (ex) { - // No state yet - } - } - const adminRoom = await this.setUpAdminRoom(botUser.intent, roomId, accountData, notifContent || NotifFilter.getDefaultContent()); - // Call this on startup to set the state - await this.onAdminRoomSettingsChanged(adminRoom, accountData, { admin_user: accountData.admin_user }); - log.debug(`Room ${roomId} is connected to: ${adminRoom.toString()}`); - } catch (ex) { - log.error(`Failed to set up admin room ${roomId}:`, ex); + const adminRoom = await this.setUpAdminRoom(botUser.intent, roomId, accountData, notifContent || NotifFilter.getDefaultContent()); + // Call this on startup to set the state + await this.onAdminRoomSettingsChanged(adminRoom, accountData, { admin_user: accountData.admin_user }); + log.debug(`Room ${roomId} is connected to: ${adminRoom.toString()}`); + } catch (ex) { + log.error(`Failed to set up admin room ${roomId}:`, ex); + } } - })); + } + + // Concurrency of two. + const roomQueue = await Promise.all([processRooms(), processRooms()]) // Handle spaces for (const discussion of connManager.getAllConnectionsOfType(GitHubDiscussionSpace)) { @@ -785,7 +787,7 @@ export class Bridge { if (this.config.metrics?.enabled) { this.listener.bindResource('metrics', Metrics.expressRouter); } - await queue.onIdle(); + await roomQueue; log.info(`All connections loaded`); // Load feeds after connections, to limit the chances of us double diff --git a/src/CommentProcessor.ts b/src/CommentProcessor.ts index 7f1bf2f7c..b2d0fb6e8 100644 --- a/src/CommentProcessor.ts +++ b/src/CommentProcessor.ts @@ -16,7 +16,6 @@ const log = new Logger("CommentProcessor"); const mime = import('mime'); interface IMatrixCommentEvent extends MatrixMessageContent { - // eslint-disable-next-line camelcase external_url: string; "uk.half-shot.matrix-hookshot.github.comment": { id: number; @@ -158,7 +157,7 @@ export class CommentProcessor { body = body.replace(rawUrl, url); } catch (ex) { - log.warn("Failed to upload file"); + log.warn("Failed to upload file", ex); } } return body; diff --git a/src/Connections/FigmaFileConnection.ts b/src/Connections/FigmaFileConnection.ts index 1a662dede..918fc7360 100644 --- a/src/Connections/FigmaFileConnection.ts +++ b/src/Connections/FigmaFileConnection.ts @@ -65,7 +65,7 @@ export class FigmaFileConnection extends BaseConnection implements IConnection { } } - private readonly grantChecker: GrantChecker<{fileId: string, instanceName: string}> = new ConfigGrantChecker("figma", this.as, this.config); + private readonly grantChecker: GrantChecker<{fileId: string, instanceName: string}>; constructor( roomId: string, @@ -76,6 +76,7 @@ export class FigmaFileConnection extends BaseConnection implements IConnection { private readonly intent: Intent, private readonly storage: IBridgeStorageProvider) { super(roomId, stateKey, FigmaFileConnection.CanonicalEventType) + this.grantChecker = new ConfigGrantChecker("figma", this.as, this.config); } public isInterestedInStateEvent() { diff --git a/src/Connections/GithubIssue.ts b/src/Connections/GithubIssue.ts index 4c0a728da..f5656e49a 100644 --- a/src/Connections/GithubIssue.ts +++ b/src/Connections/GithubIssue.ts @@ -20,7 +20,6 @@ export interface GitHubIssueConnectionState { repo: string; state: string; issues: string[]; - // eslint-disable-next-line camelcase comments_processed: number; } diff --git a/src/Connections/GithubProject.ts b/src/Connections/GithubProject.ts index f9e99fe25..6d21db51b 100644 --- a/src/Connections/GithubProject.ts +++ b/src/Connections/GithubProject.ts @@ -7,7 +7,6 @@ import { ConfigGrantChecker, GrantChecker } from "../grants/GrantCheck"; import { BridgeConfig } from "../config/Config"; export interface GitHubProjectConnectionState { - // eslint-disable-next-line camelcase project_id: number; state: "open"|"closed"; } diff --git a/src/Connections/GithubRepo.ts b/src/Connections/GithubRepo.ts index dc4e452d8..bdd863b77 100644 --- a/src/Connections/GithubRepo.ts +++ b/src/Connections/GithubRepo.ts @@ -549,7 +549,7 @@ export class GitHubRepoConnection extends CommandConnection, timeout: NodeJS.Timeout}>(); - private readonly grantChecker = new GitHubGrantChecker(this.as, this.tokenStore); + private readonly grantChecker; constructor( roomId: string, @@ -576,6 +576,7 @@ export class GitHubRepoConnection extends CommandConnection { content: T; event_id: string; @@ -8,10 +7,7 @@ export interface MatrixEvent { type: string; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface MatrixEventContent { - -} +type MatrixEventContent = object; export interface MatrixMemberContent extends MatrixEventContent { avatar_url: string|null; diff --git a/src/Notifications/UserNotificationWatcher.ts b/src/Notifications/UserNotificationWatcher.ts index 4531c7b96..fbd47315e 100644 --- a/src/Notifications/UserNotificationWatcher.ts +++ b/src/Notifications/UserNotificationWatcher.ts @@ -48,7 +48,7 @@ export class UserNotificationWatcher { [...this.userIntervals.values()].forEach((v) => { v.stop(); }); - this.queue.stop ? this.queue.stop() : undefined; + this.queue.stop?.(); } public removeUser(userId: string, type: "github"|"gitlab", instanceUrl?: string) { diff --git a/src/NotificationsProcessor.ts b/src/NotificationsProcessor.ts index 8b4ea8013..640e85cb8 100644 --- a/src/NotificationsProcessor.ts +++ b/src/NotificationsProcessor.ts @@ -10,7 +10,6 @@ import { GitHubUserNotification } from "./github/Types"; import { components } from "@octokit/openapi-types/types"; import { NotifFilter } from "./NotificationFilters"; - const log = new Logger("NotificationProcessor"); const md = new markdown(); @@ -21,18 +20,15 @@ export interface IssueDiff { merged: boolean; mergedBy: null|{ login: string; - // eslint-disable-next-line camelcase html_url: string; }; user: { login: string; - // eslint-disable-next-line camelcase html_url: string; }; } export interface CachedReviewData { - // eslint-disable-next-line camelcase requested_reviewers: PullsListRequestedReviewersResponseData; reviews: PullsListReviewsResponseData; } @@ -40,8 +36,6 @@ export interface CachedReviewData { type PROrIssue = IssuesGetResponseData|PullGetResponseData; export class NotificationProcessor { - - // eslint-disable-next-line camelcase private static formatUser(user: {login: string, html_url: string}) { return `**[${user.login}](${user.html_url})**`; } diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 4597f7ca0..863d5ed12 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -8,7 +8,7 @@ import { ApiError, ErrCode, Logger } from "matrix-appservice-bridge"; import qs from "querystring"; import axios from "axios"; import { IGitLabWebhookEvent, IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookReleaseEvent } from "./Gitlab/WebhookTypes"; -import { EmitterWebhookEvent, EmitterWebhookEventName, Webhooks as OctokitWebhooks } from "@octokit/webhooks" +import { EmitterWebhookEvent, Webhooks as OctokitWebhooks } from "@octokit/webhooks" import { IJiraWebhookEvent } from "./jira/WebhookTypes"; import { JiraWebhooksRouter } from "./jira/Router"; import { OAuthRequest } from "./WebhookTypes"; @@ -18,6 +18,7 @@ import { FigmaWebhooksRouter } from "./figma/router"; import { GenericWebhooksRouter } from "./generic/Router"; import { GithubInstance } from "./github/GithubInstance"; import QuickLRU from "@alloc/quick-lru"; +import type { WebhookEventName } from "@octokit/webhooks-types"; const log = new Logger("Webhooks"); @@ -178,7 +179,7 @@ export class Webhooks extends EventEmitter { } this.ghWebhooks.verifyAndReceive({ id: githubGuid as string, - name: req.headers["x-github-event"] as EmitterWebhookEventName, + name: req.headers["x-github-event"] as WebhookEventName, payload: githubData.payload, signature: githubData.signature, }).catch((err) => { diff --git a/src/config/Config.ts b/src/config/Config.ts index 8c2061b8f..c2aa5abdc 100644 --- a/src/config/Config.ts +++ b/src/config/Config.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import YAML from "yaml"; import { promises as fs } from "fs"; import { IAppserviceRegistration, LogLevel, MatrixClient } from "matrix-bot-sdk"; @@ -49,11 +51,8 @@ interface BridgeConfigGitHubYAML { secret: string; }; oauth?: { - // eslint-disable-next-line camelcase client_id: string; - // eslint-disable-next-line camelcase client_secret: string; - // eslint-disable-next-line camelcase redirect_uri: string; }; defaultOptions?: GitHubRepoConnectionOptions; @@ -72,11 +71,8 @@ export class BridgeConfigGitHub { }; @configKey("Settings for allowing users to sign in via OAuth.", true) readonly oauth?: { - // eslint-disable-next-line camelcase client_id: string; - // eslint-disable-next-line camelcase client_secret: string; - // eslint-disable-next-line camelcase redirect_uri: string; }; @configKey("Default options for GitHub connections.", true) @@ -109,18 +105,14 @@ export class BridgeConfigGitHub { } export interface BridgeConfigJiraCloudOAuth { - // eslint-disable-next-line camelcase client_id: string; - // eslint-disable-next-line camelcase client_secret: string; - // eslint-disable-next-line camelcase redirect_uri: string; } export interface BridgeConfigJiraOnPremOAuth { consumerKey: string; privateKey: string; - // eslint-disable-next-line camelcase redirect_uri: string; } @@ -184,11 +176,6 @@ export class BridgeConfigJira implements BridgeConfigJiraYAML { export interface GitLabInstance { url: string; - // oauth: { - // client_id: string; - // client_secret: string; - // redirect_uri: string; - // }; } export interface BridgeConfigGitLabYAML { @@ -744,11 +731,9 @@ export async function parseRegistrationFile(filename: string) { if (require.main === module) { Logger.configure({console: "info"}); BridgeConfig.parseConfig(process.argv[2] || "config.yml", process.env).then(() => { - // eslint-disable-next-line no-console console.log('Config successfully validated.'); process.exit(0); }).catch(ex => { - // eslint-disable-next-line no-console console.error('Error in config:', ex); process.exit(1); }); diff --git a/src/config/Defaults.ts b/src/config/Defaults.ts index 8a110c36e..fa511f953 100644 --- a/src/config/Defaults.ts +++ b/src/config/Defaults.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { BridgeConfig, BridgeConfigRoot } from "./Config"; import { getConfigKeyMetadata, keyIsHidden } from "./Decorators"; import { Node, YAMLSeq, default as YAML } from "yaml"; @@ -246,19 +248,16 @@ async function renderRegistrationFile(configPath?: string) { rooms: [], }, }; - // eslint-disable-next-line no-console + console.log(YAML.stringify(obj)); } - // Can be called directly if (require.main === module) { if (process.argv[2] === '--config') { - // eslint-disable-next-line no-console console.log(renderDefaultConfig()); } else if (process.argv[2] === '--registration') { renderRegistrationFile(process.argv[3]).catch(ex => { - // eslint-disable-next-line no-console console.error(ex); process.exit(1); }); diff --git a/src/generic/types.ts b/src/generic/types.ts index 611c490fe..4011936de 100644 --- a/src/generic/types.ts +++ b/src/generic/types.ts @@ -5,10 +5,8 @@ export interface GenericWebhookEvent { hookId: string; } - export type GenericWebhookEventResult = GenericWebhookEventResultSuccess | GenericWebhookEventResultFailure; - export interface GenericWebhookEventResultSuccess { successful: true|null; response?: WebhookResponse, diff --git a/src/github/GithubInstance.ts b/src/github/GithubInstance.ts index e86c4322f..4a532750e 100644 --- a/src/github/GithubInstance.ts +++ b/src/github/GithubInstance.ts @@ -46,14 +46,14 @@ interface OAuthUrlParameters { export class GithubInstance { private internalOctokit!: Octokit; private readonly installationsCache = new Map(); - private internalAppSlug?: string; + private internalAppUrl?: string; constructor (private readonly appId: number|string, private readonly privateKey: string, private readonly baseUrl: URL) { this.appId = parseInt(appId as string, 10); } - public get appSlug() { - return this.internalAppSlug; + public get appUrl() { + return this.internalAppUrl; } public get appOctokit() { @@ -143,7 +143,10 @@ export class GithubInstance { }); const appDetails = await this.internalOctokit.apps.getAuthenticated(); - this.internalAppSlug = appDetails.data.slug; + if (!appDetails.data) { + throw Error("No information returned about GitHub App. Is your GitHub App configured correctly?"); + } + this.internalAppUrl = appDetails.data.html_url; let installPageSize = 100; let page = 1; @@ -186,12 +189,10 @@ export class GithubInstance { } public get newInstallationUrl() { - if (this.baseUrl.hostname === GITHUB_CLOUD_URL.hostname) { - // Cloud - return new URL(`/apps/${this.appSlug}/installations/new`, GITHUB_CLOUD_PUBLIC_URL); + if (!this.appUrl) { + throw Error('No configured app url, cannot get installation url'); } - // Enterprise (yes, i know right) - return new URL(`/github-apps/${this.appSlug}/installations/new`, this.baseUrl); + return new URL(this.appUrl); } public static generateOAuthUrl(baseUrl: URL, action: "authorize"|"access_token", params: OAuthUrlParameters) { diff --git a/src/github/Types.ts b/src/github/Types.ts index 7f4b62fc2..f51dc2843 100644 --- a/src/github/Types.ts +++ b/src/github/Types.ts @@ -19,7 +19,6 @@ export type CreateInstallationAccessTokenDataType = Endpoints["POST /app/install export const NAMELESS_ORG_PLACEHOLDER = "No name"; -/* eslint-disable camelcase */ export interface GitHubUserNotification { id: string; reason: "assign"|"author"|"comment"|"invitation"|"manual"|"mention"|"review_requested"| diff --git a/src/jira/GrantChecker.ts b/src/jira/GrantChecker.ts index 1fc19b146..d2a4e2457 100644 --- a/src/jira/GrantChecker.ts +++ b/src/jira/GrantChecker.ts @@ -26,7 +26,7 @@ export class JiraGrantChecker extends GrantChecker { try { await JiraProjectConnection.assertUserHasAccessToProject(this.tokenStore, sender, connectionId.url); return true; - } catch (ex) { + } catch { return false; } } diff --git a/src/jira/client/CloudClient.ts b/src/jira/client/CloudClient.ts index 4b8d86d2d..383cb825d 100644 --- a/src/jira/client/CloudClient.ts +++ b/src/jira/client/CloudClient.ts @@ -14,11 +14,6 @@ const ACCESSIBLE_RESOURCE_CACHE_TTL_MS = 60000; export class HookshotCloudJiraApi extends HookshotJiraApi { - constructor(options: JiraApi.JiraApiOptions, res: JiraAPIAccessibleResource) { - super(options, res); - } - - async getIssue(issueIdOrKey: string): Promise { return this.apiRequest(`/rest/api/3/issue/${issueIdOrKey}`); } @@ -90,7 +85,7 @@ export class JiraCloudClient implements JiraClient { if (existingPromise) { return await existingPromise; } - } catch (ex) { + } catch { // Existing failed promise, break out and try again. JiraCloudClient.resourceCache.delete(this.bearer); } diff --git a/src/libRs.js b/src/libRs.js index 819a9c4eb..d473596fc 100644 --- a/src/libRs.js +++ b/src/libRs.js @@ -2,6 +2,11 @@ try { // In production, we expect it co-located module.exports = require('./matrix-hookshot-rs.node'); } catch (ex) { - // When running under ts-node, it may not be co-located. - module.exports = require('../lib/matrix-hookshot-rs.node'); + try { + // When running under ts-node, it may not be co-located. + module.exports = require('../lib/matrix-hookshot-rs.node'); + } catch (ex) { + // Or in a test environment. + module.exports = require('../../lib/matrix-hookshot-rs.node'); + } } diff --git a/tests/FeedReader.spec.ts b/tests/FeedReader.spec.ts index 00f397450..cf8670cc7 100644 --- a/tests/FeedReader.spec.ts +++ b/tests/FeedReader.spec.ts @@ -71,6 +71,7 @@ async function constructFeedReader(feedResponse: () => {headers: Record httpServer.close()); return {config, cm, events, feedReader, feedUrl, httpServer, storage}; } @@ -100,6 +101,7 @@ describe("FeedReader", () => { expect(events[0].data.feed.title).to.equal(null); expect(events[0].data.title).to.equal(null); }); + it("should handle RSS 2.0 feeds", async () => { const { events, feedReader, feedUrl } = await constructFeedReader(() => ({ headers: {}, data: ` @@ -137,6 +139,7 @@ describe("FeedReader", () => { expect(events[0].data.link).to.equal('http://www.example.com/blog/post/1'); expect(events[0].data.pubdate).to.equal('Sun, 6 Sep 2009 16:20:00 +0000'); }); + it("should handle RSS feeds with a permalink url", async () => { const { events, feedReader, feedUrl } = await constructFeedReader(() => ({ headers: {}, data: ` @@ -173,6 +176,7 @@ describe("FeedReader", () => { expect(events[0].data.link).to.equal('http://www.example.com/blog/post/1'); expect(events[0].data.pubdate).to.equal('Sun, 6 Sep 2009 16:20:00 +0000'); }); + it("should handle Atom feeds", async () => { const { events, feedReader, feedUrl } = await constructFeedReader(() => ({ headers: {}, data: ` @@ -213,6 +217,7 @@ describe("FeedReader", () => { expect(events[0].data.link).to.equal('http://example.org/2003/12/13/atom03'); expect(events[0].data.pubdate).to.equal('Sat, 13 Dec 2003 18:30:02 +0000'); }); + it("should not duplicate feed entries", async () => { const { events, feedReader, feedUrl } = await constructFeedReader(() => ({ headers: {}, data: ` @@ -238,6 +243,7 @@ describe("FeedReader", () => { feedReader.stop(); expect(events).to.have.lengthOf(1); }); + it("should always hash to the same value for Atom feeds", async () => { const expectedHash = ['md5:d41d8cd98f00b204e9800998ecf8427e']; const { feedReader, feedUrl, storage } = await constructFeedReader(() => ({ @@ -258,6 +264,7 @@ describe("FeedReader", () => { const items = await storage.hasSeenFeedGuids(feedUrl, ...expectedHash); expect(items).to.deep.equal(expectedHash); }); + it("should always hash to the same value for RSS feeds", async () => { const expectedHash = [ 'md5:98bafde155b931e656ad7c137cd7711e', // guid diff --git a/tests/FormatUtilTest.ts b/tests/FormatUtilTest.ts index b8c35dc20..315f2ad3c 100644 --- a/tests/FormatUtilTest.ts +++ b/tests/FormatUtilTest.ts @@ -48,11 +48,13 @@ describe("FormatUtilTest", () => { "evilcorp/lab: A simple description", ); }); + it("should correctly formats a issue room name", () => { expect(FormatUtil.formatIssueRoomName(SIMPLE_ISSUE, SIMPLE_REPO)).to.equal( "evilcorp/lab#123: A simple title", ); }); + it("should correctly generate a partial body for a Github repo", () => { expect(FormatUtil.getPartialBodyForGithubRepo(SIMPLE_REPO)).to.deep.equal({ "external_url": "https://github.com/evilcorp/lab", @@ -63,6 +65,7 @@ describe("FormatUtilTest", () => { }, }); }); + it("should correctly generate a partial body for a Github issue", () => { expect(FormatUtil.getPartialBodyForGithubIssue(SIMPLE_REPO, SIMPLE_ISSUE)).to.deep.equal({ "external_url": "https://github.com/evilcorp/lab/issues/123", @@ -79,29 +82,34 @@ describe("FormatUtilTest", () => { }, }); }); + it("should correctly formats a room topic", () => { expect(FormatUtil.formatRoomTopic(SIMPLE_ISSUE)).to.equal( "Status: open | https://github.com/evilcorp/lab/issues/123", ); }); + it("should correctly format one simple label", () => { expect(FormatUtil.formatLabels([{name: "foo"}])).to.deep.equal({ plain: "foo", html: "foo" }); }); + it("should correctly format many simple labels", () => { expect(FormatUtil.formatLabels([{name: "foo"},{name: "bar"}])).to.deep.equal({ plain: "foo, bar", html: "foo bar" }); }); + it("should correctly format one detailed label", () => { expect(FormatUtil.formatLabels([{name: "foo", color: 'FFFFFF', description: 'My label'}])).to.deep.equal({ plain: "foo", html: "foo" }); }); + it("should correctly format many detailed labels", () => { expect(FormatUtil.formatLabels([ {name: "foo", color: 'FFFFFF', description: 'My label'}, @@ -112,6 +120,7 @@ describe("FormatUtilTest", () => { + "bar" },); }); + it("should correctly format a JIRA issue", () => { expect(FormatUtil.getPartialBodyForJiraIssue(SIMPLE_JIRA_ISSUE)).to.deep.equal({ "external_url": "http://example-api.url.com/browse/TEST-001", @@ -127,6 +136,7 @@ describe("FormatUtilTest", () => { }, }); }); + it("should hash an ID", () => { expect(FormatUtil.hashId("foobar")).to.equal('3858f62230ac3c915f300c664312c63f'); }); diff --git a/tests/HookFilter.ts b/tests/HookFilter.ts index f5e8821d1..16fa1423d 100644 --- a/tests/HookFilter.ts +++ b/tests/HookFilter.ts @@ -6,13 +6,16 @@ const ENABLED_SET = ['enabled-hook', 'enabled-but-ignored']; describe("HookFilter", () => { let filter: HookFilter; + beforeEach(() => { filter = new HookFilter(ENABLED_SET); }); + describe('shouldSkip', () => { it('should allow a hook named in enabled set', () => { expect(filter.shouldSkip('enabled-hook')).to.be.false; }); + it('should not allow a hook not named in enabled set', () => { expect(filter.shouldSkip('not-enabled-hook')).to.be.true; }); diff --git a/tests/IntentUtilsTest.ts b/tests/IntentUtilsTest.ts index 38bcd6dc0..b611cb240 100644 --- a/tests/IntentUtilsTest.ts +++ b/tests/IntentUtilsTest.ts @@ -25,7 +25,7 @@ describe("IntentUtils", () => { return; } expect(roomId).to.equal(ROOM_ID); - throw new MatrixError({ errcode: "M_FORBIDDEN", error: "Test forced error"}, 401, { }) + throw new MatrixError({ errcode: "M_FORBIDDEN", error: "Test forced error"}, 401, {}) }; // This should invite the puppet user. @@ -40,7 +40,7 @@ describe("IntentUtils", () => { expect(hasInvited).to.be.true; }); - it("invites the user to the room and joins", () => { + it("invites the user to the room and handles the failure", () => { const targetIntent = IntentMock.create(SENDER_USER_ID); const matrixClient = MatrixClientMock.create(); diff --git a/tests/MessageQueueTest.ts b/tests/MessageQueueTest.ts index f2cd6e45e..00974fb5c 100644 --- a/tests/MessageQueueTest.ts +++ b/tests/MessageQueueTest.ts @@ -23,6 +23,7 @@ describe("MessageQueueTest", () => { data: 51, }); }); + it("should be able to push an event, and respond to it", async () => { mq.subscribe("fakeevent2"); mq.subscribe("response.fakeevent2"); diff --git a/tests/config/config.ts b/tests/config/config.ts index 0ac75e0a9..f942edc9f 100644 --- a/tests/config/config.ts +++ b/tests/config/config.ts @@ -12,6 +12,7 @@ describe("Config/BridgeConfig", () => { expect(config.queue).to.be.undefined; expect(config.cache?.redisUri).to.equal("redis://localhost:6379"); }); + it("with a host parameter", () => { const config = new BridgeConfig({ ...DefaultConfigRoot, queue: { monolithic: true, @@ -20,6 +21,7 @@ describe("Config/BridgeConfig", () => { expect(config.queue).to.be.undefined; expect(config.cache?.redisUri).to.equal("redis://bark:6379"); }); + it("with a port parameter", () => { const config = new BridgeConfig({ ...DefaultConfigRoot, queue: { monolithic: true, @@ -28,6 +30,7 @@ describe("Config/BridgeConfig", () => { expect(config.queue).to.be.undefined; expect(config.cache?.redisUri).to.equal("redis://localhost:6379"); }); + it("with a host and port parameter", () => { const config = new BridgeConfig({ ...DefaultConfigRoot, queue: { monolithic: true, @@ -37,6 +40,7 @@ describe("Config/BridgeConfig", () => { expect(config.queue).to.be.undefined; expect(config.cache?.redisUri).to.equal("redis://bark:6379"); }); + it("with monolithic disabled", () => { const config = new BridgeConfig({ ...DefaultConfigRoot, @@ -51,6 +55,7 @@ describe("Config/BridgeConfig", () => { expect(config.cache?.redisUri).to.equal("redis://localhost:6379"); }); }); + describe("will handle the queue option", () => { it("with redisUri", () => { const config = new BridgeConfig({ ...DefaultConfigRoot, @@ -66,6 +71,7 @@ describe("Config/BridgeConfig", () => { expect(config.cache).to.be.undefined; }); }); + describe("will handle the cache option", () => { it("with redisUri", () => { const config = new BridgeConfig({ diff --git a/tests/config/permissions.ts b/tests/config/permissions.ts index a61254d90..33487469d 100644 --- a/tests/config/permissions.ts +++ b/tests/config/permissions.ts @@ -21,43 +21,53 @@ describe("Config/BridgePermissions", () => { const bridgePermissions = new BridgePermissions([]); expect(bridgePermissions.checkAction("@foo:bar", "empty-service", "commands")).to.be.false; }); + it("will return false for an insufficent level", () => { const bridgePermissions = genBridgePermissions('@foo:bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "notifications")).to.be.false; }); + it("will return false if the there are no matching services", () => { const bridgePermissions = genBridgePermissions('@foo:bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "other-service", "login")).to.be.false; }); + it("will return false if the target does not match", () => { const bridgePermissions = genBridgePermissions('@foo:bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:baz", "my-service", "login")).to.be.false; }); + it("will return true if there is a matching level and service", () => { const bridgePermissions = genBridgePermissions('@foo:bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.true; }); + it("will return true for a matching actor domain", () => { const bridgePermissions = genBridgePermissions('bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.true; }); + it("will return true for a wildcard actor", () => { const bridgePermissions = genBridgePermissions('*', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.true; }); + it("will return true for a wildcard service", () => { const bridgePermissions = genBridgePermissions('@foo:bar', '*', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.true; }); + it("will return false if a user is not present in a room", () => { const bridgePermissions = genBridgePermissions('!foo:bar', 'my-service', 'login'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.false; }); + it("will return true if a user is present in a room", () => { const bridgePermissions = genBridgePermissions('!foo:bar', 'my-service', 'login'); bridgePermissions.addMemberToCache('!foo:bar', '@foo:bar'); expect(bridgePermissions.checkAction("@foo:bar", "my-service", "login")).to.be.true; }); + it("will fall through and return true for multiple permission sets", () => { const bridgePermissions = new BridgePermissions([ { @@ -98,6 +108,7 @@ describe("Config/BridgePermissions", () => { const bridgePermissions = new BridgePermissions([]); expect(bridgePermissions.checkActionAny("@foo:bar", "commands")).to.be.false; }); + it(`will return false for a service with an insufficent level`, () => { const bridgePermissions = genBridgePermissions("@foo:bar", "fake-service", "commands"); expect( @@ -107,8 +118,7 @@ describe("Config/BridgePermissions", () => { ) ).to.be.false; }); - const checkActorValues = ["@foo:bar", "bar", "*"]; - checkActorValues.forEach(actor => { + for (const actor of ["@foo:bar", "bar", "*"]) { it(`will return true for a service defintion of '${actor}' that has a sufficent level`, () => { const bridgePermissions = genBridgePermissions("@foo:bar", "fake-service", "commands"); expect( @@ -118,6 +128,6 @@ describe("Config/BridgePermissions", () => { ) ).to.be.true; }); - }); + } }) }) \ No newline at end of file diff --git a/tests/connections/FeedTest.spec.ts b/tests/connections/FeedTest.spec.ts index 6bb268411..7359349ea 100644 --- a/tests/connections/FeedTest.spec.ts +++ b/tests/connections/FeedTest.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { AppserviceMock } from "../utils/AppserviceMock"; import { FeedConnection, FeedConnectionState } from "../../src/Connections"; import { FeedEntry } from "../../src/feeds/FeedReader"; @@ -47,6 +46,7 @@ describe("FeedConnection", () => { expect(matrixEvt.content.external_url).to.equal(FEED_ENTRY_DEFAULTS.link); expect(matrixEvt.content.body).to.equal("New post in Test feed: [Foo](foo/bar)"); }); + it("will handle simple feed message without a title and link ", async () => { const [connection, intent] = createFeed(); await connection.handleFeedEntry({ @@ -60,6 +60,7 @@ describe("FeedConnection", () => { expect(matrixEvt.content.external_url).to.be.undefined; expect(matrixEvt.content.body).to.equal("New post in Test feed"); }); + it("will handle simple feed message with a missing title ", async () => { const [connection, intent] = createFeed(); await connection.handleFeedEntry({ @@ -71,6 +72,7 @@ describe("FeedConnection", () => { expect(matrixEvt.roomId).to.equal(ROOM_ID); expect(matrixEvt.content.body).to.equal("New post in Test feed: [foo/bar](foo/bar)"); }); + it("will handle simple feed message with a missing link ", async () => { const [connection, intent] = createFeed(); await connection.handleFeedEntry({ @@ -82,6 +84,7 @@ describe("FeedConnection", () => { expect(matrixEvt.roomId).to.equal(ROOM_ID); expect(matrixEvt.content.body).to.equal("New post in Test feed: Foo"); }); + it("will handle simple feed message with all the template options possible ", async () => { const [connection, intent] = createFeed({ template: `$FEEDNAME $FEEDURL $FEEDTITLE $TITLE $LINK $AUTHOR $DATE $SUMMARY` @@ -94,6 +97,7 @@ describe("FeedConnection", () => { expect(matrixEvt.roomId).to.equal(ROOM_ID); expect(matrixEvt.content.body).to.equal("Test feed https://example.com/feed.xml Test feed Foo [Foo](foo/bar) Me! today! fibble fobble"); }); + it("will handle html in the feed summary ", async () => { const [connection, intent] = createFeed({ template: `$FEEDNAME $SUMMARY` @@ -107,6 +111,7 @@ describe("FeedConnection", () => { expect(matrixEvt.roomId).to.equal(ROOM_ID); expect(matrixEvt.content.body).to.equal('Test feed

Some HTML with which should be ignored and an

'); }); + it("will handle partial html in the feed summary ", async () => { const [connection, intent] = createFeed({ template: `$FEEDNAME $SUMMARY` diff --git a/tests/connections/GenericHookTest.ts b/tests/connections/GenericHookTest.ts index ef4ed5619..975d250d8 100644 --- a/tests/connections/GenericHookTest.ts +++ b/tests/connections/GenericHookTest.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { assert, expect } from "chai"; import { Appservice, Intent, MatrixError } from "matrix-bot-sdk"; import { BridgeConfigGenericWebhooks, BridgeGenericWebhooksConfigYAML } from "../../src/config/sections"; @@ -75,10 +74,12 @@ describe("GenericHookConnection", () => { before(async () => { await GenericHookConnection.initialiseQuickJS(); }) + it("will handle simple hook events", async () => { const [connection, mq] = createGenericHook(); await testSimpleWebhook(connection, mq, "data"); }); + it("will handle a hook event containing text", async () => { const webhookData = {text: "simple-message"}; const [connection, mq] = createGenericHook(); @@ -97,6 +98,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event containing markdown", async () => { const webhookData = {text: "**bold-message** _italic-message_"}; const [connection, mq] = createGenericHook(); @@ -115,6 +117,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event containing markdown with newlines", async () => { const webhookData = {text: "# Oh wow\n\n`some-code`"}; const [connection, mq] = createGenericHook(); @@ -133,6 +136,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event containing html", async () => { const webhookData = {text: "simple-message", html: "simple-message"}; const [connection, mq] = createGenericHook(); @@ -151,6 +155,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event containing a username", async () => { const webhookData = {username: "Bobs-integration", type: 42}; const [connection, mq] = createGenericHook(); @@ -169,6 +174,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event with a v1 transformation function", async () => { const webhookData = {question: 'What is the meaning of life?', answer: 42}; const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V1TFFunction}, { @@ -190,6 +196,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event with a v2 transformation function", async () => { const webhookData = {question: 'What is the meaning of life?', answer: 42}; const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V2TFFunction}, { @@ -211,6 +218,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a hook event with a top-level return", async () => { const webhookData = {question: 'What is the meaning of life?', answer: 42}; const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V2TFFunctionWithReturn}, { @@ -232,6 +240,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will fail to handle a webhook with an invalid script", async () => { const webhookData = {question: 'What is the meaning of life?', answer: 42}; const [connection, mq] = createGenericHook({name: 'test', transformationFunction: "bibble bobble"}, { @@ -253,6 +262,7 @@ describe("GenericHookConnection", () => { type: 'm.room.message', }); }); + it("will handle a message containing floats", async () => { const [connection, mq] = createGenericHook(); let messagePromise = handleMessage(mq); @@ -357,6 +367,7 @@ describe("GenericHookConnection", () => { })).to.throw("'expirationDate' must be a valid date"); } }); + it('should fail to create a hook with a too short expiry time', async () => { const as = AppserviceMock.create(); try { @@ -375,6 +386,7 @@ describe("GenericHookConnection", () => { expect(ex.message).to.contain('Expiration date must at least be a hour in the future'); } }); + it('should fail to create a hook with a too long expiry time', async () => { const as = AppserviceMock.create(); try { @@ -396,6 +408,7 @@ describe("GenericHookConnection", () => { expect(ex.message).to.contain('Expiration date cannot exceed the configured max expiry time'); } }); + it('should fail to create a hook without an expiry time when required by config', async () => { const as = AppserviceMock.create(); try { @@ -417,12 +430,14 @@ describe("GenericHookConnection", () => { expect(ex.message).to.contain('Expiration date must be set'); } }); + it('should create a hook and handle a request within the expiry time', async () => { const [connection, mq] = createGenericHook({ expirationDate: add(new Date(), { seconds: 30 }).toISOString(), }); await testSimpleWebhook(connection, mq, "test"); }); + it('should reject requests to an expired hook', async () => { const [connection] = createGenericHook({ expirationDate: new Date().toISOString(), diff --git a/tests/connections/GithubRepoTest.ts b/tests/connections/GithubRepoTest.ts index 7359e55bc..8c8a0e0dd 100644 --- a/tests/connections/GithubRepoTest.ts +++ b/tests/connections/GithubRepoTest.ts @@ -55,7 +55,6 @@ function createConnection(state: Record = {}, isExistingState=f "state_key", githubInstance, // Default config always contains GitHub - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion DefaultConfig.github! ); return {connection, intent: intent as IntentMock}; @@ -84,6 +83,7 @@ describe("GitHubRepoConnection", () => { } } as GitHubRepoConnectionState as unknown as Record); }); + it("will convert ignoredHooks for existing state", () => { const state = GitHubRepoConnection.validateState({ org: "foo", @@ -94,6 +94,7 @@ describe("GitHubRepoConnection", () => { } as GitHubRepoConnectionState as unknown as Record, true); expect(state.enableHooks).to.not.contain('issue'); }); + it("will disallow invalid state", () => { try { GitHubRepoConnection.validateState({ @@ -106,6 +107,7 @@ describe("GitHubRepoConnection", () => { } } }); + it("will disallow enabledHooks to contains invalid enums if this is new state", () => { try { GitHubRepoConnection.validateState({ @@ -119,6 +121,7 @@ describe("GitHubRepoConnection", () => { } } }); + it("will allow enabledHooks to contains invalid enums if this is old state", () => { GitHubRepoConnection.validateState({ org: "foo", @@ -127,6 +130,7 @@ describe("GitHubRepoConnection", () => { }, true); }); }); + describe("onIssueCreated", () => { it("will handle a simple issue", async () => { const { connection, intent } = createConnection(); @@ -136,6 +140,7 @@ describe("GitHubRepoConnection", () => { intent.expectEventBodyContains(GITHUB_ISSUE_CREATED_PAYLOAD.issue.html_url, 0); intent.expectEventBodyContains(GITHUB_ISSUE_CREATED_PAYLOAD.issue.title, 0); }); + it("will handle assignees on issue creation", async () => { const { connection, intent } = createConnection(); await connection.onIssueCreated({ @@ -151,6 +156,7 @@ describe("GitHubRepoConnection", () => { intent.expectEventBodyContains(GITHUB_ISSUE_CREATED_PAYLOAD.issue.html_url, 0); intent.expectEventBodyContains(GITHUB_ISSUE_CREATED_PAYLOAD.issue.title, 0); }); + it("will filter out issues not matching includingLabels.", async () => { const { connection, intent } = createConnection({ includingLabels: ["include-me"] @@ -168,6 +174,7 @@ describe("GitHubRepoConnection", () => { await connection.onIssueCreated(GITHUB_ISSUE_CREATED_PAYLOAD as never); intent.expectNoEvent(); }); + it("will filter out issues matching excludingLabels.", async () => { const { connection, intent } = createConnection({ excludingLabels: ["exclude-me"] @@ -183,6 +190,7 @@ describe("GitHubRepoConnection", () => { } as never); intent.expectNoEvent(); }); + it("will include issues matching includingLabels.", async () => { const { connection, intent } = createConnection({ includingIssues: ["include-me"] diff --git a/tests/connections/GitlabRepoTest.ts b/tests/connections/GitlabRepoTest.ts index 81df12db0..58315ba27 100644 --- a/tests/connections/GitlabRepoTest.ts +++ b/tests/connections/GitlabRepoTest.ts @@ -115,6 +115,7 @@ describe("GitLabRepoConnection", () => { excludingLabels: ["but-not-me"], } as GitLabRepoConnectionState as unknown as Record); }); + it("will convert ignoredHooks for existing state", () => { const state = GitLabRepoConnection.validateState({ instance: "foo", @@ -126,6 +127,7 @@ describe("GitLabRepoConnection", () => { } as GitLabRepoConnectionState as unknown as Record, true); expect(state.enableHooks).to.not.contain('merge_request'); }); + it("will disallow invalid state", () => { try { GitLabRepoConnection.validateState({ @@ -138,6 +140,7 @@ describe("GitLabRepoConnection", () => { } } }); + it("will disallow enabledHooks to contains invalid enums if this is new state", () => { try { GitLabRepoConnection.validateState({ @@ -151,6 +154,7 @@ describe("GitLabRepoConnection", () => { } } }); + it("will allow enabledHooks to contains invalid enums if this is old state", () => { GitLabRepoConnection.validateState({ instance: "bar", @@ -159,6 +163,7 @@ describe("GitLabRepoConnection", () => { }, true); }); }); + describe("onCommentCreated", () => { it("will handle an MR comment", async () => { const { connection, intent } = createConnection(); @@ -169,6 +174,7 @@ describe("GitLabRepoConnection", () => { 'event body indicates MR comment' ); }); + it("will debounce MR comments", async () => { const { connection, intent } = createConnection(); await connection.onCommentCreated(GITLAB_MR_COMMENT as never); @@ -188,6 +194,7 @@ describe("GitLabRepoConnection", () => { 0, ); }); + it("will add new comments in a Matrix thread", async () => { const { connection, intent } = createConnection(); await connection.onCommentCreated(GITLAB_MR_COMMENT as never); @@ -201,6 +208,7 @@ describe("GitLabRepoConnection", () => { 1, ); }); + it("will correctly map new comments to aggregated discussions", async () => { const { connection, intent } = createConnection(); await connection.onCommentCreated({ @@ -251,6 +259,7 @@ describe("GitLabRepoConnection", () => { ); }); }); + describe("onIssueCreated", () => { it("will handle a simple issue", async () => { const { connection, intent } = createConnection(); @@ -260,6 +269,7 @@ describe("GitLabRepoConnection", () => { intent.expectEventBodyContains(GITLAB_ISSUE_CREATED_PAYLOAD.object_attributes.url, 0); intent.expectEventBodyContains(GITLAB_ISSUE_CREATED_PAYLOAD.object_attributes.title, 0); }); + it("will filter out issues not matching includingLabels.", async () => { const { connection, intent } = createConnection({ includingLabels: ["include-me"] @@ -274,6 +284,7 @@ describe("GitLabRepoConnection", () => { await connection.onMergeRequestOpened(GITLAB_ISSUE_CREATED_PAYLOAD as never); intent.expectNoEvent(); }); + it("will filter out issues matching excludingLabels.", async () => { const { connection, intent } = createConnection({ excludingLabels: ["exclude-me"] @@ -286,6 +297,7 @@ describe("GitLabRepoConnection", () => { } as never); intent.expectNoEvent(); }); + it("will include issues matching includingLabels.", async () => { const { connection, intent } = createConnection({ includingIssues: ["include-me"] diff --git a/tests/github/AdminCommands.ts b/tests/github/AdminCommands.ts index 5bc0aa5e7..f614b82e3 100644 --- a/tests/github/AdminCommands.ts +++ b/tests/github/AdminCommands.ts @@ -13,6 +13,7 @@ describe("GitHub", () => { }) ).equals('https://github.com/login/oauth/authorize?state=my_state&client_id=123&redirect_uri=456'); }); + it("can generate an authorize URL for enterprise URLs", () => { expect( GithubInstance.generateOAuthUrl(new URL("https://mygithuburl.com/foo/bar"), "authorize", { @@ -22,6 +23,7 @@ describe("GitHub", () => { }) ).equals('https://mygithuburl.com/foo/bar/login/oauth/authorize?state=my_state&client_id=123&redirect_uri=456'); }); + it("can generate an access_token URL for the cloud URL", () => { expect( GithubInstance.generateOAuthUrl(GITHUB_CLOUD_URL, "access_token", { @@ -33,6 +35,7 @@ describe("GitHub", () => { }) ).equals('https://github.com/login/oauth/access_token?client_id=123&client_secret=the-secret&code=the-code&redirect_uri=456&state=my_state'); }); + it("can generate an access_token URL for enterprise URLs", () => { expect( GithubInstance.generateOAuthUrl(new URL("https://mygithuburl.com/foo/bar"), "access_token", { diff --git a/tests/grants/GrantChecker.spec.ts b/tests/grants/GrantChecker.spec.ts index caa6e59df..5e81e625a 100644 --- a/tests/grants/GrantChecker.spec.ts +++ b/tests/grants/GrantChecker.spec.ts @@ -39,6 +39,7 @@ describe("GrantChecker", () => { let check: GrantChecker; // eslint-disable-next-line @typescript-eslint/no-explicit-any let intent: any; + beforeEach(() => { intent = IntentMock.create('@foo:bar'); check = new TestGrantChecker(intent, GRANT_SERVICE); @@ -95,9 +96,11 @@ describe("GrantChecker", () => { ); }); }); + describe('config fallback', () => { let check: GrantChecker; let as: AppserviceMock; + beforeEach(() => { const mockAs = AppserviceMock.create(); as = mockAs; diff --git a/tests/jira/Utils.ts b/tests/jira/Utils.ts index 3f9050ed4..e9e3fb2f4 100644 --- a/tests/jira/Utils.ts +++ b/tests/jira/Utils.ts @@ -9,13 +9,15 @@ describe("Jira", () => { key: "TEST-111", })).to.equal("https://my-test-jira/browse/TEST-111"); }); + it("processes a jira issue into a URL with a port", () => { expect(generateJiraWebLinkFromIssue({ self: "https://my-test-jira:9995/", key: "TEST-111", })).to.equal("https://my-test-jira:9995/browse/TEST-111"); }); - it("processes a jira issue into a URL with a port", () => { + + it("processes a jira issue into a URL with a port and a version", () => { expect(generateJiraWebLinkFromVersion({ self: "https://my-test-jira:9995/", description: "foo", diff --git a/tests/tokens/tokenencryption.spec.ts b/tests/tokens/tokenencryption.spec.ts index fcbb67d49..d79ccea85 100644 --- a/tests/tokens/tokenencryption.spec.ts +++ b/tests/tokens/tokenencryption.spec.ts @@ -53,17 +53,20 @@ describe("TokenEncryption", () => { })); }, ); + it('should be able to encrypt a string into a single part', async() => { const tokenEncryption = await createTokenEncryption(); const result = tokenEncryption.encrypt('hello world'); expect(result).to.have.lengthOf(1); }); + it('should be able to decrypt from a single part into a string', async() => { const tokenEncryption = await createTokenEncryption(); const value = tokenEncryption.encrypt('hello world'); const result = tokenEncryption.decrypt(value, Algo.RSAPKCS1v15); expect(result).to.equal('hello world'); }); + it('should be able to decrypt from many parts into string', async() => { const plaintext = 'This is a very long string that needs to be encoded into multiple parts in order for us to store it properly. This ' + ' should end up as multiple encrypted values in base64.'; @@ -73,11 +76,13 @@ describe("TokenEncryption", () => { const result = tokenEncryption.decrypt(value, Algo.RSAPKCS1v15); expect(result).to.equal(plaintext); }); + it('should support pkcs1 format keys', async() => { const tokenEncryption = new TokenEncryption(await keyPromisePKCS1); const result = tokenEncryption.encrypt('hello world'); expect(result).to.have.lengthOf(1); }); + it('should be to decrypt a string from the old crypto implementation', async() => { const legacyString = await legacyEncryptFunction('hello world'); const tokenEncryption = await createTokenEncryption(); diff --git a/tsconfig.json b/tsconfig.json index 7d9a92ac3..7a2f088c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node18/tsconfig.json", + "extends": "@tsconfig/node22/tsconfig.json", "compilerOptions": { "incremental": true, "declaration": false, @@ -17,16 +17,13 @@ "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "esModuleInterop": true, "inlineSourceMap": true, "inlineSources": true, "allowJs": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, // TODO: Type errors - "useUnknownInCatchVariables": false, - // ES2022+ is currently broken https://github.com/matrix-org/matrix-hookshot/issues/729 - "target": "es2021" + "useUnknownInCatchVariables": false }, "include": [ "src/**/*" diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 000000000..fb6a30d7f --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": false, + "declaration": false, + "outDir": "./spec-lib", + "rootDir": "./", + "allowJs": true + }, + "include": [ + "spec/**/*" + ], + "exclude": [ + "tests/**/*", + "web/**/*" + ] +} diff --git a/vite.config.mjs b/vite.config.mjs index 010694409..7789d7aea 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -31,4 +31,11 @@ export default defineConfig({ }, emptyOutDir: true, }, + css: { + preprocessorOptions: { + scss: { + api: 'modern' + } + } + } }) diff --git a/web/App.tsx b/web/App.tsx index bac2078e9..e19581c5b 100644 --- a/web/App.tsx +++ b/web/App.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { Component } from 'preact'; import WA, { MatrixCapabilities } from 'matrix-widget-api'; import { BridgeAPI, BridgeAPIError, EmbedType, embedTypeParameter } from './BridgeAPI'; @@ -84,8 +83,6 @@ export default class App extends Component { const roomState = widgetKind === "admin" ? await bridgeApi.state() : undefined; const supportedServices = await bridgeApi.getEnabledConfigSections(); await widgetReady; - // Calling setState is ok because we've awaited a network request. - // eslint-disable-next-line react/no-did-mount-set-state this.setState({ userId, roomState, @@ -106,8 +103,6 @@ export default class App extends Component { error = "Could not contact your homeserver. Your instance may be misconfigured."; } } - // Calling setState is ok because we've awaited a network request. - // eslint-disable-next-line react/no-did-mount-set-state this.setState({ error, busy: false, diff --git a/web/BridgeAPI.ts b/web/BridgeAPI.ts index e6b5791e3..80fbcd81c 100644 --- a/web/BridgeAPI.ts +++ b/web/BridgeAPI.ts @@ -46,7 +46,6 @@ export class BridgeAPI { const creds = await widgetApi.requestOpenIDConnectToken(); const { matrix_server_name, access_token } = creds; - // eslint-disable-next-line camelcase if (!matrix_server_name || !access_token) { throw Error('Server OpenID response missing values'); } @@ -55,9 +54,7 @@ export class BridgeAPI { cache: 'no-cache', method: 'POST', body: JSON.stringify({ - // eslint-disable-next-line camelcase matrixServer: matrix_server_name, - // eslint-disable-next-line camelcase openIdToken: access_token, } as ExchangeOpenAPIRequestBody), headers: { diff --git a/web/components/AdminSettings.tsx b/web/components/AdminSettings.tsx index c9a7d99e7..f192c7e58 100644 --- a/web/components/AdminSettings.tsx +++ b/web/components/AdminSettings.tsx @@ -36,23 +36,23 @@ export default function AdminSettings(props: IProps) { [setCurrentTab] ); if (busy) { - return
+ return
; } - return
-

Hookshot Bridge settings

-
-