lively 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eb71ca34e43090f3b06b4577cc5c29ed82d3282df6fb26c69adb02cc5eff5fe
4
- data.tar.gz: 7df1288ae0defc42656da59f5a12e92f3619adc9993e54a4b5e55ac7d10cd795
3
+ metadata.gz: 1fe993ef2758a622d5da68c03094ea040678f62618b26bd8f888ab6127366300
4
+ data.tar.gz: 4919d6e2ec9fd70f2388d838a98740a6f911875ee233e3d351e55e59f426fa46
5
5
  SHA512:
6
- metadata.gz: dafdbe14c26702f71de2bc519ac398d141ffc2d1b7f2d1b94f32c44602ea9efc6c083282268f48c7d08798269947322d13bc7005d664b02863fb7bf4f041f031
7
- data.tar.gz: d65fd9f4c15cd6992c67bf2ffb1ee21d4d86d15643b71e092f38f55386f438bea86e1aa1b0a535a1c0fbd48836e6ec99e77575e751eec513f7c74f77d89064f5
6
+ metadata.gz: 1b8e0c1657776e309f5e164f79ad66e345aac9a455ef604bf2c326dfef9ce06e2a8f8370cd6052706a825bbf159ce5e5f60ade6b3cfd5f651c38aa2ee3c15bc6
7
+ data.tar.gz: 2f2e3e3043956ae4ce65c5a82e82c62af462d1b750ef51d0aa4dc71c9ffc0f731ad80779517fe44263b5c9f57a1d187f84e2cb9d0a325c46855c799c9d404c3d
checksums.yaml.gz.sig CHANGED
Binary file
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Lively
7
- VERSION = "0.4.0"
7
+ VERSION = "0.5.0"
8
8
  end
@@ -20,16 +20,21 @@ export class Live {
20
20
  this.events = [];
21
21
 
22
22
  this.failures = 0;
23
+ this.reconnectTimer = null;
23
24
 
24
25
  // Track visibility state and connect if required:
25
26
  this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
26
27
  this.handleVisibilityChange();
27
28
 
29
+ const elementNodeType = this.window.Node.ELEMENT_NODE;
30
+
28
31
  // Create a MutationObserver to watch for removed nodes
29
32
  this.observer = new this.window.MutationObserver((mutationsList, observer) => {
30
33
  for (let mutation of mutationsList) {
31
34
  if (mutation.type === 'childList') {
32
35
  for (let node of mutation.removedNodes) {
36
+ if (node.nodeType !== elementNodeType) continue;
37
+
33
38
  if (node.classList?.contains('live')) {
34
39
  this.unbind(node);
35
40
  }
@@ -41,7 +46,9 @@ export class Live {
41
46
  }
42
47
 
43
48
  for (let node of mutation.addedNodes) {
44
- if (node.classList?.contains('live')) {
49
+ if (node.nodeType !== elementNodeType) continue;
50
+
51
+ if (node.classList.contains('live')) {
45
52
  this.bind(node);
46
53
  }
47
54
 
@@ -55,20 +62,26 @@ export class Live {
55
62
  });
56
63
 
57
64
  this.observer.observe(this.document.body, {childList: true, subtree: true});
58
-
59
- this.attach();
60
65
  }
61
66
 
62
67
  // -- Connection Handling --
63
68
 
64
69
  connect() {
65
- if (this.server) return this.server;
70
+ if (this.server) {
71
+ return this.server;
72
+ }
66
73
 
67
74
  let server = this.server = new this.window.WebSocket(this.url);
68
75
 
76
+ if (this.reconnectTimer) {
77
+ clearTimeout(this.reconnectTimer);
78
+ this.reconnectTimer = null;
79
+ }
80
+
69
81
  server.onopen = () => {
70
82
  this.failures = 0;
71
83
  this.flush();
84
+ this.attach();
72
85
  };
73
86
 
74
87
  server.onmessage = (message) => {
@@ -84,13 +97,18 @@ export class Live {
84
97
 
85
98
  server.addEventListener('close', () => {
86
99
  // Explicit disconnect will clear `this.server`:
87
- if (this.server) {
100
+ if (this.server && !this.reconnectTimer) {
88
101
  // We need a minimum delay otherwise this can end up immediately invoking the callback:
89
102
  const delay = Math.max(100 * (this.failures + 1) ** 2, 60000);
90
- setTimeout(() => this.connect(), delay);
103
+ this.reconnectTimer = setTimeout(() => {
104
+ this.reconnectTimer = null;
105
+ this.connect();
106
+ }, delay);
91
107
  }
92
108
 
93
- this.server = null;
109
+ if (this.server === server) {
110
+ this.server = null;
111
+ }
94
112
  });
95
113
 
96
114
  return server;
@@ -102,6 +120,11 @@ export class Live {
102
120
  this.server = null;
103
121
  server.close();
104
122
  }
123
+
124
+ if (this.reconnectTimer) {
125
+ clearTimeout(this.reconnectTimer);
126
+ this.reconnectTimer = null;
127
+ }
105
128
  }
106
129
 
107
130
  send(message) {
@@ -109,7 +132,7 @@ export class Live {
109
132
  try {
110
133
  return this.server.send(message);
111
134
  } catch (error) {
112
- // Ignore.
135
+ // console.log("Live.send", "failed to send message to server", error);
113
136
  }
114
137
  }
115
138
 
@@ -144,7 +167,9 @@ export class Live {
144
167
  unbind(element) {
145
168
  console.log("unbind", element.id, element.dataset);
146
169
 
147
- this.send(JSON.stringify(['unbind', element.id]));
170
+ if (this.server) {
171
+ this.send(JSON.stringify(['unbind', element.id]));
172
+ }
148
173
  }
149
174
 
150
175
  attach() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketry/live",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.11.0",
5
5
  "description": "Live HTML tags for Ruby.",
6
6
  "main": "Live.js",
7
7
  "repository": {
@@ -1,13 +1,52 @@
1
- import {describe, before, after, it} from 'node:test';
1
+ import {describe, before, beforeEach, after, it} from 'node:test';
2
2
  import {ok, strict, strictEqual, deepStrictEqual} from 'node:assert';
3
3
 
4
4
  import {WebSocket} from 'ws';
5
5
  import {JSDOM} from 'jsdom';
6
6
  import {Live} from '../Live.js';
7
7
 
8
+ class Queue {
9
+ constructor() {
10
+ this.items = [];
11
+ this.waiting = [];
12
+ }
13
+
14
+ push(item) {
15
+ if (this.waiting.length > 0) {
16
+ let resolve = this.waiting.shift();
17
+ resolve(item);
18
+ } else {
19
+ this.items.push(item);
20
+ }
21
+ }
22
+
23
+ pop() {
24
+ return new Promise(resolve => {
25
+ if (this.items.length > 0) {
26
+ resolve(this.items.shift());
27
+ } else {
28
+ this.waiting.push(resolve);
29
+ }
30
+ });
31
+ }
32
+
33
+ async popUntil(callback) {
34
+ while (true) {
35
+ let item = await this.pop();
36
+ if (callback(item)) return item;
37
+ }
38
+ }
39
+
40
+ clear() {
41
+ this.items = [];
42
+ this.waiting = [];
43
+ }
44
+ }
45
+
8
46
  describe('Live', function () {
9
47
  let dom;
10
48
  let webSocketServer;
49
+ let messages = new Queue();
11
50
 
12
51
  const webSocketServerConfig = {port: 3000};
13
52
  const webSocketServerURL = `ws://localhost:${webSocketServerConfig.port}/live`;
@@ -18,13 +57,24 @@ describe('Live', function () {
18
57
  webSocketServer.on('error', reject);
19
58
  });
20
59
 
21
- dom = new JSDOM('<!DOCTYPE html><html><body><div id="my"><p>Hello World</p></div></body></html>');
60
+ dom = new JSDOM('<!DOCTYPE html><html><body><div id="my" class="live"><p>Hello World</p></div></body></html>');
22
61
  // Ensure the WebSocket class is available:
23
62
  dom.window.WebSocket = WebSocket;
24
63
 
25
64
  await new Promise(resolve => dom.window.addEventListener('load', resolve));
26
65
 
27
66
  await listening;
67
+
68
+ webSocketServer.on('connection', socket => {
69
+ socket.on('message', message => {
70
+ let payload = JSON.parse(message);
71
+ messages.push(payload);
72
+ });
73
+ });
74
+ });
75
+
76
+ beforeEach(function () {
77
+ messages.clear();
28
78
  });
29
79
 
30
80
  after(function () {
@@ -49,23 +99,31 @@ describe('Live', function () {
49
99
  live.disconnect();
50
100
  });
51
101
 
52
- it('should handle visibility changes', function () {
102
+ it('should handle visibility changes', async function () {
53
103
  const live = new Live(dom.window, webSocketServerURL);
54
104
 
55
- var hidden = false;
105
+ let hidden = false;
56
106
  Object.defineProperty(dom.window.document, "hidden", {
57
107
  get() {return hidden},
58
108
  });
59
109
 
110
+ // The document starts out hidden... we have defined a property to make it not hidden, let's propagate that change:
60
111
  live.handleVisibilityChange();
61
112
 
62
- ok(live.server);
113
+ // We should receive a bind message for the live element:
114
+ deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
63
115
 
64
116
  hidden = true;
117
+ live.handleVisibilityChange();
118
+ ok(!live.server);
65
119
 
120
+ hidden = false;
66
121
  live.handleVisibilityChange();
122
+ ok(live.server);
67
123
 
68
- ok(!live.server);
124
+ deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
125
+
126
+ live.disconnect();
69
127
  });
70
128
 
71
129
  it('should handle updates', async function () {
@@ -79,18 +137,11 @@ describe('Live', function () {
79
137
 
80
138
  let socket = await connected;
81
139
 
82
- const reply = new Promise((resolve, reject) => {
83
- socket.on('message', message => {
84
- let payload = JSON.parse(message);
85
- if (payload[0] == 'reply') resolve(payload);
86
- });
87
- });
88
-
89
140
  socket.send(
90
141
  JSON.stringify(['update', 'my', '<div id="my"><p>Goodbye World!</p></div>', {reply: true}])
91
142
  );
92
143
 
93
- await reply;
144
+ await messages.popUntil(message => message[0] == 'reply');
94
145
 
95
146
  strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Goodbye World!</p>');
96
147
 
@@ -108,21 +159,11 @@ describe('Live', function () {
108
159
 
109
160
  let socket = await connected;
110
161
 
111
- const reply = new Promise((resolve, reject) => {
112
- socket.on('message', message => {
113
- let payload = JSON.parse(message);
114
- console.log("message", payload);
115
- if (payload[0] == 'bind') resolve(payload);
116
- else console.log("ignoring", payload);
117
- });
118
- });
119
-
120
162
  socket.send(
121
163
  JSON.stringify(['update', 'my', '<div id="my"><div id="mychild" class="live"></div></div>'])
122
164
  );
123
165
 
124
- let payload = await reply;
125
-
166
+ let payload = await messages.popUntil(message => message[0] == 'bind');
126
167
  deepStrictEqual(payload, ['bind', 'mychild', {}]);
127
168
 
128
169
  live.disconnect();
@@ -135,26 +176,9 @@ describe('Live', function () {
135
176
 
136
177
  live.connect();
137
178
 
138
- const connected = new Promise(resolve => {
139
- webSocketServer.on('connection', resolve);
140
- });
141
-
142
- let socket = await connected;
143
-
144
- const reply = new Promise((resolve, reject) => {
145
- socket.on('message', message => {
146
- let payload = JSON.parse(message);
147
- if (payload[0] == 'unbind') resolve(payload);
148
- else console.log("ignoring", payload);
149
- });
150
- });
151
-
152
- live.attach();
153
-
154
179
  dom.window.document.getElementById('my').remove();
155
180
 
156
- let payload = await reply;
157
-
181
+ let payload = await messages.popUntil(message => message[0] == 'unbind');
158
182
  deepStrictEqual(payload, ['unbind', 'my']);
159
183
 
160
184
  live.disconnect();
@@ -173,20 +197,11 @@ describe('Live', function () {
173
197
 
174
198
  let socket = await connected;
175
199
 
176
- const reply = new Promise((resolve, reject) => {
177
- socket.on('message', message => {
178
- let payload = JSON.parse(message);
179
- if (payload[0] == 'reply') resolve(payload);
180
- else console.log("ignoring", payload);
181
- });
182
- });
183
-
184
200
  socket.send(
185
201
  JSON.stringify(['replace', '#my p', '<p>Replaced!</p>', {reply: true}])
186
202
  );
187
203
 
188
- await reply;
189
-
204
+ await messages.popUntil(message => message[0] == 'reply');
190
205
  strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Replaced!</p>');
191
206
 
192
207
  live.disconnect();
@@ -207,20 +222,11 @@ describe('Live', function () {
207
222
  JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
208
223
  );
209
224
 
210
- const reply = new Promise((resolve, reject) => {
211
- socket.on('message', message => {
212
- let payload = JSON.parse(message);
213
- if (payload[0] == 'reply') resolve(payload);
214
- else console.log("ignoring", payload);
215
- });
216
- });
217
-
218
225
  socket.send(
219
226
  JSON.stringify(['prepend', '#my', '<p>Prepended!</p>', {reply: true}])
220
227
  );
221
228
 
222
- await reply;
223
-
229
+ await messages.popUntil(message => message[0] == 'reply');
224
230
  strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Prepended!</p><p>Middle</p>');
225
231
 
226
232
  live.disconnect();
@@ -241,20 +247,11 @@ describe('Live', function () {
241
247
  JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
242
248
  );
243
249
 
244
- const reply = new Promise((resolve, reject) => {
245
- socket.on('message', message => {
246
- let payload = JSON.parse(message);
247
- if (payload[0] == 'reply') resolve(payload);
248
- else console.log("ignoring", payload);
249
- });
250
- });
251
-
252
250
  socket.send(
253
251
  JSON.stringify(['append', '#my', '<p>Appended!</p>', {reply: true}])
254
252
  );
255
253
 
256
- await reply;
257
-
254
+ await messages.popUntil(message => message[0] == 'reply');
258
255
  strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Middle</p><p>Appended!</p>');
259
256
 
260
257
  live.disconnect();
@@ -275,19 +272,11 @@ describe('Live', function () {
275
272
  JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
276
273
  );
277
274
 
278
- const reply = new Promise((resolve, reject) => {
279
- socket.on('message', message => {
280
- let payload = JSON.parse(message);
281
- if (payload[0] == 'reply') resolve(payload);
282
- });
283
- });
284
-
285
275
  socket.send(
286
276
  JSON.stringify(['remove', '#my p', {reply: true}])
287
277
  );
288
278
 
289
- await reply;
290
-
279
+ await messages.popUntil(message => message[0] == 'reply');
291
280
  strictEqual(dom.window.document.getElementById('my').innerHTML, '');
292
281
 
293
282
  live.disconnect();
@@ -304,18 +293,11 @@ describe('Live', function () {
304
293
 
305
294
  let socket = await connected;
306
295
 
307
- const reply = new Promise((resolve, reject) => {
308
- socket.on('message', message => {
309
- let payload = JSON.parse(message);
310
- if (payload[0] == 'reply') resolve(payload);
311
- });
312
- });
313
-
314
296
  socket.send(
315
297
  JSON.stringify(['dispatchEvent', '#my', 'click', {reply: true}])
316
298
  );
317
299
 
318
- await reply;
300
+ await messages.popUntil(message => message[0] == 'reply');
319
301
 
320
302
  live.disconnect();
321
303
  });
@@ -331,21 +313,13 @@ describe('Live', function () {
331
313
 
332
314
  let socket = await connected;
333
315
 
334
- const reply = new Promise((resolve, reject) => {
335
- socket.on('message', message => {
336
- let payload = JSON.parse(message);
337
- if (payload[0] == 'event') resolve(payload);
338
- });
339
- });
340
-
341
316
  dom.window.document.getElementById('my').addEventListener('click', event => {
342
317
  live.forwardEvent('my', event);
343
318
  });
344
319
 
345
320
  dom.window.document.getElementById('my').click();
346
321
 
347
- let payload = await reply;
348
-
322
+ let payload = await messages.popUntil(message => message[0] == 'event');
349
323
  strictEqual(payload[1], 'my');
350
324
  strictEqual(payload[2].type, 'click');
351
325
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lively
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,7 +37,7 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2024-05-05 00:00:00.000000000 Z
40
+ date: 2024-05-06 00:00:00.000000000 Z
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: falcon
metadata.gz.sig CHANGED
Binary file