4 Commits

Author SHA1 Message Date
Skylar Sadlier
5119bc42d4 - WIP 2022-03-04 00:12:59 -07:00
Skylar Sadlier
2993dea974 - WIP 2022-03-04 00:09:15 -07:00
Skylar Sadlier
678702e9c6 WIP on secret storage node 2022-03-04 00:09:15 -07:00
Skylar Sadlier
36c8208b01 - WIP on getting device validation to work 2022-03-04 00:09:15 -07:00
38 changed files with 1456 additions and 1171 deletions

Binary file not shown.

View File

@@ -1,5 +1,5 @@
# node-red-contrib-matrix-chat
[Matrix](https://matrix.org/) chat server client for [Node-RED](https://nodered.org/)
Matrix chat server client for [Node-RED](https://nodered.org/)
***Currently we are in beta. We ask that you open any issues you have on our repository to help us reach a stable well tested version. Things may change & break before our first release so check changelog before updating.***
@@ -11,10 +11,8 @@ The following is supported from this package:
- End-to-end encryption
- [Currently a WIP](#end-to-end-encryption-notes)
- Receive events from a room (messages, reactions, images, audio, locations, and files) whether encrypted or not
- Receive events from a room (messages, reactions, images, and files) whether encrypted or not
- Send Images/Files (sending files to e2ee room doesn't currently encrypt them yet)
- Edit messages
- Delete events (messages, reactions, etc)
- Decrypt files in e2ee rooms
- Send HTML/Plain Text Message/Notice
- React to messages

View File

@@ -15,7 +15,6 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
- [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image)
- [Respond to "file" with an uploaded file](#respond-to-file-with-an-uploaded-file)
- [Respond to "react" with a reaction](#respond-to-react-with-a-reaction)
- [Remove messages containing "delete"](#remove-messages-containing-delete)
- [Respond to "users" with full list of server users](#respond-to-users-with-full-list-of-server-users)
- [Respond to "newroom" by creating new room and inviting user](#respond-to-newroom-by-creating-new-room-and-inviting-user)
- [Respond to "joinroom <room_id_or_alias>" by joining mentioned room](#respond-to-joinroom-room_id_or_alias-by-joining-mentioned-room)
@@ -53,7 +52,7 @@ Allows an administrator to create or modify a user account with a specified `msg
[View JSON](custom-redact-function-node.json)
If we do not have a node for something you want to do you can do this manually with a function node. We now have a node for removing events but this is still a good example.
If we do not have a node for something you want to do (such as redacting events/messages) you can do this manually with a function node.
**Note:** You should make sure to catch any errors in your function node otherwise you could cause Node-RED to crash.
@@ -61,8 +60,6 @@ To view what sort of functions you have access to check out the `client.ts` file
![custom-redact-function-node.png](custom-redact-function-node.png)
### Respond to "ping" with "pong"
[View JSON](respond-ping-pong.json)
@@ -113,16 +110,6 @@ Give a 👍 reaction when someone says "react"
### Remove messages containing "delete"
[View JSON](delete-event.json)
Any messages containing "delete" will try to be removed by the client.
![respond-react-with-reaction.png](delete-event.png)
### Respond to "users" with full list of server users
[View JSON](respond-users-list.json)

View File

@@ -1,73 +0,0 @@
[
{
"id": "fed9197df27197a4",
"type": "matrix-receive",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"roomId": "",
"acceptText": true,
"acceptEmotes": true,
"acceptStickers": true,
"acceptReactions": true,
"acceptFiles": true,
"acceptImages": true,
"x": 340,
"y": 1560,
"wires": [
[
"b289bb4fed9fa166"
]
]
},
{
"id": "b289bb4fed9fa166",
"type": "switch",
"z": "f025a8b9fbd1b054",
"name": "",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "cont",
"v": "delete",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 490,
"y": 1560,
"wires": [
[
"48766b632ab2e6a1"
]
]
},
{
"id": "48766b632ab2e6a1",
"type": "matrix-delete-event",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"roomId": "",
"reason": "Requested deletion",
"x": 630,
"y": 1560,
"wires": [
[],
[]
]
},
{
"id": "11f9cbbed7b95c83",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Delete messages containing \"delete\"",
"info": "",
"x": 480,
"y": 1520,
"wires": []
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

924
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.6.1",
"version": "0.4.6",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"fs-extra": "^10.0.1",
"got": "^12.0.2",
"fs-extra": "^10.0.0",
"got": "^12.0.1",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^16.0.0",
"matrix-js-sdk": "^15.5.0",
"node-localstorage": "^2.2.1",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/271/download",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"utf8": "^3.0.0"
},
"node-red": {
@@ -17,7 +17,6 @@
"matrix-server-config": "src/matrix-server-config.js",
"matrix-receive": "src/matrix-receive.js",
"matrix-send-message": "src/matrix-send-message.js",
"matrix-delete-event": "src/matrix-delete-event.js",
"matrix-send-file": "src/matrix-send-file.js",
"matrix-send-image": "src/matrix-send-image.js",
"matrix-react": "src/matrix-react.js",
@@ -33,7 +32,9 @@
"matrix-synapse-deactivate-user": "src/matrix-synapse-deactivate-user.js",
"matrix-synapse-join-room": "src/matrix-synapse-join-room.js",
"matrix-whois-user": "src/matrix-whois-user.js",
"matrix-room-users": "src/matrix-room-users.js"
"matrix-room-users": "src/matrix-room-users.js",
"matrix-device-verify": "src/matrix-device-verify.js",
"matrix-secret-storage": "src/matrix-secret-storage.js"
}
},
"engines": {

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-create-room">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>

View File

@@ -18,14 +18,14 @@
<script type="text/html" data-template-name="matrix-decrypt-file">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="matrix-decrypt-file">
<h3>Details</h3>
<p>Files sent in an encrypted room are themselves encrypted. Use this node to decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
<p>Files sent in an encrypted room are themselves encrypted. Use this node to encrypt/decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
<h3>Inputs</h3>
<dl class="message-properties">

View File

@@ -1,96 +0,0 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-delete-event',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
reason: { value: "" },
},
label: function() {
return this.name||"Delete Event";
},
paletteLabel: 'Delete Event'
});
</script>
<script type="text/html" data-template-name="matrix-delete-event">
<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-roomId"><i class="fa fa-comments"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-reason"><i class="fa fa-sticky-note"></i> Reason</label>
<input type="text" id="node-input-reason" placeholder="msg.reason">
</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-delete-event">
<h3>Details</h3>
<p>Delete an event in a room</p>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> Room ID from where the event should be deleted from. Optional if configured on the node. If configured on the node this input will be overridden.</dd>
<dt>msg.eventId
<span class="property-type">string</span>
</dt>
<dd>Event ID of the Event which should be deleted.</dd>
<dt>msg.reason
<span class="property-type">string</span>
</dt>
<dd>Reason why the event is deleted. Default an empty string</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the deleted event.</dd>
</dl>
<dl class="message-properties">
<dt>msg.deleted <span class="property-type">boolean</span></dt>
<dd>True, if the event is deleted</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
<dl class="message-properties">
<dt>msg.deleted <span class="property-type">boolean</span></dt>
<dd>False, if the event is not deleted</dd>
</dl>
</dl>
</li>
</ol>
</script>

View File

@@ -1,75 +0,0 @@
module.exports = function(RED) {
function MatrixDeleteEvent(n) {
RED.nodes.createNode(this,n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
this.reason = n.reason
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
node.on('input', function(msg) {
if(!msg.eventId) {
node.error("eventId is missing");
node.send([null, msg])
return;
}
if (!node.server || !node.server.matrixClient) {
node.warn("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.warn("Room must be specified in msg.topic or in configuration");
return;
}
msg.reason = node.reason || msg.reason;
if(!msg.reason) {
msg.reason = '';
}
node.server.matrixClient.redactEvent(msg.topic, msg.eventId, undefined,{
reason: msg.reason
})
.then(function(e) {
msg.deleted = true
node.send([msg, null]);
})
.catch(function(e){
node.warn("Error deleting event " + e);
msg.error = e;
msg.deleted = false
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-delete-event",MatrixDeleteEvent);
}

View File

@@ -0,0 +1,308 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-device-verify-request', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 0,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Device Verify Request";
},
paletteLabel: 'Device Verify Request'
});
</script>
<script type="text/html" data-template-name="matrix-device-verify-request">
<div class="form-row">
<label for="node-input-name"><i class="icon-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>
</script>
<script type="text/html" data-help-name="matrix-device-verify-request">
<h3>Details</h3>
<p>
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
</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>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be invited to the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for the membership change.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Currently this endpoint returns an empty object</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>
<script type="text/javascript">
RED.nodes.registerType('matrix-device-verify-start', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 1,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Device Verify Start";
},
paletteLabel: 'Device Verify Start'
});
</script>
<script type="text/html" data-template-name="matrix-device-verify-start">
<div class="form-row">
<label for="node-input-name"><i class="icon-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>
</script>
<script type="text/html" data-help-name="matrix-device-verify-start">
<h3>Details</h3>
<p>
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
</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>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be invited to the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for the membership change.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Currently this endpoint returns an empty object</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>
<script type="text/javascript">
RED.nodes.registerType('matrix-device-verify-cancel', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 1,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Device Verify Cancel";
},
paletteLabel: 'Device Verify Cancel'
});
</script>
<script type="text/html" data-template-name="matrix-device-verify-cancel">
<div class="form-row">
<label for="node-input-name"><i class="icon-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>
</script>
<script type="text/html" data-help-name="matrix-device-verify-cancel">
<h3>Details</h3>
<p>
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
</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>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be invited to the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for the membership change.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Currently this endpoint returns an empty object</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>
<script type="text/javascript">
RED.nodes.registerType('matrix-device-verify-accept', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 1,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Device Verify Accept";
},
paletteLabel: 'Device Verify Accept'
});
</script>
<script type="text/html" data-template-name="matrix-device-verify-accept">
<div class="form-row">
<label for="node-input-name"><i class="icon-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>
</script>
<script type="text/html" data-help-name="matrix-device-verify-accept">
<h3>Details</h3>
<p>
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
</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>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be invited to the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for the membership change.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Currently this endpoint returns an empty object</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

271
src/matrix-device-verify.js Normal file
View File

@@ -0,0 +1,271 @@
module.exports = function(RED) {
const verificationRequests = new Map();
function MatrixDeviceVerifyRequest(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
/**
* 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("crypto.verification.request", async function(data){
console.log("[######### crypto.verification.request #########]", data.phase, data);
if(data.phase === 5 || data.phase === 6) {
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,
type: 'crypto.verification.request',
selfVerification: data.isSelfVerification
});
}
});
}
RED.nodes.registerType("matrix-device-verify-request", MatrixDeviceVerifyRequest);
function MatrixDeviceVerifyStart(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
node.on('close', function(done) {
verificationRequests.clear();
done();
});
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
// if(msg.userId && msg.deviceId) {
// node.server.beginKeyVerification("m.sas.v1", msg.userId, msg.deviceId);
// }
node.error("Invaid verification request: " + (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() {
var that = this;
console.log("[##### VERIFICATION PHASE CHANGE #######]", this.phase);
if(this.phase === 4) {
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 verificaiton cancelled " + e);
verifierCancel();
});
let show_sas = function(e) {
// 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 = e.sas;
msg.emojis = e.sas.emoji.map(function(emoji, i) {
return emoji[0];
});
msg.emojis_text = e.sas.emoji.map(function(emoji, i) {
return emoji[1];
});
node.send(msg);
};
data._verifier.on('show_sas', show_sas);
data._verifier.verify()
.then(function(e){
console.log("!!!!!!!!!!! VERIFY THEN", e);
data._verifier.off('show_sas', show_sas);
data._verifier.done();
}, function(e) {
verifierCancel();
node.warn(e);
// @todo return over second output
});
}
});
data.emit("change");
await data.accept();
} catch(e) {
console.log("ERROR", e);
}
}
});
}
RED.nodes.registerType("matrix-device-verify-start", MatrixDeviceVerifyStart);
function MatrixDeviceVerifyCancel(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
node.on('close', function(done) {
verificationRequests.clear();
done();
});
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
node.error("Invaid verification request: " + (msg.verifyRequestId || null));
}
var data = verificationRequests.get(msg.verifyRequestId);
if(data) {
data.cancel();
}
});
}
RED.nodes.registerType("matrix-device-verify-cancel", MatrixDeviceVerifyCancel);
function MatrixDeviceVerifyAccept(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
node.on('close', function(done) {
verificationRequests.clear();
done();
});
node.on('input', async function(msg){
if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) {
node.error("Invaid verification request: " + (msg.verifyRequestId || null));
}
var data = verificationRequests.get(msg.verifyRequestId);
if(data._verifier && data._verifier.sasEvent) {
data._verifier.sasEvent.confirm()
.then(function(e){
console.log("!!!!!!!! CONFIRMED VERIFY", e);
})
.catch(function(e) {
console.log("!!!!!!!! CONFIRMED VERIFY FAILED", e);
});
} else {
console.log("Verification must be started", data);
node.error("Verification must be started");
}
});
}
RED.nodes.registerType("matrix-device-verify-accept", MatrixDeviceVerifyAccept);
}

View File

@@ -20,7 +20,7 @@
<script type="text/html" data-template-name="matrix-invite-room">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
@@ -30,21 +30,9 @@
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</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-invite-room">

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-join-room">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">

View File

@@ -9,8 +9,7 @@
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
reaction: { value: null }
roomId: { value: null }
},
label: function() {
return this.name || "React";
@@ -21,7 +20,7 @@
<script type="text/html" data-template-name="matrix-react">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -29,25 +28,9 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-reaction"><i class="fa fa-thumbs-up"></i> Reaction</label>
<input type="text" id="node-input-reaction" placeholder="msg.payload">
</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-react">
@@ -59,7 +42,7 @@
<dt>msg.payload
<span class="property-type">string</span>
</dt>
<dd> Usually an emoji but can also be text. If configured on the node this is ignored otherwise it required. </dd>
<dd> Usually an emoji but can also be text. </dd>
<dt>msg.topic
<span class="property-type">string | null</span>

View File

@@ -7,7 +7,6 @@ module.exports = function(RED) {
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
this.reaction = n.reaction;
if (!node.server) {
node.warn("No configuration node");
@@ -41,9 +40,8 @@ module.exports = function(RED) {
return;
}
let payload = n.reaction || msg.payload;
if(!payload) {
node.error('msg.payload must be defined or the reaction configured on the node.');
if(!msg.payload) {
node.error('msg.payload is required');
return;
}
@@ -61,7 +59,7 @@ module.exports = function(RED) {
{
"m.relates_to": {
event_id: eventId,
key: payload,
key: msg.payload,
rel_type: "m.annotation"
}
}

View File

@@ -15,9 +15,7 @@
acceptStickers: {"value": true},
acceptReactions: {"value": true},
acceptFiles: {"value": true},
acceptAudio: {"value": true},
acceptImages: {"value": true},
acceptLocations: {"value": true},
},
label: function() {
return this.name || "Matrix Receive";
@@ -36,9 +34,8 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-tips">Enter a single room, comma separated list of rooms, or leave blank to get from all</div>
<div class="form-row" style="margin-left: 100px;margin-top:10px;font-weight:bold;">
@@ -51,7 +48,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptText" style="width: auto">
Accept text <code style="text-transform: none;">m.text</code>
Accept text <code>m.text</code>
</label>
</div>
<div class="form-row">
@@ -61,7 +58,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptEmotes" style="width: auto">
Accept emotes <code style="text-transform: none;">m.emote</code>
Accept emotes <code>m.emote</code>
</label>
</div>
<div class="form-row">
@@ -71,7 +68,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptStickers" style="width: auto">
Accept stickers <code style="text-transform: none;">m.sticker</code>
Accept stickers <code>m.sticker</code>
</label>
</div>
<div class="form-row">
@@ -81,7 +78,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptReactions" style="width: auto">
Accept reactions <code style="text-transform: none;">m.reaction</code>
Accept reactions <code>m.reaction</code>
</label>
</div>
<div class="form-row">
@@ -91,17 +88,7 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptFiles" style="width: auto">
Accept files <code style="text-transform: none;">m.file</code>
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-acceptAudio"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptAudio" style="width: auto">
Accept files <code style="text-transform: none;">m.audio</code>
Accept files <code>m.file</code>
</label>
</div>
<div class="form-row">
@@ -111,30 +98,9 @@
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptImages" style="width: auto">
Accept images <code style="text-transform: none;">m.image</code>
Accept images <code>m.image</code>
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-acceptLocations"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-acceptLocations" style="width: auto">
Accept locations <code style="text-transform: none;">m.location</code>
</label>
</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-receive">
@@ -150,11 +116,6 @@
</dd>
</dl>
<dl class="message-properties">
<dt>msg.isDM <span class="property-type">bool</span></dt>
<dd> returns true if message is from a direct message room.</dd>
</dl>
<dl class="message-properties">
<dt>msg.encrypted <span class="property-type">bool</span></dt>
<dd> returns true if message was encrypted (e2ee).</dd>
@@ -259,38 +220,6 @@
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.audio</strong>'
<dl class="message-properties">
<dt>msg.filename <span class="property-type">string</span></dt>
<dd>the image's parsed filename</dd>
</dl>
<dl class="message-properties">
<dt>msg.mimetype <span class="property-type">string</span></dt>
<dd>audio file mimetype (ex: audio/ogg)</dd>
</dl>
<dl class="message-properties">
<dt>msg.url <span class="property-type">string</span></dt>
<dd>the file's URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.mxc_url <span class="property-type">string</span></dt>
<dd>the file's Matrix URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.duration <span class="property-type">integer</span></dt>
<dd>duration of audio file in milliseconds</dd>
</dl>
<dl class="message-properties">
<dt>msg.waveform <span class="property-type">array[int]</span></dt>
<dd>waveform of the audio clip</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.image</strong>'
<dl class="message-properties">
<dt>msg.filename <span class="property-type">string</span></dt>
@@ -317,12 +246,5 @@
<dd>the image's thumbnail Matrix URL</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.location</strong>'
<dl class="message-properties">
<dt>msg.geo_uri <span class="property-type">string</span></dt>
<dd>URI format of the geolocation</dd>
</dl>
</li>
</ul>
</script>

View File

@@ -11,9 +11,7 @@ module.exports = function(RED) {
this.acceptStickers = n.acceptStickers;
this.acceptReactions = n.acceptReactions;
this.acceptFiles = n.acceptFiles;
this.acceptAudio = n.acceptAudio;
this.acceptImages = n.acceptImages;
this.acceptLocations = n.acceptLocations;
this.roomId = n.roomId;
this.roomIds = this.roomId ? this.roomId.split(',') : [];
@@ -74,27 +72,6 @@ module.exports = function(RED) {
}
break;
case 'm.audio':
if(!node.acceptAudio) return;
if(msg.encrypted) {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
msg.mxc_url = msg.content.file.url;
} else {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
}
if('org.matrix.msc1767.file' in msg.content) {
msg.filename = msg.content['org.matrix.msc1767.file'].name;
msg.mimetype = msg.content['org.matrix.msc1767.file'].mimetype;
}
if('org.matrix.msc1767.audio' in msg.content) {
msg.duration = msg.content['org.matrix.msc1767.audio'].duration;
msg.waveform = msg.content['org.matrix.msc1767.audio'].waveform;
}
break;
case 'm.image':
if(!node.acceptImages) return;
msg.filename = msg.content.filename || msg.content.body;
@@ -111,12 +88,6 @@ module.exports = function(RED) {
}
break;
case 'm.location':
if(!node.acceptLocations) return;
msg.geo_uri = msg.content.geo_uri;
msg.payload = msg.content.body;
break;
case 'm.reaction':
if(!node.acceptReactions) return;
msg.info = msg.content["m.relates_to"].info;

View File

@@ -9,8 +9,7 @@
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
reason: { value: null }
roomId: { value: null }
},
label: function() {
return this.name || "Room Ban";
@@ -21,7 +20,7 @@
<script type="text/html" data-template-name="matrix-room-ban">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -29,25 +28,9 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
<input type="text" id="node-input-reason" placeholder="msg.topic">
</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-room-ban">
@@ -69,7 +52,7 @@
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for banning the user. If configured on the node it will overwrite this input</dd>
<dd> Reason for banning the user.</dd>
</dl>
<h3>Outputs</h3>

View File

@@ -7,7 +7,6 @@ module.exports = function(RED) {
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
this.reason = n.reason;
if (!node.server) {
node.warn("No configuration node");
@@ -46,7 +45,7 @@ module.exports = function(RED) {
return;
}
node.server.matrixClient.ban(msg.topic, msg.userId, n.reason || msg.reason || undefined)
node.server.matrixClient.ban(msg.topic, msg.userId, msg.reason || undefined)
.then(function(e) {
node.log("Successfully banned " + msg.userId + " from " + msg.topic);
msg.eventId = e.event_id;

View File

@@ -9,8 +9,7 @@
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
reason: { value: null }
roomId: { value: null }
},
label: function() {
return this.name || "Room Kick";
@@ -21,7 +20,7 @@
<script type="text/html" data-template-name="matrix-room-kick">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -29,25 +28,9 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
<input type="text" id="node-input-reason" placeholder="msg.topic">
</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-room-kick">
@@ -69,7 +52,7 @@
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for kicking the user. If configured on the node it will overwrite this input</dd>
<dd> Reason for kicking the user.</dd>
</dl>
<h3>Outputs</h3>

View File

@@ -7,7 +7,6 @@ module.exports = function(RED) {
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
this.reason = n.reason;
if (!node.server) {
node.warn("No configuration node");
@@ -46,7 +45,7 @@ module.exports = function(RED) {
return;
}
node.server.matrixClient.kick(msg.topic, msg.userId, n.reason || msg.reason || undefined)
node.server.matrixClient.kick(msg.topic, msg.userId, msg.reason || undefined)
.then(function(e) {
node.log("Successfully kicked " + msg.userId + " from " + msg.topic);
msg.eventId = e.event_id;

View File

@@ -20,7 +20,7 @@
<script type="text/html" data-template-name="matrix-room-users">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -30,19 +30,7 @@
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Room Id</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</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-room-users">

View File

@@ -0,0 +1,71 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-secret-storage', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
inputs: 1,
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Secret Storage";
},
paletteLabel: 'Secret Storage'
});
</script>
<script type="text/html" data-template-name="matrix-secret-storage">
<div class="form-row">
<label for="node-input-name"><i class="icon-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>
</script>
<script type="text/html" data-help-name="matrix-secret-storage">
<h3>Details</h3>
<p>
Secure backup node. Use this to setup security key backup to the remote server. You can also use this node to import an existing secure backup.
</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>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be invited to the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">string</span>
</dt>
<dd> Reason for the membership change.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>Currently this endpoint returns an empty object</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

View File

@@ -0,0 +1,92 @@
module.exports = function(RED) {
const verificationRequests = new Map();
function MatrixSecretStorage(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
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" });
});
node.on('input', async function(msg){
try {
msg.hasSecretStorage = await node.server.matrixClient.hasSecretStorageKey();
} catch(e) {
console.log("ERROR", e);
}
if(msg.action) {
if(msg.action === 'create') {
if(msg.hasSecretStorage && !msg.forceReset) {
node.error("Secret storage already setup. Pass msg.forceReset to bypass and regenerate.");
return;
}
// copying this from https://github.com/matrix-org/matrix-react-sdk/blob/e78a1adb6f1af2ea425b0bae9034fb7344a4b2e8/src/SecurityManager.ts#L294
const recoveryKey = await node.server.matrixClient.createRecoveryKeyFromPassphrase(msg.key || undefined);
if(msg.forceReset) {
await node.server.matrixClient.bootstrapSecretStorage({
createSecretStorageKey: async () => recoveryKey,
setupNewKeyBackup: true,
setupNewSecretStorage: true,
});
} else {
// For password authentication users after 2020-09, this cross-signing
// step will be a no-op since it is now setup during registration or login
// when needed. We should keep this here to cover other cases such as:
// * Users with existing sessions prior to 2020-09 changes
// * SSO authentication users which require interactive auth to upload
// keys (and also happen to skip all post-authentication flows at the
// moment via token login)
await node.server.matrixClient.bootstrapCrossSigning({
// maybe we can skip this?
// authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
});
const backupInfo = await node.server.matrixClient.getKeyBackupVersion();
await node.server.matrixClient.bootstrapSecretStorage({
createSecretStorageKey: async () => this._recoveryKey,
keyBackupInfo: backupInfo,
setupNewKeyBackup: !backupInfo,
getKeyBackupPassphrase: () => {
// We may already have the backup key if we earlier went
// through the restore backup path, so pass it along
// rather than prompting again.
if (this._backupKey) {
return this._backupKey;
}
return promptForBackupPassphrase();
},
});
}
}
if(msg.action === 'download') {
if(!msg.hasSecretStorage) {
node.error("Secret storage not setup so cannot download.");
return;
}
}
}
node.send(msg);
});
}
RED.nodes.registerType("matrix-secret-storage", MatrixSecretStorage);
}

View File

@@ -21,7 +21,7 @@
<script type="text/html" data-template-name="matrix-send-file">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -29,9 +29,8 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
@@ -40,17 +39,6 @@
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a> (ex: application/pdf) or left empty
</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-send-file">

View File

@@ -21,7 +21,7 @@
<script type="text/html" data-template-name="matrix-send-image">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
@@ -29,9 +29,8 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
@@ -40,17 +39,6 @@
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a> (ex: image/png) or left empty
</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-send-image">

View File

@@ -10,10 +10,8 @@
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
message: { value: null },
messageType: { value: 'm.text' },
messageFormat: { value: '' },
replaceMessage : { value: false }
},
label: function() {
return this.name || "Send Message";
@@ -24,7 +22,7 @@
<script type="text/html" data-template-name="matrix-send-message">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
@@ -34,25 +32,8 @@
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row">
<label for="node-input-message"><i class="fa fa-comment"></i> Message</label>
<textarea id="node-input-message" placeholder="msg.payload" style="width: 70%;"></textarea>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-replaceMessage"
style="width: auto; margin-left: 105px; vertical-align: top"
/>
<label for="node-input-replaceMessage" style="width: auto;max-width:50%;">
Update existing message if <code>msg.eventId</code> is set
</label>
</div>
<div class="form-row">
@@ -79,17 +60,6 @@
<option value="msg.format">msg.format input</option>
</select>
</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-send-message">
@@ -106,17 +76,12 @@
<dt>msg.payload
<span class="property-type">string</span>
</dt>
<dd> the message text. If configured on the node this is ignored otherwise it required. </dd>
<dt>msg.replace
<span class="property-type">bool</span>
</dt>
<dd> If true and <code>msg.eventId</code> is present it will update an existing message. Posts a new message if false or <code>msg.eventId</code> is missing. </dd>
<dd> the message text. </dd>
<dt class="optional">msg.formatted_payload
<span class="property-type">string</span>
</dt>
<dd> the formatted HTML message (uses <code>msg.payload</code> if not defined). This only affects HTML messages.</dd>
<dd> the formatted HTML message (uses msg.payload if not defined). This only affects HTML messages.</dd>
<dt class="optional">msg.type
<span class="property-type">string | null</span>

View File

@@ -1,5 +1,3 @@
const {RelationType} = require("matrix-js-sdk");
module.exports = function(RED) {
function MatrixSendImage(n) {
RED.nodes.createNode(this, n);
@@ -11,8 +9,6 @@ module.exports = function(RED) {
this.roomId = n.roomId;
this.messageType = n.messageType;
this.messageFormat = n.messageFormat;
this.replaceMessage = n.replaceMessage;
this.message = n.message;
// taken from https://github.com/matrix-org/synapse/blob/master/synapse/push/mailer.py
this.allowedTags = [
@@ -102,15 +98,14 @@ module.exports = function(RED) {
return;
}
let payload = n.message || msg.payload;
if(!payload) {
node.error('msg.payload must be defined or the message configured on the node.');
if(!msg.payload) {
node.error('msg.payload is required');
return;
}
let content = {
msgtype: msgType,
body: payload.toString()
body: msg.payload.toString()
};
if(msgFormat === 'html') {
@@ -118,31 +113,12 @@ module.exports = function(RED) {
content.formatted_body =
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
? msg.formatted_payload.toString()
: payload.toString();
}
if((node.replaceMessage || msg.replace) && msg.eventId) {
content['m.new_content'] = {
msgtype: content.msgtype,
body: content.body
};
if('format' in content) {
content['m.new_content']['format'] = content['format'];
}
if('formatted_body' in content) {
content['m.new_content']['formatted_body'] = content['formatted_body'];
}
content['m.relates_to'] = {
rel_type: RelationType.Replace,
event_id: msg.eventId
};
content['body'] = ' * ' + content['body'];
: msg.payload.toString();
}
node.server.matrixClient.sendMessage(msg.topic, content)
.then(function(e) {
node.log("Message sent: " + payload);
node.log("Message sent: " + msg.payload);
msg.eventId = e.event_id;
node.send([msg, null]);
})

View File

@@ -29,8 +29,9 @@
userId: { type: "text", required: true },
deviceLabel: { type: "text", required: false },
accessToken: { type: "password", required: true },
deviceId: { type: "text", required: false },
url: { type: "text", required: true },
deviceId: { type: "text", required: true },
secureStoragePassphrase: { type: "text", required: false },
url: { type: "text", required: true }
},
defaults: {
name: { value: null },
@@ -95,6 +96,14 @@
WARNING: If you change this after the client has already initialized you will break encryption. Your Device ID is tied to your encryption keys.
</div>
<div class="form-row">
<label for="node-config-input-secureStoragePassphrase"><i class="fa fa-key"></i> Secure Storage Passphrase</label>
<input type="text" id="node-config-input-secureStoragePassphrase">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
If set secure storage will be setup with this passphrase. If secure storage already exists on the account it will attempt to decrypt it. Leave blank to skip.
</div>
<div class="form-row">
<input
type="checkbox"
@@ -198,6 +207,12 @@
}
);
});
$(function(){
$("#node-config-input-enableE2ee").on('change', function(e){
$("#node-config-input-secureStoragePassphrase").attr('disabled', !$(this).is(':checked'));
}).change();
});
</script>
</script>

View File

@@ -4,7 +4,7 @@ const sdk = require("matrix-js-sdk");
const { resolve } = require('path');
const { LocalStorage } = require('node-localstorage');
const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
const {RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent} = require("matrix-js-sdk");
const {deriveKey} = require("matrix-js-sdk/lib/crypto/key_passphrase");
module.exports = function(RED) {
function MatrixFolderNameFromUserId(name) {
@@ -28,13 +28,14 @@ module.exports = function(RED) {
this.userId = this.credentials.userId;
this.deviceLabel = this.credentials.deviceLabel || null;
this.deviceId = this.credentials.deviceId || null;
this.secureStoragePassphrase = this.credentials.secureStoragePassphrase || null;
this.url = this.credentials.url;
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
this.e2ee = n.enableE2ee || false;
this.enableE2ee = n.enableE2ee || false;
this.e2ee = (this.enableE2ee && this.deviceId);
this.globalAccess = n.global;
this.initializedAt = new Date();
if(!this.userId) {
node.log("Matrix connection failed: missing user ID in configuration.");
return;
@@ -65,37 +66,39 @@ module.exports = function(RED) {
// store Device ID internally
let stored_device_id = getStoredDeviceId(localStorage),
device_id = this.matrixClient.getDeviceId();
if(!stored_device_id || stored_device_id !== device_id) {
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
storeDeviceId(localStorage, device_id);
}
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.")
} else {
if(!stored_device_id || stored_device_id !== device_id) {
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
storeDeviceId(localStorage, device_id);
}
// update device label
if(node.deviceLabel) {
node.matrixClient
.getDevice(device_id)
.then(
function(response) {
if(response.display_name !== node.deviceLabel) {
node.matrixClient.setDeviceDetails(device_id, {
display_name: node.deviceLabel
}).then(
function(response) {},
function(error) {
node.error("Failed to set device label: " + error);
}
);
}
},
function(error) {
node.error("Failed to fetch device: " + error);
// update device label
if(node.deviceLabel) {
node.matrixClient
.getDevice(device_id)
.then(
function(response) {
if(response.display_name !== node.deviceLabel) {
node.matrixClient.setDeviceDetails(device_id, {
display_name: node.deviceLabel
}).then(
function(response) {},
function(error) {
node.error("Failed to set device label: " + error);
}
);
}
);
}
},
function(error) {
node.error("Failed to fetch device: " + error);
}
);
}
try {
await accessSecretStorage(function(){});
} catch(e) {
node.error("secret storage bootstrap failure: " + e);
console.log("secret storage bootstrap failure: ", e);
}
initialSetup = true;
@@ -113,6 +116,7 @@ module.exports = function(RED) {
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
upgradeDirectoryIfNecessary(node, storageDir);
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
@@ -120,7 +124,62 @@ module.exports = function(RED) {
cryptoStore: new LocalStorageCryptoStore(localStorage),
userId: this.userId,
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
// verificationMethods: ["m.sas.v1"]
verificationMethods: ["m.sas.v1"],
// cryptoCallbacks: {
// getSecretStorageKey: async function(
// { keys: keyInfos },
// ssssItemName,
// ){
// const cli = node.matrixClient;
// let keyId = await cli.getDefaultSecretStorageKeyId();
// // console.log("DEFAULT SECRET STORAGE KEY ID: " + keyId, keyInfos);
// //
// // let decodeBase64 = function(base64) {
// // return Buffer.from(base64, "base64");
// // }
// // return await this.crypto.getSecretStorageKey(keyId);
// let keyInfo;
// if (keyId) {
// // use the default SSSS key if set
// keyInfo = keyInfos[keyId];
// if (!keyInfo) {
// // if the default key is not available, pretend the default key
// // isn't set
// keyId = undefined;
// }
// }
// if (!keyId) {
// // if no default SSSS key is set, fall back to a heuristic of using the
// // only available key, if only one key is set
// const keyInfoEntries = Object.entries(keyInfos);
// if (keyInfoEntries.length > 1) {
// throw new Error("Multiple storage key requests not implemented");
// }
// [keyId, keyInfo] = keyInfoEntries[0];
// }
//
// // Check the in-memory cache
// // if (isCachingAllowed() && secretStorageKeys[keyId]) {
// // return [keyId, secretStorageKeys[keyId]];
// // }
//
// // if (dehydrationCache.key) {
// // if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
// // cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
// // return [keyId, dehydrationCache.key];
// // }
// // }
//
// // const backupInfo = await node.matrixClient.getKeyBackupVersion();
// const backupInfo = await node.matrixClient.getAccountDataFromServer(
// "m.secret_storage.key." + keyId
// );
//
// // if(await cli.checkSecretStorageKey(key, keyInfo)) {
// // }
// return [keyId, await node.matrixClient.keyBackupKeyFromPassword(node.secureStoragePassphrase, backupInfo)] ;
// }
// }
});
// set globally if configured to do so
@@ -148,7 +207,7 @@ module.exports = function(RED) {
return node.connected;
};
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
node.matrixClient.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data) {
if (toStartOfTimeline) {
return; // ignore paginated results
}
@@ -169,32 +228,16 @@ module.exports = function(RED) {
return;
}
const isDmRoom = (room) => {
// Find out if this is a direct message room.
let isDM = !!room.getDMInviter();
const allMembers = room.currentState.getMembers();
if (!isDM && allMembers.length <= 2) {
// if not a DM, but there are 2 users only
// double check DM (needed because getDMInviter works only if you were invited, not if you invite)
// hence why we check for each member
if (allMembers.some((m) => m.getDMInviter())) {
return true;
}
}
return allMembers.length <= 2 && isDM;
};
let msg = {
encrypted : event.isEncrypted(),
redacted : event.isRedacted(),
content : event.getContent(),
type : (event.getContent()['msgtype'] || event.getType()) || null,
payload : (event.getContent()['body'] || event.getContent()) || null,
isDM : isDmRoom(room),
userId : event.getSender(),
topic : event.getRoomId(),
eventId : event.getId(),
event : event
event : event,
};
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body + (toStartOfTimeline ? ' [PAGINATED]' : ''));
@@ -207,9 +250,9 @@ module.exports = function(RED) {
*
* @event module:client~MatrixClient#"crypto.suggestKeyRestore"
*/
// node.matrixClient.on("crypto.suggestKeyRestore", function(){
//
// });
node.matrixClient.on("crypto.suggestKeyRestore", function(){
});
// node.matrixClient.on("RoomMember.typing", async function(event, member) {
// let isTyping = member.typing;
@@ -228,8 +271,7 @@ module.exports = function(RED) {
// });
// handle auto-joining rooms
node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) {
node.matrixClient.on("RoomMember.membership", async function(event, member) {
if (member.membership === "invite" && member.userId === node.userId) {
if(node.autoAcceptRoomInvites) {
node.matrixClient.joinRoom(member.roomId).then(function() {
@@ -243,7 +285,7 @@ module.exports = function(RED) {
}
});
node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) {
node.matrixClient.on("sync", async function(state, prevState, data) {
node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]");
if(prevState === null && state === "PREPARED" ) {
// Occurs when the initial sync is completed first time.
@@ -311,8 +353,7 @@ module.exports = function(RED) {
}
});
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
node.matrixClient.on("Session.logged_out", async function(errorObj){
// Example if user auth token incorrect:
// {
// errcode: 'M_UNKNOWN_TOKEN',
@@ -328,13 +369,88 @@ module.exports = function(RED) {
stopClient();
});
/**
* This helper should be used whenever you need to access secret storage. It
* ensures that secret storage (and also cross-signing since they each depend on
* each other in a cycle of sorts) have been bootstrapped before running the
* provided function.
*
* Bootstrapping secret storage may take one of these paths:
* 1. Create secret storage from a passphrase and store cross-signing keys
* in secret storage.
* 2. Access existing secret storage by requesting passphrase and accessing
* cross-signing keys as needed.
* 3. All keys are loaded and there's nothing to do.
*
* @param {Function} [func] An operation to perform once secret storage has been
* bootstrapped. Optional.
* @param {boolean} [forceReset] Reset secret storage even if it's already set up
*/
let accessSecretStorage = async function(func = async () => { }, forceReset = false) {
// only do this if we have a secure storage password
if(!node.secureStoragePassphrase) {
return;
}
const recoveryKey = await node.matrixClient.createRecoveryKeyFromPassphrase(node.secureStoragePassphrase);
const cli = node.matrixClient;
try {
if (!(await cli.hasSecretStorageKey()) || forceReset) {
// For password authentication users after 2020-09, this cross-signing
// step will be a no-op since it is now setup during registration or login
// when needed. We should keep this here to cover other cases such as:
// * Users with existing sessions prior to 2020-09 changes
// * SSO authentication users which require interactive auth to upload
// keys (and also happen to skip all post-authentication flows at the
// moment via token login)
if(!await node.matrixClient.isCrossSigningReady()) {
await node.matrixClient.bootstrapCrossSigning({
// maybe we can skip this?
authUploadDeviceSigningKeys: () => {
return true;
}
});
}
const backupInfo = await node.matrixClient.getKeyBackupVersion();
await node.matrixClient.bootstrapSecretStorage({
createSecretStorageKey: async () => recoveryKey,
keyBackupInfo: backupInfo,
setupNewKeyBackup: !backupInfo,
getKeyBackupPassphrase: () => {
return recoveryKey;
},
});
} else {
await node.matrixClient.bootstrapSecretStorage({
getKeyBackupPassphrase: async () => recoveryKey,
});
}
// `return await` needed here to ensure `finally` block runs after the
// inner operation completes.
return await func();
} catch (e) {
node.error("Secret storage init failure: " + e);
}
};
async function run() {
try {
if(node.e2ee){
if(node.e2ee && node.matrixClient.initCrypto){
node.log("Initializing crypto...");
await node.matrixClient.initCrypto();
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
try {
await node.matrixClient.initCrypto();
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
node.matrixClient.setCryptoTrustCrossSignedDevices(true); // false = manually verify sessions
// await tryToUnlockSecretStorageWithDehydrationKey(this.matrixClient);
} catch (e) {
node.error("Failed to initialize crypto: " + e);
console.log(e);
}
}
node.log("Connecting to Matrix server...");
await node.matrixClient.startClient({
initialSyncLimit: 8
@@ -351,29 +467,9 @@ module.exports = function(RED) {
return;
}
/**
* We do a /whoami request before starting for a few reasons:
* - validate our auth token
* - make sure auth token belongs to provided node.userId
* - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033)
*/
node.matrixClient.whoami()
node.matrixClient.getAccountDataFromServer()
.then(
function(data) {
if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) {
node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.");
}
if('device_id' in data && data['device_id'] && !node.deviceId) {
// if we have no device_id configured lets use the one
// returned by /whoami for this access_token
node.matrixClient.deviceId = data['device_id'];
}
// make sure our userId matches the access token's
if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) {
node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`);
return;
}
function() {
run().catch((error) => node.error(error));
},
function(err) {
@@ -394,6 +490,7 @@ module.exports = function(RED) {
userId: { type: "text", required: true },
accessToken: { type: "text", required: true },
deviceId: { type: "text", required: false },
secureStoragePassphrase: { type: "text", required: false },
url: { type: "text", required: true }
}
});
@@ -464,7 +561,7 @@ module.exports = function(RED) {
fs.renameSync(oldStorageDir, oldStorageDir + "-backup");
}
if(RED.settings.userDir !== resolve('./') && resolve(oldStorageDir2) !== resolve(storageDir)) {
if(RED.settings.userDir !== resolve('./')) {
// user directory does not match running directory
// check if we stored stuff in wrong directory and move it
if(fs.pathExistsSync(oldStorageDir2)){
@@ -481,18 +578,10 @@ 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) {
return null;
}
return deviceId;
return localStorage.getItem('my_device_id');
}
function storeDeviceId(localStorage, deviceId) {
if(!deviceId) {
return false;
}
localStorage.setItem('my_device_id', deviceId);
return true;
}
}

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-synapse-create-edit-user">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-synapse-deactivate-user">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>

View File

@@ -20,7 +20,7 @@
<script type="text/html" data-template-name="matrix-synapse-join-room">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
@@ -30,25 +30,13 @@
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint.
</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-synapse-join-room">

View File

@@ -22,7 +22,7 @@
<script type="text/html" data-template-name="matrix-synapse-register">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-synapse-users">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">

View File

@@ -19,7 +19,7 @@
<script type="text/html" data-template-name="matrix-whois-user">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>