lively 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bin/lively +16 -0
- data/lib/lively/application.rb +29 -5
- data/lib/lively/environment/application.rb +6 -1
- data/lib/lively/hello_world.rb +59 -0
- data/lib/lively/version.rb +1 -1
- data/public/_components/@socketry/live/Live.js +109 -84
- data/public/_components/@socketry/live/package.json +1 -1
- data/public/_components/@socketry/live/test/Live.js +46 -17
- data/public/_static/Falcon.png +0 -0
- data/public/_static/index.css +5 -1
- data/public/_static/site.css +1 -1
- data.tar.gz.sig +0 -0
- metadata +8 -4
- 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: 5792cd3cb017762c79c0c8efcf99724db21dd444aa0e6e1f0c7f4f8e0e61774a
|
4
|
+
data.tar.gz: 0275c27630c5e0af1408771737e9fa87276ce3b6e2f06de788eaa9e45d60a6a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c0f91c91d6d25d138aa78332cfb9c51d4f5793f5134c059bed8d1b6d1dd4b327a70f3d8e47106a49965534157d58913952b60f7508e903b461d3e15d48381bb
|
7
|
+
data.tar.gz: c4b7817e61109d157bb68c157e8c50590776db6c1eca1c4d7f65936339ba2d02da62f07dfc903cd939d8807231dc382e6b7f18702ef0096266b3bd458b25f4cd
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bin/lively
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'async/service'
|
4
|
+
require_relative '../lib/lively/environment/application'
|
5
|
+
|
6
|
+
ARGV.each do |path|
|
7
|
+
require(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
configuration = Async::Service::Configuration.build do
|
11
|
+
service "lively" do
|
12
|
+
include Lively::Environment::Application
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Async::Service::Controller.run(configuration)
|
data/lib/lively/application.rb
CHANGED
@@ -4,12 +4,32 @@
|
|
4
4
|
# Copyright, 2021-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require 'live'
|
7
|
+
require 'protocol/http/middleware'
|
7
8
|
require 'async/websocket/adapters/http'
|
8
9
|
|
9
10
|
require_relative 'pages/index'
|
11
|
+
require_relative 'hello_world'
|
10
12
|
|
11
13
|
module Lively
|
12
14
|
class Application < Protocol::HTTP::Middleware
|
15
|
+
def self.[](tag)
|
16
|
+
klass = Class.new(self)
|
17
|
+
|
18
|
+
klass.define_singleton_method(:resolver) do
|
19
|
+
Live::Resolver.allow(tag)
|
20
|
+
end
|
21
|
+
|
22
|
+
klass.define_method(:body) do
|
23
|
+
tag.new
|
24
|
+
end
|
25
|
+
|
26
|
+
return klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.resolver
|
30
|
+
Live::Resolver.allow(HelloWorld)
|
31
|
+
end
|
32
|
+
|
13
33
|
def initialize(delegate, resolver: self.class.resolver)
|
14
34
|
super(delegate)
|
15
35
|
|
@@ -24,19 +44,23 @@ module Lively
|
|
24
44
|
self.class.name
|
25
45
|
end
|
26
46
|
|
27
|
-
def body
|
28
|
-
|
47
|
+
def body(...)
|
48
|
+
HelloWorld.new(...)
|
49
|
+
end
|
50
|
+
|
51
|
+
def index(...)
|
52
|
+
Pages::Index.new(title: self.title, body: self.body(...))
|
29
53
|
end
|
30
54
|
|
31
|
-
def
|
32
|
-
|
55
|
+
def handle(request, ...)
|
56
|
+
return Protocol::HTTP::Response[200, [], [self.index(...).call]]
|
33
57
|
end
|
34
58
|
|
35
59
|
def call(request)
|
36
60
|
if request.path == '/live'
|
37
61
|
return Async::WebSocket::Adapters::HTTP.open(request, &self.method(:live)) || Protocol::HTTP::Response[400]
|
38
62
|
else
|
39
|
-
return
|
63
|
+
return handle(request)
|
40
64
|
end
|
41
65
|
end
|
42
66
|
end
|
@@ -3,13 +3,18 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2021-2024, by Samuel Williams.
|
5
5
|
|
6
|
+
require_relative '../application'
|
7
|
+
require_relative '../assets'
|
8
|
+
|
9
|
+
require 'falcon/environment/server'
|
10
|
+
|
6
11
|
module Lively
|
7
12
|
module Environment
|
8
13
|
module Application
|
9
14
|
include Falcon::Environment::Server
|
10
15
|
|
11
16
|
def application
|
12
|
-
if Object.const_defined?(:Application
|
17
|
+
if Object.const_defined?(:Application)
|
13
18
|
::Application
|
14
19
|
else
|
15
20
|
Console.warn(self, "No Application class defined, using default.")
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Lively
|
2
|
+
class HelloWorld < Live::View
|
3
|
+
def initialize(...)
|
4
|
+
super
|
5
|
+
|
6
|
+
@clock = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def bind(page)
|
10
|
+
super
|
11
|
+
|
12
|
+
@clock ||= Async do
|
13
|
+
while true
|
14
|
+
self.update!
|
15
|
+
|
16
|
+
sleep 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
@clock&.stop
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def render(builder)
|
28
|
+
builder.tag(:h1) do
|
29
|
+
builder.text("Hello, I'm Lively!")
|
30
|
+
end
|
31
|
+
|
32
|
+
builder.tag(:p) do
|
33
|
+
builder.text("The time is #{Time.now}.")
|
34
|
+
end
|
35
|
+
|
36
|
+
builder.tag(:p) do
|
37
|
+
builder.text(<<~TEXT)
|
38
|
+
Lively is a simple client-server SPA framework. It is designed to be easy to use and understand, while providing a solid foundation for building interactive web applications. Create an `application.rb` file and define your own `Application` class to get started.
|
39
|
+
TEXT
|
40
|
+
end
|
41
|
+
|
42
|
+
builder.inline_tag(:pre) do
|
43
|
+
builder.text(<<~TEXT)
|
44
|
+
#!/usr/bin/env lively
|
45
|
+
|
46
|
+
class Application < Lively::Application
|
47
|
+
def body
|
48
|
+
Lively::HelloWorld.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
TEXT
|
52
|
+
end
|
53
|
+
|
54
|
+
builder.tag(:p) do
|
55
|
+
builder.text("Check the `examples/` directory for... you guessed it... more examples.")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/lively/version.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
import morphdom from 'morphdom';
|
2
2
|
|
3
3
|
export class Live {
|
4
|
+
#window;
|
5
|
+
#document;
|
6
|
+
#server;
|
7
|
+
#events;
|
8
|
+
#failures;
|
9
|
+
#reconnectTimer;
|
10
|
+
|
4
11
|
static start(options = {}) {
|
5
12
|
let window = options.window || globalThis;
|
6
13
|
let path = options.path || 'live'
|
@@ -13,35 +20,37 @@ export class Live {
|
|
13
20
|
}
|
14
21
|
|
15
22
|
constructor(window, url) {
|
16
|
-
this
|
17
|
-
this
|
23
|
+
this.#window = window;
|
24
|
+
this.#document = window.document;
|
18
25
|
|
19
26
|
this.url = url;
|
20
|
-
this
|
27
|
+
this.#server = null;
|
28
|
+
this.#events = [];
|
21
29
|
|
22
|
-
this
|
23
|
-
this
|
30
|
+
this.#failures = 0;
|
31
|
+
this.#reconnectTimer = null;
|
24
32
|
|
25
33
|
// Track visibility state and connect if required:
|
26
|
-
this
|
27
|
-
|
34
|
+
this.#document.addEventListener("visibilitychange", () => this.#handleVisibilityChange());
|
35
|
+
|
36
|
+
this.#handleVisibilityChange();
|
28
37
|
|
29
|
-
const elementNodeType = this
|
38
|
+
const elementNodeType = this.#window.Node.ELEMENT_NODE;
|
30
39
|
|
31
40
|
// Create a MutationObserver to watch for removed nodes
|
32
|
-
this.observer = new this
|
41
|
+
this.observer = new this.#window.MutationObserver((mutationsList, observer) => {
|
33
42
|
for (let mutation of mutationsList) {
|
34
43
|
if (mutation.type === 'childList') {
|
35
44
|
for (let node of mutation.removedNodes) {
|
36
45
|
if (node.nodeType !== elementNodeType) continue;
|
37
46
|
|
38
47
|
if (node.classList?.contains('live')) {
|
39
|
-
this
|
48
|
+
this.#unbind(node);
|
40
49
|
}
|
41
50
|
|
42
51
|
// Unbind any child nodes:
|
43
52
|
for (let child of node.getElementsByClassName('live')) {
|
44
|
-
this
|
53
|
+
this.#unbind(child);
|
45
54
|
}
|
46
55
|
}
|
47
56
|
|
@@ -49,65 +58,65 @@ export class Live {
|
|
49
58
|
if (node.nodeType !== elementNodeType) continue;
|
50
59
|
|
51
60
|
if (node.classList.contains('live')) {
|
52
|
-
this
|
61
|
+
this.#bind(node);
|
53
62
|
}
|
54
63
|
|
55
64
|
// Bind any child nodes:
|
56
65
|
for (let child of node.getElementsByClassName('live')) {
|
57
|
-
this
|
66
|
+
this.#bind(child);
|
58
67
|
}
|
59
68
|
}
|
60
69
|
}
|
61
70
|
}
|
62
71
|
});
|
63
72
|
|
64
|
-
this.observer.observe(this
|
73
|
+
this.observer.observe(this.#document.body, {childList: true, subtree: true});
|
65
74
|
}
|
66
75
|
|
67
76
|
// -- Connection Handling --
|
68
77
|
|
69
78
|
connect() {
|
70
|
-
if (this
|
71
|
-
return this
|
79
|
+
if (this.#server) {
|
80
|
+
return this.#server;
|
72
81
|
}
|
73
82
|
|
74
|
-
let server = this
|
83
|
+
let server = this.#server = new this.#window.WebSocket(this.url);
|
75
84
|
|
76
|
-
if (this
|
77
|
-
clearTimeout(this
|
78
|
-
this
|
85
|
+
if (this.#reconnectTimer) {
|
86
|
+
clearTimeout(this.#reconnectTimer);
|
87
|
+
this.#reconnectTimer = null;
|
79
88
|
}
|
80
89
|
|
81
90
|
server.onopen = () => {
|
82
|
-
this
|
83
|
-
this
|
84
|
-
this
|
91
|
+
this.#failures = 0;
|
92
|
+
this.#flush();
|
93
|
+
this.#attach();
|
85
94
|
};
|
86
95
|
|
87
96
|
server.onmessage = (message) => {
|
88
|
-
const [name, ...
|
97
|
+
const [name, ...args] = JSON.parse(message.data);
|
89
98
|
|
90
|
-
this[name](...
|
99
|
+
this[name](...args);
|
91
100
|
};
|
92
101
|
|
93
102
|
// The remote end has disconnected:
|
94
103
|
server.addEventListener('error', () => {
|
95
|
-
this
|
104
|
+
this.#failures += 1;
|
96
105
|
});
|
97
106
|
|
98
107
|
server.addEventListener('close', () => {
|
99
|
-
// Explicit disconnect will clear `this
|
100
|
-
if (this
|
108
|
+
// Explicit disconnect will clear `this.#server`:
|
109
|
+
if (this.#server && !this.#reconnectTimer) {
|
101
110
|
// We need a minimum delay otherwise this can end up immediately invoking the callback:
|
102
|
-
const delay = Math.max(100 * (this
|
103
|
-
this
|
104
|
-
this
|
111
|
+
const delay = Math.max(100 * (this.#failures + 1) ** 2, 60000);
|
112
|
+
this.#reconnectTimer = setTimeout(() => {
|
113
|
+
this.#reconnectTimer = null;
|
105
114
|
this.connect();
|
106
115
|
}, delay);
|
107
116
|
}
|
108
117
|
|
109
|
-
if (this
|
110
|
-
this
|
118
|
+
if (this.#server === server) {
|
119
|
+
this.#server = null;
|
111
120
|
}
|
112
121
|
});
|
113
122
|
|
@@ -115,133 +124,149 @@ export class Live {
|
|
115
124
|
}
|
116
125
|
|
117
126
|
disconnect() {
|
118
|
-
if (this
|
119
|
-
const server = this
|
120
|
-
this
|
127
|
+
if (this.#server) {
|
128
|
+
const server = this.#server;
|
129
|
+
this.#server = null;
|
121
130
|
server.close();
|
122
131
|
}
|
123
132
|
|
124
|
-
if (this
|
125
|
-
clearTimeout(this
|
126
|
-
this
|
133
|
+
if (this.#reconnectTimer) {
|
134
|
+
clearTimeout(this.#reconnectTimer);
|
135
|
+
this.#reconnectTimer = null;
|
127
136
|
}
|
128
137
|
}
|
129
138
|
|
130
|
-
send(message) {
|
131
|
-
if (this
|
139
|
+
#send(message) {
|
140
|
+
if (this.#server) {
|
132
141
|
try {
|
133
|
-
return this
|
142
|
+
return this.#server.send(message);
|
134
143
|
} catch (error) {
|
135
144
|
// console.log("Live.send", "failed to send message to server", error);
|
136
145
|
}
|
137
146
|
}
|
138
147
|
|
139
|
-
this
|
148
|
+
this.#events.push(message);
|
140
149
|
}
|
141
150
|
|
142
|
-
flush() {
|
143
|
-
if (this
|
151
|
+
#flush() {
|
152
|
+
if (this.#events.length === 0) return;
|
144
153
|
|
145
|
-
let events = this
|
146
|
-
this
|
154
|
+
let events = this.#events;
|
155
|
+
this.#events = [];
|
147
156
|
|
148
157
|
for (var event of events) {
|
149
|
-
this
|
158
|
+
this.#send(event);
|
150
159
|
}
|
151
160
|
}
|
152
161
|
|
153
|
-
handleVisibilityChange() {
|
154
|
-
if (this
|
162
|
+
#handleVisibilityChange() {
|
163
|
+
if (this.#document.hidden) {
|
155
164
|
this.disconnect();
|
156
165
|
} else {
|
157
166
|
this.connect();
|
158
167
|
}
|
159
168
|
}
|
160
169
|
|
161
|
-
bind(element) {
|
170
|
+
#bind(element) {
|
162
171
|
console.log("bind", element.id, element.dataset);
|
163
172
|
|
164
|
-
this
|
173
|
+
this.#send(JSON.stringify(['bind', element.id, element.dataset]));
|
165
174
|
}
|
166
175
|
|
167
|
-
unbind(element) {
|
176
|
+
#unbind(element) {
|
168
177
|
console.log("unbind", element.id, element.dataset);
|
169
178
|
|
170
|
-
if (this
|
171
|
-
this
|
179
|
+
if (this.#server) {
|
180
|
+
this.#send(JSON.stringify(['unbind', element.id]));
|
172
181
|
}
|
173
182
|
}
|
174
183
|
|
175
|
-
attach() {
|
176
|
-
for (let node of this
|
177
|
-
this
|
184
|
+
#attach() {
|
185
|
+
for (let node of this.#document.getElementsByClassName('live')) {
|
186
|
+
this.#bind(node);
|
178
187
|
}
|
179
188
|
}
|
180
189
|
|
181
|
-
createDocumentFragment(html) {
|
182
|
-
return this
|
190
|
+
#createDocumentFragment(html) {
|
191
|
+
return this.#document.createRange().createContextualFragment(html);
|
183
192
|
}
|
184
193
|
|
185
|
-
reply(options) {
|
194
|
+
#reply(options, ...args) {
|
186
195
|
if (options?.reply) {
|
187
|
-
this
|
196
|
+
this.#send(JSON.stringify(['reply', options.reply, ...args]));
|
188
197
|
}
|
189
198
|
}
|
190
199
|
|
191
200
|
// -- RPC Methods --
|
192
201
|
|
202
|
+
script(id, code, options) {
|
203
|
+
let element = this.#document.getElementById(id);
|
204
|
+
|
205
|
+
try {
|
206
|
+
let result = this.#window.Function(code).call(element);
|
207
|
+
|
208
|
+
this.#reply(options, result);
|
209
|
+
} catch (error) {
|
210
|
+
this.#reply(options, null, {name: error.name, message: error.message, stack: error.stack});
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
193
214
|
update(id, html, options) {
|
194
|
-
let element = this
|
195
|
-
let fragment = this
|
215
|
+
let element = this.#document.getElementById(id);
|
216
|
+
let fragment = this.#createDocumentFragment(html);
|
196
217
|
|
197
218
|
morphdom(element, fragment);
|
198
219
|
|
199
|
-
this
|
220
|
+
this.#reply(options);
|
200
221
|
}
|
201
222
|
|
202
223
|
replace(selector, html, options) {
|
203
|
-
let elements = this
|
204
|
-
let fragment = this
|
224
|
+
let elements = this.#document.querySelectorAll(selector);
|
225
|
+
let fragment = this.#createDocumentFragment(html);
|
205
226
|
|
206
227
|
elements.forEach(element => morphdom(element, fragment.cloneNode(true)));
|
207
228
|
|
208
|
-
this
|
229
|
+
this.#reply(options);
|
209
230
|
}
|
210
231
|
|
211
232
|
prepend(selector, html, options) {
|
212
|
-
let elements = this
|
213
|
-
let fragment = this
|
233
|
+
let elements = this.#document.querySelectorAll(selector);
|
234
|
+
let fragment = this.#createDocumentFragment(html);
|
214
235
|
|
215
236
|
elements.forEach(element => element.prepend(fragment.cloneNode(true)));
|
216
237
|
|
217
|
-
this
|
238
|
+
this.#reply(options);
|
218
239
|
}
|
219
240
|
|
220
241
|
append(selector, html, options) {
|
221
|
-
let elements = this
|
222
|
-
let fragment = this
|
242
|
+
let elements = this.#document.querySelectorAll(selector);
|
243
|
+
let fragment = this.#createDocumentFragment(html);
|
223
244
|
|
224
245
|
elements.forEach(element => element.append(fragment.cloneNode(true)));
|
225
246
|
|
226
|
-
this
|
247
|
+
this.#reply(options);
|
227
248
|
}
|
228
249
|
|
229
250
|
remove(selector, options) {
|
230
|
-
let elements = this
|
251
|
+
let elements = this.#document.querySelectorAll(selector);
|
231
252
|
|
232
253
|
elements.forEach(element => element.remove());
|
233
254
|
|
234
|
-
this
|
255
|
+
this.#reply(options);
|
235
256
|
}
|
236
257
|
|
237
258
|
dispatchEvent(selector, type, options) {
|
238
|
-
let elements = this
|
259
|
+
let elements = this.#document.querySelectorAll(selector);
|
239
260
|
|
240
261
|
elements.forEach(element => element.dispatchEvent(
|
241
|
-
new this
|
262
|
+
new this.#window.CustomEvent(type, options)
|
242
263
|
));
|
243
264
|
|
244
|
-
this
|
265
|
+
this.#reply(options);
|
266
|
+
}
|
267
|
+
|
268
|
+
error(message) {
|
269
|
+
console.error("Live.error", ...arguments);
|
245
270
|
}
|
246
271
|
|
247
272
|
// -- Event Handling --
|
@@ -249,19 +274,19 @@ export class Live {
|
|
249
274
|
forward(id, event) {
|
250
275
|
this.connect();
|
251
276
|
|
252
|
-
this
|
277
|
+
this.#send(
|
253
278
|
JSON.stringify(['event', id, event])
|
254
279
|
);
|
255
280
|
}
|
256
281
|
|
257
|
-
forwardEvent(id, event, detail) {
|
258
|
-
event.preventDefault();
|
282
|
+
forwardEvent(id, event, detail, preventDefault = false) {
|
283
|
+
if (preventDefault) event.preventDefault();
|
259
284
|
|
260
285
|
this.forward(id, {type: event.type, detail: detail});
|
261
286
|
}
|
262
287
|
|
263
|
-
forwardFormEvent(id, event, detail) {
|
264
|
-
event.preventDefault();
|
288
|
+
forwardFormEvent(id, event, detail, preventDefault = true) {
|
289
|
+
if (preventDefault) event.preventDefault();
|
265
290
|
|
266
291
|
let form = event.form;
|
267
292
|
let formData = new FormData(form);
|
@@ -85,9 +85,9 @@ describe('Live', function () {
|
|
85
85
|
const live = Live.start({window: dom.window, base: 'http://localhost/'});
|
86
86
|
ok(live);
|
87
87
|
|
88
|
-
strictEqual(live.window, dom.window);
|
89
|
-
strictEqual(live.document, dom.window.document);
|
90
88
|
strictEqual(live.url.href, 'ws://localhost/live');
|
89
|
+
|
90
|
+
live.disconnect();
|
91
91
|
});
|
92
92
|
|
93
93
|
it('should connect to the WebSocket server', function () {
|
@@ -102,26 +102,46 @@ describe('Live', function () {
|
|
102
102
|
it('should handle visibility changes', async function () {
|
103
103
|
const live = new Live(dom.window, webSocketServerURL);
|
104
104
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
// It's tricky to test the method directly.
|
106
|
+
// - Changing document.hidden is a hack.
|
107
|
+
// - Sending custom events seems to cause a hang.
|
108
|
+
|
109
|
+
live.connect();
|
110
|
+
deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
|
109
111
|
|
110
|
-
|
111
|
-
live.handleVisibilityChange();
|
112
|
+
live.disconnect();
|
112
113
|
|
113
|
-
|
114
|
+
live.connect()
|
114
115
|
deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
|
115
116
|
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
live.disconnect();
|
118
|
+
});
|
119
|
+
|
120
|
+
it('can execute scripts', async function () {
|
121
|
+
const live = new Live(dom.window, webSocketServerURL);
|
119
122
|
|
120
|
-
|
121
|
-
live.handleVisibilityChange();
|
122
|
-
ok(live.server);
|
123
|
+
live.connect();
|
123
124
|
|
124
|
-
|
125
|
+
const connected = new Promise(resolve => {
|
126
|
+
webSocketServer.on('connection', resolve);
|
127
|
+
});
|
128
|
+
|
129
|
+
let socket = await connected;
|
130
|
+
|
131
|
+
socket.send(
|
132
|
+
JSON.stringify(['script', 'my', 'return 1+2', {reply: true}])
|
133
|
+
);
|
134
|
+
|
135
|
+
let successReply = await messages.popUntil(message => message[0] == 'reply');
|
136
|
+
strictEqual(successReply[2], 3);
|
137
|
+
|
138
|
+
socket.send(
|
139
|
+
JSON.stringify(['script', 'my', 'throw new Error("Test Error")', {reply: true}])
|
140
|
+
);
|
141
|
+
|
142
|
+
let errorReply = await messages.popUntil(message => message[0] == 'reply');
|
143
|
+
strictEqual(errorReply[2], null);
|
144
|
+
console.log(errorReply);
|
125
145
|
|
126
146
|
live.disconnect();
|
127
147
|
});
|
@@ -178,7 +198,10 @@ describe('Live', function () {
|
|
178
198
|
|
179
199
|
dom.window.document.getElementById('my').remove();
|
180
200
|
|
181
|
-
let payload = await messages.popUntil(message =>
|
201
|
+
let payload = await messages.popUntil(message => {
|
202
|
+
return message[0] == 'unbind' && message[1] == 'my';
|
203
|
+
});
|
204
|
+
|
182
205
|
deepStrictEqual(payload, ['unbind', 'my']);
|
183
206
|
|
184
207
|
live.disconnect();
|
@@ -325,4 +348,10 @@ describe('Live', function () {
|
|
325
348
|
|
326
349
|
live.disconnect();
|
327
350
|
});
|
351
|
+
|
352
|
+
it ('can log errors', function () {
|
353
|
+
const live = new Live(dom.window, webSocketServerURL);
|
354
|
+
|
355
|
+
live.error('my', 'Test Error');
|
356
|
+
});
|
328
357
|
});
|
Binary file
|
data/public/_static/index.css
CHANGED
data/public/_static/site.css
CHANGED
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -59,14 +59,14 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0.
|
62
|
+
version: '0.9'
|
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.9'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: xrb
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,14 +83,17 @@ dependencies:
|
|
83
83
|
version: '0'
|
84
84
|
description:
|
85
85
|
email:
|
86
|
-
executables:
|
86
|
+
executables:
|
87
|
+
- lively
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
91
|
+
- bin/lively
|
90
92
|
- lib/lively.rb
|
91
93
|
- lib/lively/application.rb
|
92
94
|
- lib/lively/assets.rb
|
93
95
|
- lib/lively/environment/application.rb
|
96
|
+
- lib/lively/hello_world.rb
|
94
97
|
- lib/lively/pages/index.rb
|
95
98
|
- lib/lively/pages/index.xrb
|
96
99
|
- lib/lively/version.rb
|
@@ -104,6 +107,7 @@ files:
|
|
104
107
|
- public/_components/morphdom/morphdom-umd.js
|
105
108
|
- public/_components/morphdom/morphdom-umd.min.js
|
106
109
|
- public/_components/morphdom/morphdom.js
|
110
|
+
- public/_static/Falcon.png
|
107
111
|
- public/_static/icon.png
|
108
112
|
- public/_static/index.css
|
109
113
|
- public/_static/site.css
|
metadata.gz.sig
CHANGED
Binary file
|