message_bus 3.2.0 → 3.3.4

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.
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: ''