@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.
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
+ }