@schukai/monster 3.1.2 → 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.1.2",
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,54 +6,50 @@
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
26
21
  * @type {Symbol}
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");
25
+ const webConnectSymbol = Symbol("connection");
31
26
 
32
27
  /**
33
- * @private
34
- * @type {symbol}
28
+ *
29
+ * @param self
30
+ * @param obj
31
+ * @returns {*}
35
32
  */
36
- const manualCloseSymbol = Symbol("manualClose");
33
+ function doTransform(type, obj) {
34
+ const self = this;
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
+ }
46
+ }
37
47
 
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
- };
48
+ obj = pipe.run(obj);
49
+ }
50
+
51
+ return obj;
52
+ }
57
53
 
58
54
  /**
59
55
  * The RestAPI is a class that enables a REST API server.
@@ -73,6 +69,8 @@ class WebSocketDatasource extends Datasource {
73
69
  */
74
70
  constructor(options) {
75
71
  super();
72
+
73
+ const self = this;
76
74
 
77
75
  if (isString(options)) {
78
76
  options = {url: options};
@@ -80,83 +78,32 @@ class WebSocketDatasource extends Datasource {
80
78
 
81
79
  if (!isObject(options)) options = {};
82
80
  this.setOptions(options);
83
- this[receiveQueueSymbol] = new Queue();
84
-
85
- this[connectionSymbol] = {};
86
- this[connectionSymbol].socket = null;
87
- this[connectionSymbol].reconnectCounter = 0;
88
- 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
+ });
89
92
  }
90
93
 
91
94
  /**
92
95
  *
93
- * @returns {Websocketdatasource}
94
- * @throws {Error} No url defined for websocket datasource.
96
+ * @returns {Promise}
95
97
  */
96
98
  connect() {
97
- const self = this;
98
-
99
- let connected = false;
100
- let reconnectTimeout = self.getOption('reconnect.timeout');
101
- if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000) reconnectTimeout = 1000;
102
- let reconnectAttempts = self.getOption('reconnect.attempts');
103
- if (!isInteger(reconnectAttempts) || reconnectAttempts < 1) reconnectAttempts = 1;
104
- let reconnectEnabled = self.getOption('reconnect.enabled');
105
- if (reconnectEnabled !== true) reconnectEnabled = false;
106
-
107
- self[manualCloseSymbol] = false;
108
- self[connectionSymbol].reconnectCounter++;
109
-
110
- if (self[connectionSymbol].socket && self[connectionSymbol].socket.readyState < 2) {
111
- self[connectionSymbol].socket.close();
112
- }
113
- self[connectionSymbol].socket = null;
114
-
115
- const url = self.getOption('url');
116
- if (!url) throw new Error('No url defined for websocket datasource.');
117
-
118
- self[connectionSymbol].socket = new WebSocket(url);
119
-
120
- self[connectionSymbol].socket.onmessage = function (event) {
121
- self[receiveQueueSymbol].add(event);
122
- setTimeout(function () {
123
- self.read();
124
- }, 0);
125
- };
126
-
127
- self[connectionSymbol].socket.onopen = function () {
128
- connected = true;
129
- self[connectionSymbol].reconnectCounter = 0;
130
- };
131
-
132
- self[connectionSymbol].socket.close = function (event) {
133
-
134
- if (self[manualCloseSymbol]) {
135
- self[manualCloseSymbol] = false;
136
- return;
137
- }
138
-
139
- if (reconnectEnabled && this[connectionSymbol].reconnectCounter < reconnectAttempts) {
140
- setTimeout(() => {
141
- self.connect();
142
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
143
- }
144
-
145
- };
146
-
147
- self[connectionSymbol].socket.onerror = (error) => {
148
-
149
- if (reconnectEnabled && self[connectionSymbol].reconnectCounter < reconnectAttempts) {
150
- setTimeout(() => {
151
- self.connect();
152
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
153
- }
154
-
155
- };
99
+ return this[webConnectSymbol].connect();
156
100
  }
157
101
 
102
+ /**
103
+ * @returns {boolean}
104
+ */
158
105
  isConnected() {
159
- return this[connectionSymbol].socket && this[connectionSymbol].socket.readyState === 1;
106
+ return this[webConnectSymbol].isConnected();
160
107
  }
161
108
 
162
109
  /**
@@ -169,19 +116,20 @@ class WebSocketDatasource extends Datasource {
169
116
 
170
117
  /**
171
118
  * @property {string} url=undefined Defines the resource that you wish to fetch.
172
- * @property {Number} reconnect.timeout The timeout in milliseconds for the reconnect.
173
- * @property {Number} reconnect.attempts The maximum number of reconnects.
174
- * @property {Bool} reconnect.enabled If the reconnect is enabled.
119
+ * @property {Object} connection
120
+ * @property {Object} connection.timeout=5000 Defines the timeout for the connection.
121
+ * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect.
122
+ * @property {Number} connection.reconnect.attempts The maximum number of reconnects.
123
+ * @property {Bool} connection.reconnect.enabled If the reconnect is enabled.
175
124
  * @property {Object} write={} Options
176
125
  * @property {Object} write.mapping the mapping is applied before writing.
177
126
  * @property {String} write.mapping.transformer Transformer to select the appropriate entries
178
127
  * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
179
- * @property {Object} write.report
180
- * @property {String} write.report.path Path to validations
181
128
  * @property {Object} write.sheathing
182
129
  * @property {Object} write.sheathing.object Object to be wrapped
183
130
  * @property {string} write.sheathing.path Path to the data
184
131
  * @property {Object} read={} Options
132
+ * @property {String} read.path Path to data
185
133
  * @property {Object} read.mapping the mapping is applied after reading.
186
134
  * @property {String} read.mapping.transformer Transformer to select the appropriate entries
187
135
  * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading.
@@ -192,10 +140,7 @@ class WebSocketDatasource extends Datasource {
192
140
  write: {
193
141
  mapping: {
194
142
  transformer: undefined,
195
- callbacks: []
196
- },
197
- report: {
198
- path: undefined
143
+ callbacks: {}
199
144
  },
200
145
  sheathing: {
201
146
  object: undefined,
@@ -205,28 +150,28 @@ class WebSocketDatasource extends Datasource {
205
150
  read: {
206
151
  mapping: {
207
152
  transformer: undefined,
208
- callbacks: []
153
+ callbacks: {}
209
154
  },
155
+ path: undefined,
210
156
  },
211
- reconnect: {
212
- timeout: 1000,
213
- attempts: 10,
214
- enabled: true
157
+ connection: {
158
+ timeout: 5000,
159
+ reconnect: {
160
+ timeout: 1000,
161
+ attempts: 1,
162
+ enabled: false,
163
+ }
215
164
  }
216
165
  });
217
166
  }
218
167
 
219
168
  /**
220
169
  * This method closes the connection.
221
- *
170
+ *
222
171
  * @returns {Promise}
223
172
  */
224
173
  close() {
225
- this[manualCloseSymbol]=true;
226
- if (this[connectionSymbol].socket) {
227
- this[connectionSymbol].socket.close();
228
- }
229
- return this;
174
+ return this[webConnectSymbol].close();
230
175
  }
231
176
 
232
177
  /**
@@ -234,99 +179,130 @@ class WebSocketDatasource extends Datasource {
234
179
  */
235
180
  read() {
236
181
  const self = this;
237
- let response;
238
-
239
- if (self[connectionSymbol]?.socket?.readyState!==1) {
240
- return Promise.reject('The connection is not established.');
241
- }
242
182
 
243
183
  return new Promise((resolve, reject) => {
244
- if (self[receiveQueueSymbol].isEmpty()) {
245
- resolve();
246
- }
247
-
248
- while (!self[receiveQueueSymbol].isEmpty()) {
249
-
250
- const event = self[receiveQueueSymbol].poll();
251
- const body = event?.data;
252
- if (!body) continue;
253
-
254
- let obj;
255
- try {
256
- obj = JSON.parse(body);
257
- } catch (e) {
258
-
259
- let msg = 'the response does not contain a valid json (actual: ';
260
-
261
- if (body.length > 100) {
262
- msg += body.substring(0, 97) + '...';
263
- } else {
264
- msg += body;
265
- }
266
184
 
267
- 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
+ }
268
191
 
269
- reject(msg);
192
+ if (!(obj instanceof Message)) {
193
+ reject(new Error('The received data is not a Message.'));
194
+ return;
270
195
  }
271
196
 
272
- let transformation = self.getOption('read.mapping.transformer');
273
- if (transformation !== undefined) {
274
- const pipe = new Pipe(transformation);
197
+ obj = obj.getData();
275
198
 
276
- for (const callback of self.getOption('read.mapping.callbacks')) {
277
- pipe.setCallback(callback.constructor.name, callback);
278
- }
199
+ obj = self.transformServerPayload.call(self, obj);
200
+ self.set( obj);
201
+ }
279
202
 
280
- obj = pipe.run(obj);
281
- }
203
+ resolve(self.get());
282
204
 
283
- self.set(obj);
284
- return response;
285
- }
286
205
  })
287
- }
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
+ //}
288
254
 
289
255
  /**
290
- * @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}
291
262
  */
292
- write() {
263
+ transformServerPayload(payload) {
293
264
  const self = this;
294
-
295
- if (self[connectionSymbol]?.socket?.readyState!==1) {
296
- return Promise.reject('The connection is not established.');
265
+ payload = doTransform.call(self, 'read', payload);
266
+
267
+ const dataPath = self.getOption('read.path');
268
+ if (dataPath) {
269
+ payload = (new Pathfinder(payload)).getVia(dataPath);
297
270
  }
298
271
 
299
- let obj = self.get();
300
- let transformation = self.getOption('write.mapping.transformer');
301
- if (transformation !== undefined) {
302
- const pipe = new Pipe(transformation);
272
+ return payload;
273
+ }
303
274
 
304
- for (const callback of self.getOption('write.mapping.callbacks')) {
305
- pipe.setCallback(callback.constructor.name, callback);
306
- }
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;
307
284
 
308
- obj = pipe.run(obj);
309
- }
285
+ payload = doTransform.call(self, 'write', payload);
310
286
 
311
287
  let sheathingObject = self.getOption('write.sheathing.object');
312
288
  let sheathingPath = self.getOption('write.sheathing.path');
313
- let reportPath = self.getOption('write.report.path');
314
289
 
315
290
  if (sheathingObject && sheathingPath) {
316
- const sub = obj;
317
- obj = sheathingObject;
318
- (new Pathfinder(obj)).setVia(sheathingPath, sub);
291
+ const sub = payload;
292
+ payload = sheathingObject;
293
+ (new Pathfinder(payload)).setVia(sheathingPath, sub);
319
294
  }
320
295
 
321
- return new Promise((resolve, reject) => {
322
-
323
- if (self[connectionSymbol].socket.readyState !== 1) {
324
- reject('the socket is not ready');
325
- }
296
+ return payload;
297
+ }
326
298
 
327
- self[connectionSymbol].socket.send(JSON.stringify(obj))
328
- resolve();
329
- });
299
+ /**
300
+ * @return {Promise}
301
+ */
302
+ write() {
303
+ const self = this;
304
+ let obj = self.prepareServerPayload(self.get());
305
+ return self[webConnectSymbol].send(obj)
330
306
  }
331
307
 
332
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
+ }