lively 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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