message_bus 3.1.0 → 3.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of message_bus might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe33cef07b7712b1bbcc2ab444e123fd1121665a5ecf1db7fb24dba190e48b38
4
- data.tar.gz: 325300ad20aeb221d468326247c7755022dc8e594e8de1311601dec89b4a2c2b
3
+ metadata.gz: 2df4ed426f8b349c63bd56de7f73f846a65cef0a689de7c3d5a017a7eabaf2ac
4
+ data.tar.gz: a1f05881617167d9b680c8a1ac192fdd02dea903e1ff4b5fa692fe26c4533633
5
5
  SHA512:
6
- metadata.gz: 216a6156aad68865beb14a519bda4f709e10d96bcc8c58fa4bc042ef596fd7563926517f5b5c82d547f4ff26ce7bd3ac0a74998ce9cf7b4b2d66c792f049c602
7
- data.tar.gz: fb521cf6b6928586f7ba1f2da19a8def8d3f77acc0d6cb1c417fe03d8a36f0af8a470c29cc26ce400c12ba8648223ded2de43109a4616b5c89b967ed4e6a67aa
6
+ metadata.gz: a9feb43d0b0725bcc3190704f9f673bf3b7ce2e6d1c5c6e70212f21ebad241e082c0095ed08b1144d73cbef50f0585f858cc4cd1215e72e668eeb37df4d2f1bd
7
+ data.tar.gz: e69da95765f2017b005977486f4be3077055357b9ff17a70894a4a0d95c2e9fa02cd2b50dc14d301961d07eccb097733be378f2b05fb3edbd0c1b9a32376999c
@@ -1,9 +1,9 @@
1
1
  before_install: gem install bundler
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4
5
4
  - 2.5
6
5
  - 2.6
6
+ - 2.7
7
7
  gemfile:
8
8
  - Gemfile
9
9
  addons:
data/CHANGELOG CHANGED
@@ -1,3 +1,49 @@
1
+ - Unrelease
2
+
3
+ 18-09-2020
4
+
5
+ - Version 3.3.3
6
+
7
+ - FIX: `queue_in_memory` option not being passed to the backends.
8
+ - FIX: `MessageBus::DistributedCache#publish` should raise on error.
9
+
10
+ On the redis backend, any errors encountered during `MessageBus#publish`
11
+ will add the message into an in memory queue and silently swallow the
12
+ error. While this is behavior is OK for normal message_bus usage, it may
13
+ lead to inconsistency when using `DistributedCache`. If a process
14
+ doesn't publish successfully to another process, it will still update
15
+ its in memory cache leaving the other processes unaware. As such, the
16
+ distributed cache is out of sync and will require another successful
17
+ write to the cache to resync all the caches.
18
+
19
+ 15-09-2020
20
+
21
+ - Version 3.3.2
22
+
23
+ - FIX: In the JavaScript client throw when when lastId is given but is not a number.
24
+ - FEATURE: raise when attempting to publish to invalid targets
25
+ - Log when DistributedCache encounters an error when publishing.
26
+
27
+ 09-06-2020
28
+
29
+ - Version 3.3.1
30
+
31
+ - FIX: Disconnect Redis conn when rescuing errors in global subscribe.
32
+ - FIX: `MessageBus::Backends::Redis#global_subscribe` not closing Redis connections.
33
+
34
+ 15-05-2020
35
+
36
+ - Version 3.3.0
37
+
38
+ - FEATURE: `MessageBus.base_route=` to alter the route that message bus will listen on.
39
+
40
+ 07-05-2020
41
+
42
+ - Version 3.2.0
43
+
44
+ - FIX: compatability with Rails 6.0.3, note: apps without ActionDispatch::Flash may stop working after this upgrade
45
+ to correct this disable middleware injection with `config.skip_message_bus_middleware = true` and configure middleware by hand with `app.middleware.use(MessageBus::Rack::Middleware)`
46
+
1
47
  28-04-2020
2
48
 
3
49
  - Version 3.1.0
data/README.md CHANGED
@@ -304,6 +304,11 @@ MessageBus.subscribe("/channel", function(data){
304
304
  MessageBus.subscribe("/channel", function(data){
305
305
  // data shipped from server
306
306
  }, -3);
307
+
308
+ // you will get the entire backlog
309
+ MessageBus.subscribe("/channel", function(data){
310
+ // data shipped from server
311
+ }, 0);
307
312
  ```
308
313
 
309
314
  #### JavaScript Client settings
@@ -319,7 +324,7 @@ minPollInterval|100|When polling requests succeed, this is the minimum amount of
319
324
  maxPollInterval|180000|If request to the server start failing, MessageBus will backoff, this is the upper limit of the backoff.
320
325
  alwaysLongPoll|false|For debugging you may want to disable the "is browser in background" check and always long-poll
321
326
  shouldLongPollCallback|undefined|A callback returning true or false that determines if we should long-poll or not, if unset ignore and simply depend on window visibility.
322
- baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there
327
+ baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there. See `MessageBus.base_route=` on how to configure the MessageBus server to listen on a sub-path.
323
328
  ajax|$.ajax falling back to XMLHttpRequest|MessageBus will first attempt to use jQuery and then fallback to a plain XMLHttpRequest version that's contained in the `message-bus-ajax.js` file. `message-bus-ajax.js` must be loaded after `message-bus.js` for it to be used. You may override this option with a function that implements an ajax request by some other means
324
329
  headers|{}|Extra headers to be include with requests. Properties and values of object must be valid values for HTTP Headers, i.e. no spaces or control characters.
325
330
  minHiddenPollInterval|1500|Time to wait between poll requests performed by background or hidden tabs and windows, shared state via localStorage
@@ -341,8 +346,6 @@ enableChunkedEncoding|true|Allows streaming of message bus data over the HTTP co
341
346
 
342
347
  `MessageBus.status()` : Returns status (started, paused, stopped)
343
348
 
344
- `MessageBus.noConflict()` : Removes MessageBus from the global namespace by replacing it with whatever was present before MessageBus was loaded. Returns a reference to the MessageBus object.
345
-
346
349
  `MessageBus.diagnostics()` : Returns a log that may be used for diagnostics on the status of message bus.
347
350
 
348
351
  #### Ruby
data/Rakefile CHANGED
@@ -92,3 +92,7 @@ end
92
92
 
93
93
  desc "Run all tests, link checks and confirms documentation compiles without error"
94
94
  task default: [:spec, :rubocop, :test_doc]
95
+
96
+ Rake::Task['release'].enhance do
97
+ sh "npm publish"
98
+ end
@@ -1,55 +1,52 @@
1
1
  /*jshint bitwise: false*/
2
- (function(global, document, undefined) {
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 () {
3
17
  "use strict";
4
- var previousMessageBus = global.MessageBus;
5
18
 
6
19
  // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
7
- var callbacks,
8
- clientId,
9
- failCount,
10
- shouldLongPoll,
11
- queue,
12
- responseCallbacks,
13
- uniqueId,
14
- baseUrl;
15
- var me,
16
- started,
17
- stopped,
18
- longPoller,
19
- pollTimeout,
20
- paused,
21
- later,
22
- jQuery,
23
- interval,
24
- chunkedBackoff;
25
- var delayPollTimeout;
26
-
27
- var ajaxInProgress = false;
28
-
29
- uniqueId = function() {
20
+ var uniqueId = function() {
30
21
  return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) {
31
- var r, v;
32
- r = (Math.random() * 16) | 0;
33
- v = c === "x" ? r : (r & 0x3) | 0x8;
22
+ var r = (Math.random() * 16) | 0;
23
+ var v = c === "x" ? r : (r & 0x3) | 0x8;
34
24
  return v.toString(16);
35
25
  });
36
26
  };
37
27
 
38
- clientId = uniqueId();
39
- responseCallbacks = {};
40
- callbacks = [];
41
- queue = [];
42
- interval = null;
43
- failCount = 0;
44
- baseUrl = "/";
45
- paused = false;
46
- later = [];
47
- chunkedBackoff = 0;
48
- jQuery = global.jQuery;
49
- var hiddenProperty;
50
-
51
- (function() {
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() {
52
48
  var prefixes = ["", "webkit", "ms", "moz"];
49
+ var hiddenProperty;
53
50
  for (var i = 0; i < prefixes.length; i++) {
54
51
  var prefix = prefixes[i];
55
52
  var check = prefix + (prefix === "" ? "hidden" : "Hidden");
@@ -57,15 +54,15 @@
57
54
  hiddenProperty = check;
58
55
  }
59
56
  }
60
- })();
61
57
 
62
- var isHidden = function() {
63
- if (hiddenProperty !== undefined) {
64
- return document[hiddenProperty];
65
- } else {
66
- return !document.hasFocus;
67
- }
68
- };
58
+ return function() {
59
+ if (hiddenProperty !== undefined) {
60
+ return document[hiddenProperty];
61
+ } else {
62
+ return !document.hasFocus;
63
+ }
64
+ };
65
+ })();
69
66
 
70
67
  var hasLocalStorage = (function() {
71
68
  try {
@@ -98,24 +95,19 @@
98
95
  return me.enableChunkedEncoding && hasonprogress;
99
96
  };
100
97
 
101
- shouldLongPoll = function() {
98
+ var shouldLongPoll = function() {
102
99
  return (
103
100
  me.alwaysLongPoll ||
104
101
  (me.shouldLongPollCallback ? me.shouldLongPollCallback() : !isHidden())
105
102
  );
106
103
  };
107
104
 
108
- var totalAjaxFailures = 0;
109
- var totalAjaxCalls = 0;
110
- var lastAjax;
111
-
112
105
  var processMessages = function(messages) {
113
106
  var gotData = false;
114
- if (!messages) return false; // server unexpectedly closed connection
107
+ if ((!messages) || (messages.length === 0)) { return false; }
115
108
 
116
109
  for (var i = 0; i < messages.length; i++) {
117
110
  var message = messages[i];
118
- gotData = true;
119
111
  for (var j = 0; j < callbacks.length; j++) {
120
112
  var callback = callbacks[j];
121
113
  if (callback.channel === message.channel) {
@@ -141,7 +133,7 @@
141
133
  }
142
134
  }
143
135
 
144
- return gotData;
136
+ return true;
145
137
  };
146
138
 
147
139
  var reqSuccess = function(messages) {
@@ -158,7 +150,7 @@
158
150
  return false;
159
151
  };
160
152
 
161
- longPoller = function(poll, data) {
153
+ var longPoller = function(poll, data) {
162
154
  if (ajaxInProgress) {
163
155
  // never allow concurrent ajax reqs
164
156
  return;
@@ -180,9 +172,7 @@
180
172
  chunked = false;
181
173
  }
182
174
 
183
- var headers = {
184
- "X-SILENCE-LOGGER": "true"
185
- };
175
+ var headers = { "X-SILENCE-LOGGER": "true" };
186
176
  for (var name in me.headers) {
187
177
  headers[name] = me.headers[name];
188
178
  }
@@ -383,11 +373,7 @@
383
373
  shouldLongPollCallback: undefined,
384
374
  baseUrl: baseUrl,
385
375
  headers: {},
386
- ajax: jQuery && jQuery.ajax,
387
- noConflict: function() {
388
- global.MessageBus = global.MessageBus.previousMessageBus;
389
- return this;
390
- },
376
+ ajax: typeof jQuery !== "undefined" && jQuery.ajax,
391
377
  diagnostics: function() {
392
378
  console.log("Stopped: " + stopped + " Started: " + started);
393
379
  console.log("Current callbacks");
@@ -429,15 +415,11 @@
429
415
 
430
416
  // Start polling
431
417
  start: function() {
432
- var poll;
433
-
434
418
  if (started) return;
435
419
  started = true;
436
420
  stopped = false;
437
421
 
438
- poll = function() {
439
- var data;
440
-
422
+ var poll = function() {
441
423
  if (stopped) {
442
424
  return;
443
425
  }
@@ -452,7 +434,7 @@
452
434
  return;
453
435
  }
454
436
 
455
- data = {};
437
+ var data = {};
456
438
  for (var i = 0; i < callbacks.length; i++) {
457
439
  data[callbacks[i].channel] = callbacks[i].last_id;
458
440
  }
@@ -466,7 +448,7 @@
466
448
 
467
449
  // monitor visibility, issue a new long poll when the page shows
468
450
  if (document.addEventListener && "hidden" in document) {
469
- me.visibilityEvent = global.document.addEventListener(
451
+ me.visibilityEvent = document.addEventListener(
470
452
  "visibilitychange",
471
453
  function() {
472
454
  if (!document.hidden && !me.longPoll && pollTimeout) {
@@ -502,13 +484,16 @@
502
484
  // -1 will subscribe to all new messages
503
485
  // -2 will recieve last message + all new messages
504
486
  // -3 will recieve last 2 messages + all new messages
487
+ // if undefined will default to -1
505
488
  subscribe: function(channel, func, lastId) {
506
489
  if (!started && !stopped) {
507
490
  me.start();
508
491
  }
509
492
 
510
- if (typeof lastId !== "number") {
493
+ if (lastId === null || typeof lastId === "undefined") {
511
494
  lastId = -1;
495
+ } else if (typeof lastId !== "number") {
496
+ throw "lastId has type " + typeof lastId + " but a number was expected.";
512
497
  }
513
498
 
514
499
  if (typeof channel !== "string") {
@@ -532,7 +517,7 @@
532
517
  // TODO allow for globbing in the middle of a channel name
533
518
  // like /something/*/something
534
519
  // at the moment we only support globbing /something/*
535
- var glob;
520
+ var glob = false;
536
521
  if (channel.indexOf("*", channel.length - 1) !== -1) {
537
522
  channel = channel.substr(0, channel.length - 1);
538
523
  glob = true;
@@ -567,5 +552,5 @@
567
552
  return removed;
568
553
  }
569
554
  };
570
- global.MessageBus = me;
571
- })(window, document);
555
+ return me;
556
+ }));
@@ -20,6 +20,7 @@ end
20
20
  module MessageBus; end
21
21
  MessageBus::BACKENDS = {}
22
22
  class MessageBus::InvalidMessage < StandardError; end
23
+ class MessageBus::InvalidMessageTarget < MessageBus::InvalidMessage; end
23
24
  class MessageBus::BusDestroyed < StandardError; end
24
25
 
25
26
  # The main server-side interface to a message bus for the purposes of
@@ -149,6 +150,19 @@ module MessageBus::Implementation
149
150
  @config[:long_polling_interval] || 25 * 1000
150
151
  end
151
152
 
153
+ # @param [String] route Message bus will listen to requests on this route.
154
+ # @return [void]
155
+ def base_route=(route)
156
+ configure(base_route: route.gsub(Regexp.new('\A(?!/)|(?<!/)\Z|//+'), "/"))
157
+ end
158
+
159
+ # @return [String] the route that message bus will respond to. If not
160
+ # explicitly set, defaults to "/". Requests to "#{base_route}message-bus/*" will be handled
161
+ # by the message bus server.
162
+ def base_route
163
+ @config[:base_route] || "/"
164
+ end
165
+
152
166
  # @return [Boolean] whether the bus is disabled or not
153
167
  def off?
154
168
  @off
@@ -316,6 +330,7 @@ module MessageBus::Implementation
316
330
  #
317
331
  # @raise [MessageBus::BusDestroyed] if the bus is destroyed
318
332
  # @raise [MessageBus::InvalidMessage] if attempting to put permission restrictions on a globally-published message
333
+ # @raise [MessageBus::InvalidMessageTarget] if attempting to publish to a empty group of users
319
334
  def publish(channel, data, opts = nil)
320
335
  return if @off
321
336
 
@@ -335,7 +350,13 @@ module MessageBus::Implementation
335
350
  site_id = opts[:site_id]
336
351
  end
337
352
 
338
- raise ::MessageBus::InvalidMessage if (user_ids || group_ids) && global?(channel)
353
+ if (user_ids || group_ids) && global?(channel)
354
+ raise ::MessageBus::InvalidMessage
355
+ end
356
+
357
+ if (user_ids == []) || (group_ids == []) || (client_ids == [])
358
+ raise ::MessageBus::InvalidMessageTarget
359
+ end
339
360
 
340
361
  encoded_data = JSON.dump(
341
362
  data: data,
@@ -344,13 +365,17 @@ module MessageBus::Implementation
344
365
  client_ids: client_ids
345
366
  )
346
367
 
347
- channel_opts = nil
368
+ channel_opts = {}
369
+
370
+ if opts
371
+ if ((age = opts[:max_backlog_age]) || (size = opts[:max_backlog_size]))
372
+ channel_opts[:max_backlog_size] = size,
373
+ channel_opts[:max_backlog_age] = age
374
+ end
348
375
 
349
- if opts && ((age = opts[:max_backlog_age]) || (size = opts[:max_backlog_size]))
350
- channel_opts = {
351
- max_backlog_size: size,
352
- max_backlog_age: age
353
- }
376
+ if opts.has_key?(:queue_in_memory)
377
+ channel_opts[:queue_in_memory] = opts[:queue_in_memory]
378
+ end
354
379
  end
355
380
 
356
381
  encoded_channel_name = encode_channel_name(channel, site_id)
@@ -251,11 +251,11 @@ LUA
251
251
 
252
252
  # (see Base#global_unsubscribe)
253
253
  def global_unsubscribe
254
- if @redis_global
255
- # new connection to avoid deadlock
256
- new_redis_connection.publish(redis_channel_name, UNSUB_MESSAGE)
257
- @redis_global.disconnect
258
- @redis_global = nil
254
+ begin
255
+ new_redis = new_redis_connection
256
+ new_redis.publish(redis_channel_name, UNSUB_MESSAGE)
257
+ ensure
258
+ new_redis&.disconnect!
259
259
  end
260
260
  end
261
261
 
@@ -278,13 +278,13 @@ LUA
278
278
  end
279
279
 
280
280
  begin
281
- @redis_global = new_redis_connection
281
+ global_redis = new_redis_connection
282
282
 
283
283
  if highest_id
284
284
  clear_backlog.call(&blk)
285
285
  end
286
286
 
287
- @redis_global.subscribe(redis_channel_name) do |on|
287
+ global_redis.subscribe(redis_channel_name) do |on|
288
288
  on.subscribe do
289
289
  if highest_id
290
290
  clear_backlog.call(&blk)
@@ -298,7 +298,7 @@ LUA
298
298
 
299
299
  on.message do |_c, m|
300
300
  if m == UNSUB_MESSAGE
301
- @redis_global.unsubscribe
301
+ global_redis.unsubscribe
302
302
  return
303
303
  end
304
304
  m = MessageBus::Message.decode m
@@ -318,9 +318,12 @@ LUA
318
318
  end
319
319
  end
320
320
  rescue => error
321
- @logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack #{error.backtrace}"
321
+ @logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack #{error.backtrace.join("\n")}"
322
322
  sleep 1
323
+ global_redis&.disconnect!
323
324
  retry
325
+ ensure
326
+ global_redis&.disconnect!
324
327
  end
325
328
  end
326
329
 
@@ -133,7 +133,6 @@ class MessageBus::Client
133
133
  user_allowed = false
134
134
  group_allowed = false
135
135
 
136
- # this is an inconsistency we should fix anyway, publishing `user_ids: nil` should work same as groups
137
136
  has_users = msg.user_ids && msg.user_ids.length > 0
138
137
  has_groups = msg.group_ids && msg.group_ids.length > 0
139
138
 
@@ -16,11 +16,12 @@ module MessageBus
16
16
 
17
17
  attr_accessor :app_version
18
18
 
19
- def initialize(message_bus = nil)
19
+ def initialize(message_bus = nil, publish_queue_in_memory: true)
20
20
  @subscribers = []
21
21
  @subscribed = false
22
22
  @lock = Mutex.new
23
23
  @message_bus = message_bus || MessageBus
24
+ @publish_queue_in_memory = publish_queue_in_memory
24
25
  end
25
26
 
26
27
  def subscribers
@@ -75,7 +76,11 @@ module MessageBus
75
76
  message[:origin] = hash.identity
76
77
  message[:hash_key] = hash.key
77
78
  message[:app_version] = @app_version if @app_version
78
- @message_bus.publish(CHANNEL_NAME, message, user_ids: [-1])
79
+
80
+ @message_bus.publish(CHANNEL_NAME, message,
81
+ user_ids: [-1],
82
+ queue_in_memory: @publish_queue_in_memory
83
+ )
79
84
  end
80
85
 
81
86
  def set(hash, key, value)
@@ -20,10 +20,10 @@ class MessageBus::Message < Struct.new(:global_id, :message_id, :channel, :data)
20
20
 
21
21
  # only tricky thing to encode is pipes in a channel name ... do a straight replace
22
22
  def encode
23
- global_id.to_s << "|" << message_id.to_s << "|" << channel.gsub("|", "$$123$$") << "|" << data
23
+ "#{global_id}|#{message_id}|#{channel.gsub("|", "$$123$$")}|#{data}"
24
24
  end
25
25
 
26
26
  def encode_without_ids
27
- channel.gsub("|", "$$123$$") << "|" << data
27
+ "#{channel.gsub("|", "$$123$$")}|#{data}"
28
28
  end
29
29
  end
@@ -16,9 +16,9 @@ class MessageBus::Rack::Diagnostics
16
16
  # Process an HTTP request from a subscriber client
17
17
  # @param [Rack::Request::Env] env the request environment
18
18
  def call(env)
19
- return @app.call(env) unless env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
19
+ return @app.call(env) unless env['PATH_INFO'].start_with? "#{@bus.base_route}message-bus/_diagnostics"
20
20
 
21
- route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
21
+ route = env['PATH_INFO'].split("#{@bus.base_route}message-bus/_diagnostics")[1]
22
22
 
23
23
  if @bus.is_admin_lookup.nil? || !@bus.is_admin_lookup.call(env)
24
24
  return [403, {}, ['not allowed']]
@@ -39,6 +39,10 @@ class MessageBus::Rack::Middleware
39
39
  @bus = config[:message_bus] || MessageBus
40
40
  @connection_manager = MessageBus::ConnectionManager.new(@bus)
41
41
  @started_listener = false
42
+ @base_route = "#{@bus.base_route}message-bus/"
43
+ @base_route_length = @base_route.length
44
+ @diagnostics_route = "#{@base_route}_diagnostics"
45
+ @broadcast_route = "#{@base_route}broadcast"
42
46
  start_listener unless @bus.off?
43
47
  end
44
48
 
@@ -54,7 +58,7 @@ class MessageBus::Rack::Middleware
54
58
  # Process an HTTP request from a subscriber client
55
59
  # @param [Rack::Request::Env] env the request environment
56
60
  def call(env)
57
- return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus\//
61
+ return @app.call(env) unless env['PATH_INFO'].start_with? @base_route
58
62
 
59
63
  handle_request(env)
60
64
  end
@@ -63,18 +67,18 @@ class MessageBus::Rack::Middleware
63
67
 
64
68
  def handle_request(env)
65
69
  # special debug/test route
66
- if @bus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'
70
+ if @bus.allow_broadcast? && env['PATH_INFO'] == @broadcast_route
67
71
  parsed = Rack::Request.new(env)
68
72
  @bus.publish parsed["channel"], parsed["data"]
69
73
  return [200, { "Content-Type" => "text/html" }, ["sent"]]
70
74
  end
71
75
 
72
- if env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
76
+ if env['PATH_INFO'].start_with? @diagnostics_route
73
77
  diags = MessageBus::Rack::Diagnostics.new(@app, message_bus: @bus)
74
78
  return diags.call(env)
75
79
  end
76
80
 
77
- client_id = env['PATH_INFO'].split("/")[2]
81
+ client_id = env['PATH_INFO'][@base_route_length..-1].split("/")[0]
78
82
  return [404, {}, ["not found"]] unless client_id
79
83
 
80
84
  user_id = @bus.user_id_lookup.call(env) if @bus.user_id_lookup
@@ -11,27 +11,29 @@ class MessageBus::Rails::Railtie < ::Rails::Railtie
11
11
  # the Rails app is configured that might be ActionDispatch::Session::CookieStore, or potentially
12
12
  # ActionDispatch::Session::ActiveRecordStore.
13
13
  #
14
- # To handle either case, we insert it before ActionDispatch::Flash.
15
- #
16
- # For APIs or apps that have ActionDispatch::Flash deleted from the middleware
17
- # stack we just push MessageBus to the bottom.
18
- if api_only?(app.config) || flash_middleware_deleted?(app.middleware)
19
- app.middleware.use(MessageBus::Rack::Middleware)
20
- else
21
- app.middleware.insert_before(ActionDispatch::Flash, MessageBus::Rack::Middleware)
14
+ # given https://github.com/rails/rails/commit/fedde239dcee256b417dc9bcfe5fef603bf0d952#diff-533a9a9cc17a8a899cb830626089e5f9
15
+ # there is no way of walking the stack for operations
16
+ if !skip_middleware?(app.config)
17
+ if api_only?(app.config)
18
+ app.middleware.use(MessageBus::Rack::Middleware)
19
+ else
20
+ app.middleware.insert_before(ActionDispatch::Flash, MessageBus::Rack::Middleware)
21
+ end
22
22
  end
23
23
 
24
24
  MessageBus.logger = Rails.logger
25
25
  end
26
26
 
27
+ def skip_middleware?(config)
28
+ return false if !config.respond_to?(:skip_message_bus_middleware)
29
+
30
+ config.skip_message_bus_middleware
31
+ end
32
+
27
33
  def api_only?(config)
28
- return false unless config.respond_to?(:api_only)
34
+ return false if !config.respond_to?(:api_only)
29
35
 
30
36
  config.api_only
31
37
  end
32
38
 
33
- def flash_middleware_deleted?(middleware)
34
- ops = middleware.instance_variable_get(:@operations)
35
- ops.any? { |m| m[0] == :delete && m[1].include?(ActionDispatch::Flash) }
36
- end
37
39
  end
@@ -45,7 +45,14 @@ class MessageBus::TimerThread
45
45
  while running
46
46
  @mutex.synchronize do
47
47
  running = @thread && @thread.alive?
48
- @thread.wakeup if running
48
+
49
+ if running
50
+ begin
51
+ @thread.wakeup
52
+ rescue ThreadError
53
+ raise if @thread.alive?
54
+ end
55
+ end
49
56
  end
50
57
  sleep 0
51
58
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MessageBus
4
- VERSION = "3.1.0"
4
+ VERSION = "3.3.3"
5
5
  end
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "message-bus-client",
3
+ "version": "3.3.0",
4
+ "description": "A message bus client in Javascript",
5
+ "main": "assets/message-bus.js",
6
+ "keywords": "es6, modules",
7
+ "files": ["assets/message-bus.js"],
8
+ "jsnext:main": "assets/message-bus.js",
9
+ "module": "assets/message-bus.js",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/discourse/message_bus.git"
13
+ },
14
+ "author": "Sam Saffron, Robin Ward",
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/discourse/message_bus/issues"
18
+ },
19
+ "homepage": "https://github.com/discourse/message_bus#readme"
20
+ }
@@ -75,15 +75,6 @@ describe("Messagebus", function() {
75
75
  })
76
76
  });
77
77
 
78
- it('removes itself from root namespace when noConflict is called', function(){
79
- expect(window.MessageBus).not.toBeUndefined();
80
- var mb = window.MessageBus;
81
- expect(mb).toEqual(window.MessageBus.noConflict());
82
- expect(window.MessageBus).toBeUndefined();
83
- // reset it so afterEach has something to work on
84
- window.MessageBus = mb;
85
- });
86
-
87
78
  it('respects minPollInterval setting with defaults', function(){
88
79
  expect(MessageBus.minPollInterval).toEqual(100);
89
80
  MessageBus.minPollInterval = 1000;
@@ -8,11 +8,13 @@ require 'rack/test'
8
8
  describe MessageBus::Rack::Middleware do
9
9
  include Rack::Test::Methods
10
10
  let(:extra_middleware) { nil }
11
+ let(:base_route) { nil }
11
12
 
12
13
  before do
13
14
  bus = @bus = MessageBus::Instance.new
14
15
  @bus.configure(MESSAGE_BUS_CONFIG)
15
16
  @bus.long_polling_enabled = false
17
+ @bus.base_route = base_route if base_route
16
18
 
17
19
  e_m = extra_middleware
18
20
  builder = Rack::Builder.new {
@@ -44,12 +46,27 @@ describe MessageBus::Rack::Middleware do
44
46
  @bus.long_polling_enabled = true
45
47
  end
46
48
 
49
+ describe "with altered base_route" do
50
+ let(:base_route) { "/base/route/" }
51
+
52
+ it "should respond as normal" do
53
+ post "/base/route/message-bus/ABC?dlp=t", '/foo1' => 0
54
+ @async_middleware.in_async?.must_equal false
55
+ last_response.ok?.must_equal true
56
+ end
57
+ end
58
+
47
59
  it "should respond right away if dlp=t" do
48
60
  post "/message-bus/ABC?dlp=t", '/foo1' => 0
49
61
  @async_middleware.in_async?.must_equal false
50
62
  last_response.ok?.must_equal true
51
63
  end
52
64
 
65
+ it "should respond with a 404 if the client_id is missing" do
66
+ post "/message-bus/?dlp=t", '/foo1' => 0
67
+ last_response.not_found?.must_equal true
68
+ end
69
+
53
70
  it "should respond right away to long polls that are polling on -1 with the last_id" do
54
71
  post "/message-bus/ABC", '/foo' => -1
55
72
  last_response.ok?.must_equal true
@@ -141,6 +158,19 @@ describe MessageBus::Rack::Middleware do
141
158
  last_response.status.must_equal 200
142
159
  end
143
160
 
161
+ describe "with an altered base_route" do
162
+ let(:base_route) { "/base/route/" }
163
+
164
+ it "should get a 200 with html for an authorized user" do
165
+ def @bus.is_admin_lookup
166
+ proc { |_| true }
167
+ end
168
+
169
+ get "/base/route/message-bus/_diagnostics"
170
+ last_response.status.must_equal 200
171
+ end
172
+ end
173
+
144
174
  it "should get the script it asks for" do
145
175
 
146
176
  def @bus.is_admin_lookup
@@ -243,6 +273,38 @@ describe MessageBus::Rack::Middleware do
243
273
  parsed[1]["data"].must_equal "borbs"
244
274
  end
245
275
 
276
+ it "should use the correct client ID" do
277
+ id = @bus.last_id('/foo')
278
+
279
+ client_id = "aBc123"
280
+ @bus.publish("/foo", "msg1", client_ids: [client_id])
281
+ @bus.publish("/foo", "msg2", client_ids: ["not_me#{client_id}"])
282
+
283
+ post "/message-bus/#{client_id}",
284
+ '/foo' => id
285
+
286
+ parsed = JSON.parse(last_response.body)
287
+ parsed.length.must_equal 2
288
+ parsed[0]["data"].must_equal("msg1")
289
+ parsed[1]["data"].wont_equal("msg2")
290
+ end
291
+
292
+ it "should use the correct client ID with additional path" do
293
+ id = @bus.last_id('/foo')
294
+
295
+ client_id = "aBc123"
296
+ @bus.publish("/foo", "msg1", client_ids: [client_id])
297
+ @bus.publish("/foo", "msg2", client_ids: ["not_me#{client_id}"])
298
+
299
+ post "/message-bus/#{client_id}/path/not/needed",
300
+ '/foo' => id
301
+
302
+ parsed = JSON.parse(last_response.body)
303
+ parsed.length.must_equal 2
304
+ parsed[0]["data"].must_equal("msg1")
305
+ parsed[1]["data"].wont_equal("msg2")
306
+ end
307
+
246
308
  it "should have no cross talk" do
247
309
  seq = 0
248
310
  @bus.site_id_lookup do
@@ -37,6 +37,23 @@ describe MessageBus do
37
37
  @bus.after_fork
38
38
  end
39
39
 
40
+ describe "#base_route=" do
41
+ it "adds leading and trailing slashes" do
42
+ @bus.base_route = "my/base/route"
43
+ @bus.base_route.must_equal '/my/base/route/'
44
+ end
45
+
46
+ it "leaves existing leading and trailing slashes" do
47
+ @bus.base_route = "/my/base/route/"
48
+ @bus.base_route.must_equal '/my/base/route/'
49
+ end
50
+
51
+ it "removes duplicate slashes" do
52
+ @bus.base_route = "//my///base/route"
53
+ @bus.base_route.must_equal '/my/base/route/'
54
+ end
55
+ end
56
+
40
57
  it "can subscribe from a point in time" do
41
58
  @bus.publish("/minion", "banana")
42
59
 
@@ -249,15 +266,37 @@ describe MessageBus do
249
266
  end
250
267
 
251
268
  it "should exception if publishing restricted messages to user" do
252
- lambda do
269
+ assert_raises(MessageBus::InvalidMessage) do
253
270
  @bus.publish("/global/test", "test", user_ids: [1])
254
- end.must_raise(MessageBus::InvalidMessage)
271
+ end
255
272
  end
256
273
 
257
274
  it "should exception if publishing restricted messages to group" do
258
- lambda do
275
+ assert_raises(MessageBus::InvalidMessage) do
259
276
  @bus.publish("/global/test", "test", user_ids: [1])
260
- end.must_raise(MessageBus::InvalidMessage)
277
+ end
278
+ end
279
+
280
+ it "should raise if we publish to an empty group or user list" do
281
+ assert_raises(MessageBus::InvalidMessageTarget) do
282
+ @bus.publish "/foo", "bar", user_ids: []
283
+ end
284
+
285
+ assert_raises(MessageBus::InvalidMessageTarget) do
286
+ @bus.publish "/foo", "bar", group_ids: []
287
+ end
288
+
289
+ assert_raises(MessageBus::InvalidMessageTarget) do
290
+ @bus.publish "/foo", "bar", client_ids: []
291
+ end
292
+
293
+ assert_raises(MessageBus::InvalidMessageTarget) do
294
+ @bus.publish "/foo", "bar", group_ids: [], user_ids: [1]
295
+ end
296
+
297
+ assert_raises(MessageBus::InvalidMessageTarget) do
298
+ @bus.publish "/foo", "bar", group_ids: [1], user_ids: []
299
+ end
261
300
  end
262
301
  end
263
302
 
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: 3.1.0
4
+ version: 3.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-28 00:00:00.000000000 Z
11
+ date: 2020-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -113,6 +113,7 @@ files:
113
113
  - lib/message_bus/timer_thread.rb
114
114
  - lib/message_bus/version.rb
115
115
  - message_bus.gemspec
116
+ - package.json
116
117
  - spec/assets/SpecHelper.js
117
118
  - spec/assets/message-bus.spec.js
118
119
  - spec/assets/support/jasmine.yml
@@ -155,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
156
  - !ruby/object:Gem::Version
156
157
  version: '0'
157
158
  requirements: []
158
- rubygems_version: 3.0.3
159
+ rubygems_version: 3.1.2
159
160
  signing_key:
160
161
  specification_version: 4
161
162
  summary: ''