unetjs 2.0.10 → 3.1.0

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/README.md CHANGED
@@ -63,14 +63,18 @@ The JavaScript version of the UnetSocket API allows a user to connect to a node
63
63
 
64
64
  The UnetSocket API allows a user to cache responses to parameter requests. This is useful in a scenario where the parameters aren't changing very often, and the user wants to reduce the round trip time for parameter requests.
65
65
 
66
+ > This behaviour used to be enabled by default in unetsocket.js v2.0.0 till v2.0.10. From v3.0.0 onwards, this behaviour is **disabled** by default but a available as an option.
67
+
68
+ You can use the `CachingGateway` class instead of the `Gateway` class to enable this behaviour.
69
+
66
70
  UnetSocket API acheives this using two mechanism, firstly, it can request ALL of the parameters from an Agent instead of just one, and then cache the responses. This is called the `greedy` mode. The greedy mode is enabled by default but can be disabled by setting the `greedy` property to `false` in the `CachingAgentID` constructor.
67
71
 
68
72
  Secondly, the UnetSocket API caches the responses to parameter requests for a limited time. If the user requests the same parameter again, the UnetSocket will return the cached response. The time to cache a response is set by the `cacheTime` property in the `CachingAgentID` constructor.
69
73
 
70
74
  ```js
71
- import {UnetMessages, Gateway} from 'unetjs'
72
- let gw = new Gateway({...});
73
- let nodeinfo = await gw.agentForService(Services.NODE_INFO); // returns a CachingAgentID by default.
75
+ import {UnetMessages, CachingGateway} from 'unetjs'
76
+ let gw = new CachingGateway({...});
77
+ let nodeinfo = await gw.agentForService(Services.NODE_INFO); // returns a CachingAgentID
74
78
  let cLoc = nodeinfo.get('location'); // this will request all the Parameters from the Agent, and cache the responses.
75
79
  ...
76
80
  cLoc = nodeinfo.get('location', maxage=5000); // this will return the cached response if it was called within 5000ms of the original request.
@@ -79,9 +83,9 @@ cLoc = nodeinfo.get('location', maxage=0); // this will force the Gateway to req
79
83
  ```
80
84
 
81
85
  ```js
82
- import {UnetMessages, Gateway} from 'unetjs'
83
- let gw = new Gateway({...});
84
- let nodeinfo = await gw.agentForService(Services.NODE_INFO); // returns a CachingAgentID by default.
86
+ import {UnetMessages, CachingGateway} from 'unetjs'
87
+ let gw = new CachingGateway({...});
88
+ let nodeinfo = await gw.agentForService(Services.NODE_INFO); // returns a CachingAgentID
85
89
  let nonCachingNodeInfo = await gw.agentForService(Services.NODE_INFO, false); // returns an AgentID without caching (original fjage.js functionality).
86
90
  let cLoc = nonCachingNodeInfo.get('location'); // this will request the `location` parameter from the Agent.
87
91
  ...
package/dist/cjs/unet.cjs CHANGED
@@ -1,28 +1,8 @@
1
- /* unet.js v2.0.10 2022-07-27T05:47:35.162Z */
1
+ /* unet.js v3.1.0 2024-01-26T11:16:07.040Z */
2
2
 
3
3
  'use strict';
4
4
 
5
- Object.defineProperty(exports, '__esModule', { value: true });
6
-
7
- function _interopNamespace(e) {
8
- if (e && e.__esModule) return e;
9
- var n = Object.create(null);
10
- if (e) {
11
- Object.keys(e).forEach(function (k) {
12
- if (k !== 'default') {
13
- var d = Object.getOwnPropertyDescriptor(e, k);
14
- Object.defineProperty(n, k, d.get ? d : {
15
- enumerable: true,
16
- get: function () { return e[k]; }
17
- });
18
- }
19
- });
20
- }
21
- n["default"] = e;
22
- return Object.freeze(n);
23
- }
24
-
25
- /* fjage.js v1.10.3 */
5
+ /* fjage.js v1.12.0 */
26
6
 
27
7
  const isBrowser =
28
8
  typeof window !== "undefined" && typeof window.document !== "undefined";
@@ -47,10 +27,13 @@ const isJsDom =
47
27
  (navigator.userAgent.includes("Node.js") ||
48
28
  navigator.userAgent.includes("jsdom")));
49
29
 
50
- typeof Deno !== "undefined" && typeof Deno.core !== "undefined";
30
+ typeof Deno !== "undefined" &&
31
+ typeof Deno.version !== "undefined" &&
32
+ typeof Deno.version.deno !== "undefined";
51
33
 
52
34
  const SOCKET_OPEN = 'open';
53
35
  const SOCKET_OPENING = 'opening';
36
+ const DEFAULT_RECONNECT_TIME$1 = 5000; // ms, delay between retries to connect to the server.
54
37
 
55
38
  var createConnection;
56
39
 
@@ -62,17 +45,24 @@ class TCPconnector {
62
45
 
63
46
  /**
64
47
  * Create an TCPConnector to connect to a fjage master over TCP
65
- * @param {Object} opts
66
- * @param {String} [opts.hostname='localhost'] - ip address/hostname of the master container to connect to
67
- * @param {number} opts.port - port number of the master container to connect to
48
+ * @param {Object} opts
49
+ * @param {string} [opts.hostname='localhost'] - hostname/ip address of the master container to connect to
50
+ * @param {number} opts.port - port number of the master container to connect to
51
+ * @param {string} opts.pathname - path of the master container to connect to
52
+ * @param {boolean} opts.keepAlive - try to reconnect if the connection is lost
53
+ * @param {number} [opts.reconnectTime=5000] - time before reconnection is attempted after an error
68
54
  */
69
55
  constructor(opts = {}) {
70
56
  this.url = new URL('tcp://localhost');
71
57
  let host = opts.hostname || 'localhost';
72
58
  let port = opts.port || -1;
73
- this.url.hostname = opts.hostname;
74
- this.url.port = opts.port;
59
+ this.url.hostname = host;
60
+ this.url.port = port;
75
61
  this._buf = '';
62
+ this._reconnectTime = opts.reconnectTime || DEFAULT_RECONNECT_TIME$1;
63
+ this._keepAlive = opts.keepAlive || true;
64
+ this._firstConn = true; // if the Gateway has managed to connect to a server before
65
+ this._firstReConn = true; // if the Gateway has attempted to reconnect to a server before
76
66
  this.pendingOnOpen = []; // list of callbacks make as soon as gateway is open
77
67
  this.connListeners = []; // external listeners wanting to listen connection events
78
68
  this._sockInit(host, port);
@@ -88,7 +78,7 @@ class TCPconnector {
88
78
  _sockInit(host, port){
89
79
  if (!createConnection){
90
80
  try {
91
- Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('net')); }).then(module => {
81
+ import('net').then(module => {
92
82
  createConnection = module.createConnection;
93
83
  this._sockSetup(host, port);
94
84
  });
@@ -97,7 +87,7 @@ class TCPconnector {
97
87
  }
98
88
  }else {
99
89
  this._sockSetup(host, port);
100
- }
90
+ }
101
91
  }
102
92
 
103
93
  _sockSetup(host, port){
@@ -116,18 +106,18 @@ class TCPconnector {
116
106
  }
117
107
 
118
108
  _sockReconnect(){
119
- if (this._firstConn || !this.keepAlive || this.sock.readyState == SOCKET_OPENING || this.sock.readyState == SOCKET_OPEN) return;
109
+ if (this._firstConn || !this._keepAlive || this.sock.readyState == SOCKET_OPENING || this.sock.readyState == SOCKET_OPEN) return;
120
110
  if (this._firstReConn) this._sendConnEvent(false);
121
111
  this._firstReConn = false;
122
- if(this.debug) console.log('Reconnecting to ', this.sock.remoteAddress + ':' + this.sock.remotePort);
123
112
  setTimeout(() => {
124
113
  this.pendingOnOpen = [];
125
- this._sockSetup(this.sock.url);
114
+ this._sockSetup(this.url.hostname, this.url.port);
126
115
  }, this._reconnectTime);
127
116
  }
128
117
 
129
118
  _onSockOpen() {
130
119
  this._sendConnEvent(true);
120
+ this._firstConn = false;
131
121
  this.sock.on('close', this._sockReconnect.bind(this));
132
122
  this.sock.on('data', this._processSockData.bind(this));
133
123
  this.pendingOnOpen.forEach(cb => cb());
@@ -184,7 +174,7 @@ class TCPconnector {
184
174
  * @ignore
185
175
  * @param {string} s - incoming message string
186
176
  */
187
-
177
+
188
178
  /**
189
179
  * Add listener for connection events
190
180
  * @param {function} listener - a listener callback that is called when the connection is opened/closed
@@ -215,12 +205,16 @@ class TCPconnector {
215
205
  if (this.sock.readyState == SOCKET_OPENING) {
216
206
  this.pendingOnOpen.push(() => {
217
207
  this.sock.send('{"alive": false}\n');
218
- this.sock.onclose = null;
208
+ this.sock.removeAllListeners('connect');
209
+ this.sock.removeAllListeners('error');
210
+ this.sock.removeAllListeners('close');
219
211
  this.sock.destroy();
220
212
  });
221
213
  } else if (this.sock.readyState == SOCKET_OPEN) {
222
214
  this.sock.send('{"alive": false}\n');
223
- this.sock.onclose = null;
215
+ this.sock.removeAllListeners('connect');
216
+ this.sock.removeAllListeners('error');
217
+ this.sock.removeAllListeners('close');
224
218
  this.sock.destroy();
225
219
  }
226
220
  }
@@ -245,11 +239,11 @@ class WSConnector {
245
239
  */
246
240
  constructor(opts = {}) {
247
241
  this.url = new URL('ws://localhost');
248
- this.url.hostname = opts.hostname;
242
+ this.url.hostname = opts.hostname;
249
243
  this.url.port = opts.port;
250
244
  this.url.pathname = opts.pathname;
251
245
  this._reconnectTime = opts.reconnectTime || DEFAULT_RECONNECT_TIME;
252
- this._keepAlive = opts.keepAlive;
246
+ this._keepAlive = opts.keepAlive || true;
253
247
  this.debug = opts.debug || false; // debug info to be logged to console?
254
248
  this._firstConn = true; // if the Gateway has managed to connect to a server before
255
249
  this._firstReConn = true; // if the Gateway has attempted to reconnect to a server before
@@ -335,7 +329,7 @@ class WSConnector {
335
329
  * @ignore
336
330
  * @param {string} s - incoming message string
337
331
  */
338
-
332
+
339
333
  /**
340
334
  * Add listener for connection events
341
335
  * @param {function} listener - a listener callback that is called when the connection is opened/closed
@@ -379,6 +373,7 @@ class WSConnector {
379
373
 
380
374
  /* global global Buffer */
381
375
 
376
+
382
377
  const DEFAULT_QUEUE_SIZE = 128; // max number of old unreceived messages to store
383
378
 
384
379
  /**
@@ -507,14 +502,17 @@ class AgentID {
507
502
  msg.index = Number.isInteger(index) ? index : -1;
508
503
  const rsp = await this.owner.request(msg, timeout);
509
504
  var ret = Array.isArray(params) ? new Array(params.length).fill(null) : null;
510
- if (!rsp || rsp.perf != Performative.INFORM || !rsp.param) return ret;
505
+ if (!rsp || rsp.perf != Performative.INFORM || !rsp.param) {
506
+ if (this.owner._returnNullOnFailedResponse) return ret;
507
+ else throw new Error(`Unable to set ${this.name}.${params} to ${values}`);
508
+ }
511
509
  if (Array.isArray(params)) {
512
510
  if (!rsp.values) rsp.values = {};
513
511
  if (rsp.param) rsp.values[rsp.param] = rsp.value;
514
512
  const rvals = Object.keys(rsp.values);
515
513
  return params.map( p => {
516
514
  let f = rvals.find(rv => rv.endsWith(p));
517
- return f ? rsp.values[f] : null;
515
+ return f ? rsp.values[f] : undefined;
518
516
  });
519
517
  } else {
520
518
  return rsp.value;
@@ -545,7 +543,10 @@ class AgentID {
545
543
  msg.index = Number.isInteger(index) ? index : -1;
546
544
  const rsp = await this.owner.request(msg, timeout);
547
545
  var ret = Array.isArray(params) ? new Array(params.length).fill(null) : null;
548
- if (!rsp || rsp.perf != Performative.INFORM || (params && (!rsp.param))) return ret;
546
+ if (!rsp || rsp.perf != Performative.INFORM || !rsp.param) {
547
+ if (this.owner._returnNullOnFailedResponse) return ret;
548
+ else throw new Error(`Unable to get ${this.name}.${params}`);
549
+ }
549
550
  // Request for listing of all parameters.
550
551
  if (!params) {
551
552
  if (!rsp.values) rsp.values = {};
@@ -557,7 +558,7 @@ class AgentID {
557
558
  const rvals = Object.keys(rsp.values);
558
559
  return params.map(p => {
559
560
  let f = rvals.find(rv => rv.endsWith(p));
560
- return f ? rsp.values[f] : null;
561
+ return f ? rsp.values[f] : undefined;
561
562
  });
562
563
  } else {
563
564
  return rsp.value;
@@ -641,12 +642,17 @@ class Message {
641
642
  return null;
642
643
  }
643
644
  }
644
- let qclazz = obj.clazz;
645
- let clazz = qclazz.replace(/^.*\./, '');
646
- let rv = MessageClass[clazz] ? new MessageClass[clazz] : new Message();
647
- rv.__clazz__ = qclazz;
648
- rv._inflate(obj.data);
649
- return rv;
645
+ try {
646
+ let qclazz = obj.clazz;
647
+ let clazz = qclazz.replace(/^.*\./, '');
648
+ let rv = MessageClass[clazz] ? new MessageClass[clazz] : new Message();
649
+ rv.__clazz__ = qclazz;
650
+ rv._inflate(obj.data);
651
+ return rv;
652
+ } catch (err) {
653
+ console.warn('Error trying to deserialize JSON object : ', obj, err);
654
+ return null;
655
+ }
650
656
  }
651
657
  }
652
658
 
@@ -663,6 +669,7 @@ class Message {
663
669
  * @param {string} [opts.keepAlive=true] - try to reconnect if the connection is lost
664
670
  * @param {number} [opts.queueSize=128] - size of the queue of received messages that haven't been consumed yet
665
671
  * @param {number} [opts.timeout=1000] - timeout for fjage level messages in ms
672
+ * @param {boolean} [opts.returnNullOnFailedResponse=true] - return null instead of throwing an error when a parameter is not found
666
673
  * @param {string} [hostname="localhost"] - <strike>Deprecated : hostname/ip address of the master container to connect to</strike>
667
674
  * @param {number} [port=] - <strike>Deprecated : port number of the master container to connect to</strike>
668
675
  * @param {string} [pathname=="/ws/"] - <strike>Deprecated : path of the master container to connect to (for WebSockets)</strike>
@@ -691,6 +698,7 @@ class Gateway {
691
698
  this._timeout = opts.timeout; // timeout for fjage level messages (agentForService etc)
692
699
  this._keepAlive = opts.keepAlive; // reconnect if connection gets closed/errored
693
700
  this._queueSize = opts.queueSize; // size of queue
701
+ this._returnNullOnFailedResponse = opts.returnNullOnFailedResponse; // null or error
694
702
  this.pending = {}; // msgid to callback mapping for pending requests to server
695
703
  this.subscriptions = {}; // hashset for all topics that are subscribed
696
704
  this.listener = {}; // set of callbacks that want to listen to incoming messages
@@ -706,7 +714,13 @@ class Gateway {
706
714
  _sendEvent(type, val) {
707
715
  if (Array.isArray(this.eventListeners[type])) {
708
716
  this.eventListeners[type].forEach(l => {
709
- l && {}.toString.call(l) === '[object Function]' && l(val);
717
+ if (l && {}.toString.call(l) === '[object Function]'){
718
+ try {
719
+ l(val);
720
+ } catch (error) {
721
+ console.warn('Error in event listener : ' + error);
722
+ }
723
+ }
710
724
  });
711
725
  }
712
726
  }
@@ -735,18 +749,26 @@ class Gateway {
735
749
  var consumed = false;
736
750
  if (Array.isArray(this.eventListeners['message'])){
737
751
  for (var i = 0; i < this.eventListeners['message'].length; i++) {
738
- if (this.eventListeners['message'][i](msg)) {
739
- consumed = true;
740
- break;
752
+ try {
753
+ if (this.eventListeners['message'][i](msg)) {
754
+ consumed = true;
755
+ break;
756
+ }
757
+ } catch (error) {
758
+ console.warn('Error in message listener : ' + error);
741
759
  }
742
760
  }
743
761
  }
744
762
  // iterate over internal callbacks, until one consumes the message
745
763
  for (var key in this.listener){
746
764
  // callback returns true if it has consumed the message
747
- if (this.listener[key](msg)) {
748
- consumed = true;
749
- break;
765
+ try {
766
+ if (this.listener[key](msg)) {
767
+ consumed = true;
768
+ break;
769
+ }
770
+ } catch (error) {
771
+ console.warn('Error in listener : ' + error);
750
772
  }
751
773
  }
752
774
  if(!consumed) {
@@ -834,6 +856,7 @@ class Gateway {
834
856
  this.connector.write('{"alive": true}');
835
857
  this._update_watch();
836
858
  }
859
+ this._sendEvent('conn', state);
837
860
  });
838
861
  return conn;
839
862
  }
@@ -847,7 +870,12 @@ class Gateway {
847
870
  } else if (filter.__proto__.name == 'Message' || filter.__proto__.__proto__.name == 'Message') {
848
871
  return filter.__clazz__ == msg.__clazz__;
849
872
  } else if (typeof filter == 'function') {
850
- return filter(msg);
873
+ try {
874
+ return filter(msg);
875
+ }catch(e){
876
+ console.warn('Error in filter : ' + e);
877
+ return false;
878
+ }
851
879
  } else {
852
880
  return msg instanceof filter;
853
881
  }
@@ -1021,6 +1049,30 @@ class Gateway {
1021
1049
  this._update_watch();
1022
1050
  }
1023
1051
 
1052
+ /**
1053
+ * Gets a list of all agents in the container.
1054
+ * @returns {Promise<AgentID[]>} - a promise which returns an array of all agent ids when resolved
1055
+ */
1056
+ async agents() {
1057
+ let rq = { action: 'agents' };
1058
+ let rsp = await this._msgTxRx(rq);
1059
+ if (!rsp || !Array.isArray(rsp.agentIDs)) throw new Error('Unable to get agents');
1060
+ return rsp.agentIDs.map(aid => new AgentID(aid, false, this));
1061
+ }
1062
+
1063
+ /**
1064
+ * Check if an agent with a given name exists in the container.
1065
+ *
1066
+ * @param {AgentID|String} agentID - the agent id to check
1067
+ * @returns {Promise<boolean>} - a promise which returns true if the agent exists when resolved
1068
+ */
1069
+ async containsAgent(agentID) {
1070
+ let rq = { action: 'containsAgent', agentID: agentID instanceof AgentID ? agentID.getName() : agentID };
1071
+ let rsp = await this._msgTxRx(rq);
1072
+ if (!rsp) throw new Error('Unable to check if agent exists');
1073
+ return !!rsp.answer;
1074
+ }
1075
+
1024
1076
  /**
1025
1077
  * Finds an agent that provides a named service. If multiple agents are registered
1026
1078
  * to provide a given service, any of the agents' id may be returned.
@@ -1031,7 +1083,11 @@ class Gateway {
1031
1083
  async agentForService(service) {
1032
1084
  let rq = { action: 'agentForService', service: service };
1033
1085
  let rsp = await this._msgTxRx(rq);
1034
- if (!rsp || !rsp.agentID) return;
1086
+ if (!rsp) {
1087
+ if (this._returnNullOnFailedResponse) return null;
1088
+ else throw new Error('Unable to get agent for service');
1089
+ }
1090
+ if (!rsp.agentID) return null;
1035
1091
  return new AgentID(rsp.agentID, false, this);
1036
1092
  }
1037
1093
 
@@ -1045,7 +1101,11 @@ class Gateway {
1045
1101
  let rq = { action: 'agentsForService', service: service };
1046
1102
  let rsp = await this._msgTxRx(rq);
1047
1103
  let aids = [];
1048
- if (!rsp || !Array.isArray(rsp.agentIDs)) return aids;
1104
+ if (!rsp) {
1105
+ if (this._returnNullOnFailedResponse) return aids;
1106
+ else throw new Error('Unable to get agents for service');
1107
+ }
1108
+ if (!Array.isArray(rsp.agentIDs)) return aids;
1049
1109
  for (var i = 0; i < rsp.agentIDs.length; i++)
1050
1110
  aids.push(new AgentID(rsp.agentIDs[i], false, this));
1051
1111
  return aids;
@@ -1117,7 +1177,7 @@ class Gateway {
1117
1177
  let timer;
1118
1178
  if (timeout > 0){
1119
1179
  timer = setTimeout(() => {
1120
- delete this.listener[lid];
1180
+ this.listener[lid] && delete this.listener[lid];
1121
1181
  if (this.debug) console.log('Receive Timeout : ' + filter);
1122
1182
  resolve();
1123
1183
  }, timeout);
@@ -1125,7 +1185,7 @@ class Gateway {
1125
1185
  this.listener[lid] = msg => {
1126
1186
  if (!this._matchMessage(filter, msg)) return false;
1127
1187
  if(timer) clearTimeout(timer);
1128
- delete this.listener[lid];
1188
+ this.listener[lid] && delete this.listener[lid];
1129
1189
  resolve(msg);
1130
1190
  return true;
1131
1191
  };
@@ -1264,7 +1324,8 @@ if (isBrowser || isWebWorker){
1264
1324
  'pathname' : '/ws/',
1265
1325
  'timeout': 1000,
1266
1326
  'keepAlive' : true,
1267
- 'queueSize': DEFAULT_QUEUE_SIZE
1327
+ 'queueSize': DEFAULT_QUEUE_SIZE,
1328
+ 'returnNullOnFailedResponse': true
1268
1329
  });
1269
1330
  DEFAULT_URL = new URL('ws://localhost');
1270
1331
  // Enable caching of Gateways
@@ -1278,7 +1339,8 @@ if (isBrowser || isWebWorker){
1278
1339
  'pathname': '',
1279
1340
  'timeout': 1000,
1280
1341
  'keepAlive' : true,
1281
- 'queueSize': DEFAULT_QUEUE_SIZE
1342
+ 'queueSize': DEFAULT_QUEUE_SIZE,
1343
+ 'returnNullOnFailedResponse': true
1282
1344
  });
1283
1345
  DEFAULT_URL = new URL('tcp://localhost');
1284
1346
  gObj.atob = a => Buffer.from(a, 'base64').toString('binary');
@@ -1724,7 +1786,7 @@ class UnetSocket {
1724
1786
 
1725
1787
  constructor(hostname, port, path='') {
1726
1788
  return (async () => {
1727
- this.gw = new CachingGateway({
1789
+ this.gw = new Gateway({
1728
1790
  hostname : hostname,
1729
1791
  port : port,
1730
1792
  path : path
@@ -1984,7 +2046,8 @@ class UnetSocket {
1984
2046
 
1985
2047
  exports.AgentID = AgentID;
1986
2048
  exports.CachingAgentID = CachingAgentID;
1987
- exports.Gateway = CachingGateway;
2049
+ exports.CachingGateway = CachingGateway;
2050
+ exports.Gateway = Gateway;
1988
2051
  exports.Message = Message;
1989
2052
  exports.MessageClass = MessageClass;
1990
2053
  exports.Performative = Performative;