14 Commits
0.2.4 ... 0.4.0

Author SHA1 Message Date
Skylar Sadlier
4d1ec9e65c - Make it clear that manually generating a Device ID is bad behavior
- Make DeviceID no longer required for matrix-server-config
2022-02-03 10:36:09 -07:00
Skylar Sadlier
2fcd463eb6 - Update release to 0.4.0 2022-02-03 10:16:19 -07:00
Skylar Sadlier
b44629c84e - When checking authentication we should retry startup if the error we receive isn't related to invalid auth token (M_UNKNOWN_TOKEN) 2022-02-03 10:15:52 -07:00
Skylar Sadlier
9f0a962991 - Fixed: Reactions weren't working because sendCompleteEvent is a private method. 2022-02-03 09:40:32 -07:00
Skylar Sadlier
f3df8c6f97 This comment is no longer necessary since we implemented it. 2022-01-28 11:17:23 -07:00
Skylar Sadlier
d6ab098063 Fixed example readme linking to wrong json file Closes #38 2022-01-28 10:57:35 -07:00
Skylar Sadlier
614920c40c Fixed: matrix-join-room was requiring userId when it didn't need it Closes #39 2022-01-28 10:55:38 -07:00
Skylar Sadlier
58d7d23b2f - Device ID is now stored inside the matrix-client-storage directory within Node-RED's config folder. This way if you leave the Device ID blank it will automatically generate one and save it. Previously if you didn't set a Device ID it would automatically generate one every time the client started which would break encryption (since device_id is tied to your encryption keys).
- You can now generate an Access Token and Device ID from just a username and password (use the Update Access Token button when configuring a client).
- Updated the look of the matrix-server-config node (icons and reorganization)
- Invalid auth token will no longer crash Node-RED on deployment/start. We do an authorized request before starting the client to see if the auth token is valid. If it is not the client will not start and all nodes for this client will show disconnected and an error will display in the debug toolbar.
- Updated matrix olm library from 3.2.1 to 3.2.8
- Setting a Device Label from the server-config node will now update your device's label. If you view the sessions for the client you will see it gets updated after deployment.
2022-01-28 10:53:10 -07:00
Skylar Sadlier
25c92b787a - Messages used to get ignored if they were over 1 second old. This caused issues with messages being ignored on servers that took longer than a second to respond (*cough* matrix.org *cough*). Now it accepts all new messages. Moving forward any messages that are sent before Node-RED starts or gets deployed will be ignored (so that old messages don't pop up since the cache clears every deploy/node-red restart so old messages look like new ones).
- When the matrix-server node would shut down (from a deployment) it wasn't properly shutting the old instance of the client down. This would cause messages to start duplicating X times (X being however many times you deployed since you last started Node-RED). This is now fixed.
- Upgraded to matrix-js-sdk 15.3.0
- Message processing is now done on the matrix-server node. Before if you had multiple matrix-receive nodes it would output one line per matrix-receive node into the log. Now it only outputs from the matrix-server node. Note that all messages get logged that are sent to rooms the bot is in whereas before it would only log message in rooms the matrix-receive node was in.
- Fixed Shared secret registration failing if user_type was defined.
2022-01-14 12:56:14 -07:00
Skylar Sadlier
0b9c59a7be Fix eventId not being returned from matrix-send-file and matrix-send-message nodes 2021-12-19 00:34:51 -07:00
Skylar Sadlier
9c836c9262 Set version 0.2.6 2021-11-25 00:04:09 -07:00
Skylar Sadlier
9c62a73b15 Merge pull request #35 from Skylar-Tech/dev
Dev
2021-11-25 00:02:01 -07:00
Skylar Sadlier
447b18835c Closes #34 - Fix connection state logic 2021-11-25 00:00:31 -07:00
Skylar Sadlier
2341cbef10 Closes #32 - fixed leaking unencrypted messages during startup 2021-11-24 23:57:56 -07:00
13 changed files with 6045 additions and 2060 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@matrix-org:registry=https://gitlab.matrix.org/api/v4/packages/npm/

View File

@@ -54,7 +54,7 @@ To view what sort of functions you have access to check out the `client.ts` file
### Respond to "ping" with "pong"
[View JSON](respond-to-html-with-html.json)
[View JSON](respond-ping-pong.json)
Use this flow to respond to anyone that says "ping" with "pong" into the same room.

7592
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.2.4",
"version": "0.4.0",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"@matrix-org/olm": "^3.2.8",
"fs-extra": "^9.1.0",
"got": "^11.8.2",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^12.2.0",
"matrix-js-sdk": "^15.3.0",
"node-localstorage": "^2.2.1",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"process": "^0.11.10",
"utf8": "^3.0.0"
},

View File

@@ -38,11 +38,6 @@ module.exports = function(RED) {
return;
}
if(!msg.userId) {
node.error("msg.userId was not set.");
return;
}
node.server.matrixClient.joinRoom(msg.topic, msg.joinOpts || {})
.then(function(e) {
msg.payload = e;

View File

@@ -53,16 +53,14 @@ module.exports = function(RED) {
msg.type = 'm.reaction';
node.server.matrixClient.sendCompleteEvent(
node.server.matrixClient.sendEvent(
msg.topic,
'm.reaction',
{
type: 'm.reaction',
content: {
"m.relates_to": {
event_id: eventId,
key: msg.payload,
rel_type: "m.annotation"
}
"m.relates_to": {
event_id: eventId,
key: msg.payload,
rel_type: "m.annotation"
}
}
)

View File

@@ -22,7 +22,7 @@ module.exports = function(RED) {
return;
}
node.server.on("disconnected", function() {
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
@@ -30,43 +30,12 @@ module.exports = function(RED) {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.server.on("Room.timeline", async function(event, room, toStartOfTimeline, data) {
if (toStartOfTimeline) {
return; // ignore paginated results
}
if (!event.getSender() || event.getSender() === node.server.userId) {
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
return; // ignore old messages
}
node.server.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data, msg) {
// if node has a room ID set we only listen on that room
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
return;
}
try {
await node.server.matrixClient.decryptEventIfNeeded(event);
} catch (error) {
node.error(error);
return;
}
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,
userId : event.getSender(),
topic : event.getRoomId(),
eventId : event.getId(),
event : event,
};
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body);
switch(msg.type) {
case 'm.emote':
if(!node.acceptEmotes) return;

View File

@@ -80,7 +80,7 @@ module.exports = function(RED) {
.sendMessage(msg.topic, content)
.then(function(e) {
node.log("File message sent: " + e);
msg.eventId = e.eventId;
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){

View File

@@ -89,6 +89,7 @@ module.exports = function(RED) {
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
@@ -118,7 +119,7 @@ module.exports = function(RED) {
node.server.matrixClient.sendMessage(msg.topic, content)
.then(function(e) {
node.log("Message sent: " + msg.payload);
msg.eventId = e.eventId;
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){

View File

@@ -1,11 +1,35 @@
<style>
.matrix-loader {
border: 8px solid #f3f3f3;
border-radius: 50%;
border-top: 8px solid #3498db;
border-bottom: 8px solid #3498db;
width: 20px;
height: 20px;
-webkit-animation: matrix-spin 2s linear infinite;
animation: matrix-spin 2s linear infinite;
}
@-webkit-keyframes matrix-spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes matrix-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<script type="text/javascript">
RED.nodes.registerType('matrix-server-config',{
category: 'config',
color: '#00b7ca',
credentials: {
userId: { type: "text", required: true },
deviceLabel: { type: "text", required: false },
accessToken: { type: "password", required: true },
deviceId: { type: "text", required: true },
deviceId: { type: "text", required: false },
url: { type: "text", required: true }
},
defaults: {
@@ -26,28 +50,51 @@
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-config-input-userId"><i class="fa fa-server"></i> User ID</label>
<label for="node-config-input-deviceLabel"><i class="fa fa-tag"></i> Device Label</label>
<input type="text" placeholder="Node-RED" id="node-config-input-deviceLabel">
</div>
<div class="form-row">
<label for="node-config-input-url"><i class="fa fa-server"></i> Server URL</label>
<input type="text" placeholder="https://matrix.org" id="node-config-input-url">
</div>
<div class="form-row">
<label for="node-config-input-userId"><i class="fa fa-user"></i> User ID</label>
<input type="text" placeholder="@example:matrix.org" id="node-config-input-userId">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
<input type="password" placeholder="" id="node-config-input-password">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
Password is never saved and is only used to fetch an access token using the button below.
</div>
<pre class="form-tips" id="matrix-chat-login-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;display:none;"></pre>
<pre class="form-tips" id="matrix-chat-login-success" style="color: #155724;background-color: #d4edda;border-color: #c3e6cb;margin-bottom: 12px;display:none;"></pre>
<button class="ui-button ui-corner-all ui-widget primary" id="matrix-login-btn">Update Access Token</button>
<div class="matrix-loader" id="matrix-access-token-loader" style="display:none;"></div>
<div class="form-row">
<label for="node-config-input-accessToken"><i class="fa fa-key"></i> Access Token</label>
<input type="text" id="node-config-input-accessToken">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token. You can also generate them using the Shared Secret Registration node.
You can either provide/generate an access token yourself or use the login button above to do it automatically. View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here.
</div>
<div class="form-row">
<label for="node-config-input-deviceId"><i class="fa fa-key"></i> Device ID</label>
<label for="node-config-input-deviceId"><i class="fa fa-desktop"></i> Device ID</label>
<input type="text" id="node-config-input-deviceId">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
This can either be an existing Device ID attached to the above Access Token or you can enter a unique value to set a new one.
If you have a Device ID for this auth token you can place it here, or you can leave it blank to have one automatically generated (this field will always be blank if auto generated as it is saved internally). DO NOT manually set one, this must be generated by the server.<br />
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-url"><i class="fa fa-globe"></i> Server URL</label>
<input type="text" placeholder="https://matrix.org" id="node-config-input-url">
</div>
<div class="form-row">
<input
type="checkbox"
@@ -79,10 +126,79 @@
Global access to Matrix Client
</label>
<div class="form-tips" style="margin-bottom: 12px;">
If enabled this allows you to access the matrix client directly with a Function node. This way you can do <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command" target="_blank">whatever you want</a> with the client. Example:<br>
If global access is enabled you can access the client directly within a Function node. This way you can do <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command" target="_blank">whatever you want</a> with the client. Example:<br>
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
</div>
</div>
<script type="text/javascript">
$("#matrix-login-btn").on("click", function() {
function prettyPrintJson(json) {
try{
return typeof json === 'object' ? JSON.stringify(json, null, 2) : json;
}
catch (error){
return json;
}
}
let userId = $("#node-config-input-userId").val(),
userPassword = $("#node-config-input-password").val(),
serverUrl = $("#node-config-input-url").val();
if(!userId) {
alert("User ID is required to fetch access token.");
return;
}
if(!userPassword) {
alert("Password is required to fetch access token.");
return;
}
if(!serverUrl) {
alert("Server URL is required to fetch access token.");
return;
}
$("#matrix-login-btn, #matrix-chat-login-error, #matrix-chat-login-success").hide();
$("#matrix-access-token-loader").show();
$.ajax({
type: 'POST',
url: '/matrix-chat/login',
dataType: 'json',
data: {
'userId': userId,
'password': userPassword,
'baseUrl': serverUrl,
'displayName': $("#node-config-input-deviceLabel").val(),
}
}).then(
function(data) {
if(data.result && data.result === 'ok') {
$("#matrix-chat-login-error").hide();
$("#matrix-chat-login-success")
.html("Login Successful! Auth Token and Device ID have been set below.")
.show();
$("#node-config-input-accessToken").val(data.token);
$("#node-config-input-deviceId").val(data.device_id);
} else if(data.result && data.result === 'error') {
$("#matrix-chat-login-success").hide();
$("#matrix-chat-login-error")
.html(data.message ? ('Failed to login: <br />' + prettyPrintJson(data.message)) : 'Failed to login')
.show();
}
$("#matrix-login-btn").show();
$("#matrix-access-token-loader").hide();
}, function() {
$("#matrix-chat-login-success").hide();
$("#matrix-chat-login-error")
.html("Failed to login due to server error communicating with Node-RED")
.show();
$("#matrix-login-btn").show();
$("#matrix-access-token-loader").hide();
}
);
});
</script>
</script>
<script type="text/html" data-help-name="matrix-server-config">

View File

@@ -1,4 +1,4 @@
global.Olm = require('olm');
global.Olm = require('@matrix-org/olm');
const fs = require("fs-extra");
const sdk = require("matrix-js-sdk");
const { LocalStorage } = require('node-localstorage');
@@ -12,9 +12,6 @@ module.exports = function(RED) {
function MatrixServerNode(n) {
let storageDir = './matrix-client-storage';
// we should add support for getting access token automatically from username/password
// ref: https://matrix.org/docs/guides/usage-of-the-matrix-js-sdk#login-with-an-access-token
RED.nodes.createNode(this, n);
let node = this;
@@ -29,12 +26,19 @@ module.exports = function(RED) {
this.connected = null;
this.name = n.name;
this.userId = this.credentials.userId;
this.deviceLabel = this.credentials.deviceLabel || null;
this.deviceId = this.credentials.deviceId || null;
this.url = this.credentials.url;
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
this.enableE2ee = n.enableE2ee || false;
this.e2ee = (this.enableE2ee && this.deviceId);
this.globalAccess = n.global;
this.initializedAt = new Date();
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId),
localStorage = new LocalStorage(localStorageDir),
initialSetup = false;
let retryStartTimeout = null;
if(!this.credentials.accessToken) {
node.log("Matrix connection failed: missing access token.");
@@ -53,6 +57,40 @@ module.exports = function(RED) {
if (connected) {
node.log("Matrix server connection ready.");
node.emit("connected");
if(!initialSetup) {
// 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);
}
// 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);
}
);
}
initialSetup = true;
}
} else {
node.emit("disconnected");
}
@@ -64,18 +102,15 @@ module.exports = function(RED) {
};
node.setConnected(false);
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId);
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
upgradeDirectoryIfNecessary(node, storageDir);
const localStorage = new LocalStorage(localStorageDir);
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
sessionStore: new sdk.WebStorageSessionStore(localStorage),
cryptoStore: new LocalStorageCryptoStore(localStorage),
userId: this.userId,
deviceId: this.deviceId || undefined,
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined
});
// set globally if configured to do so
@@ -83,13 +118,19 @@ module.exports = function(RED) {
this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient);
}
node.on('close', function(done) {
if(node.matrixClient) {
node.matrixClient.close();
function stopClient() {
if(node.matrixClient && node.matrixClient.clientRunning) {
node.matrixClient.stopClient();
node.setConnected(false);
}
if(retryStartTimeout) {
clearTimeout(retryStartTimeout);
}
}
node.on('close', function(done) {
stopClient();
done();
});
@@ -97,8 +138,51 @@ module.exports = function(RED) {
return node.connected;
};
node.matrixClient.on("Room.timeline", async function(event, room, toStartOfTimeline, data) {
node.emit("Room.timeline", event, room, toStartOfTimeline, data);
node.matrixClient.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data) {
if (toStartOfTimeline) {
return; // ignore paginated results
}
if (!event.getSender() || event.getSender() === node.userId) {
return; // ignore our own messages
}
if (!data || !data.liveEvent) {
return; // ignore old message (we only want live events)
}
if(node.initializedAt > event.getDate()) {
return; // skip events that occurred before our client initialized
}
try {
await node.matrixClient.decryptEventIfNeeded(event);
} catch (error) {
node.error(error);
return;
}
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,
userId : event.getSender(),
topic : event.getRoomId(),
eventId : event.getId(),
event : event,
};
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body + (toStartOfTimeline ? ' [PAGINATED]' : ''));
node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg);
});
/**
* Fires when we want to suggest to the user that they restore their megolm keys
* from backup or by cross-signing the device.
*
* @event module:client~MatrixClient#"crypto.suggestKeyRestore"
*/
node.matrixClient.on("crypto.suggestKeyRestore", function(){
});
// node.matrixClient.on("RoomMember.typing", async function(event, member) {
@@ -133,36 +217,70 @@ module.exports = function(RED) {
});
node.matrixClient.on("sync", async function(state, prevState, data) {
switch (state) {
case "ERROR":
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
break;
case "RECONNECTING":
node.setConnected(false, function(){
node.log("Trying to reconnect to matrix server");
});
break;
case "STOPPED":
node.setConnected(false, function(){
node.log("Matrix client stopped");
});
break;
case "SYNCING":
case "PREPARED":
node.setConnected(true, function(){
node.log("Matrix client connected");
});
break;
// case "PREPARED":
// // the client instance is ready to be queried.
// node.log("Matrix server connection ready.");
// node.setConnected(true);
// break;
node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]");
if(prevState === null && state === "PREPARED" ) {
// Occurs when the initial sync is completed first time.
// This involves setting up filters and obtaining push rules.
node.setConnected(true, function(){
node.log("Matrix client connected");
});
} else if(prevState === null && state === "ERROR") {
// Occurs when the initial sync failed first time.
node.setConnected(false, function(){
node.error("Failed to connect to Matrix server");
});
} else if(prevState === "ERROR" && state === "PREPARED") {
// Occurs when the initial sync succeeds
// after previously failing.
node.setConnected(true, function(){
node.log("Matrix client connected");
});
} else if(prevState === "PREPARED" && state === "SYNCING") {
// Occurs immediately after transitioning to PREPARED.
// Starts listening for live updates rather than catching up.
node.setConnected(true, function(){
node.log("Matrix client connected");
});
} else if(prevState === "SYNCING" && state === "RECONNECTING") {
// Occurs when the live update fails.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
} else if(prevState === "RECONNECTING" && state === "RECONNECTING") {
// Can occur if the update calls continue to fail,
// but the keepalive calls (to /versions) succeed.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
} else if(prevState === "RECONNECTING" && state === "ERROR") {
// Occurs when the keepalive call also fails
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
} else if(prevState === "ERROR" && state === "SYNCING") {
// Occurs when the client has performed a
// live update after having previously failed.
node.setConnected(true, function(){
node.log("Matrix client connected");
});
} else if(prevState === "ERROR" && state === "ERROR") {
// Occurs when the client has failed to
// keepalive for a second time or more.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
} else if(prevState === "SYNCING" && state === "SYNCING") {
// Occurs when the client has performed a live update.
// This is called <i>after</i> processing.
node.setConnected(true, function(){
node.log("Matrix client connected");
});
} else if(state === "STOPPED") {
// Occurs once the client has stopped syncing or
// trying to sync after stopClient has been called.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
});
}
});
@@ -178,7 +296,9 @@ module.exports = function(RED) {
// httpStatus: 401
// }
node.error("[Session.logged_out] " + errorObj);
console.log("Authentication failure: ", errorObj);
node.error("Authentication failure: " + errorObj);
stopClient();
});
async function run() {
@@ -189,25 +309,88 @@ module.exports = function(RED) {
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
}
node.log("Connecting to Matrix server...");
await node.matrixClient.startClient({ initialSyncLimit: 8 });
} catch(error){
await node.matrixClient.startClient({
initialSyncLimit: 8
});
} catch(error) {
node.error(error);
}
}
run().catch((error) => node.error(error));
// do an authed request and only continue if we don't get an error
// this prevent the matrix client from crashing Node-RED on invalid auth token
(function checkAuthTokenThenStart() {
if(node.matrixClient.clientRunning) {
return;
}
node.matrixClient.getAccountDataFromServer()
.then(
function() {
run().catch((error) => node.error(error));
},
function(err) {
// if the error isn't authentication related retry in a little bit
if(err.code !== "M_UNKNOWN_TOKEN") {
retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000);
node.error("Auth check failed: " + err);
}
}
)
})();
}
}
RED.nodes.registerType("matrix-server-config", MatrixServerNode, {
credentials: {
userId: { type:"text", required: true },
accessToken: { type:"text", required: true },
deviceId: { type: "text", required: true },
url: { type: "text", required: true },
deviceLabel: { type: "text", required: false },
userId: { type: "text", required: true },
accessToken: { type: "text", required: true },
deviceId: { type: "text", required: false },
url: { type: "text", required: true }
}
});
RED.httpAdmin.post(
"/matrix-chat/login",
RED.auth.needsPermission('flows.write'),
function(req, res) {
let userId = req.body.userId || undefined,
password = req.body.password || undefined,
baseUrl = req.body.baseUrl || undefined,
deviceId = req.body.deviceId || undefined,
displayName = req.body.displayName || undefined;
const matrixClient = sdk.createClient({
baseUrl: baseUrl,
deviceId: deviceId,
localTimeoutMs: '30000'
});
matrixClient.login(
'm.login.password', {
user: userId,
password: password,
initial_device_display_name: displayName
})
.then(
function(response) {
res.json({
'result': 'ok',
'token': response.access_token,
'device_id': response.device_id,
'user_id': response.user_id,
});
},
function(err) {
res.json({
'result': 'error',
'message': err
});
}
);
});
function upgradeDirectoryIfNecessary(node, storageDir) {
let oldStorageDir = './matrix-local-storage';
@@ -233,4 +416,15 @@ module.exports = function(RED) {
fs.renameSync(oldStorageDir, oldStorageDir + "-backup");
}
}
/**
* If a device ID is stored we will use that for the client
*/
function getStoredDeviceId(localStorage) {
return localStorage.getItem('my_device_id');
}
function storeDeviceId(localStorage, deviceId) {
localStorage.setItem('my_device_id', deviceId);
}
}

View File

@@ -78,7 +78,10 @@
<dt class="optional">msg.payload.user_type
<span class="property-type">string | null</span>
</dt>
<dd> Set the user type. Leave this to null if you don't know what it is for. Check <a href="https://github.com/matrix-org/synapse/blob/master/synapse/api/constants.py">here</a> and look for <code>class UserTypes</code> to figure out what is valid.</dd>
<dd>
Set the user type. Leave this to null if you don't know what it is for. Check <a href="https://github.com/matrix-org/synapse/blob/master/synapse/api/constants.py">here</a> and look for <code>class UserTypes</code> to figure out what is valid.<br>
NOTE: Testing shows that "bot" and "support" do not work for this. Generate a normal user to use as a bot.
</dd>
</dl>
<h3>Outputs</h3>

View File

@@ -62,7 +62,13 @@ module.exports = function(RED) {
.update(utf8.encode(msg.payload.password))
.update("\x00")
.update(msg.payload.admin ? "admin" : "notadmin")
.digest('hex');
if(msg.payload.user_type || null) {
hmac.update("\x00")
.update(msg.payload.user_type);
}
hmac = hmac.digest('hex');
try {
response = await got.post(this.server + '/_synapse/admin/v1/register', {