lively 0.3.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 +86 -29
- data/public/_components/@socketry/live/package.json +1 -1
- data/public/_components/@socketry/live/test/Live.js +113 -70
- data.tar.gz.sig +0 -0
- metadata +8 -8
- 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
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
import morphdom from 'morphdom';
|
3
2
|
|
4
3
|
export class Live {
|
@@ -21,21 +20,67 @@ export class Live {
|
|
21
20
|
this.events = [];
|
22
21
|
|
23
22
|
this.failures = 0;
|
23
|
+
this.reconnectTimer = null;
|
24
24
|
|
25
25
|
// Track visibility state and connect if required:
|
26
26
|
this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
|
27
27
|
this.handleVisibilityChange();
|
28
|
+
|
29
|
+
const elementNodeType = this.window.Node.ELEMENT_NODE;
|
30
|
+
|
31
|
+
// Create a MutationObserver to watch for removed nodes
|
32
|
+
this.observer = new this.window.MutationObserver((mutationsList, observer) => {
|
33
|
+
for (let mutation of mutationsList) {
|
34
|
+
if (mutation.type === 'childList') {
|
35
|
+
for (let node of mutation.removedNodes) {
|
36
|
+
if (node.nodeType !== elementNodeType) continue;
|
37
|
+
|
38
|
+
if (node.classList?.contains('live')) {
|
39
|
+
this.unbind(node);
|
40
|
+
}
|
41
|
+
|
42
|
+
// Unbind any child nodes:
|
43
|
+
for (let child of node.getElementsByClassName('live')) {
|
44
|
+
this.unbind(child);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
for (let node of mutation.addedNodes) {
|
49
|
+
if (node.nodeType !== elementNodeType) continue;
|
50
|
+
|
51
|
+
if (node.classList.contains('live')) {
|
52
|
+
this.bind(node);
|
53
|
+
}
|
54
|
+
|
55
|
+
// Bind any child nodes:
|
56
|
+
for (let child of node.getElementsByClassName('live')) {
|
57
|
+
this.bind(child);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
});
|
63
|
+
|
64
|
+
this.observer.observe(this.document.body, {childList: true, subtree: true});
|
28
65
|
}
|
29
66
|
|
30
67
|
// -- Connection Handling --
|
31
68
|
|
32
69
|
connect() {
|
33
|
-
if (this.server)
|
70
|
+
if (this.server) {
|
71
|
+
return this.server;
|
72
|
+
}
|
34
73
|
|
35
74
|
let server = this.server = new this.window.WebSocket(this.url);
|
36
75
|
|
76
|
+
if (this.reconnectTimer) {
|
77
|
+
clearTimeout(this.reconnectTimer);
|
78
|
+
this.reconnectTimer = null;
|
79
|
+
}
|
80
|
+
|
37
81
|
server.onopen = () => {
|
38
82
|
this.failures = 0;
|
83
|
+
this.flush();
|
39
84
|
this.attach();
|
40
85
|
};
|
41
86
|
|
@@ -52,13 +97,18 @@ export class Live {
|
|
52
97
|
|
53
98
|
server.addEventListener('close', () => {
|
54
99
|
// Explicit disconnect will clear `this.server`:
|
55
|
-
if (this.server) {
|
100
|
+
if (this.server && !this.reconnectTimer) {
|
56
101
|
// We need a minimum delay otherwise this can end up immediately invoking the callback:
|
57
102
|
const delay = Math.max(100 * (this.failures + 1) ** 2, 60000);
|
58
|
-
setTimeout(() =>
|
103
|
+
this.reconnectTimer = setTimeout(() => {
|
104
|
+
this.reconnectTimer = null;
|
105
|
+
this.connect();
|
106
|
+
}, delay);
|
59
107
|
}
|
60
108
|
|
61
|
-
this.server
|
109
|
+
if (this.server === server) {
|
110
|
+
this.server = null;
|
111
|
+
}
|
62
112
|
});
|
63
113
|
|
64
114
|
return server;
|
@@ -70,14 +120,23 @@ export class Live {
|
|
70
120
|
this.server = null;
|
71
121
|
server.close();
|
72
122
|
}
|
123
|
+
|
124
|
+
if (this.reconnectTimer) {
|
125
|
+
clearTimeout(this.reconnectTimer);
|
126
|
+
this.reconnectTimer = null;
|
127
|
+
}
|
73
128
|
}
|
74
129
|
|
75
130
|
send(message) {
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
131
|
+
if (this.server) {
|
132
|
+
try {
|
133
|
+
return this.server.send(message);
|
134
|
+
} catch (error) {
|
135
|
+
// console.log("Live.send", "failed to send message to server", error);
|
136
|
+
}
|
80
137
|
}
|
138
|
+
|
139
|
+
this.events.push(message);
|
81
140
|
}
|
82
141
|
|
83
142
|
flush() {
|
@@ -91,33 +150,31 @@ export class Live {
|
|
91
150
|
}
|
92
151
|
}
|
93
152
|
|
94
|
-
|
95
|
-
|
96
|
-
this.
|
153
|
+
handleVisibilityChange() {
|
154
|
+
if (this.document.hidden) {
|
155
|
+
this.disconnect();
|
156
|
+
} else {
|
157
|
+
this.connect();
|
97
158
|
}
|
98
159
|
}
|
99
160
|
|
100
|
-
|
101
|
-
|
102
|
-
this.document.getElementsByClassName(selector)
|
103
|
-
);
|
161
|
+
bind(element) {
|
162
|
+
console.log("bind", element.id, element.dataset);
|
104
163
|
|
105
|
-
this.
|
164
|
+
this.send(JSON.stringify(['bind', element.id, element.dataset]));
|
106
165
|
}
|
107
166
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
this.
|
167
|
+
unbind(element) {
|
168
|
+
console.log("unbind", element.id, element.dataset);
|
169
|
+
|
170
|
+
if (this.server) {
|
171
|
+
this.send(JSON.stringify(['unbind', element.id]));
|
113
172
|
}
|
114
173
|
}
|
115
174
|
|
116
175
|
attach() {
|
117
|
-
|
118
|
-
this.
|
119
|
-
} else {
|
120
|
-
this.bindElementsByClassName();
|
176
|
+
for (let node of this.document.getElementsByClassName('live')) {
|
177
|
+
this.bind(node);
|
121
178
|
}
|
122
179
|
}
|
123
180
|
|
@@ -126,8 +183,8 @@ export class Live {
|
|
126
183
|
}
|
127
184
|
|
128
185
|
reply(options) {
|
129
|
-
if (options
|
130
|
-
this.send(JSON.stringify(
|
186
|
+
if (options?.reply) {
|
187
|
+
this.send(JSON.stringify(['reply', options.reply]));
|
131
188
|
}
|
132
189
|
}
|
133
190
|
|
@@ -193,7 +250,7 @@ export class Live {
|
|
193
250
|
this.connect();
|
194
251
|
|
195
252
|
this.send(
|
196
|
-
JSON.stringify(
|
253
|
+
JSON.stringify(['event', id, event])
|
197
254
|
);
|
198
255
|
}
|
199
256
|
|
@@ -1,29 +1,80 @@
|
|
1
|
-
import {describe, before, after, it} from 'node:test';
|
2
|
-
import {ok, strict, strictEqual} from 'node:assert';
|
1
|
+
import {describe, before, beforeEach, after, it} from 'node:test';
|
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`;
|
14
53
|
|
15
54
|
before(async function () {
|
16
|
-
const listening = new Promise(resolve => {
|
55
|
+
const listening = new Promise((resolve, reject) => {
|
17
56
|
webSocketServer = new WebSocket.Server(webSocketServerConfig, resolve);
|
57
|
+
webSocketServer.on('error', reject);
|
18
58
|
});
|
19
59
|
|
20
|
-
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>');
|
21
61
|
// Ensure the WebSocket class is available:
|
22
62
|
dom.window.WebSocket = WebSocket;
|
23
63
|
|
24
64
|
await new Promise(resolve => dom.window.addEventListener('load', resolve));
|
25
65
|
|
26
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();
|
27
78
|
});
|
28
79
|
|
29
80
|
after(function () {
|
@@ -48,23 +99,31 @@ describe('Live', function () {
|
|
48
99
|
live.disconnect();
|
49
100
|
});
|
50
101
|
|
51
|
-
it('should handle visibility changes', function () {
|
102
|
+
it('should handle visibility changes', async function () {
|
52
103
|
const live = new Live(dom.window, webSocketServerURL);
|
53
104
|
|
54
|
-
|
105
|
+
let hidden = false;
|
55
106
|
Object.defineProperty(dom.window.document, "hidden", {
|
56
107
|
get() {return hidden},
|
57
108
|
});
|
58
109
|
|
110
|
+
// The document starts out hidden... we have defined a property to make it not hidden, let's propagate that change:
|
59
111
|
live.handleVisibilityChange();
|
60
112
|
|
61
|
-
|
113
|
+
// We should receive a bind message for the live element:
|
114
|
+
deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
|
62
115
|
|
63
116
|
hidden = true;
|
117
|
+
live.handleVisibilityChange();
|
118
|
+
ok(!live.server);
|
64
119
|
|
120
|
+
hidden = false;
|
65
121
|
live.handleVisibilityChange();
|
122
|
+
ok(live.server);
|
66
123
|
|
67
|
-
|
124
|
+
deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
|
125
|
+
|
126
|
+
live.disconnect();
|
68
127
|
});
|
69
128
|
|
70
129
|
it('should handle updates', async function () {
|
@@ -78,25 +137,18 @@ describe('Live', function () {
|
|
78
137
|
|
79
138
|
let socket = await connected;
|
80
139
|
|
81
|
-
const reply = new Promise((resolve, reject) => {
|
82
|
-
socket.on('message', message => {
|
83
|
-
let payload = JSON.parse(message);
|
84
|
-
if (payload.reply) resolve(payload);
|
85
|
-
});
|
86
|
-
});
|
87
|
-
|
88
140
|
socket.send(
|
89
141
|
JSON.stringify(['update', 'my', '<div id="my"><p>Goodbye World!</p></div>', {reply: true}])
|
90
142
|
);
|
91
143
|
|
92
|
-
await reply;
|
144
|
+
await messages.popUntil(message => message[0] == 'reply');
|
93
145
|
|
94
146
|
strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Goodbye World!</p>');
|
95
147
|
|
96
148
|
live.disconnect();
|
97
149
|
});
|
98
150
|
|
99
|
-
it('should handle
|
151
|
+
it('should handle updates with child live elements', async function () {
|
100
152
|
const live = new Live(dom.window, webSocketServerURL);
|
101
153
|
|
102
154
|
live.connect();
|
@@ -107,19 +159,49 @@ describe('Live', function () {
|
|
107
159
|
|
108
160
|
let socket = await connected;
|
109
161
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
162
|
+
socket.send(
|
163
|
+
JSON.stringify(['update', 'my', '<div id="my"><div id="mychild" class="live"></div></div>'])
|
164
|
+
);
|
165
|
+
|
166
|
+
let payload = await messages.popUntil(message => message[0] == 'bind');
|
167
|
+
deepStrictEqual(payload, ['bind', 'mychild', {}]);
|
168
|
+
|
169
|
+
live.disconnect();
|
170
|
+
});
|
171
|
+
|
172
|
+
it('can unbind removed elements', async function () {
|
173
|
+
dom.window.document.body.innerHTML = '<div id="my" class="live"><p>Hello World</p></div>';
|
174
|
+
|
175
|
+
const live = new Live(dom.window, webSocketServerURL);
|
176
|
+
|
177
|
+
live.connect();
|
178
|
+
|
179
|
+
dom.window.document.getElementById('my').remove();
|
180
|
+
|
181
|
+
let payload = await messages.popUntil(message => message[0] == 'unbind');
|
182
|
+
deepStrictEqual(payload, ['unbind', 'my']);
|
183
|
+
|
184
|
+
live.disconnect();
|
185
|
+
});
|
186
|
+
|
187
|
+
it('should handle replacements', async function () {
|
188
|
+
dom.window.document.body.innerHTML = '<div id="my"><p>Hello World</p></div>';
|
189
|
+
|
190
|
+
const live = new Live(dom.window, webSocketServerURL);
|
191
|
+
|
192
|
+
live.connect();
|
193
|
+
|
194
|
+
const connected = new Promise(resolve => {
|
195
|
+
webSocketServer.on('connection', resolve);
|
115
196
|
});
|
116
197
|
|
198
|
+
let socket = await connected;
|
199
|
+
|
117
200
|
socket.send(
|
118
201
|
JSON.stringify(['replace', '#my p', '<p>Replaced!</p>', {reply: true}])
|
119
202
|
);
|
120
203
|
|
121
|
-
await reply;
|
122
|
-
|
204
|
+
await messages.popUntil(message => message[0] == 'reply');
|
123
205
|
strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Replaced!</p>');
|
124
206
|
|
125
207
|
live.disconnect();
|
@@ -140,19 +222,11 @@ describe('Live', function () {
|
|
140
222
|
JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
|
141
223
|
);
|
142
224
|
|
143
|
-
const reply = new Promise((resolve, reject) => {
|
144
|
-
socket.on('message', message => {
|
145
|
-
let payload = JSON.parse(message);
|
146
|
-
if (payload.reply) resolve(payload);
|
147
|
-
});
|
148
|
-
});
|
149
|
-
|
150
225
|
socket.send(
|
151
226
|
JSON.stringify(['prepend', '#my', '<p>Prepended!</p>', {reply: true}])
|
152
227
|
);
|
153
228
|
|
154
|
-
await reply;
|
155
|
-
|
229
|
+
await messages.popUntil(message => message[0] == 'reply');
|
156
230
|
strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Prepended!</p><p>Middle</p>');
|
157
231
|
|
158
232
|
live.disconnect();
|
@@ -173,19 +247,11 @@ describe('Live', function () {
|
|
173
247
|
JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
|
174
248
|
);
|
175
249
|
|
176
|
-
const reply = new Promise((resolve, reject) => {
|
177
|
-
socket.on('message', message => {
|
178
|
-
let payload = JSON.parse(message);
|
179
|
-
if (payload.reply) resolve(payload);
|
180
|
-
});
|
181
|
-
});
|
182
|
-
|
183
250
|
socket.send(
|
184
251
|
JSON.stringify(['append', '#my', '<p>Appended!</p>', {reply: true}])
|
185
252
|
);
|
186
253
|
|
187
|
-
await reply;
|
188
|
-
|
254
|
+
await messages.popUntil(message => message[0] == 'reply');
|
189
255
|
strictEqual(dom.window.document.getElementById('my').innerHTML, '<p>Middle</p><p>Appended!</p>');
|
190
256
|
|
191
257
|
live.disconnect();
|
@@ -206,19 +272,11 @@ describe('Live', function () {
|
|
206
272
|
JSON.stringify(['update', 'my', '<div id="my"><p>Middle</p></div>'])
|
207
273
|
);
|
208
274
|
|
209
|
-
const reply = new Promise((resolve, reject) => {
|
210
|
-
socket.on('message', message => {
|
211
|
-
let payload = JSON.parse(message);
|
212
|
-
if (payload.reply) resolve(payload);
|
213
|
-
});
|
214
|
-
});
|
215
|
-
|
216
275
|
socket.send(
|
217
276
|
JSON.stringify(['remove', '#my p', {reply: true}])
|
218
277
|
);
|
219
278
|
|
220
|
-
await reply;
|
221
|
-
|
279
|
+
await messages.popUntil(message => message[0] == 'reply');
|
222
280
|
strictEqual(dom.window.document.getElementById('my').innerHTML, '');
|
223
281
|
|
224
282
|
live.disconnect();
|
@@ -235,18 +293,11 @@ describe('Live', function () {
|
|
235
293
|
|
236
294
|
let socket = await connected;
|
237
295
|
|
238
|
-
const reply = new Promise((resolve, reject) => {
|
239
|
-
socket.on('message', message => {
|
240
|
-
let payload = JSON.parse(message);
|
241
|
-
if (payload.reply) resolve(payload);
|
242
|
-
});
|
243
|
-
});
|
244
|
-
|
245
296
|
socket.send(
|
246
297
|
JSON.stringify(['dispatchEvent', '#my', 'click', {reply: true}])
|
247
298
|
);
|
248
299
|
|
249
|
-
await reply;
|
300
|
+
await messages.popUntil(message => message[0] == 'reply');
|
250
301
|
|
251
302
|
live.disconnect();
|
252
303
|
});
|
@@ -262,23 +313,15 @@ describe('Live', function () {
|
|
262
313
|
|
263
314
|
let socket = await connected;
|
264
315
|
|
265
|
-
const reply = new Promise((resolve, reject) => {
|
266
|
-
socket.on('message', message => {
|
267
|
-
let payload = JSON.parse(message);
|
268
|
-
if (payload.event) resolve(payload);
|
269
|
-
});
|
270
|
-
});
|
271
|
-
|
272
316
|
dom.window.document.getElementById('my').addEventListener('click', event => {
|
273
317
|
live.forwardEvent('my', event);
|
274
318
|
});
|
275
319
|
|
276
320
|
dom.window.document.getElementById('my').click();
|
277
321
|
|
278
|
-
let payload = await
|
279
|
-
|
280
|
-
strictEqual(payload.
|
281
|
-
strictEqual(payload.event.type, 'click');
|
322
|
+
let payload = await messages.popUntil(message => message[0] == 'event');
|
323
|
+
strictEqual(payload[1], 'my');
|
324
|
+
strictEqual(payload[2].type, 'click');
|
282
325
|
|
283
326
|
live.disconnect();
|
284
327
|
});
|
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,36 +37,36 @@ 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
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - "
|
46
|
+
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
48
|
+
version: '0.47'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - "
|
53
|
+
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
55
|
+
version: '0.47'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: live
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
62
|
+
version: '0.8'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
69
|
+
version: '0.8'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: xrb
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
metadata.gz.sig
CHANGED
Binary file
|