message_bus 3.3.3 → 3.3.7

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +21 -0
  3. data/.github/workflows/ci.yml +71 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +3 -1
  6. data/CHANGELOG +36 -8
  7. data/DEV.md +7 -0
  8. data/Gemfile +0 -25
  9. data/LICENSE +1 -1
  10. data/README.md +34 -15
  11. data/Rakefile +13 -8
  12. data/assets/message-bus-ajax.js +4 -10
  13. data/assets/message-bus.js +69 -76
  14. data/bench/codecs/all_codecs.rb +39 -0
  15. data/bench/codecs/marshal.rb +11 -0
  16. data/bench/codecs/packed_string.rb +67 -0
  17. data/bench/codecs/string_hack.rb +47 -0
  18. data/bench/codecs_large_user_list.rb +29 -0
  19. data/bench/codecs_standard_message.rb +29 -0
  20. data/examples/bench/bench.lua +2 -2
  21. data/lib/message_bus/backends/base.rb +3 -5
  22. data/lib/message_bus/backends/memory.rb +0 -2
  23. data/lib/message_bus/backends/postgres.rb +7 -5
  24. data/lib/message_bus/backends/redis.rb +3 -5
  25. data/lib/message_bus/client.rb +3 -7
  26. data/lib/message_bus/codec/base.rb +18 -0
  27. data/lib/message_bus/codec/json.rb +15 -0
  28. data/lib/message_bus/codec/oj.rb +21 -0
  29. data/lib/message_bus/connection_manager.rb +1 -1
  30. data/lib/message_bus/distributed_cache.rb +2 -1
  31. data/lib/message_bus/http_client.rb +2 -2
  32. data/lib/message_bus/rack/diagnostics.rb +30 -8
  33. data/lib/message_bus/rack/middleware.rb +22 -16
  34. data/lib/message_bus/rack/thin_ext.rb +1 -1
  35. data/lib/message_bus/version.rb +1 -1
  36. data/lib/message_bus.rb +38 -23
  37. data/message_bus.gemspec +20 -5
  38. data/package-lock.json +3744 -0
  39. data/package.json +14 -4
  40. data/spec/assets/SpecHelper.js +6 -5
  41. data/spec/assets/message-bus.spec.js +9 -6
  42. data/spec/helpers.rb +17 -6
  43. data/spec/integration/http_client_spec.rb +1 -1
  44. data/spec/lib/message_bus/backend_spec.rb +12 -44
  45. data/spec/lib/message_bus/client_spec.rb +6 -6
  46. data/spec/lib/message_bus/distributed_cache_spec.rb +5 -7
  47. data/spec/lib/message_bus/multi_process_spec.rb +1 -1
  48. data/spec/lib/message_bus/rack/middleware_spec.rb +16 -5
  49. data/spec/lib/message_bus_spec.rb +18 -7
  50. data/spec/spec_helper.rb +8 -9
  51. data/spec/support/jasmine-browser.json +16 -0
  52. metadata +230 -13
  53. data/.travis.yml +0 -17
  54. data/lib/message_bus/em_ext.rb +0 -6
  55. data/spec/assets/support/jasmine.yml +0 -126
  56. data/spec/assets/support/jasmine_helper.rb +0 -11
  57. data/vendor/assets/javascripts/message-bus-ajax.js +0 -44
  58. data/vendor/assets/javascripts/message-bus.js +0 -556
@@ -1,556 +0,0 @@
1
- /*jshint bitwise: false*/
2
-
3
- (function (root, factory) {
4
- if (typeof define === 'function' && define.amd) {
5
- // AMD. Register as an anonymous module.
6
- define([], function (b) {
7
- // Also create a global in case some scripts
8
- // that are loaded still are looking for
9
- // a global even when an AMD loader is in use.
10
- return (root.MessageBus = factory());
11
- });
12
- } else {
13
- // Browser globals
14
- root.MessageBus = factory();
15
- }
16
- }(typeof self !== 'undefined' ? self : this, function () {
17
- "use strict";
18
-
19
- // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
20
- var uniqueId = function() {
21
- return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) {
22
- var r = (Math.random() * 16) | 0;
23
- var v = c === "x" ? r : (r & 0x3) | 0x8;
24
- return v.toString(16);
25
- });
26
- };
27
-
28
- var me;
29
- var delayPollTimeout;
30
- var ajaxInProgress = false;
31
- var started = false;
32
- var clientId = uniqueId();
33
- var callbacks = [];
34
- var queue = [];
35
- var interval = null;
36
- var failCount = 0;
37
- var baseUrl = "/";
38
- var paused = false;
39
- var later = [];
40
- var chunkedBackoff = 0;
41
- var stopped;
42
- var pollTimeout = null;
43
- var totalAjaxFailures = 0;
44
- var totalAjaxCalls = 0;
45
- var lastAjax;
46
-
47
- var isHidden = (function() {
48
- var prefixes = ["", "webkit", "ms", "moz"];
49
- var hiddenProperty;
50
- for (var i = 0; i < prefixes.length; i++) {
51
- var prefix = prefixes[i];
52
- var check = prefix + (prefix === "" ? "hidden" : "Hidden");
53
- if (document[check] !== undefined) {
54
- hiddenProperty = check;
55
- }
56
- }
57
-
58
- return function() {
59
- if (hiddenProperty !== undefined) {
60
- return document[hiddenProperty];
61
- } else {
62
- return !document.hasFocus;
63
- }
64
- };
65
- })();
66
-
67
- var hasLocalStorage = (function() {
68
- try {
69
- localStorage.setItem("mbTestLocalStorage", Date.now());
70
- localStorage.removeItem("mbTestLocalStorage");
71
- return true;
72
- } catch (e) {
73
- return false;
74
- }
75
- })();
76
-
77
- var updateLastAjax = function() {
78
- if (hasLocalStorage) {
79
- localStorage.setItem("__mbLastAjax", Date.now());
80
- }
81
- };
82
-
83
- var hiddenTabShouldWait = function() {
84
- if (hasLocalStorage && isHidden()) {
85
- var lastAjaxCall = parseInt(localStorage.getItem("__mbLastAjax"), 10);
86
- var deltaAjax = Date.now() - lastAjaxCall;
87
-
88
- return deltaAjax >= 0 && deltaAjax < me.minHiddenPollInterval;
89
- }
90
- return false;
91
- };
92
-
93
- var hasonprogress = new XMLHttpRequest().onprogress === null;
94
- var allowChunked = function() {
95
- return me.enableChunkedEncoding && hasonprogress;
96
- };
97
-
98
- var shouldLongPoll = function() {
99
- return (
100
- me.alwaysLongPoll ||
101
- (me.shouldLongPollCallback ? me.shouldLongPollCallback() : !isHidden())
102
- );
103
- };
104
-
105
- var processMessages = function(messages) {
106
- var gotData = false;
107
- if ((!messages) || (messages.length === 0)) { return false; }
108
-
109
- for (var i = 0; i < messages.length; i++) {
110
- var message = messages[i];
111
- for (var j = 0; j < callbacks.length; j++) {
112
- var callback = callbacks[j];
113
- if (callback.channel === message.channel) {
114
- callback.last_id = message.message_id;
115
- try {
116
- callback.func(message.data, message.global_id, message.message_id);
117
- } catch (e) {
118
- if (console.log) {
119
- console.log(
120
- "MESSAGE BUS FAIL: callback " +
121
- callback.channel +
122
- " caused exception " +
123
- e.stack
124
- );
125
- }
126
- }
127
- }
128
- if (message.channel === "/__status") {
129
- if (message.data[callback.channel] !== undefined) {
130
- callback.last_id = message.data[callback.channel];
131
- }
132
- }
133
- }
134
- }
135
-
136
- return true;
137
- };
138
-
139
- var reqSuccess = function(messages) {
140
- failCount = 0;
141
- if (paused) {
142
- if (messages) {
143
- for (var i = 0; i < messages.length; i++) {
144
- later.push(messages[i]);
145
- }
146
- }
147
- } else {
148
- return processMessages(messages);
149
- }
150
- return false;
151
- };
152
-
153
- var longPoller = function(poll, data) {
154
- if (ajaxInProgress) {
155
- // never allow concurrent ajax reqs
156
- return;
157
- }
158
-
159
- var gotData = false;
160
- var aborted = false;
161
- var rateLimited = false;
162
- var rateLimitedSeconds;
163
-
164
- lastAjax = new Date();
165
- totalAjaxCalls += 1;
166
- data.__seq = totalAjaxCalls;
167
-
168
- var longPoll = shouldLongPoll() && me.enableLongPolling;
169
- var chunked = longPoll && allowChunked();
170
- if (chunkedBackoff > 0) {
171
- chunkedBackoff--;
172
- chunked = false;
173
- }
174
-
175
- var headers = { "X-SILENCE-LOGGER": "true" };
176
- for (var name in me.headers) {
177
- headers[name] = me.headers[name];
178
- }
179
-
180
- if (!chunked) {
181
- headers["Dont-Chunk"] = "true";
182
- }
183
-
184
- var dataType = chunked ? "text" : "json";
185
-
186
- var handle_progress = function(payload, position) {
187
- var separator = "\r\n|\r\n";
188
- var endChunk = payload.indexOf(separator, position);
189
-
190
- if (endChunk === -1) {
191
- return position;
192
- }
193
-
194
- var chunk = payload.substring(position, endChunk);
195
- chunk = chunk.replace(/\r\n\|\|\r\n/g, separator);
196
-
197
- try {
198
- reqSuccess(JSON.parse(chunk));
199
- } catch (e) {
200
- if (console.log) {
201
- console.log("FAILED TO PARSE CHUNKED REPLY");
202
- console.log(data);
203
- }
204
- }
205
-
206
- return handle_progress(payload, endChunk + separator.length);
207
- };
208
-
209
- var disableChunked = function() {
210
- if (me.longPoll) {
211
- me.longPoll.abort();
212
- chunkedBackoff = 30;
213
- }
214
- };
215
-
216
- var setOnProgressListener = function(xhr) {
217
- var position = 0;
218
- // if it takes longer than 3000 ms to get first chunk, we have some proxy
219
- // this is messing with us, so just backoff from using chunked for now
220
- var chunkedTimeout = setTimeout(disableChunked, 3000);
221
- xhr.onprogress = function() {
222
- clearTimeout(chunkedTimeout);
223
- if (
224
- xhr.getResponseHeader("Content-Type") ===
225
- "application/json; charset=utf-8"
226
- ) {
227
- // not chunked we are sending json back
228
- chunked = false;
229
- return;
230
- }
231
- position = handle_progress(xhr.responseText, position);
232
- };
233
- };
234
- if (!me.ajax) {
235
- throw new Error("Either jQuery or the ajax adapter must be loaded");
236
- }
237
-
238
- updateLastAjax();
239
-
240
- ajaxInProgress = true;
241
- var req = me.ajax({
242
- url:
243
- me.baseUrl +
244
- "message-bus/" +
245
- me.clientId +
246
- "/poll" +
247
- (!longPoll ? "?dlp=t" : ""),
248
- data: data,
249
- cache: false,
250
- async: true,
251
- dataType: dataType,
252
- type: "POST",
253
- headers: headers,
254
- messageBus: {
255
- chunked: chunked,
256
- onProgressListener: function(xhr) {
257
- var position = 0;
258
- // if it takes longer than 3000 ms to get first chunk, we have some proxy
259
- // this is messing with us, so just backoff from using chunked for now
260
- var chunkedTimeout = setTimeout(disableChunked, 3000);
261
- return (xhr.onprogress = function() {
262
- clearTimeout(chunkedTimeout);
263
- if (
264
- xhr.getResponseHeader("Content-Type") ===
265
- "application/json; charset=utf-8"
266
- ) {
267
- chunked = false; // not chunked, we are sending json back
268
- } else {
269
- position = handle_progress(xhr.responseText, position);
270
- }
271
- });
272
- }
273
- },
274
- xhr: function() {
275
- var xhr = jQuery.ajaxSettings.xhr();
276
- if (!chunked) {
277
- return xhr;
278
- }
279
- this.messageBus.onProgressListener(xhr);
280
- return xhr;
281
- },
282
- success: function(messages) {
283
- if (!chunked) {
284
- // we may have requested text so jQuery will not parse
285
- if (typeof messages === "string") {
286
- messages = JSON.parse(messages);
287
- }
288
- gotData = reqSuccess(messages);
289
- }
290
- },
291
- error: function(xhr, textStatus, err) {
292
- if (xhr.status === 429) {
293
- var tryAfter =
294
- parseInt(
295
- xhr.getResponseHeader && xhr.getResponseHeader("Retry-After")
296
- ) || 0;
297
- tryAfter = tryAfter || 0;
298
- if (tryAfter < 15) {
299
- tryAfter = 15;
300
- }
301
- rateLimitedSeconds = tryAfter;
302
- rateLimited = true;
303
- } else if (textStatus === "abort") {
304
- aborted = true;
305
- } else {
306
- failCount += 1;
307
- totalAjaxFailures += 1;
308
- }
309
- },
310
- complete: function() {
311
- ajaxInProgress = false;
312
-
313
- var interval;
314
- try {
315
- if (rateLimited) {
316
- interval = Math.max(me.minPollInterval, rateLimitedSeconds * 1000);
317
- } else if (gotData || aborted) {
318
- interval = me.minPollInterval;
319
- } else {
320
- interval = me.callbackInterval;
321
- if (failCount > 2) {
322
- interval = interval * failCount;
323
- } else if (!shouldLongPoll()) {
324
- interval = me.backgroundCallbackInterval;
325
- }
326
- if (interval > me.maxPollInterval) {
327
- interval = me.maxPollInterval;
328
- }
329
-
330
- interval -= new Date() - lastAjax;
331
-
332
- if (interval < 100) {
333
- interval = 100;
334
- }
335
- }
336
- } catch (e) {
337
- if (console.log && e.message) {
338
- console.log("MESSAGE BUS FAIL: " + e.message);
339
- }
340
- }
341
-
342
- if (pollTimeout) {
343
- clearTimeout(pollTimeout);
344
- pollTimeout = null;
345
- }
346
-
347
- if (started) {
348
- pollTimeout = setTimeout(function() {
349
- pollTimeout = null;
350
- poll();
351
- }, interval);
352
- }
353
-
354
- me.longPoll = null;
355
- }
356
- });
357
-
358
- return req;
359
- };
360
-
361
- me = {
362
- /* shared between all tabs */
363
- minHiddenPollInterval: 1500,
364
- enableChunkedEncoding: true,
365
- enableLongPolling: true,
366
- callbackInterval: 15000,
367
- backgroundCallbackInterval: 60000,
368
- minPollInterval: 100,
369
- maxPollInterval: 3 * 60 * 1000,
370
- callbacks: callbacks,
371
- clientId: clientId,
372
- alwaysLongPoll: false,
373
- shouldLongPollCallback: undefined,
374
- baseUrl: baseUrl,
375
- headers: {},
376
- ajax: typeof jQuery !== "undefined" && jQuery.ajax,
377
- diagnostics: function() {
378
- console.log("Stopped: " + stopped + " Started: " + started);
379
- console.log("Current callbacks");
380
- console.log(callbacks);
381
- console.log(
382
- "Total ajax calls: " +
383
- totalAjaxCalls +
384
- " Recent failure count: " +
385
- failCount +
386
- " Total failures: " +
387
- totalAjaxFailures
388
- );
389
- console.log(
390
- "Last ajax call: " + (new Date() - lastAjax) / 1000 + " seconds ago"
391
- );
392
- },
393
-
394
- pause: function() {
395
- paused = true;
396
- },
397
-
398
- resume: function() {
399
- paused = false;
400
- processMessages(later);
401
- later = [];
402
- },
403
-
404
- stop: function() {
405
- stopped = true;
406
- started = false;
407
- if (delayPollTimeout) {
408
- clearTimeout(delayPollTimeout);
409
- delayPollTimeout = null;
410
- }
411
- if (me.longPoll) {
412
- me.longPoll.abort();
413
- }
414
- },
415
-
416
- // Start polling
417
- start: function() {
418
- if (started) return;
419
- started = true;
420
- stopped = false;
421
-
422
- var poll = function() {
423
- if (stopped) {
424
- return;
425
- }
426
-
427
- if (callbacks.length === 0 || hiddenTabShouldWait()) {
428
- if (!delayPollTimeout) {
429
- delayPollTimeout = setTimeout(function() {
430
- delayPollTimeout = null;
431
- poll();
432
- }, parseInt(500 + Math.random() * 500));
433
- }
434
- return;
435
- }
436
-
437
- var data = {};
438
- for (var i = 0; i < callbacks.length; i++) {
439
- data[callbacks[i].channel] = callbacks[i].last_id;
440
- }
441
-
442
- // could possibly already be started
443
- // notice the delay timeout above
444
- if (!me.longPoll) {
445
- me.longPoll = longPoller(poll, data);
446
- }
447
- };
448
-
449
- // monitor visibility, issue a new long poll when the page shows
450
- if (document.addEventListener && "hidden" in document) {
451
- me.visibilityEvent = document.addEventListener(
452
- "visibilitychange",
453
- function() {
454
- if (!document.hidden && !me.longPoll && pollTimeout) {
455
- clearTimeout(pollTimeout);
456
- clearTimeout(delayPollTimeout);
457
-
458
- delayPollTimeout = null;
459
- pollTimeout = null;
460
- poll();
461
- }
462
- }
463
- );
464
- }
465
-
466
- poll();
467
- },
468
-
469
- status: function() {
470
- if (paused) {
471
- return "paused";
472
- } else if (started) {
473
- return "started";
474
- } else if (stopped) {
475
- return "stopped";
476
- } else {
477
- throw "Cannot determine current status";
478
- }
479
- },
480
-
481
- // Subscribe to a channel
482
- // if lastId is 0 or larger, it will recieve messages AFTER that id
483
- // if lastId is negative it will perform lookbehind
484
- // -1 will subscribe to all new messages
485
- // -2 will recieve last message + all new messages
486
- // -3 will recieve last 2 messages + all new messages
487
- // if undefined will default to -1
488
- subscribe: function(channel, func, lastId) {
489
- if (!started && !stopped) {
490
- me.start();
491
- }
492
-
493
- if (lastId === null || typeof lastId === "undefined") {
494
- lastId = -1;
495
- } else if (typeof lastId !== "number") {
496
- throw "lastId has type " + typeof lastId + " but a number was expected.";
497
- }
498
-
499
- if (typeof channel !== "string") {
500
- throw "Channel name must be a string!";
501
- }
502
-
503
- callbacks.push({
504
- channel: channel,
505
- func: func,
506
- last_id: lastId
507
- });
508
- if (me.longPoll) {
509
- me.longPoll.abort();
510
- }
511
-
512
- return func;
513
- },
514
-
515
- // Unsubscribe from a channel
516
- unsubscribe: function(channel, func) {
517
- // TODO allow for globbing in the middle of a channel name
518
- // like /something/*/something
519
- // at the moment we only support globbing /something/*
520
- var glob = false;
521
- if (channel.indexOf("*", channel.length - 1) !== -1) {
522
- channel = channel.substr(0, channel.length - 1);
523
- glob = true;
524
- }
525
-
526
- var removed = false;
527
-
528
- for (var i = callbacks.length - 1; i >= 0; i--) {
529
- var callback = callbacks[i];
530
- var keep;
531
-
532
- if (glob) {
533
- keep = callback.channel.substr(0, channel.length) !== channel;
534
- } else {
535
- keep = callback.channel !== channel;
536
- }
537
-
538
- if (!keep && func && callback.func !== func) {
539
- keep = true;
540
- }
541
-
542
- if (!keep) {
543
- callbacks.splice(i, 1);
544
- removed = true;
545
- }
546
- }
547
-
548
- if (removed && me.longPoll) {
549
- me.longPoll.abort();
550
- }
551
-
552
- return removed;
553
- }
554
- };
555
- return me;
556
- }));