@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schukai/monster",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Monster is a simple library for creating fast, robust and lightweight websites.",
5
5
  "keywords": [
6
6
  "framework",
@@ -6,20 +6,15 @@
6
6
  */
7
7
 
8
8
  import {internalSymbol, instanceSymbol} from "../../constants.mjs";
9
- import {isInteger, isString, isObject} from "../../types/is.mjs";
10
- import {Queue} from "../../types/queue.mjs";
9
+ import {isString, isObject} from "../../types/is.mjs";
10
+ import {WebConnect} from "../../net/webconnect.mjs";
11
+ import {Message} from "../../net/webconnect/message.mjs";
11
12
  import {Datasource} from "../datasource.mjs";
12
13
  import {Pathfinder} from "../pathfinder.mjs";
13
14
  import {Pipe} from "../pipe.mjs";
14
15
 
15
16
  export {WebSocketDatasource}
16
17
 
17
- /**
18
- * @private
19
- * @type {Symbol}
20
- */
21
- const receiveQueueSymbol = Symbol("queue");
22
-
23
18
 
24
19
  /**
25
20
  * @private
@@ -27,122 +22,33 @@ const receiveQueueSymbol = Symbol("queue");
27
22
  *
28
23
  * hint: this name is used in the tests. if you want to change it, please change it in the tests as well.
29
24
  */
30
- const connectionSymbol = Symbol("connection");
31
-
32
- /**
33
- * @private
34
- * @type {symbol}
35
- */
36
- const manualCloseSymbol = Symbol("manualClose");
37
-
38
- /**
39
- * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
40
- * @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}}
41
- */
42
- const connectionStatusCode = {
43
- 1000: "Normal closure",
44
- 1001: "Going away",
45
- 1002: "Protocol error",
46
- 1003: "Unsupported data",
47
- 1004: "Reserved",
48
- 1005: "No status code",
49
- 1006: "Connection closed abnormally",
50
- 1007: "Invalid frame payload data",
51
- 1008: "Policy violation",
52
- 1009: "Message too big",
53
- 1010: "Mandatory extension",
54
- 1011: "Internal server error",
55
- 1015: "TLS handshake"
56
- };
25
+ const webConnectSymbol = Symbol("connection");
57
26
 
58
27
  /**
59
- * @private
60
- * @this {WebSocketDatasource}
61
- * @throws {Error} No url defined for websocket datasource.
28
+ *
29
+ * @param self
30
+ * @param obj
31
+ * @returns {*}
62
32
  */
63
- function connectServer(resolve, reject) {
33
+ function doTransform(type, obj) {
64
34
  const self = this;
65
-
66
- let promiseAllredyResolved = false;
67
- let connectionTimeout = self.getOption('connection.timeout');
68
- if (!isInteger(connectionTimeout) || connectionTimeout < 100) {
69
- connectionTimeout = 5000;
70
- }
71
-
72
- setTimeout(() => {
73
- if (promiseAllredyResolved) {
74
- return;
35
+ let transformation = self.getOption(type + '.mapping.transformer');
36
+ if (transformation !== undefined) {
37
+ const pipe = new Pipe(transformation);
38
+ const callbacks = self.getOption(type + '.mapping.callbacks')
39
+
40
+ if (isObject(callbacks)) {
41
+ for (const key in callbacks) {
42
+ if (callbacks.hasOwnProperty(key) && typeof callbacks[key] === 'function') {
43
+ pipe.setCallback(key, callbacks[key]);
44
+ }
45
+ }
75
46
  }
76
- reject(new Error("Connection timeout"));
77
- }, connectionTimeout);
78
-
79
- let reconnectTimeout = self.getOption('connection.reconnect.timeout');
80
- if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000) reconnectTimeout = 1000;
81
- let reconnectAttempts = self.getOption('connection.reconnect.attempts');
82
- if (!isInteger(reconnectAttempts) || reconnectAttempts < 1) reconnectAttempts = 1;
83
- let reconnectEnabled = self.getOption('connection.reconnect.enabled');
84
- if (reconnectEnabled !== true) reconnectEnabled = false;
85
-
86
- self[manualCloseSymbol] = false;
87
- self[connectionSymbol].reconnectCounter++;
88
-
89
- if (self[connectionSymbol].socket && self[connectionSymbol].socket.readyState < 2) {
90
- self[connectionSymbol].socket.close();
91
- }
92
- self[connectionSymbol].socket = null;
93
47
 
94
- const url = self.getOption('url');
95
- if (!url) {
96
- reject('No url defined for websocket datasource.');
97
- return;
48
+ obj = pipe.run(obj);
98
49
  }
99
50
 
100
- self[connectionSymbol].socket = new WebSocket(url);
101
-
102
- self[connectionSymbol].socket.onmessage = function (event) {
103
- self[receiveQueueSymbol].add(event);
104
- setTimeout(function () {
105
- self.read();
106
- }, 1);
107
- };
108
-
109
- self[connectionSymbol].socket.onopen = function () {
110
- self[connectionSymbol].reconnectCounter = 0;
111
- if (typeof resolve === 'function' && !promiseAllredyResolved) {
112
- promiseAllredyResolved = true;
113
- resolve();
114
- }
115
- };
116
-
117
- self[connectionSymbol].socket.close = function (event) {
118
-
119
- if (self[manualCloseSymbol]) {
120
- self[manualCloseSymbol] = false;
121
- return;
122
- }
123
-
124
- if (reconnectEnabled && this[connectionSymbol].reconnectCounter < reconnectAttempts) {
125
- setTimeout(() => {
126
- self.connect();
127
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
128
- }
129
-
130
- };
131
-
132
- self[connectionSymbol].socket.onerror = (error) => {
133
-
134
- if (reconnectEnabled && self[connectionSymbol].reconnectCounter < reconnectAttempts) {
135
- setTimeout(() => {
136
- self.connect();
137
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
138
- } else {
139
- if (typeof reject === 'function' && !promiseAllredyResolved) {
140
- promiseAllredyResolved = true;
141
- reject(error);
142
- }
143
- }
144
-
145
- };
51
+ return obj;
146
52
  }
147
53
 
148
54
  /**
@@ -163,6 +69,8 @@ class WebSocketDatasource extends Datasource {
163
69
  */
164
70
  constructor(options) {
165
71
  super();
72
+
73
+ const self = this;
166
74
 
167
75
  if (isString(options)) {
168
76
  options = {url: options};
@@ -170,12 +78,17 @@ class WebSocketDatasource extends Datasource {
170
78
 
171
79
  if (!isObject(options)) options = {};
172
80
  this.setOptions(options);
173
- this[receiveQueueSymbol] = new Queue();
174
-
175
- this[connectionSymbol] = {};
176
- this[connectionSymbol].socket = null;
177
- this[connectionSymbol].reconnectCounter = 0;
178
- this[manualCloseSymbol] = false;
81
+ this[webConnectSymbol] = new WebConnect({
82
+ url: self.getOption('url'),
83
+ connection: {
84
+ timeout: self.getOption('connection.timeout'),
85
+ reconnect: {
86
+ timeout: self.getOption('connection.reconnect.timeout'),
87
+ attempts: self.getOption('connection.reconnect.attempts'),
88
+ enabled: self.getOption('connection.reconnect.enabled')
89
+ }
90
+ }
91
+ });
179
92
  }
180
93
 
181
94
  /**
@@ -183,18 +96,14 @@ class WebSocketDatasource extends Datasource {
183
96
  * @returns {Promise}
184
97
  */
185
98
  connect() {
186
- const self = this;
187
-
188
- return new Promise((resolve, reject) => {
189
- connectServer.call(this, resolve, reject);
190
- });
99
+ return this[webConnectSymbol].connect();
191
100
  }
192
101
 
193
102
  /**
194
103
  * @returns {boolean}
195
104
  */
196
105
  isConnected() {
197
- return this[connectionSymbol]?.socket?.readyState === 1;
106
+ return this[webConnectSymbol].isConnected();
198
107
  }
199
108
 
200
109
  /**
@@ -216,12 +125,11 @@ class WebSocketDatasource extends Datasource {
216
125
  * @property {Object} write.mapping the mapping is applied before writing.
217
126
  * @property {String} write.mapping.transformer Transformer to select the appropriate entries
218
127
  * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
219
- * @property {Object} write.report
220
- * @property {String} write.report.path Path to validations
221
128
  * @property {Object} write.sheathing
222
129
  * @property {Object} write.sheathing.object Object to be wrapped
223
130
  * @property {string} write.sheathing.path Path to the data
224
131
  * @property {Object} read={} Options
132
+ * @property {String} read.path Path to data
225
133
  * @property {Object} read.mapping the mapping is applied after reading.
226
134
  * @property {String} read.mapping.transformer Transformer to select the appropriate entries
227
135
  * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading.
@@ -232,10 +140,7 @@ class WebSocketDatasource extends Datasource {
232
140
  write: {
233
141
  mapping: {
234
142
  transformer: undefined,
235
- callbacks: []
236
- },
237
- report: {
238
- path: undefined
143
+ callbacks: {}
239
144
  },
240
145
  sheathing: {
241
146
  object: undefined,
@@ -245,8 +150,9 @@ class WebSocketDatasource extends Datasource {
245
150
  read: {
246
151
  mapping: {
247
152
  transformer: undefined,
248
- callbacks: []
153
+ callbacks: {}
249
154
  },
155
+ path: undefined,
250
156
  },
251
157
  connection: {
252
158
  timeout: 5000,
@@ -265,11 +171,7 @@ class WebSocketDatasource extends Datasource {
265
171
  * @returns {Promise}
266
172
  */
267
173
  close() {
268
- this[manualCloseSymbol] = true;
269
- if (this[connectionSymbol].socket) {
270
- this[connectionSymbol].socket.close();
271
- }
272
- return this;
174
+ return this[webConnectSymbol].close();
273
175
  }
274
176
 
275
177
  /**
@@ -277,99 +179,130 @@ class WebSocketDatasource extends Datasource {
277
179
  */
278
180
  read() {
279
181
  const self = this;
280
- let response;
281
-
282
- if (self[connectionSymbol]?.socket?.readyState !== 1) {
283
- return Promise.reject('The connection is not established.');
284
- }
285
182
 
286
183
  return new Promise((resolve, reject) => {
287
- if (self[receiveQueueSymbol].isEmpty()) {
288
- resolve();
289
- }
290
184
 
291
- while (!self[receiveQueueSymbol].isEmpty()) {
292
-
293
- const event = self[receiveQueueSymbol].poll();
294
- const body = event?.data;
295
- if (!body) continue;
296
-
297
- let obj;
298
- try {
299
- obj = JSON.parse(body);
300
- } catch (e) {
301
-
302
- let msg = 'the response does not contain a valid json (actual: ';
303
-
304
- if (body.length > 100) {
305
- msg += body.substring(0, 97) + '...';
306
- } else {
307
- msg += body;
308
- }
309
-
310
- msg += "; " + e.message + ')';
185
+ while (this[webConnectSymbol].dataReceived() === true) {
186
+ let obj = this[webConnectSymbol].poll();
187
+ if (!isObject(obj)) {
188
+ reject(new Error('The received data is not an object.'));
189
+ return;
190
+ }
311
191
 
312
- reject(msg);
192
+ if (!(obj instanceof Message)) {
193
+ reject(new Error('The received data is not a Message.'));
194
+ return;
313
195
  }
314
196
 
315
- let transformation = self.getOption('read.mapping.transformer');
316
- if (transformation !== undefined) {
317
- const pipe = new Pipe(transformation);
197
+ obj = obj.getData();
318
198
 
319
- for (const callback of self.getOption('read.mapping.callbacks')) {
320
- pipe.setCallback(callback.constructor.name, callback);
321
- }
199
+ obj = self.transformServerPayload.call(self, obj);
200
+ self.set( obj);
201
+ }
322
202
 
323
- obj = pipe.run(obj);
324
- }
203
+ resolve(self.get());
325
204
 
326
- self.set(obj);
327
- return response;
328
- }
329
205
  })
330
- }
206
+
207
+ };
208
+
209
+ // const self = this;
210
+ // let response;
211
+ //
212
+ // if (self[webConnectSymbol]?.socket?.readyState !== 1) {
213
+ // return Promise.reject('The connection is not established.');
214
+ // }
215
+ //
216
+ // return new Promise((resolve, reject) => {
217
+ // if (self[receiveQueueSymbol].isEmpty()) {
218
+ // resolve();
219
+ // }
220
+ //
221
+ // while (!self[receiveQueueSymbol].isEmpty()) {
222
+ //
223
+ // const event = self[receiveQueueSymbol].poll();
224
+ // const body = event?.data;
225
+ // if (!body) continue;
226
+ //
227
+ // let obj;
228
+ // try {
229
+ // obj = JSON.parse(body);
230
+ // } catch (e) {
231
+ //
232
+ // let msg = 'the response does not contain a valid json (actual: ';
233
+ //
234
+ // if (body.length > 100) {
235
+ // msg += body.substring(0, 97) + '...';
236
+ // } else {
237
+ // msg += body;
238
+ // }
239
+ //
240
+ // msg += "; " + e.message + ')';
241
+ //
242
+ // reject(msg);
243
+ // return;
244
+ // }
245
+ //
246
+ // obj = self.transformServerPayload.call(self, obj);
247
+ //
248
+ //
249
+ // self.set(obj);
250
+ // return response;
251
+ // }
252
+ // })
253
+ //}
331
254
 
332
255
  /**
333
- * @return {Promise}
256
+ * This prepares the data that comes from the server.
257
+ * Should not be called directly.
258
+ *
259
+ * @private
260
+ * @param {Object} payload
261
+ * @returns {Object}
334
262
  */
335
- write() {
263
+ transformServerPayload(payload) {
336
264
  const self = this;
265
+ payload = doTransform.call(self, 'read', payload);
337
266
 
338
- if (self[connectionSymbol]?.socket?.readyState !== 1) {
339
- return Promise.reject('The connection is not established.');
267
+ const dataPath = self.getOption('read.path');
268
+ if (dataPath) {
269
+ payload = (new Pathfinder(payload)).getVia(dataPath);
340
270
  }
341
271
 
342
- let obj = self.get();
343
- let transformation = self.getOption('write.mapping.transformer');
344
- if (transformation !== undefined) {
345
- const pipe = new Pipe(transformation);
272
+ return payload;
273
+ }
346
274
 
347
- for (const callback of self.getOption('write.mapping.callbacks')) {
348
- pipe.setCallback(callback.constructor.name, callback);
349
- }
275
+ /**
276
+ * This prepares the data for writing and should not be called directly.
277
+ *
278
+ * @private
279
+ * @param {Object} payload
280
+ * @returns {Object}
281
+ */
282
+ prepareServerPayload(payload) {
283
+ const self = this;
350
284
 
351
- obj = pipe.run(obj);
352
- }
285
+ payload = doTransform.call(self, 'write', payload);
353
286
 
354
287
  let sheathingObject = self.getOption('write.sheathing.object');
355
288
  let sheathingPath = self.getOption('write.sheathing.path');
356
- let reportPath = self.getOption('write.report.path');
357
289
 
358
290
  if (sheathingObject && sheathingPath) {
359
- const sub = obj;
360
- obj = sheathingObject;
361
- (new Pathfinder(obj)).setVia(sheathingPath, sub);
291
+ const sub = payload;
292
+ payload = sheathingObject;
293
+ (new Pathfinder(payload)).setVia(sheathingPath, sub);
362
294
  }
363
295
 
364
- return new Promise((resolve, reject) => {
365
-
366
- if (self[connectionSymbol].socket.readyState !== 1) {
367
- reject('the socket is not ready');
368
- }
296
+ return payload;
297
+ }
369
298
 
370
- self[connectionSymbol].socket.send(JSON.stringify(obj))
371
- resolve();
372
- });
299
+ /**
300
+ * @return {Promise}
301
+ */
302
+ write() {
303
+ const self = this;
304
+ let obj = self.prepareServerPayload(self.get());
305
+ return self[webConnectSymbol].send(obj)
373
306
  }
374
307
 
375
308
 
@@ -0,0 +1,55 @@
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 {Base} from "../../types/base.mjs";
9
+ import {validateObject, validateString} from "../../types/validate.mjs";
10
+
11
+ export {Message}
12
+
13
+ const dataSymbol = Symbol("@@data");
14
+
15
+ /**
16
+ * This class represents a WebSocket message.
17
+ */
18
+ class Message extends Base {
19
+
20
+ /**
21
+ * @param {Object} data
22
+ * @throws {TypeError} value is not a object
23
+ */
24
+ constructor(data) {
25
+ super();
26
+ this[dataSymbol] = validateObject(data);
27
+ }
28
+
29
+ /**
30
+ * Returns the raw message.
31
+ *
32
+ * @returns {object}
33
+ */
34
+ getData() {
35
+ return this[dataSymbol];
36
+ }
37
+
38
+ /**
39
+ * @returns {*}
40
+ */
41
+ toJSON() {
42
+ return this[dataSymbol];
43
+ }
44
+
45
+ /**
46
+ * @param {string} json
47
+ * @returns {Message}
48
+ * @throws {TypeError} value is not a string
49
+ */
50
+ static fromJSON(json) {
51
+ validateString(json);
52
+ return new Message(JSON.parse(json));
53
+ }
54
+
55
+ }