diff --git a/Dockerfile b/Dockerfile index d35888991..2cb072c1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ WORKDIR /build RUN apt-get update && apt-get install -y git python3 libicu-dev build-essential COPY src/ /build/src/ -COPY types/ /build/types/ COPY .eslintrc *json /build/ RUN npm ci diff --git a/changelog.d/1101.misc b/changelog.d/1101.misc new file mode 100644 index 000000000..9cc5667d5 --- /dev/null +++ b/changelog.d/1101.misc @@ -0,0 +1 @@ +Use types from `matrix-appservice-bridge` rather than local definitions. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 39e77a8eb..73b52b56a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -141,7 +141,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", - "optional": true, "requires": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -151,14 +150,12 @@ "enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "optional": true + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "optional": true + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" } } }, @@ -283,21 +280,23 @@ "dev": true }, "@types/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", - "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", + "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", + "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", - "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", + "integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==", "requires": { "@types/node": "*", + "@types/qs": "*", "@types/range-parser": "*" } }, @@ -320,9 +319,9 @@ "dev": true }, "@types/mime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, "@types/nedb": { "version": "1.8.9", @@ -380,9 +379,9 @@ } }, "@types/serve-static": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", - "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -1463,7 +1462,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz", "integrity": "sha512-VYb3HZ/GiAGUCrfeakO8Mp54YGswNUHvL7P09WQcXAJNSj3iQ5QraYSp3cIn1MUyw6uzfgN/EFOarCNa4JvUHQ==", - "optional": true, "requires": { "moment": "^2.11.2" } @@ -1549,8 +1547,7 @@ "fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "optional": true + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "foreground-child": { "version": "1.5.6", @@ -2279,9 +2276,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==" }, "lowdb": { "version": "1.0.0", @@ -2327,29 +2324,55 @@ } }, "matrix-appservice": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-0.4.1.tgz", - "integrity": "sha512-mxHr9XDOvN/p6OFrfb4kkcEjCPftnXNzMS8Lg9Cz/pDy1arfRWq11vl9pL9bjzBaAouBGLpW1JzmCR2MsW+VKA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-0.5.0.tgz", + "integrity": "sha512-TzfoXdZCFbwnjHFwkmpxWEDN0SWu4Sgho8Jaa47wTei+Kg8/hLCc/JvRN3AXNtx419+tolsXYX8KO0JJorQSpA==", "requires": { - "body-parser": "^1.18.3", - "express": "^4.16.3", - "js-yaml": "^3.2.7", - "morgan": "^1.9.1", - "request": "^2.88.0" + "@types/express": "^4.17.8", + "body-parser": "^1.19.0", + "express": "^4.17.1", + "js-yaml": "^3.14.0", + "morgan": "^1.10.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + } + } } }, "matrix-appservice-bridge": { - "version": "1.14.0-rc2", - "resolved": "https://registry.npmjs.org/matrix-appservice-bridge/-/matrix-appservice-bridge-1.14.0-rc2.tgz", - "integrity": "sha512-XrgEyMCfiuHYTZ837IanAJ3C1mAgzfFwIo18DN6G+Dfc4oy/u4XsUYwViZVCvTfdMgJt2ZQdK31V8EpHD/yRww==", + "version": "2.0.0-rc1", + "resolved": "https://registry.npmjs.org/matrix-appservice-bridge/-/matrix-appservice-bridge-2.0.0-rc1.tgz", + "integrity": "sha512-LrqqXGAlEPZ8OV56xsSABnFvljJCAXVyZ0yqCWDrBMEKfHFmSZ+Bdnzq7H17s3PcBfxJTDJaxxErVb/k5zd9yA==", "requires": { - "@types/express": "^4.17.7", - "bluebird": "^3.7.2", "chalk": "^4.1.0", "extend": "^3.0.2", "is-my-json-valid": "^2.20.5", "js-yaml": "^3.14.0", - "matrix-appservice": "^0.4.2", + "matrix-appservice": "^0.5.0", "matrix-js-sdk": "^8.0.1", "nedb": "^1.8.0", "nopt": "^4.0.3", @@ -2359,22 +2382,10 @@ "winston-daily-rotate-file": "^4.5.0" }, "dependencies": { - "@types/express": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", - "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "optional": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -2383,14 +2394,12 @@ "async": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "optional": true + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "optional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2400,16 +2409,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, "requires": { "color-name": "~1.1.4" } }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2418,20 +2421,17 @@ "fecha": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", - "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==", - "optional": true + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "js-yaml": { "version": "3.14.0", @@ -2446,7 +2446,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", - "optional": true, "requires": { "colors": "^1.2.1", "fast-safe-stringify": "^2.0.4", @@ -2455,30 +2454,6 @@ "triple-beam": "^1.3.0" } }, - "matrix-appservice": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-0.4.2.tgz", - "integrity": "sha512-YD2zFiPo9FedjA86gfWFN2UVDwDBhWL1Kas/txG0iwpT6jcJmOvpPqbfuFeYk3Q3G3c5BZ8k8tABMpzS9Bu39A==", - "requires": { - "body-parser": "^1.19.0", - "express": "^4.17.1", - "js-yaml": "^3.14.0", - "morgan": "^1.10.0", - "request": "^2.88.2" - } - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - } - }, "nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -2492,7 +2467,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "optional": true, "requires": { "fn.name": "1.x.x" } @@ -2501,16 +2475,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, "requires": { "safe-buffer": "~5.1.0" } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "optional": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { "has-flag": "^4.0.0" } @@ -2519,7 +2491,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", - "optional": true, "requires": { "@dabh/diagnostics": "^2.0.2", "async": "^3.1.0", @@ -2536,7 +2507,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.5.0.tgz", "integrity": "sha512-/HqeWiU48dzGqcrABRlxYWVMdL6l3uKCtFSJyrqK+E2rLnSFNsgYpvwx15EgTitBLNzH69lQd/+z2ASryV2aqw==", - "optional": true, "requires": { "file-stream-rotator": "^0.5.7", "object-hash": "^2.0.1", @@ -2548,7 +2518,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", - "optional": true, "requires": { "readable-stream": "^2.3.7", "triple-beam": "^1.2.0" @@ -2558,7 +2527,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2636,9 +2604,9 @@ } }, "matrix-js-sdk": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-8.0.1.tgz", - "integrity": "sha512-DT2YjWi8l2eHyNTKZOhBkN/EakIMDDEglSCg+RWY4QzFaXYlSwfwfzzCujjtq1hxVSKim8NC7KqxgNetOiBegA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-8.3.0.tgz", + "integrity": "sha512-ndKedUtZt72/4KWjlMevNwNDGfhPTOn/i4U6Iv1ZEfm7uZfbp5u3hVIyr8tyOiVuvMIxmcTajRdwSlRsNtYFkA==", "requires": { "@babel/runtime": "^7.8.3", "another-json": "^0.2.0", @@ -2885,8 +2853,7 @@ "object-hash": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", - "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", - "optional": true + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" }, "on-finished": { "version": "2.3.0", diff --git a/package.json b/package.json index eee54ea02..b89f39e11 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "irc": "matrix-org/node-irc#9028c2197c216dd8e6fc2cb3cc07ce2d6bf741a7", "js-yaml": "^3.2.7", "logform": "^2.1.2", - "matrix-appservice": "^0.4.1", - "matrix-appservice-bridge": "^1.14.0-rc2", + "matrix-appservice": "^0.5.0", + "matrix-appservice-bridge": "2.0.0-rc1", "matrix-lastactive": "^0.1.5", "nedb": "^1.1.2", "nopt": "^3.0.1", @@ -51,7 +51,7 @@ }, "devDependencies": { "@types/bluebird": "^3.5.27", - "@types/express": "^4.17.2", + "@types/express": "^4.17.7", "@types/extend": "^3.0.1", "@types/he": "^1.1.0", "@types/nedb": "^1.8.9", diff --git a/spec/integ/dynamic-channels.spec.js b/spec/integ/dynamic-channels.spec.js index efecd733e..910f21d66 100644 --- a/spec/integ/dynamic-channels.spec.js +++ b/spec/integ/dynamic-channels.spec.js @@ -64,7 +64,7 @@ describe("Dynamic channels", function() { return Promise.resolve({}); }); - env.mockAppService._queryAlias(tAlias).done(function() { + env.mockAppService._queryAlias(tAlias).then(function() { if (joinedIrcChannel) { done(); } @@ -112,7 +112,7 @@ describe("Dynamic channels", function() { return Promise.resolve({}); }); - env.mockAppService._queryAlias(tAlias).done(function() { + env.mockAppService._queryAlias(tAlias).then(function() { expect(joinedIrcChannel).toBe(true, "Failed to join irc channel"); done(); }, function(e) { @@ -163,7 +163,7 @@ describe("Dynamic channels", function() { env.mockAppService._queryAlias(tAlias).then(function() { return env.mockAppService._queryAlias(tCapsAlias); - }).done(function() { + }).then(function() { expect(madeAlias).toBe(true, "Failed to create alias"); done(); }); @@ -204,7 +204,7 @@ describe("Dynamic channels", function() { return Promise.resolve({}); }); - env.mockAppService._queryAlias(tAlias).done(function() { + env.mockAppService._queryAlias(tAlias).then(function() { expect(joinedIrcChannel).toBe(true, "Failed to join irc channel"); done(); }, function(e) { @@ -280,7 +280,7 @@ describe("Dynamic channels (federation disabled)", function() { return Promise.resolve({}); }); - env.mockAppService._queryAlias(tAlias).done(function() { + env.mockAppService._queryAlias(tAlias).then(function() { expect(joinedIrcChannel).toBe(true, "Failed to join irc channel"); done(); }, function(e) { @@ -352,7 +352,7 @@ describe("Dynamic channels (disabled)", function() { return Promise.resolve({}); }); - env.mockAppService._queryAlias(tAlias).done(function() { + env.mockAppService._queryAlias(tAlias).then(function() { expect(joinedIrcChannel).toBe(false, "Joined channel by alias"); done(); }); diff --git a/spec/integ/hs-queries.spec.js b/spec/integ/hs-queries.spec.js index 9f1cca1d5..ee8b00123 100644 --- a/spec/integ/hs-queries.spec.js +++ b/spec/integ/hs-queries.spec.js @@ -47,9 +47,7 @@ describe("Homeserver user queries", function() { returnUserId: testUserId }); - env.mockAppService._queryUser(testUserId).done(function(res) { - done(); - }); + env.mockAppService._queryUser(testUserId).then(done); }); }); @@ -109,7 +107,7 @@ describe("Homeserver alias queries", function() { } }); - env.mockAppService._queryAlias(testAlias).done(function() { + env.mockAppService._queryAlias(testAlias).then(function() { expect(botJoined).toBe(true, "Bot didn't join " + testChannel); done(); }, function(err) { diff --git a/spec/integ/init.spec.js b/spec/integ/init.spec.js index 7fe11e112..eea9a7995 100644 --- a/spec/integ/init.spec.js +++ b/spec/integ/init.spec.js @@ -51,7 +51,7 @@ describe("Initialisation", function() { env.ircMock._whenClient(roomMapping.server, ircNick, "connect", function(client, cb) { // after the connect callback, modify their nick and emit an event. - client._invokeCallback(cb).done(function() { + client._invokeCallback(cb).then(function() { process.nextTick(function() { client.nick = assignedNick; client.emit("nick", ircNick, assignedNick); diff --git a/spec/integ/invite-rooms.spec.js b/spec/integ/invite-rooms.spec.js index 818804970..5ad026234 100644 --- a/spec/integ/invite-rooms.spec.js +++ b/spec/integ/invite-rooms.spec.js @@ -62,7 +62,7 @@ describe("Invite-only rooms", function() { room_id: adminRoomId, type: "m.room.member" }); - }).done(function() { + }).then(function() { expect(joinRoomCount).toEqual(2, "Failed to join admin room again"); done(); }, function(err) { @@ -100,7 +100,7 @@ describe("Invite-only rooms", function() { }); let leftRoom = false; - sdk.leave.and.callFake(function(roomId) { + sdk.kick.and.callFake(function(roomId) { expect(roomId).toEqual(roomMapping.roomId); leftRoom = true; return Promise.resolve({}); diff --git a/spec/integ/irc-client-cycling.spec.js b/spec/integ/irc-client-cycling.spec.js index 81f5d60f8..917459fc4 100644 --- a/spec/integ/irc-client-cycling.spec.js +++ b/spec/integ/irc-client-cycling.spec.js @@ -100,7 +100,7 @@ describe("IRC client cycling", function() { room_id: roomMapping.roomId, type: "m.room.message" }); - }).done(function() { + }).then(function() { // everyone should have connected/said something let i; for (i = 0; i < testUsers.length; i++) { @@ -165,7 +165,7 @@ describe("IRC client cycling", function() { room_id: roomMapping.roomId, type: "m.room.message" }); - }).done(function() { + }).then(function() { // the first guy should have 2 says, 2 connects and 1 disconnect. // We're mainly interested in that there were 2 connect calls. If // there is just 1, it indicates it used a cached copy. diff --git a/spec/integ/irc-connections.spec.js b/spec/integ/irc-connections.spec.js index 0d819dee4..df880ebc2 100644 --- a/spec/integ/irc-connections.spec.js +++ b/spec/integ/irc-connections.spec.js @@ -31,11 +31,6 @@ describe("IRC connections", function() { roomMapping.server, roomMapping.botNick, roomMapping.channel ); - // we're not interested in the joins, so autojoin them. - env.ircMock._autoJoinChannels( - roomMapping.server, testUser.nick, roomMapping.channel - ); - // do the init yield test.initEnv(env, config); })); @@ -95,7 +90,7 @@ describe("IRC connections", function() { user_id: testUser.id, room_id: roomMapping.roomId, type: "m.room.message" - }).done(function() { + }).then(function() { expect(gotConnectCall).toBe( true, nickForDisplayName + " failed to connect to IRC." ); @@ -154,7 +149,7 @@ describe("IRC connections", function() { user_id: testUser.id, room_id: roomMapping.roomId, type: "m.room.message" - }).done(function() { + }).then(function() { expect(gotConnectCall).toBe( true, nickForDisplayName + " failed to connect to IRC." ); @@ -216,7 +211,7 @@ describe("IRC connections", function() { // user error) env.ircMock._findClientAsync( roomMapping.server, roomMapping.botNick - ).done(function(client) { + ).then(function(client) { client.emit( "message", assignedNick, roomMapping.channel, "some text" ); @@ -233,6 +228,10 @@ describe("IRC connections", function() { "to be sent to IRC", async function() { let connectCount = 0; + env.ircMock._autoJoinChannels( + roomMapping.server, testUser.nick, roomMapping.channel + ); + env.ircMock._whenClient(roomMapping.server, testUser.nick, "connect", (client, cb) => { connectCount += 1; // add an artificially long delay to make sure it isn't connecting @@ -349,12 +348,12 @@ describe("IRC connections", function() { room_id: roomMapping.roomId, type: "m.room.message" }); - }).done(function() { + }).then(function() { // send an echo of the 3rd message: it shouldn't pass it through // because it is a virtual user! env.ircMock._findClientAsync( roomMapping.server, roomMapping.botNick - ).done(function(client) { + ).then(function(client) { client.emit( "message", users[0].assignedNick, roomMapping.channel, "3rd message" @@ -422,7 +421,7 @@ describe("IRC connections", function() { room_id: roomMapping.roomId, type: "m.room.message" }); - }).done(function() { + }).then(function() { expect(usr1.username).toBeDefined(); expect(usr2.username).toBeDefined(); expect(usr1.username).not.toEqual(usr2.username); @@ -486,7 +485,7 @@ describe("IRC connections", function() { room_id: roomMapping.roomId, type: "m.room.message" }); - Promise.all([p1, p2]).done(function() { + Promise.all([p1, p2]).then(function() { expect(usr1.username).toBeDefined(); expect(usr2.username).toBeDefined(); expect(usr1.username).not.toEqual(usr2.username); @@ -495,14 +494,14 @@ describe("IRC connections", function() { }); it("should gracefully fail if it fails to join a channel when sending a message", - function(done) { + async function() { env.ircMock._autoConnectNetworks( roomMapping.server, testUser.nick, roomMapping.server ); let errorEmitted = false; env.ircMock._whenClient(roomMapping.server, testUser.nick, "join", - function(client, cb) { + (client) => { errorEmitted = true; client.emit("error", { command: "err_bannedfromchan", @@ -510,18 +509,20 @@ describe("IRC connections", function() { }); }); - env.mockAppService._trigger("type:m.room.message", { - content: { - body: "A message", - msgtype: "m.text" - }, - user_id: testUser.id, - room_id: roomMapping.roomId, - type: "m.room.message" - }).catch(function(e) { + try { + await env.mockAppService._trigger("type:m.room.message", { + content: { + body: "A message", + msgtype: "m.text" + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + type: "m.room.message" + }); + throw Error('Expected exception'); + } catch (ex) { expect(errorEmitted).toBe(true); - done(); - }); + } }); it("should not bridge matrix users who are excluded", async function() { diff --git a/spec/integ/irc-modes.spec.js b/spec/integ/irc-modes.spec.js index a8da9eaac..e29d801a2 100644 --- a/spec/integ/irc-modes.spec.js +++ b/spec/integ/irc-modes.spec.js @@ -59,7 +59,7 @@ describe("IRC-to-Matrix mode bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("+mode", roomMapping.channel, "anIrcUser", "k"); }); @@ -78,7 +78,7 @@ describe("IRC-to-Matrix mode bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("+mode", roomMapping.channel, "anIrcUser", "i"); }); @@ -97,7 +97,7 @@ describe("IRC-to-Matrix mode bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("-mode", roomMapping.channel, "anIrcUser", "i"); }); @@ -116,7 +116,7 @@ describe("IRC-to-Matrix mode bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("-mode", roomMapping.channel, "anIrcUser", "k"); }); diff --git a/spec/integ/irc-to-matrix.spec.js b/spec/integ/irc-to-matrix.spec.js index f6569ad60..defa56f4d 100644 --- a/spec/integ/irc-to-matrix.spec.js +++ b/spec/integ/irc-to-matrix.spec.js @@ -62,7 +62,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("message", tFromNick, roomMapping.channel, testText); }); @@ -81,7 +81,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("ctcp-privmsg", tFromNick, roomMapping.channel, "ACTION " + testEmoteText @@ -102,7 +102,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "notice", tFromNick, roomMapping.channel, testNoticeText @@ -199,7 +199,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel.toUpperCase(), testText @@ -238,7 +238,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel, tIrcFormattedText @@ -264,7 +264,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel, tIrcFormattedText @@ -292,7 +292,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel, tIrcFormattedText @@ -319,7 +319,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel, tIrcFormattedText @@ -344,7 +344,7 @@ describe("IRC-to-Matrix message bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit( "message", tFromNick, roomMapping.channel, tIrcFormattedText @@ -421,7 +421,7 @@ describe("IRC-to-Matrix operator modes bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(cli) { cli.emit( "+mode", roomMapping.channel, "op-er", "o", tRealMatrixUserNick, "here you go" @@ -456,7 +456,7 @@ describe("IRC-to-Matrix operator modes bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(cli) { cli.emit( "+mode", roomMapping.channel, "op-er", "o", tRealMatrixUserNick, "here you go" @@ -491,7 +491,7 @@ describe("IRC-to-Matrix operator modes bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(cli) { cli.emit( "+mode", roomMapping.channel, "op-er", "v", tRealMatrixUserNick, "here you go" @@ -529,7 +529,7 @@ describe("IRC-to-Matrix operator modes bridging", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(cli) { cli.emit( "+mode", roomMapping.channel, "op-er", "o", tRealMatrixUserNick, "here you go" @@ -597,7 +597,7 @@ describe("IRC-to-Matrix name bridging", function() { done(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("message", tFromNick, roomMapping.channel, "ping"); }); @@ -638,7 +638,7 @@ describe("IRC-to-Matrix name bridging", function() { }); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { let names = { Alicia: {}, @@ -675,7 +675,7 @@ describe("IRC-to-Matrix name bridging", function() { return Promise.resolve({}); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { let names = { Alicia: {} diff --git a/spec/integ/kicking.spec.js b/spec/integ/kicking.spec.js index ef319cdfa..3df5ae698 100644 --- a/spec/integ/kicking.spec.js +++ b/spec/integ/kicking.spec.js @@ -127,9 +127,8 @@ describe("Kicking", function() { }); describe("Matrix users on IRC", function() { - it("should make the AS bot kick the Matrix user from the Matrix room", - test.coroutine(function*() { - let userKickedPromise = new Promise(function(resolve, reject) { + it("should make the AS bot kick the Matrix user from the Matrix room", async () => { + let userKickedPromise = new Promise(function(resolve) { // assert function call when the bot attempts to kick let botSdk = env.clientMock._client(config._botUserId); botSdk.kick.and.callFake(function(roomId, userId, reason) { @@ -143,12 +142,12 @@ describe("Kicking", function() { }); // send the KICK command - let botCli = yield env.ircMock._findClientAsync( + let botCli = await env.ircMock._findClientAsync( config._server, config._botnick ); botCli.emit("kick", config._chan, mxUser.nick, "KickerNick", "Reasons"); - yield userKickedPromise; - })); + await userKickedPromise; + }); }); describe("IRC users on Matrix", function() { diff --git a/spec/integ/mirroring.spec.js b/spec/integ/mirroring.spec.js index 3dc8df3c9..f1dc59b58 100644 --- a/spec/integ/mirroring.spec.js +++ b/spec/integ/mirroring.spec.js @@ -76,7 +76,7 @@ describe("Mirroring", function() { state_key: testUser.id, room_id: roomMapping.roomId, type: "m.room.member" - }).done(function() { + }).then(function() { expect(joined).toBe(true, "Didn't join"); done(); }); @@ -114,7 +114,7 @@ describe("Mirroring", function() { room_id: roomMapping.roomId, type: "m.room.member" }); - }).done(function() { + }).then(function() { expect(parted).toBe(true, "Didn't part"); done(); }); @@ -139,7 +139,7 @@ describe("Mirroring", function() { state_key: testUser.id, room_id: "!bogusroom:id", type: "m.room.member" - }).done(function() { + }).then(function() { done(); }); }); @@ -163,7 +163,7 @@ describe("Mirroring", function() { state_key: testUser.id, room_id: roomMapping.roomId, type: "m.room.member" - }).done(function() { + }).then(function() { done(); }); }); @@ -249,23 +249,23 @@ describe("Mirroring", function() { return Promise.resolve(); }); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( + env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).then( function(client) { client.emit("join", roomMapping.channel, ircUser.nick); }); }); - it("should leave the matrix room when the IRC user parts", function(done) { - sdk.leave.and.callFake(function(roomId) { + it("should leave the matrix room when the IRC user parts", async function() { + const leavePromise = new Promise(r => sdk.kick.and.callFake(async (roomId, userId, reason) => { + expect(userId).toEqual(ircUser.id); + expect(reason).toEqual("Client PARTed from channel"); expect(roomId).toEqual(roomMapping.roomId); - done(); - return Promise.resolve(); - }); + r(); + })); - env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick).done( - function(client) { - client.emit("part", roomMapping.channel, ircUser.nick); - }); + const client = await env.ircMock._findClientAsync(roomMapping.server, roomMapping.botNick); + client.emit("part", roomMapping.channel, ircUser.nick); + await leavePromise; }); }); }); diff --git a/spec/unit/Queue.spec.js b/spec/unit/Queue.spec.js index 3beb71bd9..f120c01b5 100644 --- a/spec/unit/Queue.spec.js +++ b/spec/unit/Queue.spec.js @@ -48,7 +48,7 @@ describe("Queue", function() { expect(thing).toEqual(theThing); return thePromise; }); - queue.enqueue("id", theThing).done((res) => { + queue.enqueue("id", theThing).then((res) => { expect(res).toEqual("flibble"); done(); }); @@ -108,7 +108,7 @@ describe("Queue", function() { var promise1 = queue.enqueue("id", theThing); var promise2 = queue.enqueue("id", theThing); expect(promise1).toEqual(promise2); - promise1.done((res) => { + promise1.then((res) => { expect(promise2.isPending()).toBe(false); expect(res).toEqual("flibble"); expect(callCount).toEqual(1); diff --git a/spec/util/app-service-mock.js b/spec/util/app-service-mock.js index b784bd456..2fd7e43d9 100644 --- a/spec/util/app-service-mock.js +++ b/spec/util/app-service-mock.js @@ -6,7 +6,7 @@ var instance = null; function MockAppService() { let self = this; - this.app = { + this.expressApp = { post: function(path, handler) { if (path === '/_matrix/provision/link') { self.link = handler; @@ -121,6 +121,8 @@ MockAppService.prototype._queryUser = function(user) { }); }; +MockAppService.prototype.close = async function() { /* No-op */ }; + function MockAppServiceProxy() { if (!instance) { instance = new MockAppService(); diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index d3cd18a47..21b7ceb9d 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -1,15 +1,15 @@ import Bluebird from "bluebird"; import extend from "extend"; import * as promiseutil from "../promiseutil"; -import { IrcHandler } from "./IrcHandler"; -import { MatrixHandler } from "./MatrixHandler"; +import { IrcHandler, MatrixMembership } from "./IrcHandler"; +import { MatrixHandler, MatrixEventInvite, OnMemberEventData, MatrixEventKick } from "./MatrixHandler"; import { MemberListSyncer } from "./MemberListSyncer"; import { IrcServer } from "../irc/IrcServer"; import { ClientPool } from "../irc/ClientPool"; import { BridgedClient, BridgedClientStatus } from "../irc/BridgedClient"; import { IrcUser } from "../models/IrcUser"; import { IrcRoom } from "../models/IrcRoom"; -import { BridgeRequest, BridgeRequestErr } from "../models/BridgeRequest"; +import { BridgeRequest, BridgeRequestErr, BridgeRequestData, BridgeRequestEvent } from "../models/BridgeRequest"; import { NeDBDataStore } from "../datastore/NedbDataStore"; import { PgDataStore } from "../datastore/postgres/PgDataStore"; import { getLogger } from "../logging"; @@ -28,10 +28,11 @@ import { Request, PrometheusMetrics, MembershipCache, + AgeCounters, } from "matrix-appservice-bridge"; import { IrcAction } from "../models/IrcAction"; import { DataStore } from "../datastore/DataStore"; -import { MatrixAction } from "../models/MatrixAction"; +import { MatrixAction, MatrixMessageEvent } from "../models/MatrixAction"; import { BridgeConfig } from "../config/BridgeConfig"; import { MembershipQueue } from "../util/MembershipQueue"; import { BridgeStateSyncer } from "./BridgeStateSyncer"; @@ -111,6 +112,11 @@ export class IrcBridge { userStore: `${dirPath}/users.db`, }; } + else { + bridgeStoreConfig = { + disableStores: true, + }; + } this.membershipCache = new MembershipCache(); this.bridge = new Bridge({ registration: this.registration, @@ -121,7 +127,7 @@ export class IrcBridge { onUserQuery: this.onUserQuery.bind(this), onAliasQuery: this.onAliasQuery.bind(this), onAliasQueried: this.onAliasQueried ? - this.onAliasQueried.bind(this) : null, + this.onAliasQueried.bind(this) : undefined, onLog: this.onLog.bind(this), thirdPartyLookup: { @@ -159,9 +165,8 @@ export class IrcBridge { migrateStoreEntries: false, // Only NeDB supports this. }, membershipCache: this.membershipCache, - migrateStoreEntries: false, }); - this.membershipQueue = new MembershipQueue(this.bridge); + this.membershipQueue = new MembershipQueue(this.bridge, this.appServiceUserId); this.matrixHandler = new MatrixHandler(this, this.config.matrixHandler || {}, this.membershipQueue); this.ircHandler = new IrcHandler(this, this.config.ircHandler, this.membershipQueue); @@ -183,7 +188,7 @@ export class IrcBridge { } private initialiseMetrics(bindPort: number) { - const zeroAge = new PrometheusMetrics.AgeCounters(); + const zeroAge = new AgeCounters(); const registry = new Registry(); if (!this.config.ircService.metrics) { @@ -316,6 +321,10 @@ export class IrcBridge { // Only collect if defined const currentTime = Date.now(); const appserviceBot = this.bridge.getBot(); + if (!appserviceBot) { + // Not ready yet. + return; + } this.dataStore.getLastSeenTimeForUsers().then((userSet) => { let remote = 0; let matrix = 0; @@ -406,13 +415,26 @@ export class IrcBridge { } else if (dbConfig.engine === "nedb") { await this.bridge.loadDatabases(); + const userStore = this.bridge.getUserStore(); + const roomStore = this.bridge.getRoomStore(); log.info("Using NeDBDataStore for Datastore"); + if (!userStore || !roomStore) { + throw Error('Could not load userStore or roomStore'); + } this.dataStore = new NeDBDataStore( - this.bridge.getUserStore(), - this.bridge.getRoomStore(), + userStore, + roomStore, this.config.homeserver.domain, pkeyPath, ); + if (this.config.ircService.debugApi.enabled) { + // monkey patch inspect() values to avoid useless NeDB + // struct spam on the debug API. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (userStore as any).inspect = () => "UserStore"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (roomStore as any).inspect = () => "RoomStore"; + } } else { throw Error("Incorrect database config"); @@ -428,12 +450,6 @@ export class IrcBridge { this.clientPool, this.registration.getAppServiceToken() as string ); - if (this.dataStore instanceof NeDBDataStore) { - // monkey patch inspect() values to avoid useless NeDB - // struct spam on the debug API. - this.bridge.getUserStore().inspect = () => "UserStore"; - this.bridge.getRoomStore().inspect = () => "RoomStore"; - } this.debugApi.run(); } @@ -537,8 +553,12 @@ export class IrcBridge { room_id: roomId, content: { displayname: displayName, + membership: "join", }, _injected: true, + state_key: joiningUserId, + type: "m.room.member", + event_id: "!injected", _frontier: isFrontier }, target); } @@ -574,7 +594,7 @@ export class IrcBridge { this.startedUp = true; } - private logMetric(req: Request, outcome: string) { + private logMetric(req: Request, outcome: string) { if (!this.timers) { return; // metrics are disabled } @@ -588,7 +608,7 @@ export class IrcBridge { } private addRequestCallbacks() { - function logMessage(req: Request, msg: string) { + function logMessage(req: Request, msg: string) { const data = req.getData(); const dir = data && data.isFromIrc ? "I->M" : "M->I"; const duration = " (" + req.getDuration() + "ms)"; @@ -598,37 +618,40 @@ export class IrcBridge { // SUCCESS this.bridge.getRequestFactory().addDefaultResolveCallback((req, _res) => { const res = _res as BridgeRequestErr|null; + const bridgeRequest = req as Request; if (res === BridgeRequestErr.ERR_VIRTUAL_USER) { - logMessage(req, "IGNORE virtual user"); + logMessage(bridgeRequest, "IGNORE virtual user"); return; // these aren't true successes so don't skew graphs } else if (res === BridgeRequestErr.ERR_NOT_MAPPED) { - logMessage(req, "IGNORE not mapped"); + logMessage(bridgeRequest, "IGNORE not mapped"); return; // these aren't true successes so don't skew graphs } else if (res === BridgeRequestErr.ERR_DROPPED) { - logMessage(req, "IGNORE dropped"); - this.logMetric(req, "dropped"); + logMessage(bridgeRequest, "IGNORE dropped"); + this.logMetric(bridgeRequest, "dropped"); return; } - logMessage(req, "SUCCESS"); - this.logMetric(req, "success"); + logMessage(bridgeRequest, "SUCCESS"); + this.logMetric(bridgeRequest, "success"); }); // FAILURE this.bridge.getRequestFactory().addDefaultRejectCallback((req) => { - logMessage(req, "FAILED"); - this.logMetric(req, "fail"); - BridgeRequest.HandleExceptionForSentry(req, "fail"); + const bridgeRequest = req as Request; + logMessage(bridgeRequest, "FAILED"); + this.logMetric(bridgeRequest, "fail"); + BridgeRequest.HandleExceptionForSentry(req as Request, "fail"); }); // DELAYED this.bridge.getRequestFactory().addDefaultTimeoutCallback((req) => { - logMessage(req, "DELAYED"); + logMessage(req as Request, "DELAYED"); }, DELAY_TIME_MS); // DEAD this.bridge.getRequestFactory().addDefaultTimeoutCallback((req) => { - logMessage(req, "DEAD"); - this.logMetric(req, "dead"); - BridgeRequest.HandleExceptionForSentry(req, "dead"); + const bridgeRequest = req as Request; + logMessage(bridgeRequest, "DEAD"); + this.logMetric(bridgeRequest, "dead"); + BridgeRequest.HandleExceptionForSentry(req as Request, "dead"); }, DEAD_TIME_MS); } @@ -645,6 +668,7 @@ export class IrcBridge { if (this.dataStore) { await this.dataStore.destroy(); } + await this.appservice.close(); } public get isStartedUp() { @@ -728,11 +752,11 @@ export class IrcBridge { return matrixUser; } - public onEvent(request: Request) { + public onEvent(request: BridgeRequestEvent) { request.outcomeFrom(this._onEvent(request)); } - private async _onEvent (baseRequest: Request): Promise { + private async _onEvent (baseRequest: BridgeRequestEvent): Promise { const event = baseRequest.getData(); let updatePromise: Promise|null = null; if (event.sender && (this.activityTracker || @@ -755,34 +779,40 @@ export class IrcBridge { return BridgeRequestErr.ERR_DROPPED; } } - await this.matrixHandler.onMessage(request, event); + // Cheeky crafting event into MatrixMessageEvent + await this.matrixHandler.onMessage(request, event as unknown as MatrixMessageEvent); } else if (event.type === "m.room.topic" && event.state_key === "") { - await this.matrixHandler.onMessage(request, event); + await this.matrixHandler.onMessage(request, event as unknown as MatrixMessageEvent); } - else if (event.type === "m.room.member") { + else if (event.type === "m.room.member" && event.state_key) { if (!event.content || !event.content.membership) { return BridgeRequestErr.ERR_NOT_MAPPED; } - this.ircHandler.onMatrixMemberEvent(event); + this.ircHandler.onMatrixMemberEvent({...event, state_key: event.state_key, content: { + membership: event.content.membership as MatrixMembership, + }}); const target = new MatrixUser(event.state_key); const sender = new MatrixUser(event.sender); + // We must define `state_key` explicitly again for TS to be happy. + const memberEvent = {...event, state_key: event.state_key}; if (event.content.membership === "invite") { - await this.matrixHandler.onInvite(request, event, sender, target); + await this.matrixHandler.onInvite(request, + memberEvent as unknown as MatrixEventInvite, sender, target); } else if (event.content.membership === "join") { - await this.matrixHandler.onJoin(request, event, target); + await this.matrixHandler.onJoin(request, memberEvent as unknown as OnMemberEventData, target); } - else if (["ban", "leave"].includes(event.content.membership)) { + else if (["ban", "leave"].includes(event.content.membership as string)) { // Given a "self-kick" is a leave, and you can't ban yourself, // if the 2 IDs are different then we know it is either a kick // or a ban (or a rescinded invite) const isKickOrBan = target.getId() !== sender.getId(); if (isKickOrBan) { - await this.matrixHandler.onKick(request, event, sender, target); + await this.matrixHandler.onKick(request, memberEvent as unknown as MatrixEventKick, sender, target); } else { - await this.matrixHandler.onLeave(request, event, target); + await this.matrixHandler.onLeave(request, memberEvent, target); } } } @@ -800,7 +830,7 @@ export class IrcBridge { } public async onUserQuery(matrixUser: MatrixUser) { - const baseRequest = this.bridge.getRequestFactory().newRequest(); + const baseRequest = this.bridge.getRequestFactory().newRequest(); const request = new BridgeRequest(baseRequest); await this.matrixHandler.onUserQuery(request, matrixUser.getId()); // TODO: Lean on the bridge lib more @@ -808,7 +838,7 @@ export class IrcBridge { } public async onAliasQuery (alias: string) { - const baseRequest = this.bridge.getRequestFactory().newRequest(); + const baseRequest = this.bridge.getRequestFactory().newRequest(); const request = new BridgeRequest(baseRequest); await this.matrixHandler.onAliasQuery(request, alias); // TODO: Lean on the bridge lib more @@ -824,10 +854,10 @@ export class IrcBridge { } } - public getThirdPartyProtocol() { + public async getThirdPartyProtocol() { const servers = this.getServers(); - return Bluebird.resolve({ + return { user_fields: ["domain", "nick"], location_fields: ["domain", "channel"], field_types: { @@ -846,6 +876,9 @@ export class IrcBridge { placeholder: "#channel", }, }, + // TODO: The spec requires we return an icon, but we don't have support + // for one yet. + icon: "", instances: servers.map((server: IrcServer) => { return { network_id: server.getNetworkId(), @@ -857,7 +890,7 @@ export class IrcBridge { }, }; }), - }); + }; } public async getThirdPartyLocation(protocol: string, fields: {domain?: string; channel?: string}) { @@ -1058,6 +1091,9 @@ export class IrcBridge { * so it will block indefinitely. */ const bot = this.bridge.getBot(); + if (!bot) { + throw Error('AppserviceBot is not ready'); + } let gotRooms = false; while (!gotRooms) { try { @@ -1081,10 +1117,12 @@ export class IrcBridge { const rooms = await this.getStore().getIrcChannelsForRoomId(newRoomId); // Get users who we wish to leave. const asBot = this.bridge.getBot(); + if (!asBot) { + throw Error('AppserviceBot is not ready'); + } log.info("Migrating state"); const stateEvents = await asBot.getClient().roomState(oldRoomId); - //TODO: _getRoomInfo is a private func and should be replaced. - const roomInfo = asBot._getRoomInfo(oldRoomId, { + const roomInfo = await asBot.getRoomInfo(oldRoomId, { state: { events: stateEvents } diff --git a/src/bridge/IrcHandler.ts b/src/bridge/IrcHandler.ts index 93467794b..7d10759c1 100644 --- a/src/bridge/IrcHandler.ts +++ b/src/bridge/IrcHandler.ts @@ -19,7 +19,7 @@ const NICK_USERID_CACHE_MAX = 512; const PM_POWERLEVEL_MATRIXUSER = 10; const PM_POWERLEVEL_IRCUSER = 100; -type MatrixMembership = "join"|"invite"|"leave"|"ban"; +export type MatrixMembership = "join"|"invite"|"leave"|"ban"; interface RoomIdtoPrivateMember { [roomId: string]: { diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 2ae8b8809..341e1b0b8 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -1,6 +1,6 @@ import { IrcBridge } from "./IrcBridge"; import { BridgeRequest, BridgeRequestErr } from "../models/BridgeRequest"; -import { MatrixUser, MatrixRoom, StateLookup } from "matrix-appservice-bridge"; +import { MatrixUser, MatrixRoom, StateLookup, StateLookupEvent } from "matrix-appservice-bridge"; import { IrcUser } from "../models/IrcUser"; import { MatrixAction, MatrixMessageEvent } from "../models/MatrixAction"; import { IrcRoom } from "../models/IrcRoom"; @@ -32,20 +32,28 @@ const DEFAULT_EVENT_CACHE_SIZE = 4096; /* Length of the source text in a formatted reply message */ const REPLY_SOURCE_MAX_LENGTH = 32; -interface MatrixEventInvite { +export interface MatrixEventInvite { room_id: string; state_key: string; sender: string; content: { is_direct?: boolean; + membership: "invite"; }; + type: string; + event_id: string; } -interface MatrixEventKick { +export interface MatrixEventKick { room_id: string; + sender: string; + state_key: string; content: { reason?: string; + membership: "leave"; }; + type: string; + event_id: string; } interface MatrixSimpleMessage { @@ -55,18 +63,22 @@ interface MatrixSimpleMessage { }; } -interface MatrixEventJoin { - _frontier: boolean; - _injected: boolean; +interface MatrixEventLeave { room_id: string; - content?: { - displayname?: string; - }; + _injected?: boolean; } -interface MatrixEventLeave { +export interface OnMemberEventData { + _frontier?: boolean; + _injected?: boolean; room_id: string; - _injected: boolean; + state_key: string; + type: string; + event_id: string; + content: { + displayname?: string; + membership: string; + }; } export class MatrixHandler { @@ -222,10 +234,12 @@ export class MatrixHandler { // First call begins tracking, subsequent calls do nothing await this.memberTracker.trackRoom(adminRoom.getId()); - members = this.memberTracker.getState( + members = (this.memberTracker.getState( adminRoom.getId(), - 'm.room.member' - ).filter((m) => m.content.membership && m.content.membership === "join"); + "m.room.member", + ) as Array).filter((m) => + (m.content as {membership: string}).membership === "join" + ); } else { req.log.warn('Member tracker not running'); @@ -324,9 +338,14 @@ export class MatrixHandler { * Called when the AS receives a new Matrix invite/join/leave event. * @param {Object} event : The Matrix member event. */ - private async _onMemberEvent(req: BridgeRequest, event: unknown) { + private async _onMemberEvent(req: BridgeRequest, event: OnMemberEventData) { if (!this.memberTracker) { - const matrixClient = this.ircBridge.getAppServiceBridge().getClientFactory().getClientAs(); + const clientFactory = this.ircBridge.getAppServiceBridge().getClientFactory(); + if (!clientFactory) { + // Client factory isn't ready...yet. + return; + } + const matrixClient = clientFactory.getClientAs(); this.memberTracker = new StateLookup({ client : matrixClient, @@ -438,7 +457,7 @@ export class MatrixHandler { return null; } - private async _onJoin(req: BridgeRequest, event: MatrixEventJoin, user: MatrixUser): + private async _onJoin(req: BridgeRequest, event: OnMemberEventData, user: MatrixUser): Promise { req.log.info("onJoin: %s", JSON.stringify(event)); this._onMemberEvent(req, event); @@ -1159,7 +1178,7 @@ export class MatrixHandler { // EXPORTS - public onMemberEvent(req: BridgeRequest, event: unknown) { + public onMemberEvent(req: BridgeRequest, event: OnMemberEventData) { return reqHandler(req, this._onMemberEvent(req, event)); } @@ -1167,11 +1186,11 @@ export class MatrixHandler { return reqHandler(req, this._onInvite(req, event, inviter, invitee)); } - public onJoin(req: BridgeRequest, event: MatrixEventJoin, user: MatrixUser) { + public onJoin(req: BridgeRequest, event: OnMemberEventData, user: MatrixUser) { return reqHandler(req, this._onJoin(req, event, user)); } - public onLeave(req: BridgeRequest, event: { room_id: string; _injected: boolean }, user: MatrixUser) { + public onLeave(req: BridgeRequest, event: { room_id: string; _injected?: boolean }, user: MatrixUser) { return reqHandler(req, this._onLeave(req, event, user)); } diff --git a/src/bridge/MemberListSyncer.ts b/src/bridge/MemberListSyncer.ts index 5f6da45b0..807821d25 100644 --- a/src/bridge/MemberListSyncer.ts +++ b/src/bridge/MemberListSyncer.ts @@ -3,7 +3,7 @@ import Bluebird from "bluebird"; import { IrcBridge } from "./IrcBridge"; -import { AppserviceBot } from "matrix-appservice-bridge"; +import { AppServiceBot } from "matrix-appservice-bridge"; import { IrcServer } from "../irc/IrcServer"; import { QueuePool } from "../util/QueuePool"; import logging from "../logging"; @@ -50,7 +50,7 @@ export class MemberListSyncer { matrix: {}, } private leaveQueuePool: QueuePool; - constructor(private ircBridge: IrcBridge, private appServiceBot: AppserviceBot, private server: IrcServer, + constructor(private ircBridge: IrcBridge, private appServiceBot: AppServiceBot, private server: IrcServer, private appServiceUserId: string, private injectJoinFn: InjectJoinFn) { // A queue which controls the rate at which leaves are sent to Matrix. We need this queue // because Synapse is slow. Synapse locks based on the room ID, so there is no benefit to @@ -195,7 +195,7 @@ export class MemberListSyncer { // fetch joined members allowing 50 in-flight reqs at a time const pool = new QueuePool(50, async (_roomId) => { const roomId = _roomId as string; - let userMap = null; + let userMap: Record|undefined; while (!userMap) { try { userMap = await this.appServiceBot.getJoinedMembers(roomId); diff --git a/src/datastore/DataStore.ts b/src/datastore/DataStore.ts index fec7c267f..41a920aaa 100644 --- a/src/datastore/DataStore.ts +++ b/src/datastore/DataStore.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixRoom, MatrixUser, Entry} from "matrix-appservice-bridge"; +import { MatrixRoom, MatrixUser, RoomBridgeStoreEntry as Entry} from "matrix-appservice-bridge"; import Bluebird from "bluebird"; import { IrcRoom } from "../models/IrcRoom"; import { IrcClientConfig } from "../models/IrcClientConfig"; diff --git a/src/datastore/NedbDataStore.ts b/src/datastore/NedbDataStore.ts index 9db13001d..5d028981b 100644 --- a/src/datastore/NedbDataStore.ts +++ b/src/datastore/NedbDataStore.ts @@ -20,7 +20,7 @@ import { IrcClientConfig, IrcClientConfigSeralized } from "../models/IrcClientCo import { getLogger } from "../logging"; import { MatrixRoom, MatrixUser, RemoteUser, RemoteRoom, - UserBridgeStore, RoomBridgeStore, Entry } from "matrix-appservice-bridge"; + UserBridgeStore, RoomBridgeStore, RoomBridgeStoreEntry as Entry } from "matrix-appservice-bridge"; import { DataStore, RoomOrigin, ChannelMappings, UserFeatures } from "./DataStore"; import { IrcServer, IrcServerConfig } from "../irc/IrcServer"; import { StringCrypto } from "./StringCrypto"; @@ -171,7 +171,9 @@ export class NeDBDataStore implements DataStore { * $roomId => [{networkId: 'server #channel1', channel: '#channel2'} , ...] */ public async getAllChannelMappings(): Promise { - const entries = await this.roomStore.select( + const entries = await this.roomStore.select< + unknown, + { remote: { domain: string; channel: string}; matrix_id: string}>( { matrix_id: {$exists: true}, remote_id: {$exists: true}, @@ -181,7 +183,7 @@ export class NeDBDataStore implements DataStore { const mappings: ChannelMappings = {}; - entries.forEach((e: { remote: { domain: string; channel: string}; matrix_id: string}) => { + entries.forEach(e => { const domain = e.remote.domain; const channel = e.remote.channel; // drop unknown irc networks in the database @@ -230,7 +232,7 @@ export class NeDBDataStore implements DataStore { throw new Error('Origin must be a string = "config"|"provision"|"alias"|"join"'); } - return await this.roomStore.delete({ + await this.roomStore.delete({ id: NeDBDataStore.createMappingId(roomId, ircDomain, ircChannel), 'data.origin': origin }); @@ -309,7 +311,7 @@ export class NeDBDataStore implements DataStore { return true; } } - return e.data && origin.includes(e.data.origin); + return e.data && origin.includes(e.data.origin as RoomOrigin); }); }); } @@ -379,7 +381,7 @@ export class NeDBDataStore implements DataStore { if (!entry) { return null; } - return entry.matrix; + return entry.matrix || null; } public async getTrackedChannelsForServer(domain: string) { @@ -473,6 +475,8 @@ export class NeDBDataStore implements DataStore { await this.roomStore.upsertEntry({ id: NeDBDataStore.createAdminId(userId), matrix: room, + remote: undefined, + data: {}, }); } @@ -482,10 +486,7 @@ export class NeDBDataStore implements DataStore { public async getAdminRoomByUserId(userId: string): Promise { const entry = await this.roomStore.getEntryById(NeDBDataStore.createAdminId(userId)); - if (!entry) { - return null; - } - return entry.matrix; + return entry?.matrix || null; } public async storeMatrixUser(matrixUser: MatrixUser): Promise { @@ -625,19 +626,19 @@ export class NeDBDataStore implements DataStore { } public async getLastSeenTimeForUsers() { - const docs = await this.userStore.select({ + const docs = await this.userStore.select({ type: "matrix", "data.last_seen_ts": {$exists: true}, }); - return docs.map((doc: {id: string; data: { last_seen_ts: number }}) => ({ + return docs.map(doc => ({ user_id: doc.id, ts: doc.data.last_seen_ts, })); } public async getAllUserIds() { - const docs = await this.userStore.select({ type: "matrix" }); - return docs.map((e: {id: string}) => e.id); + const docs = await this.userStore.select({ type: "matrix" }); + return docs.map(e => e.id); } public async getRoomVisibility(roomId: string) { @@ -701,7 +702,7 @@ export class NeDBDataStore implements DataStore { log.info("Not migrating room, room doesn't exist in datastore"); continue; } - const origin = room.data.origin; + const origin = room.data.origin as RoomOrigin; await this.removeRoom(oldRoomId, ircRoom.server.domain, ircRoom.channel, origin); log.debug(`Removed old room ${oldRoomId}`); await this.storeRoom(ircRoom, new MatrixRoom(newRoomId), origin); diff --git a/src/datastore/postgres/PgDataStore.ts b/src/datastore/postgres/PgDataStore.ts index de44bd99f..5935df7bc 100644 --- a/src/datastore/postgres/PgDataStore.ts +++ b/src/datastore/postgres/PgDataStore.ts @@ -16,7 +16,7 @@ limitations under the License. import { Pool } from "pg"; -import { MatrixUser, MatrixRoom, RemoteRoom, Entry } from "matrix-appservice-bridge"; +import { MatrixUser, MatrixRoom, RemoteRoom, RoomBridgeStoreEntry as Entry } from "matrix-appservice-bridge"; import { DataStore, RoomOrigin, ChannelMappings, UserFeatures } from "../DataStore"; import { IrcRoom } from "../../models/IrcRoom"; import { IrcClientConfig } from "../../models/IrcClientConfig"; @@ -126,8 +126,6 @@ export class PgDataStore implements DataStore { domain: pgEntry.irc_domain, type: pgEntry.type, }), - matrix_id: pgEntry.room_id, - remote_id: "foobar", data: { origin: pgEntry.origin, }, diff --git a/src/irc/ClientPool.ts b/src/irc/ClientPool.ts index f04ff43ed..acf0c5c3f 100644 --- a/src/irc/ClientPool.ts +++ b/src/irc/ClientPool.ts @@ -20,7 +20,7 @@ import Bluebird from "bluebird"; import { BridgeRequest } from "../models/BridgeRequest"; import { IrcClientConfig } from "../models/IrcClientConfig"; import { IrcServer } from "../irc/IrcServer"; -import { PrometheusMetrics, MatrixUser, MatrixRoom } from "matrix-appservice-bridge"; +import { AgeCounters, MatrixUser, MatrixRoom } from "matrix-appservice-bridge"; import { BridgedClient, BridgedClientStatus } from "./BridgedClient"; import { IrcBridge } from "../bridge/IrcBridge"; import { IdentGenerator } from "./IdentGenerator"; @@ -463,7 +463,7 @@ export class ClientPool { return 0; } - public updateActiveConnectionMetrics(serverDomain: string, ageCounter: PrometheusMetrics.AgeCounters): void { + public updateActiveConnectionMetrics(serverDomain: string, ageCounter: AgeCounters): void { if (this.virtualClients[serverDomain] === undefined) { return; } diff --git a/src/main.ts b/src/main.ts index 530b811b6..e14ffbbd4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -110,9 +110,7 @@ export async function runBridge(port: number, config: BridgeConfig, reg: AppServ ircBridge.getAppServiceBridge().opts.userStore = new UserBridgeStore(new Datastore()); } else if (engine === "postgres") { - // Enforce these not to be created - ircBridge.getAppServiceBridge().opts.roomStore = undefined; - ircBridge.getAppServiceBridge().opts.userStore = undefined; + // Do nothing } else if (engine !== "nedb") { throw Error("Invalid database configuration"); diff --git a/src/models/BridgeRequest.ts b/src/models/BridgeRequest.ts index d13c94cb1..6fc2a17b2 100644 --- a/src/models/BridgeRequest.ts +++ b/src/models/BridgeRequest.ts @@ -20,10 +20,27 @@ import * as Sentry from "@sentry/node"; const log = getLogger("req"); +export type BridgeRequestEvent = Request<{ + event_id: string; + sender: string; + type: string; + state_key?: string; + room_id: string; + content: Record; + origin_server_ts: number; +}>; + +export type BridgeRequestData = { + isFromIrc?: boolean; + event_id?: string; + room_id?: string; + type?: string; +}|null; + export class BridgeRequest { log: RequestLogger; - constructor(private req: Request) { - const isFromIrc = req.getData() ? Boolean(req.getData().isFromIrc) : false; + constructor(private req: Request) { + const isFromIrc = req.getData() ? Boolean(req.getData()?.isFromIrc) : false; this.log = newRequestLogger(log, req.getId(), isFromIrc); } @@ -39,7 +56,7 @@ export class BridgeRequest { this.req.reject(err); } - public static HandleExceptionForSentry(req: Request, state: "fail"|"dead") { + public static HandleExceptionForSentry(req: Request, state: "fail"|"dead") { const reqData = req.getData() || {}; req.getPromise().catch((ex: Error) => { Sentry.withScope((scope) => { diff --git a/src/provisioning/Provisioner.ts b/src/provisioning/Provisioner.ts index 3f2b3fe52..02db16a99 100644 --- a/src/provisioning/Provisioner.ts +++ b/src/provisioning/Provisioner.ts @@ -5,7 +5,7 @@ import { ConfigValidator, MatrixRoom, MatrixUser } from "matrix-appservice-bridg import Bluebird from "bluebird"; import { IrcRoom } from "../models/IrcRoom"; import { IrcAction } from "../models/IrcAction"; -import { BridgeRequest } from "../models/BridgeRequest"; +import { BridgeRequest, BridgeRequestData } from "../models/BridgeRequest"; import { ProvisionRequest } from "./ProvisionRequest"; import logging, { RequestLogger } from "../logging"; import * as promiseutil from "../promiseutil"; @@ -106,28 +106,29 @@ export class Provisioner { } const appservice = this.ircBridge.getAppServiceBridge().appService; - // TODO: Make app public - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const app = ((appservice as any).app as express.Router); - - if (enabled && !(app.use && app.get && app.post)) { - throw new Error('Could not start provisioning API'); - } + const app = appservice?.expressApp; // Disable all provision endpoints by not calling 'next' and returning an error instead if (!enabled) { - app.use((req, res, next) => { - if (this.isProvisionRequest(req)) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept"); - res.status(500); - res.json({error : 'Provisioning is not enabled.'}); - } - else { - next(); - } - }); + if (app) { + app.use((req, res, next) => { + if (this.isProvisionRequest(req)) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept"); + res.status(500); + res.json({error : 'Provisioning is not enabled.'}); + } + else { + next(); + } + }); + } + return; + } + + if (!app) { + throw new Error('Could not start provisioning API'); } app.use((req, res, next) => { @@ -171,9 +172,7 @@ export class Provisioner { this.createProvisionEndpoint(this.getLimits, 'limits') ); - if (enabled) { - log.info("Provisioning started"); - } + log.info("Provisioning started"); } private createProvisionEndpoint(fn: (req: ProvisionRequest) => unknown, fnName: string) { @@ -365,7 +364,7 @@ export class Provisioner { try { const roomState = await matrixClient.roomState(roomId); wholeBridgingState = roomState.find( - (e) => { + (e: {type: string; state_key: string}) => { return e.type === 'm.room.bridging' && e.state_key === skey } ); @@ -820,7 +819,7 @@ export class Provisioner { try { // Cause the provisioner to join the IRC channel const bridgeReq = new BridgeRequest( - this.ircBridge.getAppServiceBridge().getRequestFactory().newRequest() + this.ircBridge.getAppServiceBridge().getRequestFactory().newRequest() ); const target = new MatrixUser(userId); // inject a fake join event which will do M->I connections and @@ -828,7 +827,13 @@ export class Provisioner { await this.ircBridge.matrixHandler.onJoin(bridgeReq, { room_id: roomId, _injected: true, - _frontier: true + _frontier: true, + state_key: userId, + type: "m.room.member", + content: { + membership: "join" + }, + event_id: "!injected_provisioner", }, target); } catch (err) { @@ -877,16 +882,23 @@ export class Provisioner { // user_id must be JOINED and must have permission to modify power levels let isJoined = false; let hasPower = false; - stateEvents.forEach((e) => { + stateEvents.forEach((e: { type: string; state_key: string; content: { + state_default?: number; + users_default?: number; + membership: string; + users?: Record; + events?: Record; + };}) => { if (e.type === "m.room.member" && e.state_key === options.user_id) { isJoined = e.content.membership === "join"; } else if (e.type === "m.room.power_levels" && e.state_key === "") { - let powerRequired = e.content.state_default; + // https://matrix.org/docs/spec/client_server/r0.6.0#m-room-power-levels + let powerRequired = e.content.state_default || 50; // Can be empty. Assume 50 as per spec. if (e.content.events && e.content.events["m.room.power_levels"]) { powerRequired = e.content.events["m.room.power_levels"]; } - let power = e.content.users_default; + let power = e.content.users_default || 0; // Can be empty. Assume 0 as per spec. if (e.content.users && e.content.users[options.user_id]) { power = e.content.users[options.user_id]; } @@ -986,7 +998,7 @@ export class Provisioner { events: stateEvents } } - const roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), joinedRoom); + const roomInfo = await asBot.getRoomInfo(matrixRooms[i].getId(), joinedRoom); for (let j = 0; j < roomInfo.realJoinedUsers.length; j++) { const userId: string = roomInfo.realJoinedUsers[j]; if (!joinedUserCounts[userId]) { @@ -1031,7 +1043,7 @@ export class Provisioner { return; } const stateEvents = await asBot.getClient().roomState(roomId); - const roomInfo = asBot._getRoomInfo(roomId, { + const roomInfo = await asBot.getRoomInfo(roomId, { state: { events: stateEvents } diff --git a/src/util/MembershipQueue.ts b/src/util/MembershipQueue.ts index 5a0a96149..3d26b62cc 100644 --- a/src/util/MembershipQueue.ts +++ b/src/util/MembershipQueue.ts @@ -15,7 +15,7 @@ interface QueueUserItem { reason?: string; attempts: number; roomId: string; - userId?: string; + userId: string; retry: boolean; req: BridgeRequest; } @@ -26,7 +26,7 @@ interface QueueUserItem { export class MembershipQueue { private queuePool: QueuePool; - constructor(private bridge: Bridge) { + constructor(private bridge: Bridge, private botUserId: string) { this.queuePool = new QueuePool(CONCURRENT_ROOM_LIMIT, this.serviceQueue.bind(this)); } @@ -40,7 +40,7 @@ export class MembershipQueue { public async join(roomId: string, userId: string|undefined, req: BridgeRequest, retry = true) { return this.queueMembership({ roomId, - userId, + userId: userId || this.botUserId, retry, req, attempts: 0, @@ -61,7 +61,7 @@ export class MembershipQueue { retry = true, reason?: string, kickUser?: string) { return this.queueMembership({ roomId, - userId, + userId: userId || this.botUserId, retry, req, attempts: 0, @@ -85,16 +85,24 @@ export class MembershipQueue { return Array.from(roomId).map((s) => s.charCodeAt(0)).reduce((a, b) => a + b, 0) % CONCURRENT_ROOM_LIMIT; } - private async serviceQueue(item: QueueUserItem): Promise { - log.debug(`${item.userId}@${item.roomId} -> ${item.type}`); - const { req, roomId, userId, reason, kickUser, attempts } = item; + private async serviceQueue(item: QueueUserItem) { + const { req, roomId, userId, reason, kickUser, attempts, type } = item; + log.debug(`${userId}@${roomId} -> ${type} (reason: ${reason || "none"}, kicker: ${kickUser})`); const intent = this.bridge.getIntent(kickUser || userId); try { - if (item.type === "join") { + if (type === "join") { await intent.join(roomId); } + + if (kickUser) { + await intent.kick(roomId, userId, reason); + } + else if (reason) { + // Self kick to add a reason + await intent.kick(roomId, userId, reason); + } else { - await intent[kickUser ? "kick" : "leave"](roomId, userId || "", reason); + await intent.leave(roomId); } } catch (ex) { diff --git a/tsconfig.json b/tsconfig.json index 315a60161..96fb49131 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,10 +11,7 @@ "composite": false, "strict": true, "esModuleInterop": true, - "strictNullChecks": true, - "typeRoots": [ - "./types" - ] + "strictNullChecks": true }, "include": [ "src/**/*" diff --git a/types/matrix-appservice-bridge/index.d.ts b/types/matrix-appservice-bridge/index.d.ts deleted file mode 100644 index ff1a7d158..000000000 --- a/types/matrix-appservice-bridge/index.d.ts +++ /dev/null @@ -1,297 +0,0 @@ -/* -Copyright 2019 Huan LI -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -/** - * This has been borrowed from https://github.com/huan/matrix-appservice-wechaty/blob/master/src/typings/matrix-appservice-bridge.d.ts - * under the Apache2 licence. - */ - -declare module 'matrix-appservice-bridge' { - - namespace PrometheusMetrics { - class AgeCounters { - constructor(buckets?: string[]); - bump (ageInSec: number): void; - } - } - - export class PrometheusMetrics { - addCollector(cb: () => void): void; - addCounter(opts: { name: string; help: string; labels: string[]; }): import("prom-client").Counter - addTimer(opts: { name: string; help: string; labels: string[]; }): import("prom-client").Histogram; - addGauge(arg0: { name: string; help: string; labels: string[]; }): import("prom-client").Gauge; - refresh(): void; - } - - interface RoomMemberDict { - [id: string]: { - display_name: string; - avatar_url: string; - }; - } - interface RemoteRoomDict { - [id: string]: RemoteRoom[]; - } - interface EntryDict { - [id: string]: Array; - } - - interface RoomCreationOpts { - room_alias_name: string; // localpart - name: string; - visibility: "public"|"private"; - preset: "public_chat"; - creation_content?: { - "m.federate"?: boolean; - }; - initial_state: any[]; - room_version?: string; - } - - export interface Entry { - id: string; // The unique ID for this entry. - matrix_id: string; // "room_id", - remote_id: string; // "remote_room_id", - matrix: null|MatrixRoom; // The matrix room, if applicable. - remote: null|RemoteRoom; // The remote room, if applicable. - data: null|any; // Information about this mapping, which may be an empty - } - - export interface UpsertableEntry { - id: string; // The unique ID for this entry. - matrix?: null|MatrixRoom; // The matrix room, if applicable. - remote?: null|RemoteRoom; // The remote room, if applicable. - data?: null|any; // Information about this mapping, which may be an empty. - } - - export class AppserviceBot { - getJoinedMembers(roomId: string): {[userId: string]: {display_name: string|null}} - isRemoteUser(userId: string): boolean; - getJoinedRooms(): Promise; - getClient(): JsClient; - //TODO: _getRoomInfo is a private func and should be replaced. - _getRoomInfo(roomId: string, data: any): any; - } - - export class MatrixRoom { - protected roomId: string; - public name: string; - public topic: string; - public _extras : any; - constructor (roomId: string, data?: object); - deserialize(data: object): void; - get(key: string): unknown; - getId(): string; - serialize(): object; - set(key: string, val: any): void; - } - - export class MatrixUser { - public static ESCAPE_DEFAULT: boolean; - public readonly localpart: string - public readonly host: string - - userId: string - - constructor (userId: string, data?: object, escape?: boolean) - escapeUserId(): void - get(key: string): unknown - getDisplayName(): null|string - getId(): string - serialize(): object - set(key: string, val: any): void - setDisplayName(name: string): void - } - - export class RemoteRoom { - constructor (identifier: string, data?: object) - get(key: string): unknown - getId(): string - serialize(): object - set(key: string, val: object|string|number): void - } - - export class RemoteUser { - constructor (id: string, data?: object) - get(key: string): unknown - getId(): string - serialize(): object - set(key: string, val: object|string|number): void - } - - export class BridgeStore { - db: Nedb - delete (query: any): Promise - insert (query: any): Promise - select (query: any, transformFn?: (item: Entry) => any): Promise - inspect: () => string; - } - - export class RoomBridgeStore extends BridgeStore { - constructor(ds: Nedb); - batchGetLinkedRemoteRooms (matrixIds: Array): Promise - getEntriesByLinkData (data: object): Promise> - getEntriesByMatrixId (matrixId: string): Promise> - getEntriesByMatrixIds (ids: Array): Promise - getEntriesByMatrixRoomData (data: object): Promise> - getEntriesByRemoteId (remoteId: string): Promise> - getEntriesByRemoteRoomData (data: object): Promise> - getEntryById (id: string): Promise - getLinkedMatrixRooms (remoteId: string): Promise> - getLinkedRemoteRooms (matrixId: string): Promise> - getMatrixRoom (roomId: string): Promise - removeEntriesByLinkData (data: object): Promise - removeEntriesByMatrixRoomData (data: object): Promise - removeEntriesByMatrixRoomId (matrixId: string): Promise - removeEntriesByRemoteRoomData (data: object): Promise - removeEntriesByRemoteRoomId (remoteId: string): Promise - setMatrixRoom (matrixRoom: MatrixRoom): Promise - upsertEntry (entry: UpsertableEntry): Promise - linkRooms ( - matrixRoom: MatrixRoom, - remoteRoom: RemoteRoom, - data?: object, - linkId?: string, - ): Promise - } - - export class UserBridgeStore extends BridgeStore { - constructor(ds: Nedb); - getByMatrixData (dataQuery: object): Promise> - getByMatrixLocalpart (localpart: string): Promise - getByRemoteData (dataQuery: object): Promise> - getMatrixLinks (remoteId: string): Promise> - getMatrixUser (userId: string): Promise - getMatrixUsersFromRemoteId (remoteId: string): Promise> - getRemoteLinks (matrixId: string): Promise> - getRemoteUser (id: string): Promise - getRemoteUsersFromMatrixId (userId: string): Promise> - linkUsers (matrixUser: MatrixUser, remoteUser: RemoteUser): Promise - setMatrixUser (matrixUser: MatrixUser): Promise - setRemoteUser (remoteUser: RemoteUser): Promise - unlinkUserIds (matrixUserId: string, remoteUserId: string): Promise - unlinkUsers (matrixUser: MatrixUser, remoteUser: RemoteUser): Promise - } - - export class ContentRepo { - static getHttpUriForMxc(baseUrl: string, mxc: string): string; - } - - export class Intent { - getEvent(room_id: string, eventId: string): Promise; - invite(room_id: string, recipient: string): Promise; - createRoom(opts: unknown): Promise<{room_id: string;}>; - roomState(room_id: string): Promise; - leave(roomId: string): Promise; - setPowerLevel(roomId: string, userId: string, level: number | undefined): Promise; - getStateEvent(roomId: string, type: string, key?: string): Promise; - getProfileInfo(userId: string, type?: "displayname"|"avatar_url", useCache?: boolean): Promise<{displayname: string|null, avatar_url: string|null}>; - setPresence(presence: string): Promise; - sendMessage(roomId: string, content: any): Promise; - sendStateEvent(roomId: string, type: string, stateKey: string, content: any): Promise; - join(roomId: string): Promise; - kick(roomId: string, userId: string, reason?: string): Promise; - setRoomTopic(roomId: string, topic: string): Promise; - readonly client: JsClient; - getClient(): JsClient; - setDisplayName(displayname: string): Promise; - } - - - export class Request { - outcomeFrom(the: Promise): void; - getData(): any; - getDuration(): number; - getPromise(): Promise; - getId(): string; - resolve(item: unknown): void; - reject(err: unknown): void; - } - - export class JsClient { - getStateEvent(roomId: string, type: string, skey?: string): Promise; - createAlias(roomAlias: string, roomId: string): Promise; - setRoomDirectoryVisibilityAppService(networkId: string, roomId: string, state: string): Promise - sendStateEvent(roomId: string, type: string, content: any, key: string): Promise; - credentials: { - userId: string; - }; - deleteAlias(alias: string): Promise; - roomState(roomId: string): Promise; - uploadContent(file: Buffer, opts: { - name: string, - type: string, - rawResponse: boolean, - onlyContentUri: boolean, - }): Promise; - joinRoom(roomIdOrAlias: string): Promise; - leave(roomId: string): Promise; - } - - export class ConfigValidator { - constructor(config: string|any); - validate(config: T, defaultConfig?: any): T; - } - - export class Bridge { - constructor(config: any); - opts: { - roomStore: RoomBridgeStore|undefined, - userStore: UserBridgeStore|undefined, - } - appService: import("matrix-appservice").AppService; - getRoomStore(): RoomBridgeStore; - getUserStore(): UserBridgeStore; - getBot(): AppserviceBot; - loadDatabases(): Promise; - getRequestFactory(): RequestFactory; - getPrometheusMetrics(registerEndpoint?: boolean, registry?: unknown): PrometheusMetrics; - getIntent(userId?: string): Intent; - getIntentFromLocalpart(localpart: string): Intent; - requestCheckToken(req: Express.Request): boolean; - run(port: number, config: undefined, appservice?: import("matrix-appservice").AppService, hostname?: string): void; - registerBridgeGauges(cb: () => void): void; - getClientFactory(): ClientFactory; - canProvisionRoom(roomId: string): Promise; - } - - export class ClientFactory { - getClientAs(): JsClient; - } - - export class RequestFactory { - newRequest(opts?: {data: {}}): Request; - addDefaultResolveCallback(cb: (req: Request, result: unknown) => void): void; - addDefaultRejectCallback(cb: (req: Request) => void): void; - addDefaultTimeoutCallback(cb: (req: Request) => void, timeout: number): void; - } - - export class Logging { - static configure(opts: {console: string}): void; - } - - export class StateLookup { - constructor(opts: {}) - onEvent(event: unknown): void; - trackRoom(roomId: string): Promise; - getState(roomId: string, type: string): any[]; - } - - export class MembershipCache { - constructor(); - setMemberEntry(roomId: string, userId: string, membership: "join"): void; - } -}