message_bus 4.2.0 → 4.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintrc.js +9 -0
- data/.github/workflows/ci.yml +4 -4
- data/CHANGELOG +16 -0
- data/Dockerfile +2 -2
- data/README.md +1 -1
- data/assets/message-bus.js +37 -22
- data/lib/message_bus/backends/redis.rb +23 -1
- data/lib/message_bus/version.rb +1 -1
- data/lib/message_bus.rb +8 -3
- data/package-lock.json +541 -430
- data/package.json +1 -1
- data/spec/assets/SpecHelper.js +10 -4
- data/spec/assets/message-bus.spec.js +70 -1
- data/spec/lib/message_bus/backend_spec.rb +0 -7
- data/spec/lib/message_bus_spec.rb +52 -20
- data/vendor/assets/javascripts/message-bus.js +37 -22
- metadata +2 -2
data/package.json
CHANGED
data/spec/assets/SpecHelper.js
CHANGED
@@ -33,8 +33,7 @@ beforeEach(function () {
|
|
33
33
|
MockedXMLHttpRequest.prototype.send = function(){
|
34
34
|
this.readyState = 4
|
35
35
|
this.responseText = encodeChunks(this, spec.responseChunks);
|
36
|
-
this.
|
37
|
-
this.status = 200;
|
36
|
+
this.status = spec.responseStatus;
|
38
37
|
if (this.onprogress){ this.onprogress(); }
|
39
38
|
this.onreadystatechange()
|
40
39
|
}
|
@@ -53,8 +52,8 @@ beforeEach(function () {
|
|
53
52
|
this.headers[k] = v;
|
54
53
|
}
|
55
54
|
|
56
|
-
MockedXMLHttpRequest.prototype.getResponseHeader = function(){
|
57
|
-
return
|
55
|
+
MockedXMLHttpRequest.prototype.getResponseHeader = function(headerName){
|
56
|
+
return spec.responseHeaders[headerName];
|
58
57
|
}
|
59
58
|
|
60
59
|
MessageBus.xhrImplementation = MockedXMLHttpRequest
|
@@ -64,12 +63,19 @@ beforeEach(function () {
|
|
64
63
|
{channel: '/test', data: {password: 'MessageBusRocks!'}}
|
65
64
|
];
|
66
65
|
|
66
|
+
this.responseStatus = 200;
|
67
|
+
this.responseHeaders = {
|
68
|
+
"Content-Type": 'text/plain; charset=utf-8',
|
69
|
+
};
|
70
|
+
|
67
71
|
MessageBus.start();
|
68
72
|
});
|
69
73
|
|
70
74
|
afterEach(function(){
|
71
75
|
MessageBus.stop()
|
72
76
|
MessageBus.callbacks.splice(0, MessageBus.callbacks.length)
|
77
|
+
MessageBus.shouldLongPollCallback = null;
|
78
|
+
MessageBus.enableChunkedEncoding = true;
|
73
79
|
});
|
74
80
|
|
75
81
|
window.testMB = function(description, testFn, path, data){
|
@@ -1,7 +1,11 @@
|
|
1
|
+
/* eslint-env es2022 */
|
1
2
|
/* global describe, it, spyOn, MessageBus, expect, jasmine, testMB */
|
2
3
|
|
3
|
-
|
4
|
+
function approxEqual(valueOne, valueTwo) {
|
5
|
+
return Math.abs(valueOne - valueTwo) < 500;
|
6
|
+
}
|
4
7
|
|
8
|
+
describe("Messagebus", function () {
|
5
9
|
it("submits change requests", function(done){
|
6
10
|
spyOn(this.MockedXMLHttpRequest.prototype, 'send').and.callThrough();
|
7
11
|
var spec = this;
|
@@ -97,4 +101,69 @@ describe("Messagebus", function() {
|
|
97
101
|
})
|
98
102
|
});
|
99
103
|
|
104
|
+
it("respects Retry-After response header when larger than 15 seconds", async function () {
|
105
|
+
spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
|
106
|
+
spyOn(window, "setTimeout").and.callThrough();
|
107
|
+
|
108
|
+
this.responseStatus = 429;
|
109
|
+
this.responseHeaders["Retry-After"] = "23";
|
110
|
+
|
111
|
+
await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
|
112
|
+
|
113
|
+
const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
|
114
|
+
expect(nextPollScheduledIn).toEqual(23000);
|
115
|
+
});
|
116
|
+
|
117
|
+
it("retries after 15s for lower retry-after values", async function () {
|
118
|
+
spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
|
119
|
+
spyOn(window, "setTimeout").and.callThrough();
|
120
|
+
|
121
|
+
this.responseStatus = 429;
|
122
|
+
this.responseHeaders["Retry-After"] = "13";
|
123
|
+
|
124
|
+
await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
|
125
|
+
|
126
|
+
const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
|
127
|
+
expect(nextPollScheduledIn).toEqual(15000);
|
128
|
+
});
|
129
|
+
|
130
|
+
it("waits for callbackInterval after receiving data in chunked long-poll mode", async function () {
|
131
|
+
// The callbackInterval is equal to the length of the server response in chunked long-poll mode, so
|
132
|
+
// this ultimately ends up being a continuous stream of requests
|
133
|
+
|
134
|
+
spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
|
135
|
+
spyOn(window, "setTimeout").and.callThrough();
|
136
|
+
|
137
|
+
await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
|
138
|
+
|
139
|
+
const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
|
140
|
+
expect(
|
141
|
+
approxEqual(nextPollScheduledIn, MessageBus.callbackInterval)
|
142
|
+
).toEqual(true);
|
143
|
+
});
|
144
|
+
|
145
|
+
it("waits for backgroundCallbackInterval after receiving data in non-long-poll mode", async function () {
|
146
|
+
spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
|
147
|
+
spyOn(window, "setTimeout").and.callThrough();
|
148
|
+
MessageBus.shouldLongPollCallback = () => false;
|
149
|
+
MessageBus.enableChunkedEncoding = false;
|
150
|
+
|
151
|
+
await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
|
152
|
+
|
153
|
+
const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
|
154
|
+
expect(
|
155
|
+
approxEqual(nextPollScheduledIn, MessageBus.backgroundCallbackInterval)
|
156
|
+
).toEqual(true);
|
157
|
+
});
|
158
|
+
|
159
|
+
it("re-polls immediately after receiving data in non-chunked long-poll mode", async function () {
|
160
|
+
spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
|
161
|
+
spyOn(window, "setTimeout").and.callThrough();
|
162
|
+
MessageBus.enableChunkedEncoding = false;
|
163
|
+
|
164
|
+
await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
|
165
|
+
|
166
|
+
const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
|
167
|
+
expect(nextPollScheduledIn).toEqual(MessageBus.minPollInterval);
|
168
|
+
});
|
100
169
|
});
|
@@ -392,11 +392,4 @@ describe BACKEND_CLASS do
|
|
392
392
|
got.map { |m| m.data }.must_equal ["12"]
|
393
393
|
end
|
394
394
|
|
395
|
-
it 'should not lose redis config' do
|
396
|
-
test_only :redis
|
397
|
-
redis_config = { connector: Redis::Client::Connector }
|
398
|
-
@bus.instance_variable_set(:@redis_config, redis_config)
|
399
|
-
@bus.send(:new_redis_connection)
|
400
|
-
expect(@bus.instance_variable_get(:@redis_config)[:connector]).must_equal Redis::Client::Connector
|
401
|
-
end
|
402
395
|
end
|
@@ -17,26 +17,6 @@ describe MessageBus do
|
|
17
17
|
@bus.destroy
|
18
18
|
end
|
19
19
|
|
20
|
-
it "can be turned off" do
|
21
|
-
@bus.off
|
22
|
-
|
23
|
-
@bus.off?.must_equal true
|
24
|
-
end
|
25
|
-
|
26
|
-
it "can call destroy multiple times" do
|
27
|
-
@bus.destroy
|
28
|
-
@bus.destroy
|
29
|
-
@bus.destroy
|
30
|
-
end
|
31
|
-
|
32
|
-
it "can be turned on after destroy" do
|
33
|
-
@bus.destroy
|
34
|
-
|
35
|
-
@bus.on
|
36
|
-
|
37
|
-
@bus.after_fork
|
38
|
-
end
|
39
|
-
|
40
20
|
it "destroying immediately after `after_fork` does not lock" do
|
41
21
|
10.times do
|
42
22
|
@bus.on
|
@@ -216,6 +196,58 @@ describe MessageBus do
|
|
216
196
|
@bus.backlog("/chuck").map { |i| i.data }.to_a.must_equal ['foo']
|
217
197
|
end
|
218
198
|
|
199
|
+
it "can be turned off" do
|
200
|
+
@bus.off
|
201
|
+
|
202
|
+
@bus.off?.must_equal true
|
203
|
+
|
204
|
+
@bus.publish("/chuck", "norris")
|
205
|
+
|
206
|
+
@bus.backlog("/chuck").to_a.must_equal []
|
207
|
+
end
|
208
|
+
|
209
|
+
it "can be turned off only for subscriptions" do
|
210
|
+
@bus.off(disable_publish: false)
|
211
|
+
|
212
|
+
@bus.off?.must_equal true
|
213
|
+
|
214
|
+
data = []
|
215
|
+
|
216
|
+
@bus.subscribe("/chuck") do |msg|
|
217
|
+
data << msg.data
|
218
|
+
end
|
219
|
+
|
220
|
+
@bus.publish("/chuck", "norris")
|
221
|
+
|
222
|
+
@bus.on
|
223
|
+
|
224
|
+
@bus.subscribe("/chuck") do |msg|
|
225
|
+
data << msg.data
|
226
|
+
end
|
227
|
+
|
228
|
+
@bus.publish("/chuck", "berry")
|
229
|
+
|
230
|
+
wait_for(2000) { data.length > 0 }
|
231
|
+
|
232
|
+
data.must_equal ["berry"]
|
233
|
+
|
234
|
+
@bus.backlog("/chuck").map(&:data).to_a.must_equal ["norris", "berry"]
|
235
|
+
end
|
236
|
+
|
237
|
+
it "can call destroy multiple times" do
|
238
|
+
@bus.destroy
|
239
|
+
@bus.destroy
|
240
|
+
@bus.destroy
|
241
|
+
end
|
242
|
+
|
243
|
+
it "can be turned on after destroy" do
|
244
|
+
@bus.destroy
|
245
|
+
|
246
|
+
@bus.on
|
247
|
+
|
248
|
+
@bus.after_fork
|
249
|
+
end
|
250
|
+
|
219
251
|
it "allows you to look up last_message" do
|
220
252
|
@bus.publish("/bob", "dylan")
|
221
253
|
@bus.publish("/bob", "marley")
|
@@ -40,7 +40,7 @@
|
|
40
40
|
var pollTimeout = null;
|
41
41
|
var totalAjaxFailures = 0;
|
42
42
|
var totalAjaxCalls = 0;
|
43
|
-
var
|
43
|
+
var lastAjaxStartedAt;
|
44
44
|
|
45
45
|
var isHidden = (function () {
|
46
46
|
var prefixes = ["", "webkit", "ms", "moz"];
|
@@ -156,11 +156,11 @@
|
|
156
156
|
}
|
157
157
|
|
158
158
|
var gotData = false;
|
159
|
-
var
|
159
|
+
var abortedByClient = false;
|
160
160
|
var rateLimited = false;
|
161
161
|
var rateLimitedSeconds;
|
162
162
|
|
163
|
-
|
163
|
+
lastAjaxStartedAt = new Date();
|
164
164
|
totalAjaxCalls += 1;
|
165
165
|
data.__seq = totalAjaxCalls;
|
166
166
|
|
@@ -281,7 +281,7 @@
|
|
281
281
|
rateLimitedSeconds = tryAfter;
|
282
282
|
rateLimited = true;
|
283
283
|
} else if (textStatus === "abort") {
|
284
|
-
|
284
|
+
abortedByClient = true;
|
285
285
|
} else {
|
286
286
|
failCount += 1;
|
287
287
|
totalAjaxFailures += 1;
|
@@ -290,27 +290,40 @@
|
|
290
290
|
complete: function () {
|
291
291
|
ajaxInProgress = false;
|
292
292
|
|
293
|
-
var
|
293
|
+
var inLongPollingMode = shouldLongPoll();
|
294
|
+
var startNextRequestAfter;
|
294
295
|
try {
|
295
296
|
if (rateLimited) {
|
296
|
-
|
297
|
-
|
298
|
-
|
297
|
+
// Respect `Retry-After` header
|
298
|
+
startNextRequestAfter = Math.max(
|
299
|
+
me.minPollInterval,
|
300
|
+
rateLimitedSeconds * 1000
|
301
|
+
);
|
302
|
+
} else if (abortedByClient) {
|
303
|
+
// Immediately trigger another poll
|
304
|
+
startNextRequestAfter = me.minPollInterval;
|
305
|
+
} else if (failCount > 2) {
|
306
|
+
// Linear backoff up to maxPollInterval
|
307
|
+
startNextRequestAfter = Math.min(
|
308
|
+
me.callbackInterval * failCount,
|
309
|
+
me.maxPollInterval
|
310
|
+
);
|
311
|
+
} else if (inLongPollingMode && gotData) {
|
312
|
+
// Immediately trigger another poll
|
313
|
+
startNextRequestAfter = me.minPollInterval;
|
299
314
|
} else {
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
if (interval > me.maxPollInterval) {
|
307
|
-
interval = me.maxPollInterval;
|
308
|
-
}
|
315
|
+
// Trigger next poll N seconds after the last one **started**
|
316
|
+
var targetRequestInterval = inLongPollingMode
|
317
|
+
? me.callbackInterval
|
318
|
+
: me.backgroundCallbackInterval;
|
319
|
+
|
320
|
+
var elapsedSinceLastAjaxStarted = new Date() - lastAjaxStartedAt;
|
309
321
|
|
310
|
-
|
322
|
+
startNextRequestAfter =
|
323
|
+
targetRequestInterval - elapsedSinceLastAjaxStarted;
|
311
324
|
|
312
|
-
if (
|
313
|
-
|
325
|
+
if (startNextRequestAfter < 100) {
|
326
|
+
startNextRequestAfter = 100;
|
314
327
|
}
|
315
328
|
}
|
316
329
|
} catch (e) {
|
@@ -328,7 +341,7 @@
|
|
328
341
|
pollTimeout = setTimeout(function () {
|
329
342
|
pollTimeout = null;
|
330
343
|
poll();
|
331
|
-
},
|
344
|
+
}, startNextRequestAfter);
|
332
345
|
}
|
333
346
|
|
334
347
|
me.longPoll = null;
|
@@ -367,7 +380,9 @@
|
|
367
380
|
totalAjaxFailures
|
368
381
|
);
|
369
382
|
console.log(
|
370
|
-
"Last ajax call: " +
|
383
|
+
"Last ajax call: " +
|
384
|
+
(new Date() - lastAjaxStartedAt) / 1000 +
|
385
|
+
" seconds ago"
|
371
386
|
);
|
372
387
|
},
|
373
388
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: message_bus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|