mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-14 02:41:17 -06:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99909a77c3 | |||
| aadd82d820 | |||
| 4e6fa50a67 | |||
| 58bf2dcb54 | |||
| c15893bab5 | |||
| f0af0e92fe | |||
| 04de0b4eb3 | |||
| 8cb52112c1 | |||
| 54a9972bbc | |||
| ad34f018ab | |||
| 20345787d2 | |||
| 99c19923c6 | |||
| 093d59893e | |||
| 913f5dfcb9 | |||
| e0947dd3bc | |||
| 8287f3c08a | |||
| 2a78524a90 | |||
| d01838ac84 | |||
| 2059f8455d | |||
| 0cb8ecf8aa | |||
| 77f2c4be46 | |||
| cf82daf5da |
@@ -5,6 +5,8 @@
|
||||
|
||||
Join our public Matrix room for help: [#node-red-contrib-matrix-chat:skylar.tech](https://app.element.io/#/room/#node-red-contrib-matrix-chat:skylar.tech)
|
||||
|
||||
[](https://ko-fi.com/B0B51BM7C)
|
||||
|
||||
### Features
|
||||
|
||||
Supported functionality in this package includes:
|
||||
@@ -59,7 +61,9 @@ Interested in helping? Contributions to finalize E2EE support are welcome!
|
||||
|
||||
This module includes a node to register users using the Synapse secret registration endpoint. It returns both an `access_token` and a `device_id`, perfect for setting up the bot.
|
||||
|
||||
[See how to register a user here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme).
|
||||
[Guide on registering a user via the web browser](https://skylar.tech/matrix-chat-bot-module-for-node-red/)
|
||||
|
||||
[Guide on registering using shared secret registration](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) (for server owners)
|
||||
|
||||
### Other Packages
|
||||
|
||||
|
||||
Generated
+16087
-5029
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.2",
|
||||
"description": "Matrix chat server client for Node-RED",
|
||||
"dependencies": {
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.2.0-beta.1",
|
||||
"abort-controller": "^3.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fs-extra": "^11.1.0",
|
||||
"got": "^12.0.2",
|
||||
"image-size": "^1.0.2",
|
||||
"isomorphic-webcrypto": "^2.3.8",
|
||||
"matrix-js-sdk": "^34.5.0",
|
||||
"matrix-js-sdk": "34.11.1",
|
||||
"mime": "^3.0.0",
|
||||
"node-fetch": "^3.3.0",
|
||||
"node-localstorage": "^2.2.1",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const sdkPromise = import("matrix-js-sdk");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixFetchRelations(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
@@ -41,22 +43,23 @@ 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;
|
||||
}
|
||||
|
||||
try {
|
||||
const sdk = await sdkPromise;
|
||||
const Direction = sdk.Direction;
|
||||
|
||||
function evaluateNodePropertySafe(value, type, node, msg) {
|
||||
try {
|
||||
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
return undefined; // Handle TypeError and return undefined
|
||||
return undefined;
|
||||
}
|
||||
throw e; // Re-throw other errors to prevent masking issues
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,18 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-roomId").typedInput({
|
||||
type: this.roomIdType,
|
||||
types: ['msg','flow','global','str'],
|
||||
}).typedInput('value', this.roomIdValue);
|
||||
typeField: "#node-input-roomId"
|
||||
});
|
||||
$("#node-input-roomId").typedInput("type", this.roomIdType || "msg");
|
||||
$("#node-input-roomId").typedInput("value", this.roomIdValue || "topic");
|
||||
|
||||
$("#node-input-eventId").typedInput({
|
||||
type: this.eventIdType,
|
||||
types: ['msg','flow','global','str'],
|
||||
}).typedInput('value', this.eventIdValue);
|
||||
typeField: "#node-input-eventId"
|
||||
});
|
||||
$("#node-input-eventId").typedInput("type", this.eventIdType || "msg");
|
||||
$("#node-input-eventId").typedInput("value", this.eventIdValue || "eventId");
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
||||
@@ -58,18 +62,6 @@
|
||||
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
||||
<input type="text" id="node-input-eventId">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-get-event">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixReceiveMessage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
const crypto = require('crypto');
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixMarkRead(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
let node = this;
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
@@ -36,7 +37,7 @@ module.exports = function(RED) {
|
||||
let value = property;
|
||||
if (type === "msg") {
|
||||
value = RED.util.getMessageProperty(msg, property);
|
||||
} else if ((type === 'flow') || (type === 'global')) {
|
||||
} else if (type === 'flow' || type === 'global') {
|
||||
try {
|
||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||
} catch (e2) {
|
||||
@@ -64,7 +65,7 @@ module.exports = function(RED) {
|
||||
throw new Error(`Event ${eventId} not found in room ${roomId}.`);
|
||||
}
|
||||
|
||||
await node.server.matrixClient.sendReceipt(event, "m.read")
|
||||
await node.server.matrixClient.sendReceipt(event, "m.read");
|
||||
node.send([msg, null]);
|
||||
} catch (e) {
|
||||
msg.error = `Room pagination error: ${e}`;
|
||||
@@ -77,5 +78,5 @@ module.exports = function(RED) {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-mark-read", MatrixReceiveMessage);
|
||||
RED.nodes.registerType("matrix-mark-read", MatrixMarkRead);
|
||||
}
|
||||
+20
-21
@@ -1,5 +1,8 @@
|
||||
const sdkPromise = import("matrix-js-sdk");
|
||||
const crypto = require('crypto');
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixReceiveMessage(n) {
|
||||
function MatrixPaginateRoom(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
@@ -32,9 +35,6 @@ 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;
|
||||
@@ -44,7 +44,7 @@ module.exports = function(RED) {
|
||||
let value = property;
|
||||
if (type === "msg") {
|
||||
value = RED.util.getMessageProperty(msg, property);
|
||||
} else if ((type === 'flow') || (type === 'global')) {
|
||||
} else if (type === 'flow' || type === 'global') {
|
||||
try {
|
||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||
} catch(e2) {
|
||||
@@ -62,17 +62,15 @@ module.exports = function(RED) {
|
||||
if (type === 'global' || type === 'flow') {
|
||||
var contextKey = RED.util.parseContextStore(property);
|
||||
if (/\[msg/.test(contextKey.key)) {
|
||||
// The key has a nest msg. reference to evaluate first
|
||||
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
|
||||
// The key has a nested msg. reference that must be evaluated first
|
||||
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true);
|
||||
}
|
||||
var target = node.context()[type];
|
||||
var callback = err => {
|
||||
target.set(contextKey.key, value, contextKey.store, err => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
getterErrors[rule.p] = err.message;
|
||||
}
|
||||
}
|
||||
target.set(contextKey.key, value, contextKey.store, callback);
|
||||
});
|
||||
} else if (type === 'msg') {
|
||||
if (!RED.util.setMessageProperty(msg, property, value)) {
|
||||
node.warn(RED._("change.errors.no-override", { property: property }));
|
||||
@@ -81,6 +79,12 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Dynamically load the SDK
|
||||
const sdk = await sdkPromise;
|
||||
const TimelineWindow = sdk.TimelineWindow;
|
||||
const RelationType = sdk.RelationType;
|
||||
// (Filter was imported originally but is not used, so we omit it.)
|
||||
|
||||
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
||||
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
||||
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
||||
@@ -102,24 +106,19 @@ module.exports = function(RED) {
|
||||
moreMessages = true;
|
||||
if (!timelineWindow) {
|
||||
let timelineSet = room.getUnfilteredTimelineSet();
|
||||
// node.debug(JSON.stringify(timelineSet.getFilter()));
|
||||
|
||||
// MatrixClient's option initialSyncLimit gets set to the filter we are using
|
||||
// so override that value with our pageSize
|
||||
// MatrixClient's option initialSyncLimit gets set to the filter we are using,
|
||||
// so override that value with our pageSize.
|
||||
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
||||
await timelineWindow.load(msg.eventId || null, pageSize);
|
||||
node.timelineWindows.set(pageKey, timelineWindow);
|
||||
} else {
|
||||
moreMessages = await timelineWindow.paginate(paginateBackwards ? 'b' : 'f', pageSize); // b for backwards f for forwards
|
||||
moreMessages = await timelineWindow.paginate(paginateBackwards ? 'b' : 'f', pageSize); // 'b' for backwards, 'f' for forwards
|
||||
if (moreMessages) {
|
||||
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
||||
}
|
||||
}
|
||||
|
||||
// MatrixEvent objects are massive so this throws an encode error for the string being too long
|
||||
// since msg objects convert to JSON
|
||||
// msg.payload = moreMessages ? timelineWindow.getEvents() : false;
|
||||
|
||||
// To avoid errors converting massive MatrixEvent objects to JSON, we omit them.
|
||||
msg.payload = false;
|
||||
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
||||
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
||||
@@ -153,5 +152,5 @@ module.exports = function(RED) {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-paginate-room", MatrixReceiveMessage);
|
||||
RED.nodes.registerType("matrix-paginate-room", MatrixPaginateRoom);
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
acceptOwnEvents: {"value": false},
|
||||
acceptText: {"value": true},
|
||||
acceptEmotes: {"value": true},
|
||||
acceptNotices: {"value": true},
|
||||
acceptStickers: {"value": true},
|
||||
acceptReactions: {"value": true},
|
||||
acceptFiles: {"value": true},
|
||||
@@ -66,6 +67,16 @@
|
||||
Accept text <code style="text-transform: none;">m.text</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-acceptNotices"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptNotices" style="width: auto">
|
||||
Accept notices <code style="text-transform: none;">m.notice</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -9,6 +9,7 @@ module.exports = function(RED) {
|
||||
this.acceptOwnEvents = n.acceptOwnEvents;
|
||||
this.acceptText = n.acceptText;
|
||||
this.acceptEmotes = n.acceptEmotes;
|
||||
this.acceptNotices = n.acceptNotices;
|
||||
this.acceptStickers = n.acceptStickers;
|
||||
this.acceptReactions = n.acceptReactions;
|
||||
this.acceptFiles = n.acceptFiles;
|
||||
@@ -68,6 +69,10 @@ module.exports = function(RED) {
|
||||
if (!node.acceptEmotes) return;
|
||||
break;
|
||||
|
||||
case 'm.notice':
|
||||
if (!node.acceptNotices) return;
|
||||
break;
|
||||
|
||||
case 'm.text':
|
||||
if (!node.acceptText) return;
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const sdkPromise = import("matrix-js-sdk");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixSendImage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
@@ -66,13 +67,17 @@ module.exports = function(RED) {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
// Make the input handler async so we can await the dynamic import.
|
||||
node.on("input", async function (msg) {
|
||||
const {RelationType} = await import("matrix-js-sdk");
|
||||
// Await the SDK import and get the RelationType constant.
|
||||
const sdk = await sdkPromise;
|
||||
const RelationType = sdk.RelationType;
|
||||
|
||||
function getToValue(msg, type, property) {
|
||||
let value = property;
|
||||
if (type === "msg") {
|
||||
value = RED.util.getMessageProperty(msg, property);
|
||||
} else if ((type === 'flow') || (type === 'global')) {
|
||||
} else if (type === 'flow' || type === 'global') {
|
||||
try {
|
||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||
} catch(e2) {
|
||||
@@ -119,14 +124,14 @@ module.exports = function(RED) {
|
||||
} else {
|
||||
if (msgType === 'msg.type') {
|
||||
if (!msg.type) {
|
||||
node.error("msg.type type is set to be passed in via msg.type but was not defined", msg);
|
||||
node.error("msg.type is set to be passed in via msg.type but was not defined", msg);
|
||||
return;
|
||||
}
|
||||
msgType = msg.type;
|
||||
}
|
||||
|
||||
if (msgFormat === 'msg.format') {
|
||||
if(!msg.format) {
|
||||
if (!Object.hasOwn(msg, 'format')) {
|
||||
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
|
||||
return;
|
||||
}
|
||||
@@ -164,9 +169,11 @@ module.exports = function(RED) {
|
||||
};
|
||||
content['body'] = ' * ' + content['body'];
|
||||
} else if (threadReply) {
|
||||
// if incoming message is a reply to a thread we fetch the thread parent from the m.relates_to property
|
||||
// otherwise fallback to msg.eventId
|
||||
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread ? msg?.content?.['m.relates_to']?.event_id : null) || msg.eventId;
|
||||
// If incoming message is a reply to a thread we fetch the thread parent from m.relates_to,
|
||||
// otherwise fallback to msg.eventId.
|
||||
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread
|
||||
? msg?.content?.['m.relates_to']?.event_id
|
||||
: null) || msg.eventId;
|
||||
if (threadParent) {
|
||||
content["m.relates_to"] = {
|
||||
"rel_type": RelationType.Thread,
|
||||
@@ -201,4 +208,4 @@ module.exports = function(RED) {
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
name: { value: null },
|
||||
autoAcceptRoomInvites: { value: true },
|
||||
enableE2ee: { type: "checkbox", value: true },
|
||||
global: { type: "checkbox", value: true }
|
||||
global: { type: "checkbox", value: true },
|
||||
allowUnknownDevices: { type: "checkbox", value: false }
|
||||
},
|
||||
icon: "matrix.png",
|
||||
label: function() {
|
||||
@@ -130,6 +131,20 @@
|
||||
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-config-input-allowUnknownDevices"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-config-input-allowUnknownDevices" style="width: auto">
|
||||
Allow unverified devices in rooms
|
||||
</label>
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Allow sending messages to a room with unknown devices which have not been verified.
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$("#matrix-login-btn").on("click", function() {
|
||||
function prettyPrintJson(json) {
|
||||
|
||||
+96
-69
@@ -1,36 +1,52 @@
|
||||
global.Olm = require('olm');
|
||||
const fs = require("fs-extra");
|
||||
let RelationType, sdk, LocalStorageCryptoStore, RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore;
|
||||
|
||||
(async () => {
|
||||
const mod = await import("matrix-js-sdk");
|
||||
RelationType = mod.RelationType;
|
||||
// matrix-js-sdk doesn't export a default – the top-level export is the same object:
|
||||
sdk = mod;
|
||||
|
||||
RoomEvent = mod.RoomEvent;
|
||||
RoomMemberEvent = mod.RoomMemberEvent;
|
||||
HttpApiEvent = mod.HttpApiEvent;
|
||||
ClientEvent = mod.ClientEvent;
|
||||
MemoryStore = mod.MemoryStore;
|
||||
|
||||
// For LocalStorageCryptoStore, specify the file extension for Node 20+:
|
||||
const cmod = await import("matrix-js-sdk/lib/crypto/store/localStorage-crypto-store.js");
|
||||
LocalStorageCryptoStore = cmod.LocalStorageCryptoStore;
|
||||
})();
|
||||
|
||||
const { resolve } = require('path');
|
||||
const { LocalStorage } = require('node-localstorage');
|
||||
globalThis.crypto = require('crypto');
|
||||
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) {
|
||||
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
|
||||
Object.assign(globalThis, { fetch, Headers, Request, Response })
|
||||
})
|
||||
Object.assign(globalThis, { fetch, Headers, Request, Response });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
// typeof loggingSettings.console !== 'undefined' &&
|
||||
// typeof loggingSettings.console.level !== 'undefined' &&
|
||||
// ['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
||||
// ) {
|
||||
// loggerPromise.then(({ logger }) => {
|
||||
// logger.disableAll();
|
||||
// });
|
||||
// }
|
||||
let loggingSettings = RED.settings.get('logging');
|
||||
if (
|
||||
typeof loggingSettings.console !== 'undefined' &&
|
||||
typeof loggingSettings.console.level !== 'undefined' &&
|
||||
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
||||
) {
|
||||
import('matrix-js-sdk/lib/logger.js')
|
||||
.then(({ logger }) => {
|
||||
logger.disableAll();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error loading logger module:", err);
|
||||
});
|
||||
}
|
||||
|
||||
function MatrixFolderNameFromUserId(name) {
|
||||
return name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
@@ -58,10 +74,11 @@ module.exports = function(RED) {
|
||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||
this.e2ee = n.enableE2ee || false;
|
||||
this.globalAccess = n.global;
|
||||
this.allowUnknownDevices = n.allowUnknownDevices || false;
|
||||
this.initializedAt = new Date();
|
||||
node.initialSyncLimit = 25;
|
||||
|
||||
// Keep track of all consumers of this node to be able to catch errors
|
||||
// Keep track of all consumers of this node to catch errors
|
||||
node.register = function(consumerNode) {
|
||||
node.users[consumerNode.id] = consumerNode;
|
||||
};
|
||||
@@ -85,7 +102,7 @@ module.exports = function(RED) {
|
||||
} else if(!this.url) {
|
||||
node.error("Matrix connection failed: missing server URL in configuration.", {});
|
||||
} else {
|
||||
node.setConnected = function(connected, cb) {
|
||||
node.setConnected = async function(connected, cb) {
|
||||
if (node.connected !== connected) {
|
||||
node.connected = connected;
|
||||
if(typeof cb === 'function') {
|
||||
@@ -101,7 +118,7 @@ module.exports = function(RED) {
|
||||
device_id = this.matrixClient.getDeviceId();
|
||||
|
||||
if(!device_id && node.enableE2ee) {
|
||||
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.", {})
|
||||
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.", {});
|
||||
} else {
|
||||
if(!stored_device_id || stored_device_id !== device_id) {
|
||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||
@@ -118,7 +135,7 @@ module.exports = function(RED) {
|
||||
node.matrixClient.setDeviceDetails(device_id, {
|
||||
display_name: node.deviceLabel
|
||||
}).then(
|
||||
function(response) {},
|
||||
function() {},
|
||||
function(error) {
|
||||
node.error("Failed to set device label: " + error, {});
|
||||
}
|
||||
@@ -147,22 +164,15 @@ module.exports = function(RED) {
|
||||
|
||||
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
||||
upgradeDirectoryIfNecessary(node, storageDir);
|
||||
|
||||
// Wait for the dynamic imports to resolve
|
||||
Promise.all([sdkPromise, LocalStorageCryptoStorePromise]).then(([sdkModule, LocalStorageCryptoStoreModule]) => {
|
||||
const sdk = sdkModule.default || sdkModule;
|
||||
const { LocalStorageCryptoStore } = LocalStorageCryptoStoreModule;
|
||||
|
||||
node.matrixClient = sdk.createClient({
|
||||
baseUrl: this.url,
|
||||
accessToken: this.credentials.accessToken,
|
||||
cryptoStore: new LocalStorageCryptoStore(localStorage),
|
||||
store: new sdk.MemoryStore({
|
||||
store: new MemoryStore({
|
||||
localStorage: localStorage,
|
||||
}),
|
||||
userId: this.userId,
|
||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined
|
||||
// verificationMethods: ["m.sas.v1"]
|
||||
});
|
||||
|
||||
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
||||
@@ -199,20 +209,18 @@ module.exports = function(RED) {
|
||||
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
|
||||
return;
|
||||
}
|
||||
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)
|
||||
return;
|
||||
}
|
||||
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
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -254,18 +262,42 @@ module.exports = function(RED) {
|
||||
};
|
||||
|
||||
// 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];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -363,8 +395,18 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -372,21 +414,16 @@ module.exports = function(RED) {
|
||||
async function run() {
|
||||
try {
|
||||
if(node.e2ee){
|
||||
node.matrixClient.on("crypto.legacyCryptoStoreMigrationProgress", function(progress, total){
|
||||
node.log(`Migrating from legacy crypto to rust crypto. ${progress}/${total}`);
|
||||
});
|
||||
await node.matrixClient.initRustCrypto({
|
||||
useIndexedDB: false
|
||||
});
|
||||
console.log(`CRYPTO VERSION: ${node.matrixClient.getCrypto()?.getVersion()}`);
|
||||
node.log("Initializing crypto...");
|
||||
await node.matrixClient.initCrypto();
|
||||
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
|
||||
node.matrixClient.getCrypto().globalErrorOnUnknownDevices = !node.allowUnknownDevices;
|
||||
}
|
||||
node.log("Connecting to Matrix server...");
|
||||
await node.matrixClient.startClient({
|
||||
initialSyncLimit: node.initialSyncLimit
|
||||
});
|
||||
} catch(error) {
|
||||
node.error(error);
|
||||
node.error(error, {});
|
||||
}
|
||||
}
|
||||
@@ -430,11 +467,8 @@ module.exports = function(RED) {
|
||||
node.error("Auth check failed: " + err, {});
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
})();
|
||||
}).catch((error) => {
|
||||
node.error("Failed to load Matrix SDK modules: " + error, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,21 +492,19 @@ module.exports = function(RED) {
|
||||
deviceId = req.body.deviceId || undefined,
|
||||
displayName = req.body.displayName || undefined;
|
||||
|
||||
sdkPromise.then((sdkModule) => {
|
||||
const sdk = sdkModule.default || sdk;
|
||||
|
||||
const matrixClient = sdk.createClient({
|
||||
(async () => {
|
||||
const mod = await import("matrix-js-sdk");
|
||||
const matrixClient = mod.createClient({
|
||||
baseUrl: baseUrl,
|
||||
deviceId: deviceId,
|
||||
timelineSupport: true,
|
||||
localTimeoutMs: '30000',
|
||||
request
|
||||
localTimeoutMs: '30000'
|
||||
});
|
||||
|
||||
|
||||
matrixClient.timelineSupport = true;
|
||||
|
||||
matrixClient.login(
|
||||
'm.login.password', {
|
||||
matrixClient.login('m.login.password', {
|
||||
identifier: {
|
||||
type: 'm.id.user',
|
||||
user: userId,
|
||||
@@ -496,13 +528,11 @@ module.exports = function(RED) {
|
||||
});
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
res.json({
|
||||
'result': 'error',
|
||||
'message': "Failed to load Matrix SDK: " + error
|
||||
});
|
||||
});
|
||||
})().catch(err => {
|
||||
res.json({ result: 'error', message: err.toString() });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
function upgradeDirectoryIfNecessary(node, storageDir) {
|
||||
let oldStorageDir = './matrix-local-storage',
|
||||
@@ -543,9 +573,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a device ID is stored we will use that for the client
|
||||
*/
|
||||
function getStoredDeviceId(localStorage) {
|
||||
let deviceId = localStorage.getItem('my_device_id');
|
||||
if(deviceId === "null" || !deviceId) {
|
||||
@@ -561,4 +588,4 @@ module.exports = function(RED) {
|
||||
localStorage.setItem('my_device_id', deviceId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user