message_bus 2.2.4 → 3.3.1

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: 93e3bc28f1411da6037721acabab32fa504542bb68238afe6671f10bfccfa194
4
- data.tar.gz: b25d3013be8f3a0efa180db29de0ac6a55e5ce08ea87f80c912f046b9548c821
3
+ metadata.gz: 659722c1ea3a6a805dbe81099ee2848a11ab7ebffaf8612d119f5062fd740337
4
+ data.tar.gz: 4b187421f5fb4bdff177d117b059c05314305729885f5ec520127ad827db5369
5
5
  SHA512:
6
- metadata.gz: c74493de8f9f2dc5305457696988d7f07dce56642a8796d3757a0e5bf4e14f6e9b011ed8bd9255e0d3dc197c290bbdafedcf47e2519f09ca085ec86f33848508
7
- data.tar.gz: e4778edb5d540ef011ad29916dd8e4a374b04fb5dcd57ef80542b9f0b0985f5887609bf67b071455ebd15e59386eb0abc30698d015a4585f66a4d7afd217fc05
6
+ metadata.gz: c345409c8c9c1624b079d28ca166b962f9d64bd79159fc085055df8cb760545372732fd5cf509061b55eb0a427e8ffd7018238a7b49c44f27f4e942d575f0504
7
+ data.tar.gz: d92fb533dea19da315c210f7ac96a1029cf2d1709fee3dd919032f221b557cff9ebb3d2556285e5e0c6018ebc4b227ed178fd8658711c5a9ffacf19aeb47edff
@@ -1 +1,9 @@
1
- inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml
1
+ inherit_gem:
2
+ rubocop-discourse: .rubocop.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'examples/**/*'
7
+
8
+ RSpec:
9
+ Enabled: false
@@ -1,9 +1,9 @@
1
1
  before_install: gem install bundler
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3
5
4
  - 2.4
6
5
  - 2.5
6
+ - 2.6
7
7
  gemfile:
8
8
  - Gemfile
9
9
  addons:
data/CHANGELOG CHANGED
@@ -1,3 +1,36 @@
1
+ 09-06-2020
2
+
3
+ - Version 3.3.1
4
+
5
+ - FIX: Disconnect Redis conn when rescuing errors in global subscribe.
6
+ - FIX: `MessageBus::Backends::Redis#global_subscribe` not closing Redis connections.
7
+
8
+ 15-05-2020
9
+
10
+ - Version 3.3.0
11
+
12
+ - FEATURE: `MessageBus.base_route=` to alter the route that message bus will listen on.
13
+
14
+ 07-05-2020
15
+
16
+ - Version 3.2.0
17
+
18
+ - FIX: compatability with Rails 6.0.3, note: apps without ActionDispatch::Flash may stop working after this upgrade
19
+ 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)`
20
+
21
+ 28-04-2020
22
+
23
+ - Version 3.1.0
24
+
25
+ - FEATURE: `MessageBus#register_client_message_filter` to register a custom filter so that messages can be inspected and filtered away from clients.
26
+
27
+ 27-04-2020
28
+
29
+ - Version 3.0.0
30
+
31
+ - Drop support for Ruby 2.3
32
+ - FIX: Don't publish message to intersection of `user_ids` and `group_ids` - instead use the union, this is a behavior change, hence a new major release.
33
+
1
34
  26-03-2020
2
35
 
3
36
  - Version 2.2.4
data/Gemfile CHANGED
@@ -20,8 +20,11 @@ group :test, :development do
20
20
  gem 'byebug'
21
21
  end
22
22
 
23
+ group :development do
24
+ gem 'yard'
25
+ gem 'rubocop-discourse', require: false
26
+ gem 'rubocop-rspec', require: false
27
+ end
28
+
23
29
  gem 'rack'
24
30
  gem 'concurrent-ruby' # for distributed-cache
25
-
26
- gem 'rubocop'
27
- gem 'yard'
data/README.md CHANGED
@@ -74,32 +74,40 @@ id = MessageBus.last_id("/channel")
74
74
  MessageBus.backlog "/channel", id
75
75
  ```
76
76
 
77
- ```ruby
78
- # messages can be targetted at particular users or groups
79
- MessageBus.publish "/channel", "hello", user_ids: [1,2,3], group_ids: [4,5,6]
77
+ ### Targetted messages
78
+
79
+ Messages can be targetted to particular clients by supplying the `client_ids` option when publishing a message.
80
80
 
81
- # messages can be targetted at particular clients (using MessageBus.clientId)
82
- MessageBus.publish "/channel", "hello", client_ids: ["XXX","YYY"]
81
+ ```ruby
82
+ MessageBus.publish "/channel", "hello", client_ids: ["XXX", "YYY"] # (using MessageBus.clientId)
83
+ ```
83
84
 
84
- # message bus determines the user ids and groups based on env
85
+ By configuring the `user_id_lookup` and `group_ids_lookup` options with a Proc or Lambda which will be called with a [Rack specification environment](https://github.com/rack/rack/blob/master/SPEC.rdoc#the-environment-), messages can be targetted to particular clients users or groups by supplying either the `user_ids` or `group_ids` options when publishing a message.
85
86
 
87
+ ```ruby
86
88
  MessageBus.configure(user_id_lookup: proc do |env|
87
89
  # this lookup occurs on JS-client poolings, so that server can retrieve backlog
88
90
  # for the client considering/matching/filtering user_ids set on published messages
89
91
  # if user_id is not set on publish time, any user_id returned here will receive the message
90
-
91
92
  # return the user id here
92
93
  end)
93
94
 
95
+ # Target user_ids when publishing a message
96
+ MessageBus.publish "/channel", "hello", user_ids: [1, 2, 3]
97
+
94
98
  MessageBus.configure(group_ids_lookup: proc do |env|
95
99
  # return the group ids the user belongs to
96
100
  # can be nil or []
97
101
  end)
98
102
 
99
- # example of message bus to set user_ids from an initializer in Rails and Devise:
103
+ # Target group_ids when publishing a message
104
+ MessageBus.publish "/channel", "hello", group_ids: [1, 2, 3]
105
+
106
+ # example of MessageBus to set user_ids from an initializer in Rails and Devise:
100
107
  # config/inializers/message_bus.rb
101
108
  MessageBus.user_id_lookup do |env|
102
109
  req = Rack::Request.new(env)
110
+
103
111
  if req.session && req.session["warden.user.user.key"] && req.session["warden.user.user.key"][0][0]
104
112
  user = User.find(req.session["warden.user.user.key"][0][0])
105
113
  user.id
@@ -107,6 +115,36 @@ MessageBus.user_id_lookup do |env|
107
115
  end
108
116
  ```
109
117
 
118
+ If both `user_ids` and `group_ids` options are supplied when publishing a message, the message will be targetted at clients with lookup return values that matches on either the `user_ids` **or** the `group_ids` options.
119
+
120
+ ```ruby
121
+ MessageBus.publish "/channel", "hello", user_ids: [1, 2, 3], group_ids: [1, 2, 3]
122
+ ```
123
+
124
+ If the `client_ids` option is supplied with either the `user_ids` or `group_ids` options when publising a message, the `client_ids` option will be applied unconditionally and messages will be filtered further using `user_id` or `group_id` clauses.
125
+
126
+ ```ruby
127
+ MessageBus.publish "/channel", "hello", client_ids: ["XXX", "YYY"], user_ids: [1, 2, 3], group_ids: [1, 2, 3]
128
+ ```
129
+
130
+ Passing `nil` or `[]` to either `client_ids`, `user_ids` or `group_ids` is equivalent to allowing all values on each option.
131
+
132
+ ### Filtering Client Messages
133
+
134
+ Custom client message filters can be registered via `MessageBus#register_client_message_filter`. This can be useful for filtering away messages from the client based on the message's payload.
135
+
136
+ For example, ensuring that only messages seen by the server in the last 20 seconds are published to the client:
137
+
138
+ ```
139
+ MessageBus.register_client_message_filter('/test') do |message|
140
+ (Time.now.to_i - message.data[:published_at]) <= 20
141
+ end
142
+
143
+ MessageBus.publish('/test/5', { data: "somedata", published_at: Time.now.to_i })
144
+ ```
145
+
146
+ ### Error handling
147
+
110
148
  ```ruby
111
149
  MessageBus.configure(on_middleware_error: proc do |env, e|
112
150
  # If you wish to add special handling based on error
@@ -281,7 +319,7 @@ minPollInterval|100|When polling requests succeed, this is the minimum amount of
281
319
  maxPollInterval|180000|If request to the server start failing, MessageBus will backoff, this is the upper limit of the backoff.
282
320
  alwaysLongPoll|false|For debugging you may want to disable the "is browser in background" check and always long-poll
283
321
  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.
284
- baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there
322
+ 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.
285
323
  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
286
324
  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.
287
325
  minHiddenPollInterval|1500|Time to wait between poll requests performed by background or hidden tabs and windows, shared state via localStorage
@@ -303,8 +341,6 @@ enableChunkedEncoding|true|Allows streaming of message bus data over the HTTP co
303
341
 
304
342
  `MessageBus.status()` : Returns status (started, paused, stopped)
305
343
 
306
- `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.
307
-
308
344
  `MessageBus.diagnostics()` : Returns a log that may be used for diagnostics on the status of message bus.
309
345
 
310
346
  #### 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) {
@@ -532,7 +514,7 @@
532
514
  // TODO allow for globbing in the middle of a channel name
533
515
  // like /something/*/something
534
516
  // at the moment we only support globbing /something/*
535
- var glob;
517
+ var glob = false;
536
518
  if (channel.indexOf("*", channel.length - 1) !== -1) {
537
519
  channel = channel.substr(0, channel.length - 1);
538
520
  glob = true;
@@ -567,5 +549,5 @@
567
549
  return removed;
568
550
  }
569
551
  };
570
- global.MessageBus = me;
571
- })(window, document);
552
+ return me;
553
+ }));
@@ -149,6 +149,19 @@ module MessageBus::Implementation
149
149
  @config[:long_polling_interval] || 25 * 1000
150
150
  end
151
151
 
152
+ # @param [String] route Message bus will listen to requests on this route.
153
+ # @return [void]
154
+ def base_route=(route)
155
+ configure(base_route: route.gsub(Regexp.new('\A(?!/)|(?<!/)\Z|//+'), "/"))
156
+ end
157
+
158
+ # @return [String] the route that message bus will respond to. If not
159
+ # explicitly set, defaults to "/". Requests to "#{base_route}message-bus/*" will be handled
160
+ # by the message bus server.
161
+ def base_route
162
+ @config[:base_route] || "/"
163
+ end
164
+
152
165
  # @return [Boolean] whether the bus is disabled or not
153
166
  def off?
154
167
  @off
@@ -554,6 +567,26 @@ module MessageBus::Implementation
554
567
  @config[:keepalive_interval] || 60
555
568
  end
556
569
 
570
+ # Registers a client message filter that allows messages to be filtered from the client.
571
+ #
572
+ # @param [String,Regexp] channel_prefix channel prefix to match against a message's channel
573
+ #
574
+ # @yieldparam [MessageBus::Message] message published to the channel that matched the prefix provided
575
+ # @yieldreturn [Boolean] whether the message should be published to the client
576
+ # @return [void]
577
+ def register_client_message_filter(channel_prefix, &blk)
578
+ if blk
579
+ configure(client_message_filters: []) if !@config[:client_message_filters]
580
+ @config[:client_message_filters] << [channel_prefix, blk]
581
+ end
582
+ end
583
+
584
+ # @return [Array] returns a hash of message filters that have been registered
585
+ def client_message_filters
586
+ configure(client_message_filters: []) if !@config[:client_message_filters]
587
+ @config[:client_message_filters]
588
+ end
589
+
557
590
  private
558
591
 
559
592
  ENCODE_SITE_TOKEN = "$|$"
@@ -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
@@ -320,7 +320,10 @@ LUA
320
320
  rescue => error
321
321
  @logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack #{error.backtrace}"
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
 
@@ -128,15 +128,43 @@ class MessageBus::Client
128
128
  # @return [Boolean] whether or not the client has permission to receive the
129
129
  # passed message
130
130
  def allowed?(msg)
131
- allowed = !msg.user_ids || msg.user_ids.include?(self.user_id)
132
- allowed &&= !msg.client_ids || msg.client_ids.include?(self.client_id)
133
- allowed && (
134
- msg.group_ids.nil? ||
135
- msg.group_ids.length == 0 ||
136
- (
137
- msg.group_ids - self.group_ids
131
+ client_allowed = !msg.client_ids || msg.client_ids.length == 0 || msg.client_ids.include?(self.client_id)
132
+
133
+ user_allowed = false
134
+ group_allowed = false
135
+
136
+ # this is an inconsistency we should fix anyway, publishing `user_ids: nil` should work same as groups
137
+ has_users = msg.user_ids && msg.user_ids.length > 0
138
+ has_groups = msg.group_ids && msg.group_ids.length > 0
139
+
140
+ if has_users
141
+ user_allowed = msg.user_ids.include?(self.user_id)
142
+ end
143
+
144
+ if has_groups
145
+ group_allowed = (
146
+ msg.group_ids - (self.group_ids || [])
138
147
  ).length < msg.group_ids.length
139
- )
148
+ end
149
+
150
+ has_permission = client_allowed && (user_allowed || group_allowed || (!has_users && !has_groups))
151
+
152
+ return has_permission if !has_permission
153
+
154
+ filters_allowed = true
155
+
156
+ len = @bus.client_message_filters.length
157
+ while len > 0
158
+ len -= 1
159
+ channel_prefix, blk = @bus.client_message_filters[len]
160
+
161
+ if msg.channel.start_with?(channel_prefix)
162
+ filters_allowed = blk.call(msg)
163
+ break if !filters_allowed
164
+ end
165
+ end
166
+
167
+ filters_allowed
140
168
  end
141
169
 
142
170
  # @return [Array<MessageBus::Message>] the set of messages the client is due
@@ -15,7 +15,6 @@ class MessageBus::Diagnostics
15
15
  # process to process comms
16
16
  bus.subscribe('/_diagnostics/hup') do |msg|
17
17
  if Process.pid == msg.data["pid"] && hostname == msg.data["hostname"]
18
- $shutdown = true
19
18
  sleep 4
20
19
  Process.kill("HUP", $$)
21
20
  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']]
@@ -45,7 +45,7 @@ class MessageBus::Rack::Diagnostics
45
45
  return [200, { 'Content-Type' => 'application/javascript;charset=UTF-8' }, [content]]
46
46
  end
47
47
 
48
- return [404, {}, ['not found']]
48
+ [404, {}, ['not found']]
49
49
  end
50
50
 
51
51
  private
@@ -92,6 +92,7 @@ class MessageBus::Rack::Diagnostics
92
92
  </body>
93
93
  </html>
94
94
  HTML
95
- return [200, { "content-type" => "text/html;" }, [html]]
95
+
96
+ [200, { "content-type" => "text/html;" }, [html]]
96
97
  end
97
98
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MessageBus
4
- VERSION = "2.2.4"
4
+ VERSION = "3.3.1"
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;
@@ -169,37 +169,220 @@ describe MessageBus::Client do
169
169
  log[0].data.must_equal 'world'
170
170
  end
171
171
 
172
- it "allows only client_id in list if message contains client_ids" do
173
- @message = MessageBus::Message.new(1, 2, '/test', 'hello')
174
- @message.client_ids = ["1", "2"]
175
- @client.client_id = "2"
176
- @client.allowed?(@message).must_equal true
177
-
178
- @client.client_id = "3"
179
- @client.allowed?(@message).must_equal false
180
- end
181
-
182
- describe "targetted at group" do
183
- before do
172
+ describe '#allowed?' do
173
+ it "allows only client_id in list if message contains client_ids" do
184
174
  @message = MessageBus::Message.new(1, 2, '/test', 'hello')
185
- @message.group_ids = [1, 2, 3]
186
- end
175
+ @message.client_ids = ["1", "2"]
176
+ @client.client_id = "2"
177
+ @client.allowed?(@message).must_equal true
187
178
 
188
- it "denies users that are not members of group" do
189
- @client.group_ids = [77, 0, 10]
179
+ @client.client_id = "3"
190
180
  @client.allowed?(@message).must_equal false
191
- end
192
181
 
193
- it "allows users that are members of group" do
194
- @client.group_ids = [1, 2, 3]
182
+ @message.client_ids = []
183
+
184
+ @client.client_id = "3"
195
185
  @client.allowed?(@message).must_equal true
196
- end
197
186
 
198
- it "allows all users if groups not set" do
199
- @message.group_ids = nil
200
- @client.group_ids = [77, 0, 10]
187
+ @message.client_ids = nil
188
+
189
+ @client.client_id = "3"
201
190
  @client.allowed?(@message).must_equal true
202
191
  end
192
+
193
+ describe 'targetted at user' do
194
+ before do
195
+ @message = MessageBus::Message.new(1, 2, '/test', 'hello')
196
+ @message.user_ids = [1, 2, 3]
197
+ end
198
+
199
+ it "allows client with user_id that is included in message's user_ids" do
200
+ @client.user_id = 1
201
+ @client.allowed?(@message).must_equal(true)
202
+ end
203
+
204
+ it "denies client with user_id that is not included in message's user_ids" do
205
+ @client.user_id = 4
206
+ @client.allowed?(@message).must_equal(false)
207
+ end
208
+
209
+ it "denies client with nil user_id" do
210
+ @client.user_id = nil
211
+
212
+ @client.allowed?(@message).must_equal(false)
213
+ end
214
+
215
+ it "allows client if message's user_ids is not set" do
216
+ @message.user_ids = nil
217
+ @client.user_id = 4
218
+ @client.allowed?(@message).must_equal(true)
219
+ end
220
+
221
+ it "allows client if message's user_ids is empty" do
222
+ @message.user_ids = []
223
+ @client.user_id = 4
224
+ @client.allowed?(@message).must_equal(true)
225
+ end
226
+
227
+ it "allows client with client_id that is included in message's client_ids" do
228
+ @message.client_ids = ["1", "2"]
229
+ @client.client_id = "1"
230
+ @client.user_id = 1
231
+
232
+ @client.allowed?(@message).must_equal(true)
233
+ end
234
+
235
+ it "denies client with client_id that is not included in message's client_ids" do
236
+ @message.client_ids = ["1", "2"]
237
+ @client.client_id = "3"
238
+ @client.user_id = 1
239
+
240
+ @client.allowed?(@message).must_equal(false)
241
+ end
242
+ end
243
+
244
+ describe "targetted at group" do
245
+ before do
246
+ @message = MessageBus::Message.new(1, 2, '/test', 'hello')
247
+ @message.group_ids = [1, 2, 3]
248
+ end
249
+
250
+ it "denies client that are not members of group" do
251
+ @client.group_ids = [77, 0, 10]
252
+ @client.allowed?(@message).must_equal false
253
+ end
254
+
255
+ it 'denies client with nil group_ids' do
256
+ @client.group_ids = nil
257
+ @client.allowed?(@message).must_equal false
258
+ end
259
+
260
+ it "allows client that are members of group" do
261
+ @client.group_ids = [1, 2, 3]
262
+ @client.allowed?(@message).must_equal true
263
+ end
264
+
265
+ it "allows any client if message's group_ids is not set" do
266
+ @message.group_ids = nil
267
+ @client.group_ids = [77, 0, 10]
268
+ @client.allowed?(@message).must_equal true
269
+ end
270
+
271
+ it "allows any client if message's group_ids is empty" do
272
+ @message.group_ids = []
273
+ @client.group_ids = [77, 0, 10]
274
+ @client.allowed?(@message).must_equal true
275
+ end
276
+
277
+ it "allows client with client_id that is included in message's client_ids" do
278
+ @message.client_ids = ["1", "2"]
279
+ @client.client_id = "1"
280
+ @client.group_ids = [1]
281
+
282
+ @client.allowed?(@message).must_equal(true)
283
+ end
284
+
285
+ it "denies client with client_id that is not included in message's client_ids" do
286
+ @message.client_ids = ["1", "2"]
287
+ @client.client_id = "3"
288
+ @client.group_ids = [1]
289
+
290
+ @client.allowed?(@message).must_equal(false)
291
+ end
292
+ end
293
+
294
+ describe 'targetted at group and user' do
295
+ before do
296
+ @message = MessageBus::Message.new(1, 2, '/test', 'hello')
297
+ @message.group_ids = [1, 2, 3]
298
+ @message.user_ids = [4, 5, 6]
299
+ end
300
+
301
+ it "allows client with user_id that is included in message's user_ids" do
302
+ @client.user_id = 4
303
+ @client.allowed?(@message).must_equal(true)
304
+ end
305
+
306
+ it "denies client with user_id that is not included in message's user_ids" do
307
+ @client.user_id = 1
308
+ @client.allowed?(@message).must_equal(false)
309
+ end
310
+
311
+ it "allows client with group_ids that is included in message's group_ids" do
312
+ @client.group_ids = [1, 0, 3]
313
+ @client.allowed?(@message).must_equal(true)
314
+ end
315
+
316
+ it "denies client with group_ids that is not included in message's group_ids" do
317
+ @client.group_ids = [8, 9, 10]
318
+ @client.allowed?(@message).must_equal(false)
319
+ end
320
+
321
+ it "allows client with allowed client_id and user_id" do
322
+ @message.client_ids = ["1", "2"]
323
+ @client.user_id = 4
324
+ @client.client_id = "2"
325
+
326
+ @client.allowed?(@message).must_equal(true)
327
+ end
328
+
329
+ it "denies client with allowed client_id but disallowed user_id" do
330
+ @message.client_ids = ["1", "2"]
331
+ @client.user_id = 99
332
+ @client.client_id = "2"
333
+
334
+ @client.allowed?(@message).must_equal(false)
335
+ end
336
+
337
+ it "allows client with allowed client_id and group_id" do
338
+ @message.client_ids = ["1", "2"]
339
+ @client.group_ids = [1]
340
+ @client.client_id = "2"
341
+
342
+ @client.allowed?(@message).must_equal(true)
343
+ end
344
+
345
+ it "denies client with allowed client_id but disallowed group_id" do
346
+ @message.client_ids = ["1", "2"]
347
+ @client.group_ids = [99]
348
+ @client.client_id = "2"
349
+
350
+ @client.allowed?(@message).must_equal(false)
351
+ end
352
+ end
353
+
354
+ describe 'when MessageBus#client_message_filters has been configured' do
355
+
356
+ it 'filters messages correctly' do
357
+ message = MessageBus::Message.new(1, 2, '/test/5', 'hello')
358
+ @client.allowed?(message).must_equal(true)
359
+
360
+ @bus.register_client_message_filter('/test') do |m|
361
+ m.data != 'hello'
362
+ end
363
+
364
+ @client.allowed?(message).must_equal(false)
365
+ end
366
+
367
+ it 'filters messages correctly when multiple filters have been configured' do
368
+
369
+ bob_message = MessageBus::Message.new(1, 2, '/test/5', 'bob')
370
+ fred_message = MessageBus::Message.new(1, 2, '/test/5', 'fred')
371
+ random_message = MessageBus::Message.new(1, 2, '/test/77', 'random')
372
+
373
+ @bus.register_client_message_filter('/test') do |message|
374
+ message.data == 'bob' || message.data == 'fred'
375
+ end
376
+
377
+ @bus.register_client_message_filter('/test') do |message|
378
+ message.data == 'fred'
379
+ end
380
+
381
+ @client.allowed?(fred_message).must_equal(true)
382
+ @client.allowed?(bob_message).must_equal(false)
383
+ @client.allowed?(random_message).must_equal(false)
384
+ end
385
+ end
203
386
  end
204
387
  end
205
388
  end
@@ -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
 
@@ -292,4 +309,19 @@ describe MessageBus do
292
309
 
293
310
  data.must_equal(["pre-fork", "from-fork", "continuation"])
294
311
  end
312
+
313
+ describe '#register_client_message_filter' do
314
+ it 'should register the message filter correctly' do
315
+ @bus.register_client_message_filter('/test')
316
+
317
+ @bus.client_message_filters.must_equal([])
318
+
319
+ @bus.register_client_message_filter('/test') { puts "hello world" }
320
+
321
+ channel, blk = @bus.client_message_filters[0]
322
+
323
+ blk.must_respond_to(:call)
324
+ channel.must_equal('/test')
325
+ end
326
+ end
295
327
  end
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: 2.2.4
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-26 00:00:00.000000000 Z
11
+ date: 2020-06-09 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: ''