@vitormnm/node-red-simple-opcua 1.4.2 → 1.4.3

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.
@@ -1,311 +1,311 @@
1
- "use strict";
2
-
3
-
4
-
5
- const {
6
- OPCUAServer,
7
- UserTokenType,
8
- buildApplicationUri
9
- } = require("./opcua-constants");
10
- const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
11
- const { OpcUaServerMethods } = require("./opcua-server-methods");
12
-
13
- class OpcUaServerRuntime {
14
- constructor(options) {
15
- this.node = options.node;
16
- this.registry = options.registry;
17
- this.id = options.settings.id;
18
- this.name = options.settings.name;
19
- this.serverName = options.settings.serverName;
20
- this.port = options.settings.port;
21
- this.maxConnections = options.settings.maxConnections;
22
- this.namespaceUri = options.settings.namespaceUri;
23
- this.resourcePath = options.settings.resourcePath;
24
- this.allowAnonymous = options.settings.allowAnonymous;
25
- this.users = options.settings.users;
26
- this.securityPolicy = options.settings.securityPolicy;
27
- this.securityMode = options.settings.securityMode;
28
- this.treeConfig = options.settings.treeConfig;
29
-
30
- this.server = null;
31
- this.namespace = null;
32
- this.namespaces = new Map();
33
- this.namespaceDefinitions = new Map();
34
- this.addressSpaceBuilder = null;
35
-
36
-
37
- }
38
-
39
- async start() {
40
- if (this.server) {
41
- return;
42
- }
43
-
44
- this.server = new OPCUAServer(this.buildServerOptions());
45
- await this.server.initialize();
46
-
47
- const addressSpace = this.server.engine.addressSpace;
48
-
49
- this.initializeNamespaces(this.treeConfig);
50
-
51
- this.addressSpaceBuilder = new OpcUaAddressSpaceBuilder({
52
- namespace: this.namespace,
53
- namespaces: this.namespaces,
54
- server: this.server,
55
- registry: this.registry,
56
- node: this.node,
57
- serverName: this.serverName,
58
- addressSpace: this.addressSpace
59
- });
60
-
61
-
62
- this.addressSpaceBuilder.rebuild(this.treeConfig);
63
- await this.server.start();
64
- this.registry.registerServer(this);
65
-
66
- //Methods
67
- const ServerMethods = new OpcUaServerMethods({
68
- addressSpace: addressSpace,
69
- registry: this.registry,
70
- node: this.node
71
- })
72
-
73
- ServerMethods.start();
74
-
75
-
76
- }
77
-
78
- async stop() {
79
- if (!this.server) {
80
- this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
81
- return;
82
- }
83
-
84
- try {
85
- if (this.addressSpaceBuilder) {
86
- this.addressSpaceBuilder.clearDynamicNodes();
87
- this.addressSpaceBuilder.variableStore.clear();
88
- }
89
-
90
- await this.server.shutdown(1000);
91
- } finally {
92
- this.registry.unregisterServer(this.id);
93
- this.addressSpaceBuilder = null;
94
- this.namespace = null;
95
- this.namespaces = new Map();
96
- this.namespaceDefinitions = new Map();
97
- this.server = null;
98
- this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
99
- }
100
- }
101
-
102
- ensureReady() {
103
- if (!this.server || !this.namespace || !this.addressSpaceBuilder) {
104
- throw new Error("OPC UA server is not available");
105
- }
106
- }
107
-
108
-
109
-
110
- async updateTree(treeConfig) {
111
- this.ensureReady();
112
- this.syncNamespaces(treeConfig);
113
- this.treeConfig = treeConfig;
114
- this.addressSpaceBuilder.sync(treeConfig);
115
- }
116
-
117
- readValueByPath(path) {
118
- this.ensureReady();
119
- return this.addressSpaceBuilder.readValueByPath(path);
120
- }
121
-
122
- readValueByNodeId(nodeId) {
123
- this.ensureReady();
124
- return this.addressSpaceBuilder.readValueByNodeId(nodeId);
125
- }
126
-
127
- readValue(identifierType, identifier) {
128
- this.ensureReady();
129
- return this.addressSpaceBuilder.readValue(identifierType, identifier);
130
- }
131
-
132
- writeEventByPath(valuesPayload) {
133
- this.ensureReady();
134
- return this.addressSpaceBuilder.eventValueByPath(valuesPayload);
135
- }
136
-
137
- writeValueByPath(path, value) {
138
- this.ensureReady();
139
- return this.addressSpaceBuilder.writeValueByPath(path, value);
140
- }
141
-
142
- writeValueByNodeId(nodeId, value) {
143
- this.ensureReady();
144
- return this.addressSpaceBuilder.writeValueByNodeId(nodeId, value);
145
- }
146
-
147
- writeValue(identifierType, identifier, value) {
148
- this.ensureReady();
149
- return this.addressSpaceBuilder.writeValue(identifierType, identifier, value);
150
- }
151
-
152
- getEndpointUrl() {
153
- if (!this.server || !Array.isArray(this.server.endpoints)) {
154
- return "";
155
- }
156
-
157
- for (let index = 0; index < this.server.endpoints.length; index += 1) {
158
- const endpoint = this.server.endpoints[index];
159
- if (!endpoint || typeof endpoint.endpointDescriptions !== "function") {
160
- continue;
161
- }
162
-
163
- const descriptions = endpoint.endpointDescriptions();
164
- if (Array.isArray(descriptions) && descriptions.length && descriptions[0].endpointUrl) {
165
- return descriptions[0].endpointUrl;
166
- }
167
- }
168
-
169
- return "opc.tcp://localhost:" + this.port + this.resourcePath;
170
- }
171
-
172
- buildServerOptions() {
173
- const activeUsers = Array.isArray(this.users) ? this.users : [];
174
- const userTokenPolicies = [];
175
-
176
- if (this.allowAnonymous) {
177
- userTokenPolicies.push({
178
- policyId: "anonymous",
179
- tokenType: UserTokenType.Anonymous
180
- });
181
- }
182
-
183
- if (activeUsers.length) {
184
- userTokenPolicies.push({
185
- policyId: "username",
186
- tokenType: UserTokenType.UserName
187
- });
188
- }
189
-
190
- return {
191
- port: this.port,
192
- resourcePath: this.resourcePath,
193
- buildInfo: {
194
- productName: "opc-ua-server",
195
- buildNumber: "1",
196
- buildDate: new Date()
197
- },
198
- // serverCertificateManager: {
199
- // automaticallyAcceptUnknownCertificate: true
200
- // },
201
- serverCapabilities: {
202
- maxSessions: this.maxConnections
203
- },
204
- serverInfo: {
205
- applicationName: { text: this.serverName },
206
- applicationUri: buildApplicationUri(this.serverName),
207
- productUri: "urn:node-red:opc-ua-server"
208
- },
209
- securityPolicies: [this.securityPolicy],
210
- securityModes: [this.securityMode],
211
- allowAnonymous: this.allowAnonymous,
212
- userManager: {
213
- isValidUser: (username, password) => this.isValidUser(username, password)
214
- },
215
- userTokenPolicies
216
- };
217
- }
218
-
219
- isValidUser(username, password) {
220
- return this.users.some((user) => {
221
- if (user.username !== username) {
222
- return false;
223
- }
224
-
225
- if (user.password && user.password === password) {
226
- return true;
227
- }
228
-
229
- if (user.passwordHash) {
230
- try {
231
- return bcrypt.compareSync(password, user.passwordHash);
232
- } catch (error) {
233
- this.node.warn("Failed to validate password hash for user " + username + ": " + error.message);
234
- return false;
235
- }
236
- }
237
-
238
- return false;
239
- });
240
- }
241
-
242
- initializeNamespaces(treeConfig) {
243
- const addressSpace = this.server.engine.addressSpace;
244
- const configuredNamespaces = this.buildNamespaceDefinitions(treeConfig);
245
-
246
- this.namespaces = new Map();
247
- this.namespaceDefinitions = configuredNamespaces;
248
-
249
- configuredNamespaces.forEach((uri, namespaceId) => {
250
- const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
251
- this.namespaces.set(namespaceId, namespace);
252
- });
253
-
254
- this.namespace = this.namespaces.get(2);
255
- }
256
-
257
- syncNamespaces(treeConfig) {
258
- const addressSpace = this.server.engine.addressSpace;
259
- const nextDefinitions = this.buildNamespaceDefinitions(treeConfig);
260
-
261
- this.namespaceDefinitions.forEach((uri, namespaceId) => {
262
- const nextUri = nextDefinitions.get(namespaceId);
263
- if (nextUri && nextUri !== uri) {
264
- throw new Error("Namespace URI changes require a redeploy: namespace " + namespaceId);
265
- }
266
-
267
- if (!nextUri) {
268
- throw new Error("Removing namespaces requires a redeploy: namespace " + namespaceId);
269
- }
270
- });
271
-
272
- nextDefinitions.forEach((uri, namespaceId) => {
273
- if (this.namespaces.has(namespaceId)) {
274
- return;
275
- }
276
-
277
- const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
278
- this.namespaces.set(namespaceId, namespace);
279
- });
280
-
281
- this.namespaceDefinitions = nextDefinitions;
282
- this.namespace = this.namespaces.get(2);
283
- }
284
-
285
- buildNamespaceDefinitions(treeConfig) {
286
- const definitions = new Map();
287
- const configuredNamespaces = Array.isArray(treeConfig && treeConfig.nameSpaces) ? treeConfig.nameSpaces : [];
288
- let defaultNamespaceUri = this.namespaceUri;
289
-
290
- configuredNamespaces.forEach((namespaceConfig) => {
291
- if (namespaceConfig.id === 2) {
292
- defaultNamespaceUri = namespaceConfig.name;
293
- }
294
- });
295
-
296
- definitions.set(2, defaultNamespaceUri);
297
-
298
- configuredNamespaces
299
- .slice()
300
- .sort((left, right) => left.id - right.id)
301
- .forEach((namespaceConfig) => {
302
- definitions.set(namespaceConfig.id, namespaceConfig.name);
303
- });
304
-
305
- return definitions;
306
- }
307
- }
308
-
309
- module.exports = {
310
- OpcUaServerRuntime
311
- };
1
+ "use strict";
2
+
3
+
4
+
5
+ const {
6
+ OPCUAServer,
7
+ UserTokenType,
8
+ buildApplicationUri
9
+ } = require("./opcua-constants");
10
+ const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
11
+ const { OpcUaServerMethods } = require("./opcua-server-methods");
12
+
13
+ class OpcUaServerRuntime {
14
+ constructor(options) {
15
+ this.node = options.node;
16
+ this.registry = options.registry;
17
+ this.id = options.settings.id;
18
+ this.name = options.settings.name;
19
+ this.serverName = options.settings.serverName;
20
+ this.port = options.settings.port;
21
+ this.maxConnections = options.settings.maxConnections;
22
+ this.namespaceUri = options.settings.namespaceUri;
23
+ this.resourcePath = options.settings.resourcePath;
24
+ this.allowAnonymous = options.settings.allowAnonymous;
25
+ this.users = options.settings.users;
26
+ this.securityPolicy = options.settings.securityPolicy;
27
+ this.securityMode = options.settings.securityMode;
28
+ this.treeConfig = options.settings.treeConfig;
29
+
30
+ this.server = null;
31
+ this.namespace = null;
32
+ this.namespaces = new Map();
33
+ this.namespaceDefinitions = new Map();
34
+ this.addressSpaceBuilder = null;
35
+
36
+
37
+ }
38
+
39
+ async start() {
40
+ if (this.server) {
41
+ return;
42
+ }
43
+
44
+ this.server = new OPCUAServer(this.buildServerOptions());
45
+ await this.server.initialize();
46
+
47
+ const addressSpace = this.server.engine.addressSpace;
48
+
49
+ this.initializeNamespaces(this.treeConfig);
50
+
51
+ this.addressSpaceBuilder = new OpcUaAddressSpaceBuilder({
52
+ namespace: this.namespace,
53
+ namespaces: this.namespaces,
54
+ server: this.server,
55
+ registry: this.registry,
56
+ node: this.node,
57
+ serverName: this.serverName,
58
+ addressSpace: this.addressSpace
59
+ });
60
+
61
+
62
+ this.addressSpaceBuilder.rebuild(this.treeConfig);
63
+ await this.server.start();
64
+ this.registry.registerServer(this);
65
+
66
+ //Methods
67
+ const ServerMethods = new OpcUaServerMethods({
68
+ addressSpace: addressSpace,
69
+ registry: this.registry,
70
+ node: this.node
71
+ })
72
+
73
+ ServerMethods.start();
74
+
75
+
76
+ }
77
+
78
+ async stop() {
79
+ if (!this.server) {
80
+ this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
81
+ return;
82
+ }
83
+
84
+ try {
85
+ if (this.addressSpaceBuilder) {
86
+ this.addressSpaceBuilder.clearDynamicNodes();
87
+ this.addressSpaceBuilder.variableStore.clear();
88
+ }
89
+
90
+ await this.server.shutdown(1000);
91
+ } finally {
92
+ this.registry.unregisterServer(this.id);
93
+ this.addressSpaceBuilder = null;
94
+ this.namespace = null;
95
+ this.namespaces = new Map();
96
+ this.namespaceDefinitions = new Map();
97
+ this.server = null;
98
+ this.node.status({ fill: "grey", shape: "ring", text: "stopped" });
99
+ }
100
+ }
101
+
102
+ ensureReady() {
103
+ if (!this.server || !this.namespace || !this.addressSpaceBuilder) {
104
+ throw new Error("OPC UA server is not available");
105
+ }
106
+ }
107
+
108
+
109
+
110
+ async updateTree(treeConfig) {
111
+ this.ensureReady();
112
+ this.syncNamespaces(treeConfig);
113
+ this.treeConfig = treeConfig;
114
+ this.addressSpaceBuilder.sync(treeConfig);
115
+ }
116
+
117
+ readValueByPath(path) {
118
+ this.ensureReady();
119
+ return this.addressSpaceBuilder.readValueByPath(path);
120
+ }
121
+
122
+ readValueByNodeId(nodeId) {
123
+ this.ensureReady();
124
+ return this.addressSpaceBuilder.readValueByNodeId(nodeId);
125
+ }
126
+
127
+ readValue(identifierType, identifier) {
128
+ this.ensureReady();
129
+ return this.addressSpaceBuilder.readValue(identifierType, identifier);
130
+ }
131
+
132
+ writeEventByPath(valuesPayload) {
133
+ this.ensureReady();
134
+ return this.addressSpaceBuilder.eventValueByPath(valuesPayload);
135
+ }
136
+
137
+ writeValueByPath(path, value) {
138
+ this.ensureReady();
139
+ return this.addressSpaceBuilder.writeValueByPath(path, value);
140
+ }
141
+
142
+ writeValueByNodeId(nodeId, value) {
143
+ this.ensureReady();
144
+ return this.addressSpaceBuilder.writeValueByNodeId(nodeId, value);
145
+ }
146
+
147
+ writeValue(identifierType, identifier, value) {
148
+ this.ensureReady();
149
+ return this.addressSpaceBuilder.writeValue(identifierType, identifier, value);
150
+ }
151
+
152
+ getEndpointUrl() {
153
+ if (!this.server || !Array.isArray(this.server.endpoints)) {
154
+ return "";
155
+ }
156
+
157
+ for (let index = 0; index < this.server.endpoints.length; index += 1) {
158
+ const endpoint = this.server.endpoints[index];
159
+ if (!endpoint || typeof endpoint.endpointDescriptions !== "function") {
160
+ continue;
161
+ }
162
+
163
+ const descriptions = endpoint.endpointDescriptions();
164
+ if (Array.isArray(descriptions) && descriptions.length && descriptions[0].endpointUrl) {
165
+ return descriptions[0].endpointUrl;
166
+ }
167
+ }
168
+
169
+ return "opc.tcp://localhost:" + this.port + this.resourcePath;
170
+ }
171
+
172
+ buildServerOptions() {
173
+ const activeUsers = Array.isArray(this.users) ? this.users : [];
174
+ const userTokenPolicies = [];
175
+
176
+ if (this.allowAnonymous) {
177
+ userTokenPolicies.push({
178
+ policyId: "anonymous",
179
+ tokenType: UserTokenType.Anonymous
180
+ });
181
+ }
182
+
183
+ if (activeUsers.length) {
184
+ userTokenPolicies.push({
185
+ policyId: "username",
186
+ tokenType: UserTokenType.UserName
187
+ });
188
+ }
189
+
190
+ return {
191
+ port: this.port,
192
+ resourcePath: this.resourcePath,
193
+ buildInfo: {
194
+ productName: "opc-ua-server",
195
+ buildNumber: "1",
196
+ buildDate: new Date()
197
+ },
198
+ // serverCertificateManager: {
199
+ // automaticallyAcceptUnknownCertificate: true
200
+ // },
201
+ serverCapabilities: {
202
+ maxSessions: this.maxConnections
203
+ },
204
+ serverInfo: {
205
+ applicationName: { text: this.serverName },
206
+ applicationUri: buildApplicationUri(this.serverName),
207
+ productUri: "urn:node-red:opc-ua-server"
208
+ },
209
+ securityPolicies: [this.securityPolicy],
210
+ securityModes: [this.securityMode],
211
+ allowAnonymous: this.allowAnonymous,
212
+ userManager: {
213
+ isValidUser: (username, password) => this.isValidUser(username, password)
214
+ },
215
+ userTokenPolicies
216
+ };
217
+ }
218
+
219
+ isValidUser(username, password) {
220
+ return this.users.some((user) => {
221
+ if (user.username !== username) {
222
+ return false;
223
+ }
224
+
225
+ if (user.password && user.password === password) {
226
+ return true;
227
+ }
228
+
229
+ if (user.passwordHash) {
230
+ try {
231
+ return bcrypt.compareSync(password, user.passwordHash);
232
+ } catch (error) {
233
+ this.node.warn("Failed to validate password hash for user " + username + ": " + error.message);
234
+ return false;
235
+ }
236
+ }
237
+
238
+ return false;
239
+ });
240
+ }
241
+
242
+ initializeNamespaces(treeConfig) {
243
+ const addressSpace = this.server.engine.addressSpace;
244
+ const configuredNamespaces = this.buildNamespaceDefinitions(treeConfig);
245
+
246
+ this.namespaces = new Map();
247
+ this.namespaceDefinitions = configuredNamespaces;
248
+
249
+ configuredNamespaces.forEach((uri, namespaceId) => {
250
+ const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
251
+ this.namespaces.set(namespaceId, namespace);
252
+ });
253
+
254
+ this.namespace = this.namespaces.get(2);
255
+ }
256
+
257
+ syncNamespaces(treeConfig) {
258
+ const addressSpace = this.server.engine.addressSpace;
259
+ const nextDefinitions = this.buildNamespaceDefinitions(treeConfig);
260
+
261
+ this.namespaceDefinitions.forEach((uri, namespaceId) => {
262
+ const nextUri = nextDefinitions.get(namespaceId);
263
+ if (nextUri && nextUri !== uri) {
264
+ throw new Error("Namespace URI changes require a redeploy: namespace " + namespaceId);
265
+ }
266
+
267
+ if (!nextUri) {
268
+ throw new Error("Removing namespaces requires a redeploy: namespace " + namespaceId);
269
+ }
270
+ });
271
+
272
+ nextDefinitions.forEach((uri, namespaceId) => {
273
+ if (this.namespaces.has(namespaceId)) {
274
+ return;
275
+ }
276
+
277
+ const namespace = addressSpace.getNamespace(uri) || addressSpace.registerNamespace(uri);
278
+ this.namespaces.set(namespaceId, namespace);
279
+ });
280
+
281
+ this.namespaceDefinitions = nextDefinitions;
282
+ this.namespace = this.namespaces.get(2);
283
+ }
284
+
285
+ buildNamespaceDefinitions(treeConfig) {
286
+ const definitions = new Map();
287
+ const configuredNamespaces = Array.isArray(treeConfig && treeConfig.nameSpaces) ? treeConfig.nameSpaces : [];
288
+ let defaultNamespaceUri = this.namespaceUri;
289
+
290
+ configuredNamespaces.forEach((namespaceConfig) => {
291
+ if (namespaceConfig.id === 2) {
292
+ defaultNamespaceUri = namespaceConfig.name;
293
+ }
294
+ });
295
+
296
+ definitions.set(2, defaultNamespaceUri);
297
+
298
+ configuredNamespaces
299
+ .slice()
300
+ .sort((left, right) => left.id - right.id)
301
+ .forEach((namespaceConfig) => {
302
+ definitions.set(namespaceConfig.id, namespaceConfig.name);
303
+ });
304
+
305
+ return definitions;
306
+ }
307
+ }
308
+
309
+ module.exports = {
310
+ OpcUaServerRuntime
311
+ };