@schukai/monster 4.46.6 → 4.46.7
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/source/dom/updater.mjs +53 -8
- package/source/net/webconnect.mjs +326 -287
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.46.
|
|
1
|
+
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.46.7"}
|
package/source/dom/updater.mjs
CHANGED
|
@@ -72,6 +72,18 @@ const pendingDiffsSymbol = Symbol("pendingDiffs");
|
|
|
72
72
|
*/
|
|
73
73
|
const processingSymbol = Symbol("processing");
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @private
|
|
77
|
+
* @type {symbol}
|
|
78
|
+
*/
|
|
79
|
+
const pipeCacheSymbol = Symbol("pipeCache");
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @private
|
|
83
|
+
* @type {symbol}
|
|
84
|
+
*/
|
|
85
|
+
const processingScheduledSymbol = Symbol("processingScheduled");
|
|
86
|
+
|
|
75
87
|
/**
|
|
76
88
|
* @private
|
|
77
89
|
* Performance optimization: static Set for boolean checks
|
|
@@ -137,6 +149,9 @@ class Updater extends Base {
|
|
|
137
149
|
|
|
138
150
|
this[pendingDiffsSymbol] = [];
|
|
139
151
|
this[processingSymbol] = false;
|
|
152
|
+
this[processingScheduledSymbol] = false;
|
|
153
|
+
this[pipeCacheSymbol] = new Map();
|
|
154
|
+
this[timerElementEventHandlerSymbol] = new WeakMap();
|
|
140
155
|
|
|
141
156
|
this[internalSymbol].subject.attachObserver(
|
|
142
157
|
new Observer(() => {
|
|
@@ -144,7 +159,19 @@ class Updater extends Base {
|
|
|
144
159
|
const diffResult = diff(this[internalSymbol].last, real);
|
|
145
160
|
this[internalSymbol].last = clone(real);
|
|
146
161
|
this[pendingDiffsSymbol].push(diffResult);
|
|
147
|
-
|
|
162
|
+
|
|
163
|
+
if (!this[processingScheduledSymbol]) {
|
|
164
|
+
this[processingScheduledSymbol] = true;
|
|
165
|
+
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
queueMicrotask(() => {
|
|
168
|
+
this[processingScheduledSymbol] = false;
|
|
169
|
+
this.#processQueue().finally(resolve);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return Promise.resolve();
|
|
148
175
|
}),
|
|
149
176
|
);
|
|
150
177
|
}
|
|
@@ -301,6 +328,20 @@ class Updater extends Base {
|
|
|
301
328
|
this[internalSymbol].callbacks.set(name, callback);
|
|
302
329
|
return this;
|
|
303
330
|
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @private
|
|
334
|
+
* @param {string} cmd
|
|
335
|
+
* @returns {Pipe}
|
|
336
|
+
*/
|
|
337
|
+
getPipe(cmd) {
|
|
338
|
+
let pipe = this[pipeCacheSymbol].get(cmd);
|
|
339
|
+
if (!pipe) {
|
|
340
|
+
pipe = new Pipe(cmd);
|
|
341
|
+
this[pipeCacheSymbol].set(cmd, pipe);
|
|
342
|
+
}
|
|
343
|
+
return pipe;
|
|
344
|
+
}
|
|
304
345
|
}
|
|
305
346
|
|
|
306
347
|
/**
|
|
@@ -356,22 +397,26 @@ function getControlEventHandler() {
|
|
|
356
397
|
return;
|
|
357
398
|
}
|
|
358
399
|
|
|
359
|
-
|
|
400
|
+
const switches = this[timerElementEventHandlerSymbol];
|
|
401
|
+
let dms = switches.get(element);
|
|
402
|
+
|
|
403
|
+
if (dms instanceof DeadMansSwitch) {
|
|
360
404
|
try {
|
|
361
|
-
|
|
405
|
+
dms.touch();
|
|
362
406
|
return;
|
|
363
407
|
} catch (e) {
|
|
364
|
-
delete
|
|
408
|
+
switches.delete(element);
|
|
365
409
|
}
|
|
366
410
|
}
|
|
367
411
|
|
|
368
|
-
|
|
412
|
+
dms = new DeadMansSwitch(50, () => {
|
|
369
413
|
try {
|
|
370
414
|
retrieveAndSetValue.call(this, element);
|
|
371
415
|
} catch (e) {
|
|
372
416
|
addErrorAttribute(element, e);
|
|
373
417
|
}
|
|
374
418
|
});
|
|
419
|
+
switches.set(element, dms);
|
|
375
420
|
};
|
|
376
421
|
|
|
377
422
|
return this[symbol];
|
|
@@ -653,7 +698,7 @@ function insertElement(change) {
|
|
|
653
698
|
throw new Error("pipes are not allowed when cloning a node.");
|
|
654
699
|
}
|
|
655
700
|
|
|
656
|
-
const pipe =
|
|
701
|
+
const pipe = this.getPipe(cmd);
|
|
657
702
|
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
658
703
|
pipe.setCallback(n, f);
|
|
659
704
|
});
|
|
@@ -844,7 +889,7 @@ function runUpdateContent(container, parts, subject) {
|
|
|
844
889
|
const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
|
|
845
890
|
const cmd = trimSpaces(attributes);
|
|
846
891
|
|
|
847
|
-
const pipe =
|
|
892
|
+
const pipe = this.getPipe(cmd);
|
|
848
893
|
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
849
894
|
pipe.setCallback(n, f);
|
|
850
895
|
});
|
|
@@ -936,7 +981,7 @@ function runUpdateAttributes(container, parts, subject) {
|
|
|
936
981
|
const name = trimSpaces(def.substr(0, i));
|
|
937
982
|
const cmd = trimSpaces(def.substr(i));
|
|
938
983
|
|
|
939
|
-
const pipe =
|
|
984
|
+
const pipe = this.getPipe(cmd);
|
|
940
985
|
|
|
941
986
|
this[internalSymbol].callbacks.forEach((f, n) => {
|
|
942
987
|
pipe.setCallback(n, f, element);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { instanceSymbol } from "../constants.mjs";
|
|
16
|
-
import { isInteger, isString
|
|
16
|
+
import { isInteger, isString } from "../types/is.mjs";
|
|
17
17
|
import { BaseWithOptions } from "../types/basewithoptions.mjs";
|
|
18
18
|
import { ObservableQueue } from "../types/observablequeue.mjs";
|
|
19
19
|
import { Message } from "./webconnect/message.mjs";
|
|
@@ -26,11 +26,6 @@ export { WebConnect };
|
|
|
26
26
|
* @type {symbol}
|
|
27
27
|
*/
|
|
28
28
|
const receiveQueueSymbol = Symbol("receiveQueue");
|
|
29
|
-
/**
|
|
30
|
-
* @private
|
|
31
|
-
* @type {symbol}
|
|
32
|
-
*/
|
|
33
|
-
const sendQueueSymbol = Symbol("sendQueue");
|
|
34
29
|
|
|
35
30
|
/**
|
|
36
31
|
* @private
|
|
@@ -52,19 +47,19 @@ const manualCloseSymbol = Symbol("manualClose");
|
|
|
52
47
|
* @type {Object}
|
|
53
48
|
*/
|
|
54
49
|
const connectionStatusCode = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
1000: "Normal closure",
|
|
51
|
+
1001: "Going away",
|
|
52
|
+
1002: "Protocol error",
|
|
53
|
+
1003: "Unsupported data",
|
|
54
|
+
1004: "Reserved",
|
|
55
|
+
1005: "No status code",
|
|
56
|
+
1006: "Connection closed abnormally",
|
|
57
|
+
1007: "Invalid frame payload data",
|
|
58
|
+
1008: "Policy violation",
|
|
59
|
+
1009: "The Message is too big",
|
|
60
|
+
1010: "Mandatory extension",
|
|
61
|
+
1011: "Internal server error",
|
|
62
|
+
1015: "TLS handshake",
|
|
68
63
|
};
|
|
69
64
|
|
|
70
65
|
/**
|
|
@@ -73,107 +68,142 @@ const connectionStatusCode = {
|
|
|
73
68
|
* @throws {Error} No url defined for websocket datasource.
|
|
74
69
|
*/
|
|
75
70
|
function connectServer(resolve, reject) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
71
|
+
const self = this;
|
|
72
|
+
|
|
73
|
+
const url = self.getOption("url");
|
|
74
|
+
if (!url) {
|
|
75
|
+
reject(new Error("No url defined for web connect."));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let promiseAlreadyResolved = false;
|
|
80
|
+
|
|
81
|
+
let connectionTimeout = self.getOption("connection.timeout");
|
|
82
|
+
if (!isInteger(connectionTimeout) || connectionTimeout < 100) {
|
|
83
|
+
connectionTimeout = 5000;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Timeout Handling
|
|
87
|
+
const timeoutId = setTimeout(() => {
|
|
88
|
+
if (promiseAlreadyResolved) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
promiseAlreadyResolved = true;
|
|
92
|
+
|
|
93
|
+
// Clean up hanging socket attempt
|
|
94
|
+
if (self[connectionSymbol].socket) {
|
|
95
|
+
try {
|
|
96
|
+
self[connectionSymbol].socket.close();
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
reject(new Error("Connection timeout"));
|
|
103
|
+
}, connectionTimeout);
|
|
104
|
+
|
|
105
|
+
let reconnectTimeout = self.getOption("connection.reconnect.timeout");
|
|
106
|
+
if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000)
|
|
107
|
+
reconnectTimeout = 1000;
|
|
108
|
+
|
|
109
|
+
let reconnectAttempts = self.getOption("connection.reconnect.attempts");
|
|
110
|
+
if (!isInteger(reconnectAttempts) || reconnectAttempts < 1)
|
|
111
|
+
reconnectAttempts = 1;
|
|
112
|
+
|
|
113
|
+
let reconnectEnabled = self.getOption("connection.reconnect.enabled");
|
|
114
|
+
if (reconnectEnabled !== true) reconnectEnabled = false;
|
|
115
|
+
|
|
116
|
+
self[manualCloseSymbol] = false;
|
|
117
|
+
self[connectionSymbol].reconnectCounter++;
|
|
118
|
+
|
|
119
|
+
// Cleanup existing socket
|
|
120
|
+
if (
|
|
121
|
+
self[connectionSymbol].socket &&
|
|
122
|
+
self[connectionSymbol].socket.readyState < 2
|
|
123
|
+
) {
|
|
124
|
+
// Remove listeners to prevent side effects during close
|
|
125
|
+
self[connectionSymbol].socket.onclose = null;
|
|
126
|
+
self[connectionSymbol].socket.onerror = null;
|
|
127
|
+
self[connectionSymbol].socket.onmessage = null;
|
|
128
|
+
self[connectionSymbol].socket.onopen = null;
|
|
129
|
+
self[connectionSymbol].socket.close();
|
|
130
|
+
}
|
|
131
|
+
self[connectionSymbol].socket = null;
|
|
132
|
+
|
|
133
|
+
const WebSocket = getGlobalFunction("WebSocket");
|
|
134
|
+
if (!WebSocket) {
|
|
135
|
+
clearTimeout(timeoutId);
|
|
136
|
+
reject(new Error("WebSocket is not available"));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
self[connectionSymbol].socket = new WebSocket(url);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
clearTimeout(timeoutId);
|
|
144
|
+
if (!promiseAlreadyResolved) {
|
|
145
|
+
promiseAlreadyResolved = true;
|
|
146
|
+
reject(error);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
self[connectionSymbol].socket.onmessage = function (event) {
|
|
152
|
+
if (event.data instanceof Blob) {
|
|
153
|
+
const reader = new FileReader();
|
|
154
|
+
reader.addEventListener("loadend", function () {
|
|
155
|
+
self[receiveQueueSymbol].add(new Message(reader.result));
|
|
156
|
+
});
|
|
157
|
+
// Correctly pass the Blob, not a Message object
|
|
158
|
+
reader.readAsText(event.data);
|
|
159
|
+
} else {
|
|
160
|
+
self[receiveQueueSymbol].add(Message.fromJSON(event.data));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
self[connectionSymbol].socket.onopen = function () {
|
|
165
|
+
clearTimeout(timeoutId);
|
|
166
|
+
self[connectionSymbol].reconnectCounter = 0;
|
|
167
|
+
if (typeof resolve === "function" && !promiseAlreadyResolved) {
|
|
168
|
+
promiseAlreadyResolved = true;
|
|
169
|
+
resolve();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Internal helper to handle reconnects
|
|
174
|
+
const handleReconnect = () => {
|
|
175
|
+
if (self[manualCloseSymbol]) {
|
|
176
|
+
self[manualCloseSymbol] = false;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
reconnectEnabled &&
|
|
182
|
+
self[connectionSymbol].reconnectCounter < reconnectAttempts
|
|
183
|
+
) {
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
// catch potential unhandled promise rejections from the recursive call
|
|
186
|
+
self.connect().catch(() => {});
|
|
187
|
+
}, reconnectTimeout * self[connectionSymbol].reconnectCounter);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Use onclose event instead of overriding the close method
|
|
192
|
+
self[connectionSymbol].socket.onclose = function (event) {
|
|
193
|
+
handleReconnect();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
self[connectionSymbol].socket.onerror = (error) => {
|
|
197
|
+
if (!promiseAlreadyResolved) {
|
|
198
|
+
clearTimeout(timeoutId);
|
|
199
|
+
promiseAlreadyResolved = true;
|
|
200
|
+
reject(error);
|
|
201
|
+
} else {
|
|
202
|
+
// If the connection was already established, treat error as potential disconnect
|
|
203
|
+
// Usually onclose follows onerror, but we ensure we don't double-handle logic
|
|
204
|
+
// typically we rely on onclose for reconnect logic.
|
|
205
|
+
}
|
|
206
|
+
};
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
/**
|
|
@@ -186,171 +216,180 @@ function connectServer(resolve, reject) {
|
|
|
186
216
|
* @summary The LocalStorage class encapsulates the access to data objects.
|
|
187
217
|
*/
|
|
188
218
|
class WebConnect extends BaseWithOptions {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
219
|
+
/**
|
|
220
|
+
*
|
|
221
|
+
* @param {Object} [options] options contains definitions for the webconnect.
|
|
222
|
+
*/
|
|
223
|
+
constructor(options) {
|
|
224
|
+
if (isString(options)) {
|
|
225
|
+
options = { url: options };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
super(options);
|
|
229
|
+
|
|
230
|
+
this[receiveQueueSymbol] = new ObservableQueue();
|
|
231
|
+
|
|
232
|
+
this[connectionSymbol] = {};
|
|
233
|
+
this[connectionSymbol].socket = null;
|
|
234
|
+
this[connectionSymbol].reconnectCounter = 0;
|
|
235
|
+
this[manualCloseSymbol] = false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
*
|
|
240
|
+
* @return {Promise}
|
|
241
|
+
*/
|
|
242
|
+
connect() {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
connectServer.call(this, resolve, reject);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @return {boolean}
|
|
250
|
+
*/
|
|
251
|
+
isConnected() {
|
|
252
|
+
return this[connectionSymbol]?.socket?.readyState === 1;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* This method is called by the `instanceof` operator.
|
|
257
|
+
* @return {symbol}
|
|
258
|
+
*/
|
|
259
|
+
static get [instanceSymbol]() {
|
|
260
|
+
return Symbol.for("@schukai/monster/net/webconnect");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @property {string} url=undefined Defines the resource that you wish to fetch.
|
|
265
|
+
* @property {Object} connection
|
|
266
|
+
* @property {Object} connection.timeout=5000 Defines the timeout for the connection.
|
|
267
|
+
* @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect.
|
|
268
|
+
* @property {Number} connection.reconnect.attempts The maximum number of reconnects.
|
|
269
|
+
* @property {Bool} connection.reconnect.enabled If the reconnect is enabled.
|
|
270
|
+
*/
|
|
271
|
+
get defaults() {
|
|
272
|
+
return Object.assign({}, super.defaults, {
|
|
273
|
+
url: undefined,
|
|
274
|
+
connection: {
|
|
275
|
+
timeout: 5000,
|
|
276
|
+
reconnect: {
|
|
277
|
+
timeout: 1000,
|
|
278
|
+
attempts: 1,
|
|
279
|
+
enabled: false,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* This method closes the connection.
|
|
287
|
+
*
|
|
288
|
+
* @param {Number} [code=1000] The close code.
|
|
289
|
+
* @param {String} [reason=""] The close reason.
|
|
290
|
+
* @return {Promise}
|
|
291
|
+
* @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
|
|
292
|
+
*/
|
|
293
|
+
close(statusCode, reason) {
|
|
294
|
+
if (!isInteger(statusCode) || statusCode < 1000 || statusCode > 4999) {
|
|
295
|
+
statusCode = 1000;
|
|
296
|
+
}
|
|
297
|
+
if (!isString(reason)) {
|
|
298
|
+
reason = "";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
try {
|
|
303
|
+
// Set manual close flag BEFORE calling close() to prevent reconnect
|
|
304
|
+
this[manualCloseSymbol] = true;
|
|
305
|
+
if (this[connectionSymbol].socket) {
|
|
306
|
+
this[connectionSymbol].socket.close(statusCode, reason);
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
reject(error);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
resolve();
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Polls the receive queue for new messages.
|
|
318
|
+
*
|
|
319
|
+
* @return {Message}
|
|
320
|
+
*/
|
|
321
|
+
poll() {
|
|
322
|
+
return this[receiveQueueSymbol].poll();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Are there any messages in the receive queue?
|
|
327
|
+
*
|
|
328
|
+
* @return {boolean}
|
|
329
|
+
*/
|
|
330
|
+
dataReceived() {
|
|
331
|
+
return !this[receiveQueueSymbol].isEmpty();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get Message from the receive queue, but do not remove it.
|
|
336
|
+
*
|
|
337
|
+
* @return {Object}
|
|
338
|
+
*/
|
|
339
|
+
peek() {
|
|
340
|
+
return this[receiveQueueSymbol].peek();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Attach a new observer
|
|
345
|
+
*
|
|
346
|
+
* @param {Observer} observer
|
|
347
|
+
* @return {ProxyObserver}
|
|
348
|
+
*/
|
|
349
|
+
attachObserver(observer) {
|
|
350
|
+
this[receiveQueueSymbol].attachObserver(observer);
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Detach a observer
|
|
356
|
+
*
|
|
357
|
+
* @param {Observer} observer
|
|
358
|
+
* @return {ProxyObserver}
|
|
359
|
+
*/
|
|
360
|
+
detachObserver(observer) {
|
|
361
|
+
this[receiveQueueSymbol].detachObserver(observer);
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {Observer} observer
|
|
367
|
+
* @return {boolean}
|
|
368
|
+
*/
|
|
369
|
+
containsObserver(observer) {
|
|
370
|
+
return this[receiveQueueSymbol].containsObserver(observer);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @param {Message|Object} message
|
|
375
|
+
* @return {Promise}
|
|
376
|
+
*/
|
|
377
|
+
send(message) {
|
|
378
|
+
return new Promise((resolve, reject) => {
|
|
379
|
+
if (
|
|
380
|
+
!this[connectionSymbol].socket ||
|
|
381
|
+
this[connectionSymbol].socket.readyState !== 1
|
|
382
|
+
) {
|
|
383
|
+
reject(new Error("The socket is not ready"));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
this[connectionSymbol].socket.send(JSON.stringify(message));
|
|
389
|
+
resolve();
|
|
390
|
+
} catch (e) {
|
|
391
|
+
reject(e);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
356
395
|
}
|