message_bus 3.2.0 → 3.3.4

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: 7ff8ef38b49e1ca6d2fa08b0f56a62929247d71256ea5e717c2952dde35b63bf
4
- data.tar.gz: 158ca236805f872f60737d3af030bb38319c0b7355499b9c9d26006cb001121f
3
+ metadata.gz: e75dd767a8a80412a222bf74a50517711198a32cec7d4c772b160cee0f3792ad
4
+ data.tar.gz: c2d113e2e1e02038883e532c0308342d05fb9950d1a26a403e1c97565e0fab39
5
5
  SHA512:
6
- metadata.gz: b8b0a6c5079e5a1fd025dbaa8bde07140160fc9041ee7ac25defb32fb658c5817472efe1a98f25875e67e74ce81d1ff20ab32209f89af995ff87529bb5c8da92
7
- data.tar.gz: 40baa9cb6ca20f986591ee766f1c52fbc6a73595b5beca94f2d80be1211915668f5555fd4a1c9db80b9ddf191e3dd065d9d244c4356b8c47420aca62102cb375
6
+ metadata.gz: 4f35da832720aea8ac7295664846c8fa9f1f9d56cb6f6a9920bdf00710d67b558f28dc1390faf8379c860ebf9bca3eb366bff9554ce9bdb71fa86e74f5ec7b00
7
+ data.tar.gz: 5e5da62e131c0a2f7772d69758acd27fa3f43c7e993212ef81de36b280bb0dcccddc51b444ff5d3994f0b5f0cc224898c363a60f000c0c53544dec3b34492966
@@ -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,50 @@
1
+ - Unrelease
2
+
3
+ 02-10-2020
4
+
5
+ - Version 3.3.4
6
+
7
+ - FIX: Remove trailing comma incorrectly added in ec60d8865.
8
+
9
+ 18-09-2020
10
+
11
+ - Version 3.3.3
12
+
13
+ - FIX: `queue_in_memory` option not being passed to the backends.
14
+ - FIX: `MessageBus::DistributedCache#publish` should raise on error.
15
+
16
+ On the redis backend, any errors encountered during `MessageBus#publish`
17
+ will add the message into an in memory queue and silently swallow the
18
+ error. While this is behavior is OK for normal message_bus usage, it may
19
+ lead to inconsistency when using `DistributedCache`. If a process
20
+ doesn't publish successfully to another process, it will still update
21
+ its in memory cache leaving the other processes unaware. As such, the
22
+ distributed cache is out of sync and will require another successful
23
+ write to the cache to resync all the caches.
24
+
25
+ 15-09-2020
26
+
27
+ - Version 3.3.2
28
+
29
+ - FIX: In the JavaScript client throw when when lastId is given but is not a number.
30
+ - FEATURE: raise when attempting to publish to invalid targets
31
+ - Log when DistributedCache encounters an error when publishing.
32
+
33
+ 09-06-2020
34
+
35
+ - Version 3.3.1
36
+
37
+ - FIX: Disconnect Redis conn when rescuing errors in global subscribe.
38
+ - FIX: `MessageBus::Backends::Redis#global_subscribe` not closing Redis connections.
39
+
40
+ 15-05-2020
41
+
42
+ - Version 3.3.0
43
+
44
+ - FEATURE: `MessageBus.base_route=` to alter the route that message bus will listen on.
45
+
46
+ 07-05-2020
47
+
1
48
  - Version 3.2.0
2
49
 
3
50
  - FIX: compatability with Rails 6.0.3, note: apps without ActionDispatch::Flash may stop working after this upgrade
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
@@ -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.2.0"
4
+ VERSION = "3.3.4"
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
 
@@ -184,6 +201,13 @@ describe MessageBus do
184
201
  @bus.backlog("/chuck").map { |i| i.data }.to_a.must_equal ['norris', 'foo']
185
202
  end
186
203
 
204
+ it "should correctly restrict the backlog size of a channel" do
205
+ @bus.publish("/chuck", "norris")
206
+ @bus.publish("/chuck", "foo", max_backlog_size: 1)
207
+
208
+ @bus.backlog("/chuck").map { |i| i.data }.to_a.must_equal ['foo']
209
+ end
210
+
187
211
  it "allows you to look up last_message" do
188
212
  @bus.publish("/bob", "dylan")
189
213
  @bus.publish("/bob", "marley")
@@ -249,15 +273,37 @@ describe MessageBus do
249
273
  end
250
274
 
251
275
  it "should exception if publishing restricted messages to user" do
252
- lambda do
276
+ assert_raises(MessageBus::InvalidMessage) do
253
277
  @bus.publish("/global/test", "test", user_ids: [1])
254
- end.must_raise(MessageBus::InvalidMessage)
278
+ end
255
279
  end
256
280
 
257
281
  it "should exception if publishing restricted messages to group" do
258
- lambda do
282
+ assert_raises(MessageBus::InvalidMessage) do
259
283
  @bus.publish("/global/test", "test", user_ids: [1])
260
- end.must_raise(MessageBus::InvalidMessage)
284
+ end
285
+ end
286
+
287
+ it "should raise if we publish to an empty group or user list" do
288
+ assert_raises(MessageBus::InvalidMessageTarget) do
289
+ @bus.publish "/foo", "bar", user_ids: []
290
+ end
291
+
292
+ assert_raises(MessageBus::InvalidMessageTarget) do
293
+ @bus.publish "/foo", "bar", group_ids: []
294
+ end
295
+
296
+ assert_raises(MessageBus::InvalidMessageTarget) do
297
+ @bus.publish "/foo", "bar", client_ids: []
298
+ end
299
+
300
+ assert_raises(MessageBus::InvalidMessageTarget) do
301
+ @bus.publish "/foo", "bar", group_ids: [], user_ids: [1]
302
+ end
303
+
304
+ assert_raises(MessageBus::InvalidMessageTarget) do
305
+ @bus.publish "/foo", "bar", group_ids: [1], user_ids: []
306
+ end
261
307
  end
262
308
  end
263
309
 
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.2.0
4
+ version: 3.3.4
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-05-07 00:00:00.000000000 Z
11
+ date: 2020-10-02 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: ''