message_bus 4.3.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
  });
@@ -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.3.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-11-07 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