@schukai/monster 3.2.0 → 3.3.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.
@@ -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