mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-13 18:31:16 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 474ba6ea59 | |||
| b44142c0db | |||
| 702a980c6f | |||
| 543a1e658d |
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
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)
|
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
|
### Features
|
||||||
|
|
||||||
Supported functionality in this package includes:
|
Supported functionality in this package includes:
|
||||||
@@ -61,9 +59,7 @@ 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.
|
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.
|
||||||
|
|
||||||
[Guide on registering a user via the web browser](https://skylar.tech/matrix-chat-bot-module-for-node-red/)
|
[See how to register a user here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme).
|
||||||
|
|
||||||
[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
|
### Other Packages
|
||||||
|
|
||||||
@@ -73,4 +69,4 @@ This module includes a node to register users using the Synapse secret registrat
|
|||||||
|
|
||||||
We welcome all contributions! Please submit a pull request if you add a feature so the whole community can benefit.
|
We welcome all contributions! Please submit a pull request if you add a feature so the whole community can benefit.
|
||||||
|
|
||||||
**Sharing is caring!**
|
**Sharing is caring!**
|
||||||
Generated
+5382
-16240
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red-contrib-matrix-chat",
|
"name": "node-red-contrib-matrix-chat",
|
||||||
"version": "0.9.2",
|
"version": "0.8.0",
|
||||||
"description": "Matrix chat server client for Node-RED",
|
"description": "Matrix chat server client for Node-RED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
@@ -9,11 +9,12 @@
|
|||||||
"got": "^12.0.2",
|
"got": "^12.0.2",
|
||||||
"image-size": "^1.0.2",
|
"image-size": "^1.0.2",
|
||||||
"isomorphic-webcrypto": "^2.3.8",
|
"isomorphic-webcrypto": "^2.3.8",
|
||||||
"matrix-js-sdk": "34.11.1",
|
"matrix-js-sdk": "^28.0.0",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"node-fetch": "^3.3.0",
|
"node-fetch": "^3.3.0",
|
||||||
"node-localstorage": "^2.2.1",
|
"node-localstorage": "^2.2.1",
|
||||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
||||||
|
"request": "^2.88.2",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"utf8": "^3.0.0"
|
"utf8": "^3.0.0"
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
"matrix-whois-user": "src/matrix-whois-user.js",
|
"matrix-whois-user": "src/matrix-whois-user.js",
|
||||||
"matrix-paginate-room": "src/matrix-paginate-room.js",
|
"matrix-paginate-room": "src/matrix-paginate-room.js",
|
||||||
"matrix-get-event": "src/matrix-get-event.js",
|
"matrix-get-event": "src/matrix-get-event.js",
|
||||||
"matrix-event-relations": "src/matrix-event-relations.js"
|
"matrix-event-relations": "src/matrix-event-relations.js",
|
||||||
|
"matrix-device-verification": "src/matrix-device-verification.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
let computeInputAndOutputCounts = function(node){
|
||||||
|
switch($("#node-input-mode").val()) {
|
||||||
|
default:
|
||||||
|
node.outputs = node.inputs = 0;
|
||||||
|
break;
|
||||||
|
case 'receive':
|
||||||
|
node.outputs = 1;
|
||||||
|
node.inputs = 0;
|
||||||
|
break;
|
||||||
|
case 'request':
|
||||||
|
case 'start':
|
||||||
|
case 'accept':
|
||||||
|
case 'cancel':
|
||||||
|
node.outputs = 2;
|
||||||
|
node.inputs = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RED.nodes.registerType('matrix-device-verification', {
|
||||||
|
category: 'matrix',
|
||||||
|
color: '#00b7ca',
|
||||||
|
icon: "matrix.png",
|
||||||
|
inputs: 0,
|
||||||
|
outputs: 0,
|
||||||
|
outputLabels: ["success", "error"],
|
||||||
|
defaults: {
|
||||||
|
name: { value: null },
|
||||||
|
server: { value: "", type: "matrix-server-config" },
|
||||||
|
mode: { value: null, type: "text", required: true },
|
||||||
|
inputs: { value: 0 },
|
||||||
|
outputs: { value: 0 }
|
||||||
|
},
|
||||||
|
oneditprepare: function () {
|
||||||
|
computeInputAndOutputCounts(this);
|
||||||
|
},
|
||||||
|
oneditsave: function () {
|
||||||
|
computeInputAndOutputCounts(this);
|
||||||
|
},
|
||||||
|
label: function() {
|
||||||
|
if(this.name) {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(this.mode) {
|
||||||
|
default:
|
||||||
|
return 'Device Verification';
|
||||||
|
case 'receive':
|
||||||
|
return 'Receive Device Verification';
|
||||||
|
case 'request':
|
||||||
|
return 'Request Device Verification';
|
||||||
|
case 'start':
|
||||||
|
return 'Start Device Verification';
|
||||||
|
case 'accept':
|
||||||
|
return 'Accept Device Verification';
|
||||||
|
case 'cancel':
|
||||||
|
return 'Cancel Device Verification';
|
||||||
|
}
|
||||||
|
return this.name || "Device Verify Request";
|
||||||
|
},
|
||||||
|
paletteLabel: function(){
|
||||||
|
return "Device Verification";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" data-template-name="matrix-device-verification">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||||
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
|
||||||
|
<input type="text" id="node-input-server">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-mode"><i class="fa fa-user"></i> Mode</label>
|
||||||
|
<select id="node-input-mode" style="width:70%;">
|
||||||
|
<option value="">Unconfigured</option>
|
||||||
|
<option value="receive">Receive Verification Request</option>
|
||||||
|
<option value="request">Request Verification</option>
|
||||||
|
<option value="start">Verification Start</option>
|
||||||
|
<option value="accept">Verification Accept</option>
|
||||||
|
<option value="cancel">Verification Cancel</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" data-help-name="matrix-device-verification">
|
||||||
|
<h3>Details</h3>
|
||||||
|
<p>
|
||||||
|
Handle device verification. Check out the <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme" target="_blank">examples</a> page for a good understanding of how this works.
|
||||||
|
<br />
|
||||||
|
General flow:
|
||||||
|
<ol>
|
||||||
|
<li>Request/Receive device verification</li>
|
||||||
|
<li>Start Verification</li>
|
||||||
|
<li>Compare Emojis</li>
|
||||||
|
<li>Accept/Cancel Verification</li>
|
||||||
|
</ol>
|
||||||
|
<br />
|
||||||
|
THIS NODE IS IN BETA. There is a good chance that we will change how this node works later down the road. Make sure to read the release notes before upgrading.
|
||||||
|
</p>
|
||||||
|
<a href="https://matrix-org.github.io/synapse/develop/admin_api/room_membership.html#edit-room-membership-api" target="_blank">Synapse API Endpoint Information</a>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<ul class="node-inputs">
|
||||||
|
<li><code>mode</code> set to '<strong>Receive Verification Request</strong>'
|
||||||
|
<div class="form-tips" style="margin-bottom: 12px;">
|
||||||
|
Doesn't take an input
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Request Verification</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
ID of the user to request device verification from
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.devices <span class="property-type">array[string]|null</span></dt>
|
||||||
|
<dd> list of <code>msg.userId</code>'s devices IDs to request verification from. If empty it will request from all known devices.</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Verification Start</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
Internal ID to reference the verification request throughout the flows
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.cancel <span class="property-type">bool</span></dt>
|
||||||
|
<dd>
|
||||||
|
If set and is true the verification request will be cancelled
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Verification Accept</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
Internal ID to reference the verification request throughout the flows
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Verification Cancel</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
Internal ID to reference the verification request throughout the flows
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ul class="node-outputs">
|
||||||
|
<li><code>mode</code> set to '<strong>Receive Verification Request</strong>' or '<strong>Request Verification</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.verifyRequestId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
Internal ID to reference the verification request throughout the flows
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.verifyMethods <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
Common verification methods supported by both sides
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
ID of the user to request device verification from
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.deviceIds <span class="property-type">array[string]</span></dt>
|
||||||
|
<dd>
|
||||||
|
List of devices we are verifying
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.selfVerification <span class="property-type">bool</span></dt>
|
||||||
|
<dd>
|
||||||
|
true if we are verifying one of our own devices
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.phase <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
what phase of verification we are in
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Verification Start</strong>'
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||||
|
<dd>
|
||||||
|
sas verification payload
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.emojis <span class="property-type">array[string]</span></dt>
|
||||||
|
<dd>
|
||||||
|
array of emojis for verification request
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>msg.emojis_text <span class="property-type">array[string]</span></dt>
|
||||||
|
<dd>
|
||||||
|
array of emojis in text form for verification request
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><code>mode</code> set to '<strong>Verification Accept</strong>' or '<strong>Verification Cancel</strong>'
|
||||||
|
<div class="form-tips" style="margin-bottom: 12px;">
|
||||||
|
Passes input straight to output on success. If an error occurs it goes to the second output.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
const {Phase} = require("matrix-js-sdk/lib/crypto/verification/request/VerificationRequest");
|
||||||
|
const {CryptoEvent} = require("matrix-js-sdk/lib/crypto");
|
||||||
|
|
||||||
|
module.exports = function(RED) {
|
||||||
|
const verificationRequests = new Map();
|
||||||
|
|
||||||
|
function MatrixDeviceVerification(n) {
|
||||||
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
this.name = n.name;
|
||||||
|
this.server = RED.nodes.getNode(n.server);
|
||||||
|
this.mode = n.mode;
|
||||||
|
|
||||||
|
if (!node.server) {
|
||||||
|
node.warn("No configuration node");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!node.server.e2ee) {
|
||||||
|
node.error("End-to-end encryption needs to be enabled to use this.");
|
||||||
|
}
|
||||||
|
|
||||||
|
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||||
|
|
||||||
|
node.server.on("disconnected", function(){
|
||||||
|
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||||
|
});
|
||||||
|
|
||||||
|
node.server.on("connected", function() {
|
||||||
|
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||||
|
});
|
||||||
|
|
||||||
|
function getKeyByValue(object, value) {
|
||||||
|
return Object.keys(object).find(key => object[key] === value);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(node.mode) {
|
||||||
|
default:
|
||||||
|
node.error("Node not configured with a mode");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'request':
|
||||||
|
node.on('input', async function(msg){
|
||||||
|
if(!msg.userId) {
|
||||||
|
node.error("msg.userId is required for start verification mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
node.server.matrixClient.requestDeviceVerification(msg.userId, msg.devices || undefined)
|
||||||
|
.then(function(e) {
|
||||||
|
node.log("Successfully requested verification", e);
|
||||||
|
let verifyRequestId = msg.userId + ':' + e.channel.deviceId;
|
||||||
|
verificationRequests.set(verifyRequestId, e);
|
||||||
|
node.send({
|
||||||
|
verifyRequestId: verifyRequestId, // internally used to reference between nodes
|
||||||
|
verifyMethods: e.methods,
|
||||||
|
userId: msg.userId,
|
||||||
|
deviceIds: e.channel.devices,
|
||||||
|
selfVerification: e.isSelfVerification,
|
||||||
|
phase: getKeyByValue(Phase, e.phase)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(e){
|
||||||
|
node.warn("Error requesting device verification: " + e);
|
||||||
|
msg.error = e;
|
||||||
|
node.send([null, msg]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'receive':
|
||||||
|
/**
|
||||||
|
* Fires when a key verification is requested.
|
||||||
|
* @event module:client~MatrixClient#"crypto.verification.request"
|
||||||
|
* @param {object} data
|
||||||
|
* @param {MatrixEvent} data.event the original verification request message
|
||||||
|
* @param {Array} data.methods the verification methods that can be used
|
||||||
|
* @param {Number} data.timeout the amount of milliseconds that should be waited
|
||||||
|
* before cancelling the request automatically.
|
||||||
|
* @param {Function} data.beginKeyVerification a function to call if a key
|
||||||
|
* verification should be performed. The function takes one argument: the
|
||||||
|
* name of the key verification method (taken from data.methods) to use.
|
||||||
|
* @param {Function} data.cancel a function to call if the key verification is
|
||||||
|
* rejected.
|
||||||
|
*/
|
||||||
|
node.server.matrixClient.on(CryptoEvent.VerificationRequestReceived, async function(data){
|
||||||
|
if(data.phase === Phase.Cancelled || data.phase === Phase.Done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.requested || true) {
|
||||||
|
let verifyRequestId = data.targetDevice.userId + ':' + data.targetDevice.deviceId;
|
||||||
|
verificationRequests.set(verifyRequestId, data);
|
||||||
|
node.send({
|
||||||
|
verifyRequestId: verifyRequestId, // internally used to reference between nodes
|
||||||
|
verifyMethods: data.methods,
|
||||||
|
userId: data.targetDevice.userId,
|
||||||
|
deviceId: data.targetDevice.deviceId,
|
||||||
|
selfVerification: data.isSelfVerification,
|
||||||
|
phase: getKeyByValue(Phase, data.phase)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on('close', function(done) {
|
||||||
|
// clear verification requests
|
||||||
|
verificationRequests.clear();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'start':
|
||||||
|
node.on('input', async function(msg){
|
||||||
|
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
|
||||||
|
node.error("invalid verification request (invalid msg.verifyRequestId): " + (msg.verifyRequestId || null));
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = verificationRequests.get(msg.verifyRequestId);
|
||||||
|
if(msg.cancel) {
|
||||||
|
await data.verifier.cancel();
|
||||||
|
verificationRequests.delete(msg.verifyRequestId);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
data.on('change', async function() {
|
||||||
|
// VerificationPhase {
|
||||||
|
// /** Initial state: no event yet exchanged */
|
||||||
|
// Unsent = 1,
|
||||||
|
//
|
||||||
|
// /** An `m.key.verification.request` event has been sent or received */
|
||||||
|
// Requested = 2,
|
||||||
|
//
|
||||||
|
// /** An `m.key.verification.ready` event has been sent or received, indicating the verification request is accepted. */
|
||||||
|
// Ready = 3,
|
||||||
|
//
|
||||||
|
// /** An `m.key.verification.start` event has been sent or received, choosing a verification method */
|
||||||
|
// Started = 4,
|
||||||
|
//
|
||||||
|
// /** An `m.key.verification.cancel` event has been sent or received at any time before the `done` event, cancelling the verification request */
|
||||||
|
// Cancelled = 5,
|
||||||
|
//
|
||||||
|
// /** An `m.key.verification.done` event has been **sent**, completing the verification request. */
|
||||||
|
// Done = 6,
|
||||||
|
// }
|
||||||
|
console.log("[Verification Start] VERIFIER EVENT CHANGE", this.phase);
|
||||||
|
var that = this;
|
||||||
|
if(this.phase === Phase.Started) {
|
||||||
|
console.log("[Verification Start] VERIFIER EVENT PHASE STARTED");
|
||||||
|
let verifierCancel = function(){
|
||||||
|
let verifyRequestId = that.targetDevice.userId + ':' + that.targetDevice.deviceId;
|
||||||
|
if(verificationRequests.has(verifyRequestId)) {
|
||||||
|
verificationRequests.delete(verifyRequestId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
data.verifier.on('cancel', function(e){
|
||||||
|
node.warn("Device verification cancelled " + e);
|
||||||
|
console.log(JSON.stringify(e.value));
|
||||||
|
verifierCancel();
|
||||||
|
});
|
||||||
|
const sasEventPromise = new Promise(resolve =>
|
||||||
|
data.verifier.once("show_sas", resolve)
|
||||||
|
);
|
||||||
|
console.log("[Verification Start] Starting verification");
|
||||||
|
data.verifier.verify()
|
||||||
|
.then(function() {
|
||||||
|
console.log("[Verification Start] verify() success");
|
||||||
|
}).catch(function(e) {
|
||||||
|
console.log("[Verification Start] verify() error", e);
|
||||||
|
msg.error = e;
|
||||||
|
node.send([null, msg]);
|
||||||
|
});
|
||||||
|
console.log("[Verification Start] WAITING FOR SHOW SAS EVENT");
|
||||||
|
const sasEvent = await sasEventPromise;
|
||||||
|
|
||||||
|
console.log("SHOW SAS", sasEvent);
|
||||||
|
// e = {
|
||||||
|
// sas: {
|
||||||
|
// decimal: [ 8641, 3153, 2357 ],
|
||||||
|
// emoji: [
|
||||||
|
// [Array], [Array],
|
||||||
|
// [Array], [Array],
|
||||||
|
// [Array], [Array],
|
||||||
|
// [Array]
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// confirm: [AsyncFunction: confirm],
|
||||||
|
// cancel: [Function: cancel],
|
||||||
|
// mismatch: [Function: mismatch]
|
||||||
|
// }
|
||||||
|
msg.payload = sasEvent.sas;
|
||||||
|
msg.emojis = sasEvent.sas.emoji.map(function(emoji, i) {
|
||||||
|
return emoji[0];
|
||||||
|
});
|
||||||
|
msg.emojis_text = sasEvent.sas.emoji.map(function(emoji, i) {
|
||||||
|
return emoji[1];
|
||||||
|
});
|
||||||
|
node.send(msg);
|
||||||
|
|
||||||
|
// sasEvent.mismatch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[Verification Start] Starting verification");
|
||||||
|
try {
|
||||||
|
console.log("[Verification Start] Accepting..");
|
||||||
|
await data.accept();
|
||||||
|
console.log(`[Verification] beginKeyVerification (methods=${data.methods[0]}, targetDevice=${data.targetDevice})`);
|
||||||
|
await data.beginKeyVerification(
|
||||||
|
data.methods[0],
|
||||||
|
data.targetDevice
|
||||||
|
);
|
||||||
|
} catch(e) {
|
||||||
|
console.log("[Verification Start] VERIFICATION ERROR", e);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.log("ERROR", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cancel':
|
||||||
|
node.on('input', async function(msg){
|
||||||
|
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
|
||||||
|
node.error("Invalid verification request: " + (msg.verifyRequestId || null));
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = verificationRequests.get(msg.verifyRequestId);
|
||||||
|
if(data) {
|
||||||
|
data.cancel()
|
||||||
|
.then(function(e){
|
||||||
|
node.send([msg, null]);
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
msg.error = e;
|
||||||
|
node.send([null, msg]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'accept':
|
||||||
|
node.on('input', async function(msg){
|
||||||
|
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
|
||||||
|
node.error("Invalid verification request: " + (msg.verifyRequestId || null));
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = verificationRequests.get(msg.verifyRequestId);
|
||||||
|
if(data.verifier && data.verifier.sasEvent) {
|
||||||
|
try {
|
||||||
|
await data.verifier.sasEvent.confirm();
|
||||||
|
node.send([msg, null]);
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
msg.error = e;
|
||||||
|
node.send([null, msg]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.error("Verification must be started");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType("matrix-device-verification", MatrixDeviceVerification);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const sdkPromise = import("matrix-js-sdk");
|
const {RelationType, EventType, Direction} = require("matrix-js-sdk");
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixFetchRelations(n) {
|
function MatrixFetchRelations(n) {
|
||||||
@@ -49,17 +49,14 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sdk = await sdkPromise;
|
|
||||||
const Direction = sdk.Direction;
|
|
||||||
|
|
||||||
function evaluateNodePropertySafe(value, type, node, msg) {
|
function evaluateNodePropertySafe(value, type, node, msg) {
|
||||||
try {
|
try {
|
||||||
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TypeError) {
|
if (e instanceof TypeError) {
|
||||||
return undefined;
|
return undefined; // Handle TypeError and return undefined
|
||||||
}
|
}
|
||||||
throw e;
|
throw e; // Re-throw other errors to prevent masking issues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,16 +71,16 @@ module.exports = function(RED) {
|
|||||||
to = evaluateNodePropertySafe(node.toValue, node.toType, node, msg);
|
to = evaluateNodePropertySafe(node.toValue, node.toType, node, msg);
|
||||||
|
|
||||||
let opts = { dir: direction };
|
let opts = { dir: direction };
|
||||||
if (limit) {
|
if(limit) {
|
||||||
opts.limit = limit;
|
opts.limit = limit;
|
||||||
}
|
}
|
||||||
if (recurse === true || recurse === false) {
|
if(recurse === true || recurse === false) {
|
||||||
opts.recurse = recurse;
|
opts.recurse = recurse;
|
||||||
}
|
}
|
||||||
if (from) {
|
if(from) {
|
||||||
opts.from = from;
|
opts.from = from;
|
||||||
}
|
}
|
||||||
if (to) {
|
if(to) {
|
||||||
opts.to = to;
|
opts.to = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-10
@@ -19,18 +19,14 @@
|
|||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
$("#node-input-roomId").typedInput({
|
$("#node-input-roomId").typedInput({
|
||||||
types: ['msg','flow','global','str'],
|
type: this.roomIdType,
|
||||||
typeField: "#node-input-roomId"
|
types:['msg','flow','global','str'],
|
||||||
});
|
}).typedInput('value', this.roomIdValue);
|
||||||
$("#node-input-roomId").typedInput("type", this.roomIdType || "msg");
|
|
||||||
$("#node-input-roomId").typedInput("value", this.roomIdValue || "topic");
|
|
||||||
|
|
||||||
$("#node-input-eventId").typedInput({
|
$("#node-input-eventId").typedInput({
|
||||||
types: ['msg','flow','global','str'],
|
type: this.eventIdType,
|
||||||
typeField: "#node-input-eventId"
|
types:['msg','flow','global','str'],
|
||||||
});
|
}).typedInput('value', this.eventIdValue);
|
||||||
$("#node-input-eventId").typedInput("type", this.eventIdType || "msg");
|
|
||||||
$("#node-input-eventId").typedInput("value", this.eventIdValue || "eventId");
|
|
||||||
},
|
},
|
||||||
oneditsave: function() {
|
oneditsave: function() {
|
||||||
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
||||||
@@ -62,6 +58,18 @@
|
|||||||
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
||||||
<input type="text" id="node-input-eventId">
|
<input type="text" id="node-input-eventId">
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<script type="text/html" data-help-name="matrix-get-event">
|
<script type="text/html" data-help-name="matrix-get-event">
|
||||||
|
|||||||
+12
-11
@@ -1,8 +1,9 @@
|
|||||||
|
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixMarkRead(n) {
|
function MatrixReceiveMessage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
let node = this;
|
let node = this;
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
this.server = RED.nodes.getNode(n.server);
|
this.server = RED.nodes.getNode(n.server);
|
||||||
@@ -28,7 +29,7 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
node.on("input", async function (msg) {
|
node.on("input", async function (msg) {
|
||||||
if (!node.server || !node.server.matrixClient) {
|
if (! node.server || ! node.server.matrixClient) {
|
||||||
node.error("No matrix server selected", msg);
|
node.error("No matrix server selected", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -37,15 +38,15 @@ module.exports = function(RED) {
|
|||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if (type === 'flow' || type === 'global') {
|
} else if ((type === 'flow') || (type === 'global')) {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch (e2) {
|
} catch(e2) {
|
||||||
throw new Error("Invalid value evaluation");
|
throw new Error("Invalid value evaluation");
|
||||||
}
|
}
|
||||||
} else if (type === "bool") {
|
} else if(type === "bool") {
|
||||||
value = (property === 'true');
|
value = (property === 'true');
|
||||||
} else if (type === "num") {
|
} else if(type === "num") {
|
||||||
value = Number(property);
|
value = Number(property);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -65,9 +66,9 @@ module.exports = function(RED) {
|
|||||||
throw new Error(`Event ${eventId} not found in room ${roomId}.`);
|
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]);
|
node.send([msg, null]);
|
||||||
} catch (e) {
|
} catch(e) {
|
||||||
msg.error = `Room pagination error: ${e}`;
|
msg.error = `Room pagination error: ${e}`;
|
||||||
node.error(msg.error, msg);
|
node.error(msg.error, msg);
|
||||||
node.send([null, msg]);
|
node.send([null, msg]);
|
||||||
@@ -78,5 +79,5 @@ module.exports = function(RED) {
|
|||||||
node.server.deregister(node);
|
node.server.deregister(node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-mark-read", MatrixMarkRead);
|
RED.nodes.registerType("matrix-mark-read", MatrixReceiveMessage);
|
||||||
}
|
}
|
||||||
+32
-32
@@ -1,8 +1,7 @@
|
|||||||
const sdkPromise = import("matrix-js-sdk");
|
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixPaginateRoom(n) {
|
function MatrixReceiveMessage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
let node = this;
|
let node = this;
|
||||||
@@ -35,7 +34,7 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
node.on("input", async function (msg) {
|
node.on("input", async function (msg) {
|
||||||
if (!node.server || !node.server.matrixClient) {
|
if (! node.server || ! node.server.matrixClient) {
|
||||||
node.error("No matrix server selected", msg);
|
node.error("No matrix server selected", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,47 +43,43 @@ module.exports = function(RED) {
|
|||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if (type === 'flow' || type === 'global') {
|
} else if ((type === 'flow') || (type === 'global')) {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch(e2) {
|
} catch(e2) {
|
||||||
throw new Error("Invalid value evaluation");
|
throw new Error("Invalid value evaluation");
|
||||||
}
|
}
|
||||||
} else if (type === "bool") {
|
} else if(type === "bool") {
|
||||||
value = (property === 'true');
|
value = (property === 'true');
|
||||||
} else if (type === "num") {
|
} else if(type === "num") {
|
||||||
value = Number(property);
|
value = Number(property);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToValue(value, type, property) {
|
function setToValue(value, type, property) {
|
||||||
if (type === 'global' || type === 'flow') {
|
if(type === 'global' || type === 'flow') {
|
||||||
var contextKey = RED.util.parseContextStore(property);
|
var contextKey = RED.util.parseContextStore(property);
|
||||||
if (/\[msg/.test(contextKey.key)) {
|
if (/\[msg/.test(contextKey.key)) {
|
||||||
// The key has a nested msg. reference that must be evaluated first
|
// The key has a nest msg. reference to evaluate first
|
||||||
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true);
|
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
|
||||||
}
|
}
|
||||||
var target = node.context()[type];
|
var target = node.context()[type];
|
||||||
target.set(contextKey.key, value, contextKey.store, err => {
|
var callback = err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
node.error(err, msg);
|
node.error(err, msg);
|
||||||
|
getterErrors[rule.p] = err.message;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if (type === 'msg') {
|
target.set(contextKey.key, value, contextKey.store, callback);
|
||||||
|
} else if(type === 'msg') {
|
||||||
if (!RED.util.setMessageProperty(msg, property, value)) {
|
if (!RED.util.setMessageProperty(msg, property, value)) {
|
||||||
node.warn(RED._("change.errors.no-override", { property: property }));
|
node.warn(RED._("change.errors.no-override",{property:property}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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),
|
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
||||||
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
||||||
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
||||||
@@ -92,37 +87,42 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
let room = node.server.matrixClient.getRoom(roomId);
|
let room = node.server.matrixClient.getRoom(roomId);
|
||||||
|
|
||||||
if (!room) {
|
if(!room) {
|
||||||
throw new Error(`Room ${roomId} does not exist`);
|
throw new Error(`Room ${roomId} does not exist`);
|
||||||
}
|
}
|
||||||
if (pageSize > node.server.initialSyncLimit) {
|
if(pageSize > node.server.initialSyncLimit) {
|
||||||
throw new Error(`Page size=${pageSize} cannot exceed initialSyncLimit=${node.server.initialSyncLimit}`);
|
throw new Error(`Page size=${pageSize} cannot exceed initialSyncLimit=${node.server.initialSyncLimit}`);
|
||||||
}
|
}
|
||||||
if (!pageKey) {
|
if(!pageKey) {
|
||||||
pageKey = crypto.randomUUID();
|
pageKey = crypto.randomUUID();
|
||||||
setToValue(pageKey, node.paginateKeyType, node.paginateKeyValue);
|
setToValue(pageKey, node.paginateKeyType, node.paginateKeyValue);
|
||||||
}
|
}
|
||||||
let timelineWindow = node.timelineWindows.get(pageKey),
|
let timelineWindow = node.timelineWindows.get(pageKey),
|
||||||
moreMessages = true;
|
moreMessages = true;
|
||||||
if (!timelineWindow) {
|
if(!timelineWindow) {
|
||||||
let timelineSet = room.getUnfilteredTimelineSet();
|
let timelineSet = room.getUnfilteredTimelineSet();
|
||||||
// MatrixClient's option initialSyncLimit gets set to the filter we are using,
|
// node.debug(JSON.stringify(timelineSet.getFilter()));
|
||||||
// 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);
|
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
||||||
await timelineWindow.load(msg.eventId || null, pageSize);
|
await timelineWindow.load(msg.eventId || null, pageSize);
|
||||||
node.timelineWindows.set(pageKey, timelineWindow);
|
node.timelineWindows.set(pageKey, timelineWindow);
|
||||||
} else {
|
} 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) {
|
if(moreMessages) {
|
||||||
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To avoid errors converting massive MatrixEvent objects to JSON, we omit them.
|
// 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;
|
||||||
|
|
||||||
msg.payload = false;
|
msg.payload = false;
|
||||||
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
||||||
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
||||||
if (moreMessages) {
|
if(moreMessages) {
|
||||||
msg.payload = timelineWindow.getEvents().map(function(event) {
|
msg.payload = timelineWindow.getEvents().map(function(event) {
|
||||||
return {
|
return {
|
||||||
encrypted : event.isEncrypted(),
|
encrypted : event.isEncrypted(),
|
||||||
@@ -152,5 +152,5 @@ module.exports = function(RED) {
|
|||||||
node.server.deregister(node);
|
node.server.deregister(node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-paginate-room", MatrixPaginateRoom);
|
RED.nodes.registerType("matrix-paginate-room", MatrixReceiveMessage);
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
acceptOwnEvents: {"value": false},
|
acceptOwnEvents: {"value": false},
|
||||||
acceptText: {"value": true},
|
acceptText: {"value": true},
|
||||||
acceptEmotes: {"value": true},
|
acceptEmotes: {"value": true},
|
||||||
acceptNotices: {"value": true},
|
|
||||||
acceptStickers: {"value": true},
|
acceptStickers: {"value": true},
|
||||||
acceptReactions: {"value": true},
|
acceptReactions: {"value": true},
|
||||||
acceptFiles: {"value": true},
|
acceptFiles: {"value": true},
|
||||||
@@ -67,16 +66,6 @@
|
|||||||
Accept text <code style="text-transform: none;">m.text</code>
|
Accept text <code style="text-transform: none;">m.text</code>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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;">
|
<div class="form-row" style="margin-bottom:0;">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const {RelationType} = require("matrix-js-sdk");
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixReceiveMessage(n) {
|
function MatrixReceiveMessage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
@@ -9,7 +10,6 @@ module.exports = function(RED) {
|
|||||||
this.acceptOwnEvents = n.acceptOwnEvents;
|
this.acceptOwnEvents = n.acceptOwnEvents;
|
||||||
this.acceptText = n.acceptText;
|
this.acceptText = n.acceptText;
|
||||||
this.acceptEmotes = n.acceptEmotes;
|
this.acceptEmotes = n.acceptEmotes;
|
||||||
this.acceptNotices = n.acceptNotices;
|
|
||||||
this.acceptStickers = n.acceptStickers;
|
this.acceptStickers = n.acceptStickers;
|
||||||
this.acceptReactions = n.acceptReactions;
|
this.acceptReactions = n.acceptReactions;
|
||||||
this.acceptFiles = n.acceptFiles;
|
this.acceptFiles = n.acceptFiles;
|
||||||
@@ -69,10 +69,6 @@ module.exports = function(RED) {
|
|||||||
if (!node.acceptEmotes) return;
|
if (!node.acceptEmotes) return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'm.notice':
|
|
||||||
if (!node.acceptNotices) return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm.text':
|
case 'm.text':
|
||||||
if (!node.acceptText) return;
|
if (!node.acceptText) return;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+21
-27
@@ -1,8 +1,9 @@
|
|||||||
const sdkPromise = import("matrix-js-sdk");
|
const {RelationType} = require("matrix-js-sdk");
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixSendImage(n) {
|
function MatrixSendImage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
var node = this;
|
var node = this;
|
||||||
|
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
@@ -67,17 +68,12 @@ module.exports = function(RED) {
|
|||||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make the input handler async so we can await the dynamic import.
|
node.on("input", function (msg) {
|
||||||
node.on("input", async function (msg) {
|
|
||||||
// Await the SDK import and get the RelationType constant.
|
|
||||||
const sdk = await sdkPromise;
|
|
||||||
const RelationType = sdk.RelationType;
|
|
||||||
|
|
||||||
function getToValue(msg, type, property) {
|
function getToValue(msg, type, property) {
|
||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if (type === 'flow' || type === 'global') {
|
} else if ((type === 'flow') || (type === 'global')) {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch(e2) {
|
} catch(e2) {
|
||||||
@@ -119,19 +115,19 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content = null;
|
let content = null;
|
||||||
if (typeof payload === 'object') {
|
if(typeof payload === 'object') {
|
||||||
content = payload;
|
content = payload;
|
||||||
} else {
|
} else {
|
||||||
if (msgType === 'msg.type') {
|
if(msgType === 'msg.type') {
|
||||||
if (!msg.type) {
|
if(!msg.type) {
|
||||||
node.error("msg.type is set to be passed in via msg.type but was not defined", msg);
|
node.error("msg.type type is set to be passed in via msg.type but was not defined", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
msgType = msg.type;
|
msgType = msg.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgFormat === 'msg.format') {
|
if(msgFormat === 'msg.format') {
|
||||||
if (!Object.hasOwn(msg, 'format')) {
|
if(!msg.format) {
|
||||||
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
|
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,7 +139,7 @@ module.exports = function(RED) {
|
|||||||
body: payload.toString()
|
body: payload.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (msgFormat === 'html') {
|
if(msgFormat === 'html') {
|
||||||
content.format = "org.matrix.custom.html";
|
content.format = "org.matrix.custom.html";
|
||||||
content.formatted_body =
|
content.formatted_body =
|
||||||
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
|
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
|
||||||
@@ -151,15 +147,15 @@ module.exports = function(RED) {
|
|||||||
: payload.toString();
|
: payload.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((node.replaceMessage || msg.replace) && msg.eventId) {
|
if((node.replaceMessage || msg.replace) && msg.eventId) {
|
||||||
content['m.new_content'] = {
|
content['m.new_content'] = {
|
||||||
msgtype: content.msgtype,
|
msgtype: content.msgtype,
|
||||||
body: content.body
|
body: content.body
|
||||||
};
|
};
|
||||||
if ('format' in content) {
|
if('format' in content) {
|
||||||
content['m.new_content']['format'] = content['format'];
|
content['m.new_content']['format'] = content['format'];
|
||||||
}
|
}
|
||||||
if ('formatted_body' in content) {
|
if('formatted_body' in content) {
|
||||||
content['m.new_content']['formatted_body'] = content['formatted_body'];
|
content['m.new_content']['formatted_body'] = content['formatted_body'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,19 +164,17 @@ module.exports = function(RED) {
|
|||||||
event_id: msg.eventId
|
event_id: msg.eventId
|
||||||
};
|
};
|
||||||
content['body'] = ' * ' + content['body'];
|
content['body'] = ' * ' + content['body'];
|
||||||
} else if (threadReply) {
|
} else if(threadReply) {
|
||||||
// If incoming message is a reply to a thread we fetch the thread parent from m.relates_to,
|
// 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.
|
// otherwise fallback to msg.eventId
|
||||||
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread
|
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread ? msg?.content?.['m.relates_to']?.event_id : null) || msg.eventId;
|
||||||
? msg?.content?.['m.relates_to']?.event_id
|
if(threadParent) {
|
||||||
: null) || msg.eventId;
|
|
||||||
if (threadParent) {
|
|
||||||
content["m.relates_to"] = {
|
content["m.relates_to"] = {
|
||||||
"rel_type": RelationType.Thread,
|
"rel_type": RelationType.Thread,
|
||||||
"event_id": threadParent,
|
"event_id": threadParent,
|
||||||
"is_falling_back": true,
|
"is_falling_back": true,
|
||||||
};
|
};
|
||||||
if (msg.eventId !== threadParent) {
|
if(msg.eventId !== threadParent) {
|
||||||
content["m.relates_to"]["m.in_reply_to"] = {
|
content["m.relates_to"]["m.in_reply_to"] = {
|
||||||
"event_id": msg.eventId
|
"event_id": msg.eventId
|
||||||
};
|
};
|
||||||
@@ -208,4 +202,4 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
|
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
|
||||||
};
|
}
|
||||||
@@ -36,8 +36,7 @@
|
|||||||
name: { value: null },
|
name: { value: null },
|
||||||
autoAcceptRoomInvites: { value: true },
|
autoAcceptRoomInvites: { value: true },
|
||||||
enableE2ee: { type: "checkbox", 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",
|
icon: "matrix.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
@@ -131,20 +130,6 @@
|
|||||||
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<script type="text/javascript">
|
||||||
$("#matrix-login-btn").on("click", function() {
|
$("#matrix-login-btn").on("click", function() {
|
||||||
function prettyPrintJson(json) {
|
function prettyPrintJson(json) {
|
||||||
|
|||||||
+166
-73
@@ -1,51 +1,33 @@
|
|||||||
|
const {RelationType, TimelineWindow} = require("matrix-js-sdk");
|
||||||
|
|
||||||
global.Olm = require('olm');
|
global.Olm = require('olm');
|
||||||
const fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
let RelationType, sdk, LocalStorageCryptoStore, RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore;
|
const sdk = require("matrix-js-sdk");
|
||||||
|
|
||||||
(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 { resolve } = require('path');
|
||||||
const { LocalStorage } = require('node-localstorage');
|
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
|
require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it
|
||||||
if (!globalThis.fetch) {
|
if (!globalThis.fetch) {
|
||||||
// polyfill fetch if we don't have it
|
// polyfill fetch if we don't have it
|
||||||
if (!globalThis.fetch) {
|
if (!globalThis.fetch) {
|
||||||
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
|
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) {
|
module.exports = function(RED) {
|
||||||
// disable logging if set to "off"
|
// disable logging if set to "off"
|
||||||
let loggingSettings = RED.settings.get('logging');
|
let loggingSettings = RED.settings.get('logging');
|
||||||
if (
|
if(
|
||||||
typeof loggingSettings.console !== 'undefined' &&
|
typeof loggingSettings.console !== 'undefined' &&
|
||||||
typeof loggingSettings.console.level !== 'undefined' &&
|
typeof loggingSettings.console.level !== 'undefined' &&
|
||||||
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
||||||
) {
|
) {
|
||||||
import('matrix-js-sdk/lib/logger.js')
|
const { logger } = require('matrix-js-sdk/lib/logger');
|
||||||
.then(({ logger }) => {
|
logger.disableAll();
|
||||||
logger.disableAll();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error loading logger module:", err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function MatrixFolderNameFromUserId(name) {
|
function MatrixFolderNameFromUserId(name) {
|
||||||
@@ -70,15 +52,15 @@ module.exports = function(RED) {
|
|||||||
this.userId = this.credentials.userId;
|
this.userId = this.credentials.userId;
|
||||||
this.deviceLabel = this.credentials.deviceLabel || null;
|
this.deviceLabel = this.credentials.deviceLabel || null;
|
||||||
this.deviceId = this.credentials.deviceId || null;
|
this.deviceId = this.credentials.deviceId || null;
|
||||||
|
this.secretStoragePassphrase = null;
|
||||||
this.url = this.credentials.url;
|
this.url = this.credentials.url;
|
||||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||||
this.e2ee = n.enableE2ee || false;
|
this.e2ee = n.enableE2ee || false;
|
||||||
this.globalAccess = n.global;
|
this.globalAccess = n.global;
|
||||||
this.allowUnknownDevices = n.allowUnknownDevices || false;
|
|
||||||
this.initializedAt = new Date();
|
this.initializedAt = new Date();
|
||||||
node.initialSyncLimit = 25;
|
node.initialSyncLimit = 25;
|
||||||
|
|
||||||
// Keep track of all consumers of this node to catch errors
|
// Keep track of all consumers of this node to be able to catch errors
|
||||||
node.register = function(consumerNode) {
|
node.register = function(consumerNode) {
|
||||||
node.users[consumerNode.id] = consumerNode;
|
node.users[consumerNode.id] = consumerNode;
|
||||||
};
|
};
|
||||||
@@ -91,6 +73,33 @@ module.exports = function(RED) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cryptoCallbacks = undefined;
|
||||||
|
if(node.e2ee) {
|
||||||
|
cryptoCallbacks = {
|
||||||
|
getSecretStorageKey: async ({ keys }) => {
|
||||||
|
return null; // we don't do secret storage right now
|
||||||
|
const backupPassphrase = node.secretStoragePassphrase;
|
||||||
|
if (!backupPassphrase) {
|
||||||
|
node.WARN("Missing secret storage key");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let keyId = await node.matrixClient.getDefaultSecretStorageKeyId();
|
||||||
|
if (keyId && !keys[keyId]) {
|
||||||
|
keyId = undefined;
|
||||||
|
}
|
||||||
|
if (!keyId) {
|
||||||
|
keyId = keys[0][0];
|
||||||
|
}
|
||||||
|
const backupInfo = await node.matrixClient.getKeyBackupVersion();
|
||||||
|
const key = await node.matrixClient.keyBackupKeyFromPassword(
|
||||||
|
backupPassphrase,
|
||||||
|
backupInfo
|
||||||
|
);
|
||||||
|
return [keyId, key];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId),
|
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId),
|
||||||
localStorage = new LocalStorage(localStorageDir),
|
localStorage = new LocalStorage(localStorageDir),
|
||||||
initialSetup = false;
|
initialSetup = false;
|
||||||
@@ -102,6 +111,61 @@ module.exports = function(RED) {
|
|||||||
} else if(!this.url) {
|
} else if(!this.url) {
|
||||||
node.error("Matrix connection failed: missing server URL in configuration.", {});
|
node.error("Matrix connection failed: missing server URL in configuration.", {});
|
||||||
} else {
|
} else {
|
||||||
|
/**
|
||||||
|
* Ensures secret storage and cross signing are ready for use. Does not
|
||||||
|
* support initial setup of secret storage. If the backup passphrase is not
|
||||||
|
* set, this is a no-op, else it is cleared once the operation is complete.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function bootstrapSSSS() {
|
||||||
|
if (!node.matrixClient) {
|
||||||
|
// client startup will do bootstrapping
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const password = "testphrase";
|
||||||
|
if (!password) {
|
||||||
|
// We do not support setting up secret storage, so we need a passphrase
|
||||||
|
// to bootstrap.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const backupInfo = await node.matrixClient.getKeyBackupVersion();
|
||||||
|
await node.matrixClient.getCrypto().bootstrapSecretStorage({
|
||||||
|
setupNewKeyBackup: false,
|
||||||
|
async getKeyBackupPassphrase() {
|
||||||
|
const key = await node.matrixClient.keyBackupKeyFromPassword(
|
||||||
|
password,
|
||||||
|
backupInfo
|
||||||
|
);
|
||||||
|
return key;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await node.matrixClient.getCrypto().bootstrapCrossSigning({
|
||||||
|
authUploadDeviceSigningKeys(makeRequest) {
|
||||||
|
console.log("authUploadDeviceSigningKeys");
|
||||||
|
makeRequest({
|
||||||
|
"type": "m.login.password",
|
||||||
|
"identifier": {
|
||||||
|
"type": "m.id.user",
|
||||||
|
"user": node.matrixClient.getUserId()
|
||||||
|
},
|
||||||
|
"password": "examplepass",
|
||||||
|
"session": node.matrixClient.getSessionId()
|
||||||
|
});
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await node.matrixClient.checkOwnCrossSigningTrust();
|
||||||
|
if (backupInfo) {
|
||||||
|
await node.matrixClient.restoreKeyBackupWithSecretStorage(backupInfo);
|
||||||
|
}
|
||||||
|
// Clear passphrase once bootstrap was successful
|
||||||
|
// this.imAccount.setString("backupPassphrase", "");
|
||||||
|
// this.imAccount.save();
|
||||||
|
// this._encryptionError = "";
|
||||||
|
// await this.updateEncryptionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
node.setConnected = async function(connected, cb) {
|
node.setConnected = async function(connected, cb) {
|
||||||
if (node.connected !== connected) {
|
if (node.connected !== connected) {
|
||||||
node.connected = connected;
|
node.connected = connected;
|
||||||
@@ -113,12 +177,20 @@ module.exports = function(RED) {
|
|||||||
node.log("Matrix server connection ready.");
|
node.log("Matrix server connection ready.");
|
||||||
node.emit("connected");
|
node.emit("connected");
|
||||||
if(!initialSetup) {
|
if(!initialSetup) {
|
||||||
|
console.log("INITIAL SETUP", await node.matrixClient.getCrypto().getCrossSigningStatus());
|
||||||
|
if(node.e2ee && !await node.matrixClient.getCrypto().isCrossSigningReady()) {
|
||||||
|
// bootstrap cross-signing
|
||||||
|
await bootstrapSSSS();
|
||||||
|
let crossSigningStatus = node.matrixClient.getCrypto().getCrossSigningStatus();
|
||||||
|
console.log("crossSigningStatus", crossSigningStatus);
|
||||||
|
}
|
||||||
|
|
||||||
// store Device ID internally
|
// store Device ID internally
|
||||||
let stored_device_id = getStoredDeviceId(localStorage),
|
let stored_device_id = getStoredDeviceId(localStorage),
|
||||||
device_id = this.matrixClient.getDeviceId();
|
device_id = this.matrixClient.getDeviceId();
|
||||||
|
|
||||||
if(!device_id && node.enableE2ee) {
|
if(!device_id && node.e2ee) {
|
||||||
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 {
|
} else {
|
||||||
if(!stored_device_id || stored_device_id !== device_id) {
|
if(!stored_device_id || stored_device_id !== device_id) {
|
||||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||||
@@ -135,7 +207,7 @@ module.exports = function(RED) {
|
|||||||
node.matrixClient.setDeviceDetails(device_id, {
|
node.matrixClient.setDeviceDetails(device_id, {
|
||||||
display_name: node.deviceLabel
|
display_name: node.deviceLabel
|
||||||
}).then(
|
}).then(
|
||||||
function() {},
|
function(response) {},
|
||||||
function(error) {
|
function(error) {
|
||||||
node.error("Failed to set device label: " + error, {});
|
node.error("Failed to set device label: " + error, {});
|
||||||
}
|
}
|
||||||
@@ -164,6 +236,17 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
||||||
upgradeDirectoryIfNecessary(node, storageDir);
|
upgradeDirectoryIfNecessary(node, storageDir);
|
||||||
|
|
||||||
|
// taken from https://github.com/matrix-org/matrix-react-sdk/blob/d9d0ab3d98dea8f260bd7037482c3c8cf288ae82/cypress/support/bot.ts
|
||||||
|
// these next lines are to fix "Device verification cancelled Error: No getCrossSigningKey callback supplied" error
|
||||||
|
const privateKeys = {};
|
||||||
|
const getCrossSigningKey = (type) => {
|
||||||
|
return privateKeys[type];
|
||||||
|
};
|
||||||
|
const saveCrossSigningKeys = (k) => {
|
||||||
|
Object.assign(privateKeys, k);
|
||||||
|
};
|
||||||
|
|
||||||
node.matrixClient = sdk.createClient({
|
node.matrixClient = sdk.createClient({
|
||||||
baseUrl: this.url,
|
baseUrl: this.url,
|
||||||
accessToken: this.credentials.accessToken,
|
accessToken: this.credentials.accessToken,
|
||||||
@@ -172,7 +255,15 @@ module.exports = function(RED) {
|
|||||||
localStorage: localStorage,
|
localStorage: localStorage,
|
||||||
}),
|
}),
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined
|
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
|
||||||
|
request,
|
||||||
|
verificationMethods: ["m.sas.v1"],
|
||||||
|
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
|
||||||
|
});
|
||||||
|
|
||||||
|
node.matrixClient.on("crypto.keyBackupStatus", function() {
|
||||||
|
console.log("crypto.keyBackupStatus");
|
||||||
|
bootstrapSSSS();
|
||||||
});
|
});
|
||||||
|
|
||||||
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
||||||
@@ -198,7 +289,7 @@ module.exports = function(RED) {
|
|||||||
if(node.globalAccess) {
|
if(node.globalAccess) {
|
||||||
try {
|
try {
|
||||||
node.context().global.set('matrixClient["'+node.userId+'"]', undefined);
|
node.context().global.set('matrixClient["'+node.userId+'"]', undefined);
|
||||||
} catch(e) {
|
} catch(e){
|
||||||
node.error(e.message, {});
|
node.error(e.message, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,15 +303,15 @@ module.exports = function(RED) {
|
|||||||
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
||||||
if (toStartOfTimeline) {
|
if (toStartOfTimeline) {
|
||||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result");
|
||||||
return;
|
return; // ignore paginated results
|
||||||
}
|
}
|
||||||
if (!data || !data.liveEvent) {
|
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");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message");
|
||||||
return;
|
return; // ignore old message (we only want live events)
|
||||||
}
|
}
|
||||||
if(node.initializedAt > event.getDate()) {
|
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");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init");
|
||||||
return;
|
return; // skip events that occurred before our client initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -395,6 +486,7 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
||||||
// Example if user auth token incorrect:
|
// Example if user auth token incorrect:
|
||||||
// {
|
// {
|
||||||
@@ -416,8 +508,9 @@ module.exports = function(RED) {
|
|||||||
if(node.e2ee){
|
if(node.e2ee){
|
||||||
node.log("Initializing crypto...");
|
node.log("Initializing crypto...");
|
||||||
await node.matrixClient.initCrypto();
|
await node.matrixClient.initCrypto();
|
||||||
|
node.log("Bootstrapping SSSS...");
|
||||||
|
await bootstrapSSSS();
|
||||||
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
|
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
|
||||||
node.matrixClient.getCrypto().globalErrorOnUnknownDevices = !node.allowUnknownDevices;
|
|
||||||
}
|
}
|
||||||
node.log("Connecting to Matrix server...");
|
node.log("Connecting to Matrix server...");
|
||||||
await node.matrixClient.startClient({
|
await node.matrixClient.startClient({
|
||||||
@@ -467,7 +560,7 @@ module.exports = function(RED) {
|
|||||||
node.error("Auth check failed: " + err, {});
|
node.error("Auth check failed: " + err, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,19 +585,20 @@ module.exports = function(RED) {
|
|||||||
deviceId = req.body.deviceId || undefined,
|
deviceId = req.body.deviceId || undefined,
|
||||||
displayName = req.body.displayName || undefined;
|
displayName = req.body.displayName || undefined;
|
||||||
|
|
||||||
(async () => {
|
const matrixClient = sdk.createClient({
|
||||||
const mod = await import("matrix-js-sdk");
|
baseUrl: baseUrl,
|
||||||
const matrixClient = mod.createClient({
|
deviceId: deviceId,
|
||||||
baseUrl: baseUrl,
|
timelineSupport: true,
|
||||||
deviceId: deviceId,
|
localTimeoutMs: '30000',
|
||||||
timelineSupport: true,
|
request
|
||||||
localTimeoutMs: '30000'
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
new TimelineWindow()
|
||||||
|
|
||||||
matrixClient.timelineSupport = true;
|
matrixClient.timelineSupport = true;
|
||||||
|
|
||||||
matrixClient.login('m.login.password', {
|
matrixClient.login(
|
||||||
|
'm.login.password', {
|
||||||
identifier: {
|
identifier: {
|
||||||
type: 'm.id.user',
|
type: 'm.id.user',
|
||||||
user: userId,
|
user: userId,
|
||||||
@@ -512,27 +606,23 @@ module.exports = function(RED) {
|
|||||||
password: password,
|
password: password,
|
||||||
initial_device_display_name: displayName
|
initial_device_display_name: displayName
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
function(response) {
|
function(response) {
|
||||||
res.json({
|
res.json({
|
||||||
'result': 'ok',
|
'result': 'ok',
|
||||||
'token': response.access_token,
|
'token': response.access_token,
|
||||||
'device_id': response.device_id,
|
'device_id': response.device_id,
|
||||||
'user_id': response.user_id,
|
'user_id': response.user_id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function(err) {
|
function(err) {
|
||||||
res.json({
|
res.json({
|
||||||
'result': 'error',
|
'result': 'error',
|
||||||
'message': err
|
'message': err
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})().catch(err => {
|
});
|
||||||
res.json({ result: 'error', message: err.toString() });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function upgradeDirectoryIfNecessary(node, storageDir) {
|
function upgradeDirectoryIfNecessary(node, storageDir) {
|
||||||
let oldStorageDir = './matrix-local-storage',
|
let oldStorageDir = './matrix-local-storage',
|
||||||
@@ -573,6 +663,9 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a device ID is stored we will use that for the client
|
||||||
|
*/
|
||||||
function getStoredDeviceId(localStorage) {
|
function getStoredDeviceId(localStorage) {
|
||||||
let deviceId = localStorage.getItem('my_device_id');
|
let deviceId = localStorage.getItem('my_device_id');
|
||||||
if(deviceId === "null" || !deviceId) {
|
if(deviceId === "null" || !deviceId) {
|
||||||
@@ -588,4 +681,4 @@ module.exports = function(RED) {
|
|||||||
localStorage.setItem('my_device_id', deviceId);
|
localStorage.setItem('my_device_id', deviceId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user