FEATURE: `MessageBus#register_client_message_filter`

FEATURE: MessageBus#register_client_message_filter

diff --git a/CHANGELOG b/CHANGELOG
index 31453d9..a3d6836 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+Unreleased
+  - FEATURE: `MessageBus#register_client_message_filter` to register a custom filter so that messages can be inspected and filtered away from
+      clients.
+
 27-04-2020
 
 - Version 3.0.0
diff --git a/README.md b/README.md
index e46294a..059a543 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,20 @@ MessageBus.publish "/channel", "hello", client_ids: ["XXX", "YYY"], user_ids: [1
 
 Passing `nil` or `[]` to either `client_ids`, `user_ids` or `group_ids` is equivalent to allowing all values on each option.
 
+### Filtering Client Messages
+
+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.
+
+For example, ensuring that only messages seen by the server in the last 20 seconds are published to the client:
+
+`‍``
+MessageBus.register_client_message_filter('/test') do |message|
+  (Time.now.to_i - message.data[:published_at]) <= 20
+end
+
+MessageBus.publish('/test/5', { data: "somedata", published_at: Time.now.to_i })
+`‍``
+
 ### Error handling
 
 `‍``ruby
diff --git a/lib/message_bus.rb b/lib/message_bus.rb
index ea6a31b..72ec9cf 100644
--- a/lib/message_bus.rb
+++ b/lib/message_bus.rb
@@ -554,6 +554,25 @@ module MessageBus::Implementation
     @config[:keepalive_interval] || 60
   end
 
+  # Registers a client message filter that allows messages to be filtered from the client.
+  #
+  # @param [String,Regexp] channel_prefix channel prefix to match against a message's channel
+  #
+  # @yieldparam [MessageBus::Message] message published to the channel that matched the prefix provided
+  # @yieldreturn [Boolean] whether the message should be published to the client
+  # @return [void]
+  def register_client_message_filter(channel_prefix, &blk)
+    if blk
+      configure(client_message_filters: {}) if !@config[:client_message_filters]
+      @config[:client_message_filters][channel_prefix] = blk
+    end
+  end
+
+  # @return [Hash] returns a hash of message filters that have been registered
+  def client_message_filters
+    @config[:client_message_filters] || {}
+  end
+
   private
 
   ENCODE_SITE_TOKEN = "$|$"
diff --git a/lib/message_bus/client.rb b/lib/message_bus/client.rb
index dd59f9a..78c72f8 100644
--- a/lib/message_bus/client.rb
+++ b/lib/message_bus/client.rb
@@ -147,7 +147,20 @@ class MessageBus::Client
       ).length < msg.group_ids.length
     end
 
-    client_allowed && (user_allowed || group_allowed || (!has_users && !has_groups))
+    has_permission = client_allowed && (user_allowed || group_allowed || (!has_users && !has_groups))
+
+    return has_permission if !has_permission
+
+    filters_allowed = true
+
+    @bus.client_message_filters.each do |channel_prefix, blk|
+      if msg.channel.start_with?(channel_prefix)
+        filters_allowed = blk.call(msg)
+        break if !filters_allowed
+      end
+    end
+
+    filters_allowed
   end
 
   # @return [Array<MessageBus::Message>] the set of messages the client is due
diff --git a/spec/lib/message_bus/client_spec.rb b/spec/lib/message_bus/client_spec.rb
index de1bab3..38ec554 100644
--- a/spec/lib/message_bus/client_spec.rb
+++ b/spec/lib/message_bus/client_spec.rb
@@ -350,6 +350,37 @@ describe MessageBus::Client do
           @client.allowed?(@message).must_equal(false)
         end
       end
+
+      describe 'when MessageBus#client_message_filters has been configured' do
+        before do
+          @message = MessageBus::Message.new(1, 2, '/test/5', 'hello')
+          @client.allowed?(@message).must_equal(true)
+        end
+
+        it 'filters messages correctly' do
+          @bus.register_client_message_filter('/test') do |message|
+            message.data != 'hello'
+          end
+
+          @client.allowed?(@message).must_equal(false)
+        end
+
+        it 'filters messages correctly when multiple filters have been configured' do
+          called = false
+
+          @bus.register_client_message_filter('/test') do |message|
+            message.data == 'hello'
+          end
+
+          @bus.register_client_message_filter('/tes') do |message|
+            called = true
+            message.data != 'hello'
+          end
+
+          @client.allowed?(@message).must_equal(false)
+          called.must_equal(true)
+        end
+      end
     end
   end
 end
diff --git a/spec/lib/message_bus_spec.rb b/spec/lib/message_bus_spec.rb
index e0369c2..20d5435 100644
--- a/spec/lib/message_bus_spec.rb
+++ b/spec/lib/message_bus_spec.rb
@@ -292,4 +292,16 @@ describe MessageBus do
 
     data.must_equal(["pre-fork", "from-fork", "continuation"])
   end
+
+  describe '#register_client_message_filter' do
+    it 'should register the message filter correctly' do
+      @bus.register_client_message_filter('/test')
+
+      @bus.client_message_filters.must_equal({})
+
+      @bus.register_client_message_filter('/test') { puts "hello world" }
+
+      @bus.client_message_filters['/test'].must_respond_to(:call)
+    end
+  end
 end

GitHub sha: 7fad5a3e

This commit appears in #220 which was merged by SamSaffron.