diff --git a/package-lock.json b/package-lock.json index ef3a926..40fbb77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "got": "^12.0.2", "image-size": "^1.0.2", "isomorphic-webcrypto": "^2.3.8", - "matrix-js-sdk": "^28.0.0", + "matrix-js-sdk": "^34.5.0", "mime": "^3.0.0", "node-fetch": "^3.3.0", "node-localstorage": "^2.2.1", @@ -4189,13 +4189,18 @@ } }, "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-1.2.2.tgz", - "integrity": "sha512-iSU2oel9xBZdl/q4ryE2bOZRhIe8x77X4fVY/8qVD5iTt4MwEgJMucb3aA1zGjZP4ptjomoxR0N0zWbx8GcSQw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-7.0.0.tgz", + "integrity": "sha512-MOencXiW/gI5MuTtCNsuojjwT5DXCrjMqv9xOslJC9h2tPdLFFFMGr58dY5Lis4DRd9MRWcgrGowUIHOqieWTA==", "engines": { "node": ">= 10" } }, + "node_modules/@matrix-org/olm": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz", + "integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5988,9 +5993,9 @@ } }, "node_modules/@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", + "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==" }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", @@ -6658,9 +6663,9 @@ "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" }, "node_modules/base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz", + "integrity": "sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -6867,11 +6872,11 @@ } }, "node_modules/bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", "dependencies": { - "base-x": "^4.0.0" + "base-x": "^5.0.0" } }, "node_modules/bser": { @@ -7600,11 +7605,6 @@ "node": "*" } }, - "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -10287,9 +10287,12 @@ } }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/keyv": { "version": "4.5.3", @@ -10900,41 +10903,46 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "node_modules/matrix-js-sdk": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-28.0.0.tgz", - "integrity": "sha512-AdvFETHFkAx/Hf94+JXrdPePWiPQOHu4OsqkVbhoOGDlScLCeafWJzdp5sKNx1Yq12ULXWBCgRO2AL1pU8ZY/A==", + "version": "34.5.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.5.0.tgz", + "integrity": "sha512-pbp+IxAkSwGmefrlUGCrtrs3UWyqN2iWh4lKnJW+jFIlsksXq7A8vL4cS1z8LXmpcHQAg3mKNuj8n8uhm51t1A==", "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^1.2.1", + "@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0", + "@matrix-org/olm": "3.2.15", "another-json": "^0.2.0", - "bs58": "^5.0.0", + "bs58": "^6.0.0", "content-type": "^1.0.4", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "loglevel": "^1.7.1", "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.5.0", - "oidc-client-ts": "^2.2.4", + "matrix-widget-api": "^1.8.2", + "oidc-client-ts": "^3.0.1", "p-retry": "4", "sdp-transform": "^2.14.1", "unhomoglyph": "^1.0.6", - "uuid": "9" + "uuid": "10" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/matrix-js-sdk/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/matrix-widget-api": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.6.0.tgz", - "integrity": "sha512-VXIJyAZ/WnBmT4C7ePqevgMYGneKMCP/0JuCOqntSsaNlCRHJvwvTxmqUU+ufOpzIF5gYNyIrAjbgrEbK3iqJQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.9.0.tgz", + "integrity": "sha512-au8mqralNDqrEvaVAkU37bXOb8I9SCe+ACdPk11QWw58FKstVq31q2wRz+qWA6J+42KJ6s1DggWbG/S3fEs3jw==", "dependencies": { "@types/events": "^3.0.0", "events": "^3.2.0" @@ -12468,15 +12476,14 @@ } }, "node_modules/oidc-client-ts": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.2.5.tgz", - "integrity": "sha512-omAHoLdFcylnwZeHJahOnJBwd0r78JzhmVAmsQjLGrexAnQKiHW9Ilr9FlRD5qjMikmabvaucI4k49AbQLXhmQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz", + "integrity": "sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==", "dependencies": { - "crypto-js": "^4.1.1", - "jwt-decode": "^3.1.2" + "jwt-decode": "^4.0.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=18" } }, "node_modules/olm": { @@ -18740,9 +18747,14 @@ } }, "@matrix-org/matrix-sdk-crypto-wasm": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-1.2.2.tgz", - "integrity": "sha512-iSU2oel9xBZdl/q4ryE2bOZRhIe8x77X4fVY/8qVD5iTt4MwEgJMucb3aA1zGjZP4ptjomoxR0N0zWbx8GcSQw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-7.0.0.tgz", + "integrity": "sha512-MOencXiW/gI5MuTtCNsuojjwT5DXCrjMqv9xOslJC9h2tPdLFFFMGr58dY5Lis4DRd9MRWcgrGowUIHOqieWTA==" + }, + "@matrix-org/olm": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz", + "integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -20116,9 +20128,9 @@ } }, "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", + "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==" }, "@types/http-cache-semantics": { "version": "4.0.1", @@ -20689,9 +20701,9 @@ "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" }, "base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz", + "integrity": "sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==" }, "base64-js": { "version": "1.5.1", @@ -20849,11 +20861,11 @@ } }, "bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", "requires": { - "base-x": "^4.0.0" + "base-x": "^5.0.0" } }, "bser": { @@ -21422,11 +21434,6 @@ "optional": true, "peer": true }, - "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -23544,9 +23551,9 @@ } }, "jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" }, "keyv": { "version": "4.5.3", @@ -23962,37 +23969,38 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "matrix-js-sdk": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-28.0.0.tgz", - "integrity": "sha512-AdvFETHFkAx/Hf94+JXrdPePWiPQOHu4OsqkVbhoOGDlScLCeafWJzdp5sKNx1Yq12ULXWBCgRO2AL1pU8ZY/A==", + "version": "34.5.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.5.0.tgz", + "integrity": "sha512-pbp+IxAkSwGmefrlUGCrtrs3UWyqN2iWh4lKnJW+jFIlsksXq7A8vL4cS1z8LXmpcHQAg3mKNuj8n8uhm51t1A==", "requires": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^1.2.1", + "@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0", + "@matrix-org/olm": "3.2.15", "another-json": "^0.2.0", - "bs58": "^5.0.0", + "bs58": "^6.0.0", "content-type": "^1.0.4", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "loglevel": "^1.7.1", "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.5.0", - "oidc-client-ts": "^2.2.4", + "matrix-widget-api": "^1.8.2", + "oidc-client-ts": "^3.0.1", "p-retry": "4", "sdp-transform": "^2.14.1", "unhomoglyph": "^1.0.6", - "uuid": "9" + "uuid": "10" }, "dependencies": { "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==" } } }, "matrix-widget-api": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.6.0.tgz", - "integrity": "sha512-VXIJyAZ/WnBmT4C7ePqevgMYGneKMCP/0JuCOqntSsaNlCRHJvwvTxmqUU+ufOpzIF5gYNyIrAjbgrEbK3iqJQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.9.0.tgz", + "integrity": "sha512-au8mqralNDqrEvaVAkU37bXOb8I9SCe+ACdPk11QWw58FKstVq31q2wRz+qWA6J+42KJ6s1DggWbG/S3fEs3jw==", "requires": { "@types/events": "^3.0.0", "events": "^3.2.0" @@ -25202,12 +25210,11 @@ "peer": true }, "oidc-client-ts": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.2.5.tgz", - "integrity": "sha512-omAHoLdFcylnwZeHJahOnJBwd0r78JzhmVAmsQjLGrexAnQKiHW9Ilr9FlRD5qjMikmabvaucI4k49AbQLXhmQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz", + "integrity": "sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==", "requires": { - "crypto-js": "^4.1.1", - "jwt-decode": "^3.1.2" + "jwt-decode": "^4.0.0" } }, "olm": { diff --git a/package.json b/package.json index cf1ba85..3c73161 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "got": "^12.0.2", "image-size": "^1.0.2", "isomorphic-webcrypto": "^2.3.8", - "matrix-js-sdk": "^28.0.0", + "matrix-js-sdk": "^34.5.0", "mime": "^3.0.0", "node-fetch": "^3.3.0", "node-localstorage": "^2.2.1", diff --git a/src/matrix-event-relations.js b/src/matrix-event-relations.js index 9e37030..f3aa492 100644 --- a/src/matrix-event-relations.js +++ b/src/matrix-event-relations.js @@ -1,5 +1,3 @@ -const {RelationType, EventType, Direction} = require("matrix-js-sdk"); - module.exports = function(RED) { function MatrixFetchRelations(n) { RED.nodes.createNode(this, n); @@ -43,6 +41,8 @@ module.exports = function(RED) { }); node.on("input", async function(msg) { + const {Direction} = await import("matrix-js-sdk"); + if (!node.server || !node.server.matrixClient) { node.error("No matrix server selected", msg); return; diff --git a/src/matrix-mark-read.js b/src/matrix-mark-read.js index bc8eebe..c2cebf9 100644 --- a/src/matrix-mark-read.js +++ b/src/matrix-mark-read.js @@ -1,5 +1,3 @@ -const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk"); -const crypto = require('crypto'); module.exports = function(RED) { function MatrixReceiveMessage(n) { RED.nodes.createNode(this, n); diff --git a/src/matrix-paginate-room.js b/src/matrix-paginate-room.js index 7fbab8d..ba0b38b 100644 --- a/src/matrix-paginate-room.js +++ b/src/matrix-paginate-room.js @@ -1,5 +1,3 @@ -const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk"); -const crypto = require('crypto'); module.exports = function(RED) { function MatrixReceiveMessage(n) { RED.nodes.createNode(this, n); @@ -34,6 +32,9 @@ module.exports = function(RED) { }); node.on("input", async function (msg) { + const {TimelineWindow, RelationType, Filter} = await import("matrix-js-sdk"); + const crypto = await import('crypto'); + if (! node.server || ! node.server.matrixClient) { node.error("No matrix server selected", msg); return; diff --git a/src/matrix-receive.js b/src/matrix-receive.js index a6dd836..283138d 100644 --- a/src/matrix-receive.js +++ b/src/matrix-receive.js @@ -1,4 +1,3 @@ -const {RelationType} = require("matrix-js-sdk"); module.exports = function(RED) { function MatrixReceiveMessage(n) { RED.nodes.createNode(this, n); diff --git a/src/matrix-send-message.js b/src/matrix-send-message.js index 915b173..7586fc5 100644 --- a/src/matrix-send-message.js +++ b/src/matrix-send-message.js @@ -1,5 +1,3 @@ -const {RelationType} = require("matrix-js-sdk"); - module.exports = function(RED) { function MatrixSendImage(n) { RED.nodes.createNode(this, n); @@ -68,7 +66,8 @@ module.exports = function(RED) { node.status({ fill: "green", shape: "ring", text: "connected" }); }); - node.on("input", function (msg) { + node.on("input", async function (msg) { + const {RelationType} = await import("matrix-js-sdk"); function getToValue(msg, type, property) { let value = property; if (type === "msg") { diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index f7de643..1c3e7aa 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -1,14 +1,10 @@ -const {RelationType, TimelineWindow} = require("matrix-js-sdk"); - global.Olm = require('olm'); const fs = require("fs-extra"); -const sdk = require("matrix-js-sdk"); const { resolve } = require('path'); const { LocalStorage } = require('node-localstorage'); -const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); -const {RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore} = require("matrix-js-sdk"); const request = require("request"); require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it + if (!globalThis.fetch) { // polyfill fetch if we don't have it if (!globalThis.fetch) { @@ -19,6 +15,11 @@ if (!globalThis.fetch) { } module.exports = function(RED) { + // Prepare dynamic imports + const sdkPromise = import("matrix-js-sdk"); + const LocalStorageCryptoStorePromise = import('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store.js'); + const loggerPromise = import('matrix-js-sdk/lib/logger.js'); + // disable logging if set to "off" let loggingSettings = RED.settings.get('logging'); if( @@ -26,8 +27,9 @@ module.exports = function(RED) { typeof loggingSettings.console.level !== 'undefined' && ['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0 ) { - const { logger } = require('matrix-js-sdk/lib/logger'); - logger.disableAll(); + loggerPromise.then(({ logger }) => { + logger.disableAll(); + }); } function MatrixFolderNameFromUserId(name) { @@ -66,7 +68,7 @@ module.exports = function(RED) { node.deregister = function(consumerNode) { delete node.users[consumerNode.id]; }; - + if(!this.userId) { node.log("Matrix connection failed: missing user ID in configuration."); return; @@ -83,7 +85,7 @@ module.exports = function(RED) { } else if(!this.url) { node.error("Matrix connection failed: missing server URL in configuration.", {}); } else { - node.setConnected = async function(connected, cb) { + node.setConnected = function(connected, cb) { if (node.connected !== connected) { node.connected = connected; if(typeof cb === 'function') { @@ -145,313 +147,289 @@ module.exports = function(RED) { fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist upgradeDirectoryIfNecessary(node, storageDir); - node.matrixClient = sdk.createClient({ - baseUrl: this.url, - accessToken: this.credentials.accessToken, - cryptoStore: new LocalStorageCryptoStore(localStorage), - store: new MemoryStore({ - localStorage: localStorage, - }), - userId: this.userId, - deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined, - request - // verificationMethods: ["m.sas.v1"] - }); - node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`); + // Wait for the dynamic imports to resolve + Promise.all([sdkPromise, LocalStorageCryptoStorePromise]).then(([sdkModule, LocalStorageCryptoStoreModule]) => { + const sdk = sdkModule.default || sdkModule; + const { LocalStorageCryptoStore } = LocalStorageCryptoStoreModule; - // set globally if configured to do so - if(this.globalAccess) { - this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient); - } - - function stopClient() { - if(node.matrixClient && node.matrixClient.clientRunning) { - node.matrixClient.stopClient(); - node.setConnected(false); - } - - if(retryStartTimeout) { - clearTimeout(retryStartTimeout); - } - } - - node.on('close', function(done) { - stopClient(); - if(node.globalAccess) { - try { - node.context().global.set('matrixClient["'+node.userId+'"]', undefined); - } catch(e){ - node.error(e.message, {}); - } - } - done(); - }); - - node.isConnected = function() { - return node.connected; - }; - - node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) { - if (toStartOfTimeline) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result"); - return; // ignore paginated results - } - if (!data || !data.liveEvent) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message"); - return; // ignore old message (we only want live events) - } - if(node.initializedAt > event.getDate()) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init"); - return; // skip events that occurred before our client initialized - } - - try { - await node.matrixClient.decryptEventIfNeeded(event); - } catch (error) { - node.error(error, {}); - return; - } - - const isDmRoom = (room) => { - // Find out if this is a direct message room. - let isDM = !!room.getDMInviter(); - const allMembers = room.currentState.getMembers(); - if (!isDM && allMembers.length <= 2) { - // if not a DM, but there are 2 users only - // double check DM (needed because getDMInviter works only if you were invited, not if you invite) - // hence why we check for each member - if (allMembers.some((m) => m.getDMInviter())) { - return true; - } - } - return allMembers.length <= 2 && isDM; - }; - - let msg = { - encrypted : event.isEncrypted(), - redacted : event.isRedacted(), - content : event.getContent(), - type : (event.getContent()['msgtype'] || event.getType()) || null, - payload : (event.getContent()['body'] || event.getContent()) || null, - isDM : isDmRoom(room), - isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread, - mentions : event.getContent()["m.mentions"] || null, - userId : event.getSender(), - user : node.matrixClient.getUser(event.getSender()), - topic : event.getRoomId(), - eventId : event.getId(), - event : event, - }; - - // remove keys from user property that start with an underscore - Object.keys(msg.user).forEach(function (key) { - if (/^_/.test(key)) { - delete msg.user[key]; - } + node.matrixClient = sdk.createClient({ + baseUrl: this.url, + accessToken: this.credentials.accessToken, + cryptoStore: new LocalStorageCryptoStore(localStorage), + store: new sdk.MemoryStore({ + localStorage: localStorage, + }), + userId: this.userId, + deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined, + request + // verificationMethods: ["m.sas.v1"] }); - node.log(`Received ${msg.encrypted ? 'encrypted ' : ''}timeline event [${msg.type}]: (${room.name}) ${event.getSender()} :: ${msg.content.body} ${toStartOfTimeline ? ' [PAGINATED]' : ''}`); - node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg); - }); + node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`); - /** - * Fires when we want to suggest to the user that they restore their megolm keys - * from backup or by cross-signing the device. - * - * @event module:client~MatrixClient#"crypto.suggestKeyRestore" - */ - // node.matrixClient.on("crypto.suggestKeyRestore", function(){ - // - // }); - - // node.matrixClient.on("RoomMember.typing", async function(event, member) { - // let isTyping = member.typing; - // let roomId = member.roomId; - // }); - - // node.matrixClient.on("RoomMember.powerLevel", async function(event, member) { - // let newPowerLevel = member.powerLevel; - // let newNormPowerLevel = member.powerLevelNorm; - // let roomId = member.roomId; - // }); - - // node.matrixClient.on("RoomMember.name", async function(event, member) { - // let newName = member.name; - // let roomId = member.roomId; - // }); - - // handle auto-joining rooms - - node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) { - if(node.initializedAt > event.getDate()) { - return; // skip events that occurred before our client initialized + // set globally if configured to do so + if(this.globalAccess) { + this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient); } - if (member.membership === "invite" && member.userId === node.userId) { - node.log("Got invite to join room " + member.roomId); - if(node.autoAcceptRoomInvites) { - node.matrixClient.joinRoom(member.roomId).then(function() { - node.log("Automatically accepted invitation to join room " + member.roomId); - }).catch(function(e) { - node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e); + function stopClient() { + if(node.matrixClient && node.matrixClient.clientRunning) { + node.matrixClient.stopClient(); + node.setConnected(false); + } + + if(retryStartTimeout) { + clearTimeout(retryStartTimeout); + } + } + + node.on('close', function(done) { + stopClient(); + if(node.globalAccess) { + try { + node.context().global.set('matrixClient["'+node.userId+'"]', undefined); + } catch(e){ + node.error(e.message, {}); + } + } + done(); + }); + + node.isConnected = function() { + return node.connected; + }; + + const { RelationType, RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent } = sdk; + + node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) { + if (toStartOfTimeline) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result"); + return; // ignore paginated results + } + if (!data || !data.liveEvent) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message"); + return; // ignore old message (we only want live events) + } + if(node.initializedAt > event.getDate()) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init"); + return; // skip events that occurred before our client initialized + } + + try { + await node.matrixClient.decryptEventIfNeeded(event); + } catch (error) { + node.error(error, {}); + return; + } + + const isDmRoom = (room) => { + // Find out if this is a direct message room. + let isDM = !!room.getDMInviter(); + const allMembers = room.currentState.getMembers(); + if (!isDM && allMembers.length <= 2) { + // if not a DM, but there are 2 users only + // double check DM (needed because getDMInviter works only if you were invited, not if you invite) + // hence why we check for each member + if (allMembers.some((m) => m.getDMInviter())) { + return true; + } + } + return allMembers.length <= 2 && isDM; + }; + + let msg = { + encrypted : event.isEncrypted(), + redacted : event.isRedacted(), + content : event.getContent(), + type : (event.getContent()['msgtype'] || event.getType()) || null, + payload : (event.getContent()['body'] || event.getContent()) || null, + isDM : isDmRoom(room), + isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread, + mentions : event.getContent()["m.mentions"] || null, + userId : event.getSender(), + user : node.matrixClient.getUser(event.getSender()), + topic : event.getRoomId(), + eventId : event.getId(), + event : event, + }; + + // remove keys from user property that start with an underscore + if (msg.user) { + Object.keys(msg.user).forEach(function (key) { + if (/^_/.test(key)) { + delete msg.user[key]; + } }); } - let room = node.matrixClient.getRoom(event.getRoomId()); - node.emit("Room.invite", { - type : 'm.room.member', - userId : event.getSender(), - topic : event.getRoomId(), - topicName : (room ? room.name : null) || null, - event : event, - eventId : event.getId(), - }); - } - }); + node.log(`Received ${msg.encrypted ? 'encrypted ' : ''}timeline event [${msg.type}]: (${room.name}) ${event.getSender()} :: ${msg.content.body} ${toStartOfTimeline ? ' [PAGINATED]' : ''}`); + node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg); + }); - node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) { - node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]"); - if(prevState === null && state === "PREPARED" ) { - // Occurs when the initial sync is completed first time. - // This involves setting up filters and obtaining push rules. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === null && state === "ERROR") { - // Occurs when the initial sync failed first time. - node.setConnected(false, function(){ - node.error("Failed to connect to Matrix server", {}); - }); - } else if(prevState === "ERROR" && state === "PREPARED") { - // Occurs when the initial sync succeeds - // after previously failing. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "PREPARED" && state === "SYNCING") { - // Occurs immediately after transitioning to PREPARED. - // Starts listening for live updates rather than catching up. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "SYNCING" && state === "RECONNECTING") { - // Occurs when the live update fails. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "RECONNECTING" && state === "RECONNECTING") { - // Can occur if the update calls continue to fail, - // but the keepalive calls (to /versions) succeed. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "RECONNECTING" && state === "ERROR") { - // Occurs when the keepalive call also fails - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "ERROR" && state === "SYNCING") { - // Occurs when the client has performed a - // live update after having previously failed. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "ERROR" && state === "ERROR") { - // Occurs when the client has failed to - // keepalive for a second time or more. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "SYNCING" && state === "SYNCING") { - // Occurs when the client has performed a live update. - // This is called after processing. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(state === "STOPPED") { - // Occurs once the client has stopped syncing or - // trying to sync after stopClient has been called. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } - }); + // handle auto-joining rooms - - node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){ - // Example if user auth token incorrect: - // { - // errcode: 'M_UNKNOWN_TOKEN', - // data: { - // errcode: 'M_UNKNOWN_TOKEN', - // error: 'Invalid macaroon passed.', - // soft_logout: false - // }, - // httpStatus: 401 - // } - - node.error("Authentication failure: " + errorObj, {}); - stopClient(); - }); - - async function run() { - try { - if(node.e2ee){ - node.log("Initializing crypto..."); - await node.matrixClient.initCrypto(); - node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices + node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) { + if(node.initializedAt > event.getDate()) { + return; // skip events that occurred before our client initialized } - node.log("Connecting to Matrix server..."); - await node.matrixClient.startClient({ - initialSyncLimit: node.initialSyncLimit - }); - } catch(error) { - node.error(error, {}); - } - } - // do an authed request and only continue if we don't get an error - // this prevent the matrix client from crashing Node-RED on invalid auth token - (function checkAuthTokenThenStart() { - if(node.matrixClient.clientRunning) { - return; - } - - /** - * We do a /whoami request before starting for a few reasons: - * - validate our auth token - * - make sure auth token belongs to provided node.userId - * - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033) - */ - node.matrixClient.whoami() - .then( - function(data) { - if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) { - node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.", {}); - } - if('device_id' in data && data['device_id'] && !node.deviceId) { - // if we have no device_id configured lets use the one - // returned by /whoami for this access_token - node.matrixClient.deviceId = data['device_id']; - } - - // make sure our userId matches the access token's - if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) { - node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`, {}); - return; - } - run().catch((error) => node.error(error)); - }, - function(err) { - // if the error isn't authentication related retry in a little bit - if(err.code !== "M_UNKNOWN_TOKEN") { - retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000); - node.error("Auth check failed: " + err, {}); - } + if (member.membership === "invite" && member.userId === node.userId) { + node.log("Got invite to join room " + member.roomId); + if(node.autoAcceptRoomInvites) { + node.matrixClient.joinRoom(member.roomId).then(function() { + node.log("Automatically accepted invitation to join room " + member.roomId); + }).catch(function(e) { + node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e); + }); } - ) - })(); + + let room = node.matrixClient.getRoom(event.getRoomId()); + node.emit("Room.invite", { + type : 'm.room.member', + userId : event.getSender(), + topic : event.getRoomId(), + topicName : (room ? room.name : null) || null, + event : event, + eventId : event.getId(), + }); + } + }); + + node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) { + node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]"); + if(prevState === null && state === "PREPARED" ) { + // Occurs when the initial sync is completed first time. + // This involves setting up filters and obtaining push rules. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === null && state === "ERROR") { + // Occurs when the initial sync failed first time. + node.setConnected(false, function(){ + node.error("Failed to connect to Matrix server", {}); + }); + } else if(prevState === "ERROR" && state === "PREPARED") { + // Occurs when the initial sync succeeds + // after previously failing. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "PREPARED" && state === "SYNCING") { + // Occurs immediately after transitioning to PREPARED. + // Starts listening for live updates rather than catching up. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "SYNCING" && state === "RECONNECTING") { + // Occurs when the live update fails. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost", {}); + }); + } else if(prevState === "RECONNECTING" && state === "RECONNECTING") { + // Can occur if the update calls continue to fail, + // but the keepalive calls (to /versions) succeed. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost", {}); + }); + } else if(prevState === "RECONNECTING" && state === "ERROR") { + // Occurs when the keepalive call also fails + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost", {}); + }); + } else if(prevState === "ERROR" && state === "SYNCING") { + // Occurs when the client has performed a + // live update after having previously failed. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "ERROR" && state === "ERROR") { + // Occurs when the client has failed to + // keepalive for a second time or more. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost", {}); + }); + } else if(prevState === "SYNCING" && state === "SYNCING") { + // Occurs when the client has performed a live update. + // This is called after processing. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(state === "STOPPED") { + // Occurs once the client has stopped syncing or + // trying to sync after stopClient has been called. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost", {}); + }); + } + }); + + + node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){ + node.error("Authentication failure: " + errorObj, {}); + stopClient(); + }); + + async function run() { + try { + if(node.e2ee){ + node.log("Initializing crypto..."); + await node.matrixClient.initCrypto(); + node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices + } + node.log("Connecting to Matrix server..."); + await node.matrixClient.startClient({ + initialSyncLimit: node.initialSyncLimit + }); + } catch(error) { + node.error(error, {}); + } + } + + // do an authed request and only continue if we don't get an error + // this prevent the matrix client from crashing Node-RED on invalid auth token + (function checkAuthTokenThenStart() { + if(node.matrixClient.clientRunning) { + return; + } + + /** + * We do a /whoami request before starting for a few reasons: + * - validate our auth token + * - make sure auth token belongs to provided node.userId + * - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033) + */ + node.matrixClient.whoami() + .then( + function(data) { + if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) { + node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.", {}); + } + if('device_id' in data && data['device_id'] && !node.deviceId) { + // if we have no device_id configured lets use the one + // returned by /whoami for this access_token + node.matrixClient.deviceId = data['device_id']; + } + + // make sure our userId matches the access token's + if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) { + node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`, {}); + return; + } + run().catch((error) => node.error(error)); + }, + function(err) { + // if the error isn't authentication related retry in a little bit + if(err.code !== "M_UNKNOWN_TOKEN") { + retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000); + node.error("Auth check failed: " + err, {}); + } + } + ) + })(); + }).catch((error) => { + node.error("Failed to load Matrix SDK modules: " + error, {}); + }); } } @@ -475,43 +453,50 @@ module.exports = function(RED) { deviceId = req.body.deviceId || undefined, displayName = req.body.displayName || undefined; - const matrixClient = sdk.createClient({ - baseUrl: baseUrl, - deviceId: deviceId, - timelineSupport: true, - localTimeoutMs: '30000', - request + sdkPromise.then((sdkModule) => { + const sdk = sdkModule.default || sdk; + + const matrixClient = sdk.createClient({ + baseUrl: baseUrl, + deviceId: deviceId, + timelineSupport: true, + localTimeoutMs: '30000', + request + }); + + matrixClient.timelineSupport = true; + + matrixClient.login( + 'm.login.password', { + identifier: { + type: 'm.id.user', + user: userId, + }, + password: password, + initial_device_display_name: displayName + }) + .then( + function(response) { + res.json({ + 'result': 'ok', + 'token': response.access_token, + 'device_id': response.device_id, + 'user_id': response.user_id, + }); + }, + function(err) { + res.json({ + 'result': 'error', + 'message': err + }); + } + ); + }).catch((error) => { + res.json({ + 'result': 'error', + 'message': "Failed to load Matrix SDK: " + error + }); }); - - new TimelineWindow() - - matrixClient.timelineSupport = true; - - matrixClient.login( - 'm.login.password', { - identifier: { - type: 'm.id.user', - user: userId, - }, - password: password, - initial_device_display_name: displayName - }) - .then( - function(response) { - res.json({ - 'result': 'ok', - 'token': response.access_token, - 'device_id': response.device_id, - 'user_id': response.user_id, - }); - }, - function(err) { - res.json({ - 'result': 'error', - 'message': err - }); - } - ); }); function upgradeDirectoryIfNecessary(node, storageDir) {