@node-red/runtime 4.0.0-beta.1 → 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/comms.js +33 -4
- package/lib/api/plugins.js +19 -0
- package/lib/flows/util.js +11 -3
- package/lib/index.js +2 -0
- package/lib/multiplayer/index.js +119 -0
- package/lib/nodes/index.js +5 -1
- package/lib/plugins.js +1 -0
- package/locales/en-US/runtime.json +1 -0
- package/package.json +4 -4
package/lib/api/comms.js
CHANGED
|
@@ -36,7 +36,7 @@ var connections = [];
|
|
|
36
36
|
const events = require("@node-red/util").events;
|
|
37
37
|
|
|
38
38
|
function handleCommsEvent(event) {
|
|
39
|
-
publish(event.topic,event.data,event.retain);
|
|
39
|
+
publish(event.topic,event.data,event.retain,event.session,event.excludeSession);
|
|
40
40
|
}
|
|
41
41
|
function handleStatusEvent(event) {
|
|
42
42
|
if (!event.status) {
|
|
@@ -74,13 +74,17 @@ function handleEventLog(event) {
|
|
|
74
74
|
publish("event-log/"+event.id,event.payload||{});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function publish(topic,data,retain) {
|
|
77
|
+
function publish(topic, data, retain, session, excludeSession) {
|
|
78
78
|
if (retain) {
|
|
79
79
|
retained[topic] = data;
|
|
80
80
|
} else {
|
|
81
81
|
delete retained[topic];
|
|
82
82
|
}
|
|
83
|
-
connections.forEach(connection =>
|
|
83
|
+
connections.forEach(connection => {
|
|
84
|
+
if ((!session || connection.session === session) && (!excludeSession || connection.session !== excludeSession)) {
|
|
85
|
+
connection.send(topic,data)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
|
|
@@ -109,6 +113,10 @@ var api = module.exports = {
|
|
|
109
113
|
*/
|
|
110
114
|
addConnection: async function(opts) {
|
|
111
115
|
connections.push(opts.client);
|
|
116
|
+
events.emit('comms:connection-added', {
|
|
117
|
+
session: opts.client.session,
|
|
118
|
+
user: opts.client.user
|
|
119
|
+
})
|
|
112
120
|
},
|
|
113
121
|
|
|
114
122
|
/**
|
|
@@ -126,6 +134,9 @@ var api = module.exports = {
|
|
|
126
134
|
break;
|
|
127
135
|
}
|
|
128
136
|
}
|
|
137
|
+
events.emit('comms:connection-removed', {
|
|
138
|
+
session: opts.client.session
|
|
139
|
+
})
|
|
129
140
|
},
|
|
130
141
|
|
|
131
142
|
/**
|
|
@@ -157,5 +168,23 @@ var api = module.exports = {
|
|
|
157
168
|
* @return {Promise<Object>} - resolves when complete
|
|
158
169
|
* @memberof @node-red/runtime_comms
|
|
159
170
|
*/
|
|
160
|
-
unsubscribe: async function(opts) {}
|
|
171
|
+
unsubscribe: async function(opts) {},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {Object} opts
|
|
175
|
+
* @param {User} opts.user - the user calling the api
|
|
176
|
+
* @param {CommsConnection} opts.client - the client connection
|
|
177
|
+
* @param {String} opts.topic - the message topic
|
|
178
|
+
* @param {String} opts.data - the message data
|
|
179
|
+
* @return {Promise<Object>} - resolves when complete
|
|
180
|
+
*/
|
|
181
|
+
receive: async function (opts) {
|
|
182
|
+
if (opts.topic) {
|
|
183
|
+
events.emit('comms:message:' + opts.topic, {
|
|
184
|
+
session: opts.client.session,
|
|
185
|
+
user: opts.user,
|
|
186
|
+
data: opts.data
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
161
190
|
};
|
package/lib/api/plugins.js
CHANGED
|
@@ -65,6 +65,25 @@ var api = module.exports = {
|
|
|
65
65
|
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
|
|
66
66
|
return runtime.plugins.getPluginConfigs(opts.lang);
|
|
67
67
|
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the editor content for one registered plugin
|
|
71
|
+
* @param {Object} opts
|
|
72
|
+
* @param {User} opts.user - the user calling the api
|
|
73
|
+
* @param {User} opts.user - the user calling the api
|
|
74
|
+
* @param {Object} opts.req - the request to log (optional)
|
|
75
|
+
* @return {Promise<NodeInfo>} - the plugin information
|
|
76
|
+
* @memberof @node-red/runtime_plugins
|
|
77
|
+
*/
|
|
78
|
+
getPluginConfig: async function(opts) {
|
|
79
|
+
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
|
80
|
+
throw new Error("Invalid language: "+opts.lang)
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
|
|
84
|
+
return runtime.plugins.getPluginConfig(opts.module, opts.lang);
|
|
85
|
+
},
|
|
86
|
+
|
|
68
87
|
/**
|
|
69
88
|
* Gets all registered module message catalogs
|
|
70
89
|
* @param {Object} opts
|
package/lib/flows/util.js
CHANGED
|
@@ -106,14 +106,22 @@ async function evaluateEnvProperties(flow, env, credentials) {
|
|
|
106
106
|
result = { value: result, __clone__: true}
|
|
107
107
|
}
|
|
108
108
|
evaluatedEnv[name] = result
|
|
109
|
+
} else {
|
|
110
|
+
evaluatedEnv[name] = undefined
|
|
111
|
+
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
|
|
109
112
|
}
|
|
110
113
|
resolve()
|
|
111
114
|
});
|
|
112
115
|
}))
|
|
113
116
|
} else {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
try {
|
|
118
|
+
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
|
|
119
|
+
if (typeof value === 'object') {
|
|
120
|
+
value = { value: value, __clone__: true}
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
value = undefined
|
|
124
|
+
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
evaluatedEnv[name] = value
|
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var storage = require("./storage");
|
|
|
22
22
|
var library = require("./library");
|
|
23
23
|
var plugins = require("./plugins");
|
|
24
24
|
var settings = require("./settings");
|
|
25
|
+
const multiplayer = require("./multiplayer");
|
|
25
26
|
|
|
26
27
|
var express = require("express");
|
|
27
28
|
var path = require('path');
|
|
@@ -135,6 +136,7 @@ function start() {
|
|
|
135
136
|
.then(function() { return storage.init(runtime)})
|
|
136
137
|
.then(function() { return settings.load(storage)})
|
|
137
138
|
.then(function() { return library.init(runtime)})
|
|
139
|
+
.then(function() { return multiplayer.init(runtime)})
|
|
138
140
|
.then(function() {
|
|
139
141
|
if (settings.available()) {
|
|
140
142
|
if (settings.get('instanceId') === undefined) {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
let runtime
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Active sessions, mapped by multiplayer session ids
|
|
5
|
+
*/
|
|
6
|
+
const sessions = new Map()
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Active connections, mapping comms session to multiplayer session
|
|
10
|
+
*/
|
|
11
|
+
const connections = new Map()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function getSessionsList() {
|
|
15
|
+
return Array.from(sessions.values()).filter(session => session.active)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
init: function(_runtime) {
|
|
20
|
+
runtime = _runtime
|
|
21
|
+
runtime.events.on('comms:connection-removed', (opts) => {
|
|
22
|
+
const existingSessionId = connections.get(opts.session)
|
|
23
|
+
if (existingSessionId) {
|
|
24
|
+
connections.delete(opts.session)
|
|
25
|
+
const session = sessions.get(existingSessionId)
|
|
26
|
+
session.active = false
|
|
27
|
+
session.idleTimeout = setTimeout(() => {
|
|
28
|
+
sessions.delete(existingSessionId)
|
|
29
|
+
}, 30000)
|
|
30
|
+
runtime.events.emit('comms', {
|
|
31
|
+
topic: "multiplayer/connection-removed",
|
|
32
|
+
data: { session: existingSessionId }
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
runtime.events.on('comms:message:multiplayer/connect', (opts) => {
|
|
37
|
+
let session
|
|
38
|
+
if (!sessions.has(opts.data.session)) {
|
|
39
|
+
// Brand new session
|
|
40
|
+
let user = opts.user
|
|
41
|
+
if (!user || user.anonymous) {
|
|
42
|
+
user = user || { anonymous: true }
|
|
43
|
+
user.username = `Anon ${Math.floor(Math.random()*100)}`
|
|
44
|
+
}
|
|
45
|
+
session = {
|
|
46
|
+
session: opts.data.session,
|
|
47
|
+
user,
|
|
48
|
+
active: true
|
|
49
|
+
}
|
|
50
|
+
sessions.set(opts.data.session, session)
|
|
51
|
+
connections.set(opts.session, opts.data.session)
|
|
52
|
+
runtime.log.trace(`multiplayer new session:${opts.data.session} user:${user.username}`)
|
|
53
|
+
} else {
|
|
54
|
+
// Reconnected connection - keep existing state
|
|
55
|
+
connections.set(opts.session, opts.data.session)
|
|
56
|
+
// const existingConnection = connections.get(opts.data.session)
|
|
57
|
+
session = sessions.get(opts.data.session)
|
|
58
|
+
session.active = true
|
|
59
|
+
runtime.log.trace(`multiplayer reconnected session:${opts.data.session} user:${session.user.username}`)
|
|
60
|
+
clearTimeout(session.idleTimeout)
|
|
61
|
+
}
|
|
62
|
+
// Tell existing sessions about the new connection
|
|
63
|
+
runtime.events.emit('comms', {
|
|
64
|
+
topic: "multiplayer/connection-added",
|
|
65
|
+
excludeSession: opts.session,
|
|
66
|
+
data: session
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Send init info to new connection
|
|
70
|
+
const initPacket = {
|
|
71
|
+
topic: "multiplayer/init",
|
|
72
|
+
data: getSessionsList(),
|
|
73
|
+
session: opts.session
|
|
74
|
+
}
|
|
75
|
+
// console.log('<<', initPacket)
|
|
76
|
+
runtime.events.emit('comms', initPacket)
|
|
77
|
+
})
|
|
78
|
+
runtime.events.on('comms:message:multiplayer/disconnect', (opts) => {
|
|
79
|
+
const existingSessionId = connections.get(opts.session)
|
|
80
|
+
connections.delete(opts.session)
|
|
81
|
+
sessions.delete(existingSessionId)
|
|
82
|
+
|
|
83
|
+
runtime.events.emit('comms', {
|
|
84
|
+
topic: "multiplayer/connection-removed",
|
|
85
|
+
data: { session: existingSessionId, disconnected: true }
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
runtime.events.on('comms:message:multiplayer/location', (opts) => {
|
|
89
|
+
// console.log('>>>', opts.user, opts.data)
|
|
90
|
+
|
|
91
|
+
const sessionId = connections.get(opts.session)
|
|
92
|
+
const session = sessions.get(sessionId)
|
|
93
|
+
|
|
94
|
+
if (opts.user) {
|
|
95
|
+
if (session.user.anonymous !== opts.user.anonymous) {
|
|
96
|
+
session.user = opts.user
|
|
97
|
+
runtime.events.emit('comms', {
|
|
98
|
+
topic: 'multiplayer/connection-added',
|
|
99
|
+
excludeSession: opts.session,
|
|
100
|
+
data: session
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
session.location = opts.data
|
|
106
|
+
|
|
107
|
+
const payload = {
|
|
108
|
+
session: sessionId,
|
|
109
|
+
workspace: opts.data.workspace,
|
|
110
|
+
node: opts.data.node
|
|
111
|
+
}
|
|
112
|
+
runtime.events.emit('comms', {
|
|
113
|
+
topic: 'multiplayer/location',
|
|
114
|
+
data: payload,
|
|
115
|
+
excludeSession: opts.session
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
package/lib/nodes/index.js
CHANGED
|
@@ -173,7 +173,11 @@ function installModule(module,version,url) {
|
|
|
173
173
|
if (info.pending_version) {
|
|
174
174
|
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:info.name,version:info.pending_version}});
|
|
175
175
|
} else {
|
|
176
|
-
|
|
176
|
+
if (!info.nodes.length && info.plugins.length) {
|
|
177
|
+
events.emit("runtime-event",{id:"plugin/added",retain:false,payload:info.plugins});
|
|
178
|
+
} else {
|
|
179
|
+
events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes});
|
|
180
|
+
}
|
|
177
181
|
}
|
|
178
182
|
return info;
|
|
179
183
|
});
|
package/lib/plugins.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"removing-modules": "Removing modules from config",
|
|
26
26
|
"added-types": "Added node types:",
|
|
27
27
|
"removed-types": "Removed node types:",
|
|
28
|
+
"removed-plugins": "Removed plugins:",
|
|
28
29
|
"install": {
|
|
29
30
|
"invalid": "Invalid module name",
|
|
30
31
|
"installing": "Installing module: __name__, version: __version__",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-red/runtime",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
}
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@node-red/registry": "4.0.0-beta.
|
|
20
|
-
"@node-red/util": "4.0.0-beta.
|
|
19
|
+
"@node-red/registry": "4.0.0-beta.2",
|
|
20
|
+
"@node-red/util": "4.0.0-beta.2",
|
|
21
21
|
"async-mutex": "0.4.0",
|
|
22
22
|
"clone": "2.1.2",
|
|
23
|
-
"express": "4.
|
|
23
|
+
"express": "4.19.2",
|
|
24
24
|
"fs-extra": "11.1.1",
|
|
25
25
|
"json-stringify-safe": "5.0.1"
|
|
26
26
|
}
|