message_bus 4.2.0 → 4.3.1

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.
data/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "homepage": "https://github.com/discourse/message_bus#readme",
22
22
  "devDependencies": {
23
- "eslint": "^7.27.0",
23
+ "eslint": "^8.31.0",
24
24
  "jasmine-browser-runner": "^0.10.0",
25
25
  "jasmine-core": "^3.10.1"
26
26
  }
@@ -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.statusText = 'OK';
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 'text/plain; charset=utf-8';
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
- describe("Messagebus", function() {
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 lastAjax;
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 aborted = false;
159
+ var abortedByClient = false;
160
160
  var rateLimited = false;
161
161
  var rateLimitedSeconds;
162
162
 
163
- lastAjax = new Date();
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
- aborted = true;
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 interval;
293
+ var inLongPollingMode = shouldLongPoll();
294
+ var startNextRequestAfter;
294
295
  try {
295
296
  if (rateLimited) {
296
- interval = Math.max(me.minPollInterval, rateLimitedSeconds * 1000);
297
- } else if (gotData || aborted) {
298
- interval = me.minPollInterval;
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
- interval = me.callbackInterval;
301
- if (failCount > 2) {
302
- interval = interval * failCount;
303
- } else if (!shouldLongPoll()) {
304
- interval = me.backgroundCallbackInterval;
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
- interval -= new Date() - lastAjax;
322
+ startNextRequestAfter =
323
+ targetRequestInterval - elapsedSinceLastAjaxStarted;
311
324
 
312
- if (interval < 100) {
313
- interval = 100;
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
- }, interval);
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: " + (new Date() - lastAjax) / 1000 + " seconds ago"
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.2.0
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: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack