@schukai/monster 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
3
+ * Node module: @schukai/monster
4
+ * This file is licensed under the AGPLv3 License.
5
+ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
+ */
7
+
8
+ import {instanceSymbol} from "../constants.mjs";
9
+ import {isInteger, isString, isObject} from "../types/is.mjs";
10
+ import {BaseWithOptions} from "../types/basewithoptions.mjs";
11
+ import {ObservableQueue} from "../types/observablequeue.mjs";
12
+ import {Message} from "./webconnect/message.mjs";
13
+
14
+
15
+ export {WebConnect}
16
+
17
+ /**
18
+ * @private
19
+ * @type {Symbol}
20
+ */
21
+ const receiveQueueSymbol = Symbol("receiveQueue");
22
+ /**
23
+ * @private
24
+ * @type {Symbol}
25
+ */
26
+ const sendQueueSymbol = Symbol("sendQueue");
27
+
28
+ /**
29
+ * @private
30
+ * @type {Symbol}
31
+ *
32
+ * hint: this name is used in the tests. if you want to change it, please change it in the tests as well.
33
+ */
34
+ const connectionSymbol = Symbol("connection");
35
+
36
+ /**
37
+ * @private
38
+ * @type {symbol}
39
+ */
40
+ const manualCloseSymbol = Symbol("manualClose");
41
+
42
+ /**
43
+ * @private
44
+ * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
45
+ * @type {{"1000": string, "1011": string, "1010": string, "1008": string, "1007": string, "1006": string, "1005": string, "1004": string, "1015": string, "1003": string, "1002": string, "1001": string, "1009": string}}
46
+ */
47
+ const connectionStatusCode = {
48
+ 1000: "Normal closure",
49
+ 1001: "Going away",
50
+ 1002: "Protocol error",
51
+ 1003: "Unsupported data",
52
+ 1004: "Reserved",
53
+ 1005: "No status code",
54
+ 1006: "Connection closed abnormally",
55
+ 1007: "Invalid frame payload data",
56
+ 1008: "Policy violation",
57
+ 1009: "Message too big",
58
+ 1010: "Mandatory extension",
59
+ 1011: "Internal server error",
60
+ 1015: "TLS handshake"
61
+ };
62
+
63
+ /**
64
+ * @private
65
+ * @this {WebConnect}
66
+ * @throws {Error} No url defined for websocket datasource.
67
+ */
68
+ function connectServer(resolve, reject) {
69
+ const self = this;
70
+
71
+ const url = self.getOption('url');
72
+ if (!url) {
73
+ reject('No url defined for webconnect.');
74
+ return;
75
+ }
76
+
77
+ let promiseAllredyResolved = false;
78
+
79
+ let connectionTimeout = self.getOption('connection.timeout');
80
+ if (!isInteger(connectionTimeout) || connectionTimeout < 100) {
81
+ connectionTimeout = 5000;
82
+ }
83
+
84
+ setTimeout(() => {
85
+ if (promiseAllredyResolved) {
86
+ return;
87
+ }
88
+ reject(new Error("Connection timeout"));
89
+ }, connectionTimeout);
90
+
91
+ let reconnectTimeout = self.getOption('connection.reconnect.timeout');
92
+ if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000) reconnectTimeout = 1000;
93
+ let reconnectAttempts = self.getOption('connection.reconnect.attempts');
94
+ if (!isInteger(reconnectAttempts) || reconnectAttempts < 1) reconnectAttempts = 1;
95
+ let reconnectEnabled = self.getOption('connection.reconnect.enabled');
96
+ if (reconnectEnabled !== true) reconnectEnabled = false;
97
+
98
+ self[manualCloseSymbol] = false;
99
+ self[connectionSymbol].reconnectCounter++;
100
+
101
+ if (self[connectionSymbol].socket && self[connectionSymbol].socket.readyState < 2) {
102
+ self[connectionSymbol].socket.close();
103
+ }
104
+ self[connectionSymbol].socket = null;
105
+ self[connectionSymbol].socket = new WebSocket(url);
106
+
107
+ self[connectionSymbol].socket.onmessage = function (event) {
108
+ if (event.data instanceof Blob) {
109
+ const reader = new FileReader();
110
+ reader.addEventListener("loadend", function () {
111
+ self[receiveQueueSymbol].add(new Message(reader.result))
112
+ });
113
+ reader.readAsText(new Message(event.data));
114
+ } else {
115
+ self[receiveQueueSymbol].add(Message.fromJSON(event.data));
116
+ }
117
+ };
118
+
119
+ self[connectionSymbol].socket.onopen = function () {
120
+ self[connectionSymbol].reconnectCounter = 0;
121
+ if (typeof resolve === 'function' && !promiseAllredyResolved) {
122
+ promiseAllredyResolved = true;
123
+ resolve();
124
+ }
125
+ };
126
+
127
+ self[connectionSymbol].socket.close = function (event) {
128
+
129
+ if (self[manualCloseSymbol]) {
130
+ self[manualCloseSymbol] = false;
131
+ return;
132
+ }
133
+
134
+ if (reconnectEnabled && this[connectionSymbol].reconnectCounter < reconnectAttempts) {
135
+ setTimeout(() => {
136
+ self.connect();
137
+ }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
138
+ }
139
+
140
+ };
141
+
142
+ self[connectionSymbol].socket.onerror = (error) => {
143
+
144
+ if (reconnectEnabled && self[connectionSymbol].reconnectCounter < reconnectAttempts) {
145
+ setTimeout(() => {
146
+ self.connect();
147
+ }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
148
+ } else {
149
+ if (typeof reject === 'function' && !promiseAllredyResolved) {
150
+ promiseAllredyResolved = true;
151
+ reject(error);
152
+ }
153
+ }
154
+
155
+ };
156
+ }
157
+
158
+ /**
159
+ * The RestAPI is a class that enables a REST API server.
160
+ *
161
+ * @externalExample ../../../example/data/storage/restapi.mjs
162
+ * @license AGPLv3
163
+ * @since 3.1.0
164
+ * @copyright schukai GmbH
165
+ * @memberOf Monster.Data.Datasource
166
+ * @summary The LocalStorage class encapsulates the access to data objects.
167
+ */
168
+ class WebConnect extends BaseWithOptions {
169
+
170
+ /**
171
+ *
172
+ * @param {Object} [options] options contains definitions for the datasource.
173
+ */
174
+ constructor(options) {
175
+
176
+ if (isString(options)) {
177
+ options = {url: options};
178
+ }
179
+
180
+ super(options);
181
+
182
+ this[receiveQueueSymbol] = new ObservableQueue();
183
+ this[sendQueueSymbol] = new ObservableQueue();
184
+
185
+ this[connectionSymbol] = {};
186
+ this[connectionSymbol].socket = null;
187
+ this[connectionSymbol].reconnectCounter = 0;
188
+ this[manualCloseSymbol] = false;
189
+ }
190
+
191
+ /**
192
+ *
193
+ * @returns {Promise}
194
+ */
195
+ connect() {
196
+ const self = this;
197
+
198
+ return new Promise((resolve, reject) => {
199
+ connectServer.call(this, resolve, reject);
200
+ });
201
+ }
202
+
203
+ /**
204
+ * @returns {boolean}
205
+ */
206
+ isConnected() {
207
+ return this[connectionSymbol]?.socket?.readyState === 1;
208
+ }
209
+
210
+ /**
211
+ * This method is called by the `instanceof` operator.
212
+ * @returns {symbol}
213
+ */
214
+ static get [instanceSymbol]() {
215
+ return Symbol.for("@schukai/monster/net/webconnect");
216
+ }
217
+
218
+ /**
219
+ * @property {string} url=undefined Defines the resource that you wish to fetch.
220
+ * @property {Object} connection
221
+ * @property {Object} connection.timeout=5000 Defines the timeout for the connection.
222
+ * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect.
223
+ * @property {Number} connection.reconnect.attempts The maximum number of reconnects.
224
+ * @property {Bool} connection.reconnect.enabled If the reconnect is enabled.
225
+ */
226
+ get defaults() {
227
+ return Object.assign({}, super.defaults, {
228
+ url: undefined,
229
+ connection: {
230
+ timeout: 5000,
231
+ reconnect: {
232
+ timeout: 1000,
233
+ attempts: 1,
234
+ enabled: false,
235
+ }
236
+ }
237
+ });
238
+ }
239
+
240
+ /**
241
+ * This method closes the connection.
242
+ *
243
+ * @param {Number} [code=1000] The close code.
244
+ * @param {String} [reason=""] The close reason.
245
+ * @returns {Promise}
246
+ * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
247
+ */
248
+ close(statusCode, reason ) {
249
+ if (!isInteger(statusCode) || statusCode < 1000 || statusCode > 4999) {
250
+ statusCode = 1000;
251
+ }
252
+ if (!isString(reason)) {
253
+ reason = '';
254
+ }
255
+
256
+ return new Promise((resolve, reject) => {
257
+ try {
258
+ this[manualCloseSymbol] = true;
259
+ if (this[connectionSymbol].socket) {
260
+ this[connectionSymbol].socket.close(statusCode, reason);
261
+ }
262
+ } catch (error) {
263
+ reject(error);
264
+ }
265
+ resolve();
266
+ });
267
+
268
+ }
269
+
270
+ /**
271
+ * Polls the receive queue for new messages.
272
+ *
273
+ * @returns {Message}
274
+ */
275
+ poll() {
276
+ return this[receiveQueueSymbol].poll();
277
+ }
278
+
279
+ /**
280
+ * Are there any messages in the receive queue?
281
+ *
282
+ * @returns {boolean}
283
+ */
284
+ dataReceived() {
285
+ return !this[receiveQueueSymbol].isEmpty();
286
+ }
287
+
288
+ /**
289
+ * Get Message from the receive queue, but do not remove it.
290
+ *
291
+ * @returns {Object}
292
+ */
293
+ peek() {
294
+ return this[receiveQueueSymbol].peek();
295
+ }
296
+
297
+ /**
298
+ * Attach a new observer
299
+ *
300
+ * @param {Observer} observer
301
+ * @returns {ProxyObserver}
302
+ */
303
+ attachObserver(observer) {
304
+ this[receiveQueueSymbol].attachObserver(observer);
305
+ return this;
306
+ }
307
+
308
+ /**
309
+ * Detach a observer
310
+ *
311
+ * @param {Observer} observer
312
+ * @returns {ProxyObserver}
313
+ */
314
+ detachObserver(observer) {
315
+ this[receiveQueueSymbol].detachObserver(observer);
316
+ return this;
317
+ }
318
+
319
+ /**
320
+ * @param {Observer} observer
321
+ * @returns {boolean}
322
+ */
323
+ containsObserver(observer) {
324
+ return this[receiveQueueSymbol].containsObserver(observer);
325
+ }
326
+
327
+ /**
328
+ * @param {Message|Object} message
329
+ * @return {Promise}
330
+ */
331
+ send(message) {
332
+ const self = this;
333
+
334
+ return new Promise((resolve, reject) => {
335
+
336
+ if (self[connectionSymbol].socket.readyState !== 1) {
337
+ reject('the socket is not ready');
338
+ }
339
+
340
+ self[connectionSymbol].socket.send(JSON.stringify(message))
341
+ resolve();
342
+ });
343
+ }
344
+
345
+ }
346
+
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
3
+ * Node module: @schukai/monster
4
+ * This file is licensed under the AGPLv3 License.
5
+ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
+ */
7
+
8
+ import {Queue} from './queue.mjs';
9
+ import {instanceSymbol, internalSymbol} from '../constants.mjs';
10
+ import {ObserverList} from "./observerlist.mjs";
11
+
12
+ export {ObservableQueue};
13
+
14
+ /**
15
+ * An observable queue is a list of items that are processed one after another (first in, first out).
16
+ *
17
+ * `Queue.add()` and `Queue.clear()` notify all observers.
18
+ *
19
+ * @externalExample ../../example/types/queue.mjs
20
+ * @license AGPLv3
21
+ * @since 3.3.0
22
+ * @copyright schukai GmbH
23
+ * @memberOf Monster.Types
24
+ * @summary An observable Queue (Fifo)
25
+ */
26
+ class ObservableQueue extends Queue {
27
+
28
+ /**
29
+ *
30
+ */
31
+ constructor() {
32
+ super();
33
+ this[internalSymbol]= {
34
+ observers: new ObserverList()
35
+ };
36
+ }
37
+
38
+ /**
39
+ * This method is called by the `instanceof` operator.
40
+ * @returns {symbol}
41
+ * @since 2.1.0
42
+ */
43
+ static get [instanceSymbol]() {
44
+ return Symbol.for("@schukai/monster/types/observablequeue");
45
+ }
46
+
47
+ /**
48
+ * Add a new element to the end of the queue.
49
+ *
50
+ * @param {*} value
51
+ * @returns {Queue}
52
+ */
53
+ add(value) {
54
+ super.add(value);
55
+ this.notifyObservers();
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * remove all entries
61
+ *
62
+ * @returns {Queue}
63
+ */
64
+ clear() {
65
+ super.clear();
66
+ this.notifyObservers();
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * Attach a new observer
72
+ *
73
+ * @param {Observer} observer
74
+ * @returns {ProxyObserver}
75
+ */
76
+ attachObserver(observer) {
77
+ this[internalSymbol].observers.attach(observer)
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Detach a observer
83
+ *
84
+ * @param {Observer} observer
85
+ * @returns {ProxyObserver}
86
+ */
87
+ detachObserver(observer) {
88
+ this[internalSymbol].observers.detach(observer)
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * Notify all observer
94
+ *
95
+ * @returns {Promise}
96
+ */
97
+ notifyObservers() {
98
+ return this[internalSymbol].observers.notify(this);
99
+ }
100
+
101
+ /**
102
+ * @param {Observer} observer
103
+ * @returns {boolean}
104
+ */
105
+ containsObserver(observer) {
106
+ return this[internalSymbol].observers.contains(observer)
107
+ }
108
+
109
+
110
+ }
@@ -61,9 +61,9 @@ export {ProxyObserver}
61
61
 
62
62
 
63
63
  /**
64
- * get the real object
64
+ * Get the real object
65
65
  *
66
- * changes to this object are not noticed by the observers, so you can make a large number of changes and inform the observers later.
66
+ * Changes to this object are not noticed by the observers, so you can make a large number of changes and inform the observers later.
67
67
  *
68
68
  * @returns {object}
69
69
  */
@@ -6,12 +6,13 @@
6
6
  */
7
7
 
8
8
  import {Queue} from "./queue.mjs";
9
+ import {internalSymbol} from "../constants.mjs";
9
10
  import {validateObject} from "./validate.mjs";
10
11
 
11
12
  export {UniqueQueue}
12
13
 
13
14
  /**
14
- * A UniqueQueue is a queue that contains items only once.
15
+ * An UniqueQueue is a queue that contains items only once.
15
16
  *
16
17
  * @license AGPLv3
17
18
  * @since 1.4.0
@@ -26,7 +27,9 @@ export {UniqueQueue}
26
27
  */
27
28
  constructor() {
28
29
  super();
29
- this.unique = new WeakSet();
30
+ this[internalSymbol]={
31
+ unique : new WeakSet()
32
+ };
30
33
  }
31
34
 
32
35
  /**
@@ -40,8 +43,8 @@ export {UniqueQueue}
40
43
 
41
44
  validateObject(value);
42
45
 
43
- if (!this.unique.has(value)) {
44
- this.unique.add(value);
46
+ if (!this[internalSymbol].unique.has(value)) {
47
+ this[internalSymbol].unique.add(value);
45
48
  super.add(value);
46
49
  }
47
50
 
@@ -55,7 +58,7 @@ export {UniqueQueue}
55
58
  */
56
59
  clear() {
57
60
  super.clear();
58
- this.unique = new WeakSet;
61
+ this[internalSymbol].unique = new WeakSet;
59
62
  return this;
60
63
  }
61
64
 
@@ -71,7 +74,7 @@ export {UniqueQueue}
71
74
  return undefined;
72
75
  }
73
76
  let value = this.data.shift();
74
- this.unique.delete(value);
77
+ this[internalSymbol].unique.delete(value);
75
78
  return value;
76
79
  }
77
80
 
@@ -149,7 +149,7 @@ function getMonsterVersion() {
149
149
  }
150
150
 
151
151
  /** don't touch, replaced by make with package.json version */
152
- monsterVersion = new Version('3.2.0')
152
+ monsterVersion = new Version('3.3.0')
153
153
 
154
154
  return monsterVersion;
155
155