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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/lively/version.rb +1 -1
- data/public/_components/@socketry/live/Live.js +34 -9
- data/public/_components/@socketry/live/package.json +1 -1
- data/public/_components/@socketry/live/test/Live.js +73 -99
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fe993ef2758a622d5da68c03094ea040678f62618b26bd8f888ab6127366300
|
4
|
+
data.tar.gz: 4919d6e2ec9fd70f2388d838a98740a6f911875ee233e3d351e55e59f426fa46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b8e0c1657776e309f5e164f79ad66e345aac9a455ef604bf2c326dfef9ce06e2a8f8370cd6052706a825bbf159ce5e5f60ade6b3cfd5f651c38aa2ee3c15bc6
|
7
|
+
data.tar.gz: 2f2e3e3043956ae4ce65c5a82e82c62af462d1b750ef51d0aa4dc71c9ffc0f731ad80779517fe44263b5c9f57a1d187f84e2cb9d0a325c46855c799c9d404c3d
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/lively/version.rb
CHANGED
@@ -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.
|
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)
|
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(() =>
|
103
|
+
this.reconnectTimer = setTimeout(() => {
|
104
|
+
this.reconnectTimer = null;
|
105
|
+
this.connect();
|
106
|
+
}, delay);
|
91
107
|
}
|
92
108
|
|
93
|
-
this.server
|
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
|
-
//
|
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.
|
170
|
+
if (this.server) {
|
171
|
+
this.send(JSON.stringify(['unbind', element.id]));
|
172
|
+
}
|
148
173
|
}
|
149
174
|
|
150
175
|
attach() {
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
+
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-
|
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
|