@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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.46.7] - 2025-11-22
6
+
7
+ ### Bug Fixes
8
+
9
+ - performance update updater
10
+
11
+
12
+
5
13
  ## [4.46.6] - 2025-11-22
6
14
 
7
15
  ### Bug Fixes
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.6"}
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"}
@@ -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
- return this.#processQueue();
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
- if (this[timerElementEventHandlerSymbol] instanceof DeadMansSwitch) {
400
+ const switches = this[timerElementEventHandlerSymbol];
401
+ let dms = switches.get(element);
402
+
403
+ if (dms instanceof DeadMansSwitch) {
360
404
  try {
361
- this[timerElementEventHandlerSymbol].touch();
405
+ dms.touch();
362
406
  return;
363
407
  } catch (e) {
364
- delete this[timerElementEventHandlerSymbol];
408
+ switches.delete(element);
365
409
  }
366
410
  }
367
411
 
368
- this[timerElementEventHandlerSymbol] = new DeadMansSwitch(50, () => {
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 = new Pipe(cmd);
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 = new Pipe(cmd);
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 = new Pipe(cmd);
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, isObject } from "../types/is.mjs";
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
- 1000: "Normal closure",
56
- 1001: "Going away",
57
- 1002: "Protocol error",
58
- 1003: "Unsupported data",
59
- 1004: "Reserved",
60
- 1005: "No status code",
61
- 1006: "Connection closed abnormally",
62
- 1007: "Invalid frame payload data",
63
- 1008: "Policy violation",
64
- 1009: "The Message is too big",
65
- 1010: "Mandatory extension",
66
- 1011: "Internal server error",
67
- 1015: "TLS handshake",
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
- const self = this;
77
-
78
- const url = self.getOption("url");
79
- if (!url) {
80
- reject("No url defined for web connect.");
81
- return;
82
- }
83
-
84
- let promiseAllredyResolved = false;
85
-
86
- let connectionTimeout = self.getOption("connection.timeout");
87
- if (!isInteger(connectionTimeout) || connectionTimeout < 100) {
88
- connectionTimeout = 5000;
89
- }
90
-
91
- setTimeout(() => {
92
- if (promiseAllredyResolved) {
93
- return;
94
- }
95
- reject(new Error("Connection timeout"));
96
- }, connectionTimeout);
97
-
98
- let reconnectTimeout = self.getOption("connection.reconnect.timeout");
99
- if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000)
100
- reconnectTimeout = 1000;
101
- let reconnectAttempts = self.getOption("connection.reconnect.attempts");
102
- if (!isInteger(reconnectAttempts) || reconnectAttempts < 1)
103
- reconnectAttempts = 1;
104
- let reconnectEnabled = self.getOption("connection.reconnect.enabled");
105
- if (reconnectEnabled !== true) reconnectEnabled = false;
106
-
107
- self[manualCloseSymbol] = false;
108
- self[connectionSymbol].reconnectCounter++;
109
-
110
- if (
111
- self[connectionSymbol].socket &&
112
- self[connectionSymbol].socket.readyState < 2
113
- ) {
114
- self[connectionSymbol].socket.close();
115
- }
116
- self[connectionSymbol].socket = null;
117
-
118
- const WebSocket = getGlobalFunction("WebSocket");
119
- if (!WebSocket) {
120
- reject(new Error("WebSocket is not available"));
121
- return;
122
- }
123
-
124
- self[connectionSymbol].socket = new WebSocket(url);
125
-
126
- self[connectionSymbol].socket.onmessage = function (event) {
127
- if (event.data instanceof Blob) {
128
- const reader = new FileReader();
129
- reader.addEventListener("loadend", function () {
130
- self[receiveQueueSymbol].add(new Message(reader.result));
131
- });
132
- reader.readAsText(new Message(event.data));
133
- } else {
134
- self[receiveQueueSymbol].add(Message.fromJSON(event.data));
135
- }
136
- };
137
-
138
- self[connectionSymbol].socket.onopen = function () {
139
- self[connectionSymbol].reconnectCounter = 0;
140
- if (typeof resolve === "function" && !promiseAllredyResolved) {
141
- promiseAllredyResolved = true;
142
- resolve();
143
- }
144
- };
145
-
146
- self[connectionSymbol].socket.close = function (event) {
147
- if (self[manualCloseSymbol]) {
148
- self[manualCloseSymbol] = false;
149
- return;
150
- }
151
-
152
- if (
153
- reconnectEnabled &&
154
- this[connectionSymbol].reconnectCounter < reconnectAttempts
155
- ) {
156
- setTimeout(() => {
157
- self.connect();
158
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
159
- }
160
- };
161
-
162
- self[connectionSymbol].socket.onerror = (error) => {
163
- if (
164
- reconnectEnabled &&
165
- self[connectionSymbol].reconnectCounter < reconnectAttempts
166
- ) {
167
- setTimeout(() => {
168
- self.connect();
169
- }, reconnectTimeout * this[connectionSymbol].reconnectCounter);
170
- } else {
171
- if (typeof reject === "function" && !promiseAllredyResolved) {
172
- promiseAllredyResolved = true;
173
- reject(error);
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
- * @param {Object} [options] options contains definitions for the webconnect.
192
- */
193
- constructor(options) {
194
- if (isString(options)) {
195
- options = { url: options };
196
- }
197
-
198
- super(options);
199
-
200
- this[receiveQueueSymbol] = new ObservableQueue();
201
- this[sendQueueSymbol] = new ObservableQueue();
202
-
203
- this[connectionSymbol] = {};
204
- this[connectionSymbol].socket = null;
205
- this[connectionSymbol].reconnectCounter = 0;
206
- this[manualCloseSymbol] = false;
207
- }
208
-
209
- /**
210
- *
211
- * @return {Promise}
212
- */
213
- connect() {
214
- return new Promise((resolve, reject) => {
215
- connectServer.call(this, resolve, reject);
216
- });
217
- }
218
-
219
- /**
220
- * @return {boolean}
221
- */
222
- isConnected() {
223
- return this[connectionSymbol]?.socket?.readyState === 1;
224
- }
225
-
226
- /**
227
- * This method is called by the `instanceof` operator.
228
- * @return {symbol}
229
- */
230
- static get [instanceSymbol]() {
231
- return Symbol.for("@schukai/monster/net/webconnect");
232
- }
233
-
234
- /**
235
- * @property {string} url=undefined Defines the resource that you wish to fetch.
236
- * @property {Object} connection
237
- * @property {Object} connection.timeout=5000 Defines the timeout for the connection.
238
- * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect.
239
- * @property {Number} connection.reconnect.attempts The maximum number of reconnects.
240
- * @property {Bool} connection.reconnect.enabled If the reconnect is enabled.
241
- */
242
- get defaults() {
243
- return Object.assign({}, super.defaults, {
244
- url: undefined,
245
- connection: {
246
- timeout: 5000,
247
- reconnect: {
248
- timeout: 1000,
249
- attempts: 1,
250
- enabled: false,
251
- },
252
- },
253
- });
254
- }
255
-
256
- /**
257
- * This method closes the connection.
258
- *
259
- * @param {Number} [code=1000] The close code.
260
- * @param {String} [reason=""] The close reason.
261
- * @return {Promise}
262
- * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
263
- */
264
- close(statusCode, reason) {
265
- if (!isInteger(statusCode) || statusCode < 1000 || statusCode > 4999) {
266
- statusCode = 1000;
267
- }
268
- if (!isString(reason)) {
269
- reason = "";
270
- }
271
-
272
- return new Promise((resolve, reject) => {
273
- try {
274
- this[manualCloseSymbol] = true;
275
- if (this[connectionSymbol].socket) {
276
- this[connectionSymbol].socket.close(statusCode, reason);
277
- }
278
- } catch (error) {
279
- reject(error);
280
- }
281
- resolve();
282
- });
283
- }
284
-
285
- /**
286
- * Polls the receive queue for new messages.
287
- *
288
- * @return {Message}
289
- */
290
- poll() {
291
- return this[receiveQueueSymbol].poll();
292
- }
293
-
294
- /**
295
- * Are there any messages in the receive queue?
296
- *
297
- * @return {boolean}
298
- */
299
- dataReceived() {
300
- return !this[receiveQueueSymbol].isEmpty();
301
- }
302
-
303
- /**
304
- * Get Message from the receive queue, but do not remove it.
305
- *
306
- * @return {Object}
307
- */
308
- peek() {
309
- return this[receiveQueueSymbol].peek();
310
- }
311
-
312
- /**
313
- * Attach a new observer
314
- *
315
- * @param {Observer} observer
316
- * @return {ProxyObserver}
317
- */
318
- attachObserver(observer) {
319
- this[receiveQueueSymbol].attachObserver(observer);
320
- return this;
321
- }
322
-
323
- /**
324
- * Detach a observer
325
- *
326
- * @param {Observer} observer
327
- * @return {ProxyObserver}
328
- */
329
- detachObserver(observer) {
330
- this[receiveQueueSymbol].detachObserver(observer);
331
- return this;
332
- }
333
-
334
- /**
335
- * @param {Observer} observer
336
- * @return {boolean}
337
- */
338
- containsObserver(observer) {
339
- return this[receiveQueueSymbol].containsObserver(observer);
340
- }
341
-
342
- /**
343
- * @param {Message|Object} message
344
- * @return {Promise}
345
- */
346
- send(message) {
347
- return new Promise((resolve, reject) => {
348
- if (this[connectionSymbol].socket.readyState !== 1) {
349
- reject("the socket is not ready");
350
- }
351
-
352
- this[connectionSymbol].socket.send(JSON.stringify(message));
353
- resolve();
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
  }