From 94ce6736a1eb7ab372ec4b276807fb5f86a5a136 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Mon, 4 Jan 2016 13:32:01 +0100 Subject: [PATCH 1/8] adds basic client -> server behaviour --- app/volt/tasks/message_bus_tasks.rb | 6 +++++ lib/volt/page/message_bus_client_proxy.rb | 33 +++++++++++++++++++++++ lib/volt/volt/app.rb | 8 ++++-- lib/volt/volt/client_setup/app.rb | 16 +++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 app/volt/tasks/message_bus_tasks.rb create mode 100644 lib/volt/page/message_bus_client_proxy.rb create mode 100644 lib/volt/volt/client_setup/app.rb diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb new file mode 100644 index 00000000..283af77e --- /dev/null +++ b/app/volt/tasks/message_bus_tasks.rb @@ -0,0 +1,6 @@ +class MessageBusTasks < Volt::Task + def publish(channel, message) + Volt.current_app.message_bus.publish(channel, message) + return true + end +end \ No newline at end of file diff --git a/lib/volt/page/message_bus_client_proxy.rb b/lib/volt/page/message_bus_client_proxy.rb new file mode 100644 index 00000000..13461cb7 --- /dev/null +++ b/lib/volt/page/message_bus_client_proxy.rb @@ -0,0 +1,33 @@ +require 'volt/reactive/eventable' + +module Volt + class MessageBusClientProxy + include Eventable + + # Use subscribe instead of on provided in Eventable + alias_method :subscribe, :on + + # Adds a reference to the client app from this proxy + def initialize(volt_app) + @volt_app = volt_app + end + + # publish should push out to all subscribed within the volt cluster. + def publish(channel_name, message) + puts 'publishing to channel...' + MessageBusTasks.publish(channel_name, message).then do |result| + puts 'success' + puts result + end.fail do |fail| + puts 'error' + puts fail + end + end + + # Unnecessary on clients + def disconnect! + raise "You cannot disconnect from message bus on the client. 'disconnect!' is only available on the server." + end + + end +end \ No newline at end of file diff --git a/lib/volt/volt/app.rb b/lib/volt/volt/app.rb index 86a7f432..a8407d2e 100644 --- a/lib/volt/volt/app.rb +++ b/lib/volt/volt/app.rb @@ -33,6 +33,7 @@ if RUBY_PLATFORM == 'opal' require 'volt/volt/client_setup/browser' + require 'volt/volt/client_setup/app' else require 'volt/volt/server_setup/app' require 'volt/server/template_handlers/view_processor' @@ -45,6 +46,8 @@ class App if RUBY_PLATFORM != 'opal' # Include server app setup include Volt::ServerSetup::App + else + include Volt::ClientSetup::App end attr_reader :component_paths, :router, :live_query_pool, @@ -110,9 +113,10 @@ def initialize(app_path=nil) setup_postboot_middleware setup_routes - - start_message_bus end + + # Starts message bus on server or proxy on client + start_message_bus end def templates diff --git a/lib/volt/volt/client_setup/app.rb b/lib/volt/volt/client_setup/app.rb new file mode 100644 index 00000000..4dba2a7a --- /dev/null +++ b/lib/volt/volt/client_setup/app.rb @@ -0,0 +1,16 @@ +# The following setup handles setting up the app on the client. +# Currently, this only sets up the message bus for client usage + +require 'volt/page/message_bus_client_proxy' + +module Volt + module ClientSetup + module App + + # Registers app.message_bus as a proxyfied websocket endpoint to the message bus + def start_message_bus + @message_bus = MessageBusClientProxy.new(self) + end + end + end +end From befc9bae120de5ac92358c1d40ec8d35db6d93ff Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Tue, 5 Jan 2016 20:14:16 +0100 Subject: [PATCH 2/8] finishes infrastructure of publishing and subscribing --- app/volt/tasks/message_bus_tasks.rb | 47 +++++++++++++++++ lib/volt/page/message_bus_client_proxy.rb | 64 +++++++++++++++++++---- 2 files changed, 102 insertions(+), 9 deletions(-) diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb index 283af77e..7ff4a0ef 100644 --- a/app/volt/tasks/message_bus_tasks.rb +++ b/app/volt/tasks/message_bus_tasks.rb @@ -1,6 +1,53 @@ class MessageBusTasks < Volt::Task + require 'securerandom' + + # Publishes a message in the message bus def publish(channel, message) Volt.current_app.message_bus.publish(channel, message) return true end + + # Subscribe to specific events. Returns a listener_id, useful for unsubscribing + def subscribe(*events) + listener_id = generate_listener_id + @@subscriptions ||= {} + @@subscriptions[listener_id] = [] + + events.each do |event| + @@subscriptions[listener_id] << Volt.current_app.message_bus.on(event) do |msg| + inform_subscriber(event, msg) + end + end + + return listener_id + end + + # Removes a subscription, needs the listener_id (see #subscribe for more info) + def remove(listener_id) + if @@subscriptions && @@subscriptions[listener_id] + @@subscriptions[listener_id].each &:remove + end + + return listener_id + end + + + + # todo: react on disconnect / connect + # todo: authentication / authorization layer + # todo: authenticate publishing and subscribing + # todo: reauthenticate publish/subscribe on each message + # todo: does publishing also fire the event locally? + + private + + # informs subscriber about new message in channel + def inform_subscriber(channel, msg) + @channel.send_message('message_bus_event', channel, msg) + end + + # Just returns a random listener_id + def generate_listener_id + SecureRandom.uuid + end end \ No newline at end of file diff --git a/lib/volt/page/message_bus_client_proxy.rb b/lib/volt/page/message_bus_client_proxy.rb index 13461cb7..b824a3c6 100644 --- a/lib/volt/page/message_bus_client_proxy.rb +++ b/lib/volt/page/message_bus_client_proxy.rb @@ -1,27 +1,73 @@ require 'volt/reactive/eventable' +require 'volt/server/message_bus/base_message_bus' module Volt - class MessageBusClientProxy + class MessageBusClientProxy < MessageBus::BaseMessageBus include Eventable + # Custom listener class, proxying Eventable#Listener since + # we have to inform remote on the removal of a listener + class ListenerProxy + def initialize(target, remote_listener_id) + @target = target + @listener_id = remote_listener_id + end + + # custom remove implementation: also calls task to remove listener + # returns promise of the task + def remove + @target.remove + MessageBusTasks.remove(@listener_id) + end + + # proxy all other methods + def method_missing(method, *args, &block) + @target.send(method, *args, &block) + end + end + # Use subscribe instead of on provided in Eventable alias_method :subscribe, :on + alias_method :eventable_on, :on # this is only for obtaining the original + # method behaviour although overriding it # Adds a reference to the client app from this proxy def initialize(volt_app) @volt_app = volt_app + + # Called when the backend informs us about a new subscribed message bus event + @volt_app.channel.on('message') do |*args| + if args.delete_at(0) == 'message_bus_event' + trigger!(*args) + end + end end - # publish should push out to all subscribed within the volt cluster. + # Publishes a message into the message bus, returns a promise def publish(channel_name, message) - puts 'publishing to channel...' - MessageBusTasks.publish(channel_name, message).then do |result| - puts 'success' - puts result - end.fail do |fail| - puts 'error' - puts fail + MessageBusTasks.publish(channel_name, message) + end + + # overwrites subscribe and on from Eventable to register subscription in message bus first + # this will return a promise resolving to ListenerProxy, so you can call ".remove" on it directly + def on(*events, &block) + # Promise to resolve on working subscription, giving you a listener to remove + subscription_promise = Promise.new + + MessageBusTasks.subscribe(*events).then do |remote_listener_id| + # Register event locally, TODO: direclty pass block + listener = eventable_on(*events) do |*params| + block.call(*params) + end + + # Resolve promise with object of ListenerProxy to enable removing of listener + subscription_promise.resolve(ListenerProxy.new(listener, remote_listener_id)) + end.fail do |error| + # Tell promise about failure + subscription_promise.reject(error) end + + subscription_promise end # Unnecessary on clients From 4cc691232e0b482687b5b0ee9cf8277aece50534 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Tue, 5 Jan 2016 20:51:50 +0100 Subject: [PATCH 3/8] enhances infrastructure (local subscription messaging, reacting on connection closal, ...) --- app/volt/tasks/message_bus_tasks.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb index 7ff4a0ef..7d5916c9 100644 --- a/app/volt/tasks/message_bus_tasks.rb +++ b/app/volt/tasks/message_bus_tasks.rb @@ -1,9 +1,14 @@ +require 'securerandom' + class MessageBusTasks < Volt::Task - require 'securerandom' # Publishes a message in the message bus def publish(channel, message) + # Trigger subscriptions in remote volt app (via the message bus) Volt.current_app.message_bus.publish(channel, message) + + # Trigger local subscriptions, of local volt app + Volt.current_app.message_bus.trigger!(channel, message) return true end @@ -13,12 +18,21 @@ def subscribe(*events) @@subscriptions ||= {} @@subscriptions[listener_id] = [] + # Todo: Maybe do this in a custom thread? events.each do |event| @@subscriptions[listener_id] << Volt.current_app.message_bus.on(event) do |msg| inform_subscriber(event, msg) end end + # Remove all registered listeners on client disconnect + connection_listener = Volt.current_app.on('client_disconnect') do + remove(listener_id) + connection_listener.remove # to avoid endless listeners + end + + # Todo: If a client reconnects, automatically reattach all subscriptions?! + return listener_id end @@ -26,6 +40,7 @@ def subscribe(*events) def remove(listener_id) if @@subscriptions && @@subscriptions[listener_id] @@subscriptions[listener_id].each &:remove + @@subscriptions[listener_id] = nil end return listener_id @@ -33,11 +48,9 @@ def remove(listener_id) - # todo: react on disconnect / connect # todo: authentication / authorization layer # todo: authenticate publishing and subscribing # todo: reauthenticate publish/subscribe on each message - # todo: does publishing also fire the event locally? private From abccabc82404df9a47ef2e4488aea74939ad1378 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Wed, 6 Jan 2016 13:41:37 +0100 Subject: [PATCH 4/8] adds authorization logic to message bus exposure --- app/volt/tasks/message_bus_tasks.rb | 44 +++- .../server/message_bus/client_authorizer.rb | 248 ++++++++++++++++++ 2 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 lib/volt/server/message_bus/client_authorizer.rb diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb index 7d5916c9..2e5c91c0 100644 --- a/app/volt/tasks/message_bus_tasks.rb +++ b/app/volt/tasks/message_bus_tasks.rb @@ -1,19 +1,36 @@ +# Remote endpoint for publishing and subscribing to message bus +# Generally you have the power to publish or subscribe to any channel, even to volt internals, if you +# want to. +# Nevertheless, all channels are protected by an authorization layer, so publishing and subscribing +# from client is only possible if the specified user is allowed to. Per default, channel names starting +# with 'public:' are usable for everyone. If you want to restrict some channels / use the authorization +# layer, have a look at /server/message_bus/client_authorizer, where everything you need to know is +# explained very well. +# Volt uses channels starting with 'volt:' for internal stuff, so be aware of publishing / subscribing +# to these channels (although you could do!) + require 'securerandom' +require 'volt/server/message_bus/client_authorizer' class MessageBusTasks < Volt::Task # Publishes a message in the message bus def publish(channel, message) + fail "[MessageBus] Publishing into channel #{channel} not allowed" unless publishing_allowed? channel + # Trigger subscriptions in remote volt app (via the message bus) Volt.current_app.message_bus.publish(channel, message) # Trigger local subscriptions, of local volt app Volt.current_app.message_bus.trigger!(channel, message) - return true + + nil end # Subscribe to specific events. Returns a listener_id, useful for unsubscribing def subscribe(*events) + fail "[MessageBus] Subscribing to channels #{events} not allowed" unless subscribing_allowed? *events + listener_id = generate_listener_id @@subscriptions ||= {} @@subscriptions[listener_id] = [] @@ -47,15 +64,25 @@ def remove(listener_id) end + # Checks if publishing to the given channels is allowed + def publishing_allowed?(*channels) + is_allowed? :publish, *channels + end + + # Checks if subscribing to the given channels is allowed + def subscribing_allowed?(*channels) + is_allowed? :subscribe, *channels + end - # todo: authentication / authorization layer - # todo: authenticate publishing and subscribing - # todo: reauthenticate publish/subscribe on each message + # todo: enable usage in views + # todo: use 'volt:' as a channel prefix for all volt internals? private # informs subscriber about new message in channel def inform_subscriber(channel, msg) + return unless subscribing_allowed? channel + @channel.send_message('message_bus_event', channel, msg) end @@ -63,4 +90,13 @@ def inform_subscriber(channel, msg) def generate_listener_id SecureRandom.uuid end + + # [helper method] Checks if :subscribe or :publish is allowed in all channels + def is_allowed?(method, *channels) + channels.each do |channel| + return false if Volt::MessageBus::ClientAuthorizer.authorized?(self, method, channel) != true + end + + true + end end \ No newline at end of file diff --git a/lib/volt/server/message_bus/client_authorizer.rb b/lib/volt/server/message_bus/client_authorizer.rb new file mode 100644 index 00000000..ccdfbaf7 --- /dev/null +++ b/lib/volt/server/message_bus/client_authorizer.rb @@ -0,0 +1,248 @@ +# Authorizer class for authorizing message bus requests from the client +# This class is used in message_bus_tasks to check if publishing or subscribing to a given channel is +# allowed or not. With the help of this class, you can easily add your own authorization layer, which +# is applied to certain channels or modes. To do this, just create an instance of this class, for example +# in an initializer: +# Volt::MessageBus::ClientAuthorizer.new(:mode, 'channel1', 'channel2', ...) (see #initialize for param info) +# Next, you can add rules to the authorizer which are evaluated on subscribing / publishing to a channel of +# the MessageBus from a client. To do this, call 'allow_if' with a block: +# authorizer.allow_if do |task_context| +# ... +# break true +# end +# All authorization computations are executed in a task context (MessageBusTask), you get an instance of this +# task as a parameter, if you need it. To allow an action, your block has to return true, if it returns anything +# else subscribing/publishing is not allowed. +# You can add multiple blocks/allow_ifs to one authorizer, all of these blocks have to evaluate to true to proceed +# You can also add multiple authorizers to a channel, all associated authorizers have to evaluate to true to proceed +# This class supports method chaining, so you can do: +# Volt::MessageBus::ClientAuthorizer.new(:publish, 'my-channel').allow_if{|t| ... }.allow_if{...} +# +# If there is no authorizer found for a request, e. g. if you have only defined an authorizer for :publish, not for +# :subscribe, the action is denied. +# +# +# ClientAuthorizer also supports the use of namespaces. If your channel contains a ':', the first part of the +# channel name is interpreted as the namespace. On an authorization request, this class then looks for a namespace +# defining rule, too. In addition to channel rule (if any), the namespace rule is evaluated and has to return true, too. +# With the help of this, you can create a general authorizer for a class of channels (maybe for a volt component?) and +# specific, additional authorizers for some of the channels in this namespace. Or alternativly, you could restrict +# access to all channels with a namespace like 'chat:', and afterwards add use channels with the prefix 'chat:' +# dynamically, without the need of adding additional authorizers. +# All volt internals have the namespace 'volt:', so be careful on subscribing / publishing to these channels. +# We highly encourage you to use namespaces. +# Nesting of namespaces ('namespace1:namespace2:channel1') is currently unsupported +# To define an authorizer, which should apply to a namespace, use 'namespace:*' as channel name, for example: +# Volt::MessageBus::ClientAuthorizer.new(:subscribe, 'chat:*').make_public! +# Volt::MessageBus::ClientAuthorizer.new(:publish, 'chat:*').allow_if{.. check authentication? ..} +# Now you can use channels like 'chat:messages', 'chat:signals', etc which would all include the above two rules. +# +# Per default, there is already a 'public' namespace (see end of this file), which enables everyone to +# publish/subscribe to all channels in this namespace. If you want to disable this behaviour, use: +# Volt::MessageBus::ClientAuthorizer.new(:publish_and_subscribe, 'public:*').make_private! + +module Volt + module MessageBus + class ClientAuthorizer + attr_accessor :allow_if + attr_reader :channels + + # Creates a new authorizer instance and adds it to the collection of checkable authorizers. + # apply_on must be one of :publish, :subscribe or :publish_and_subscribe and defines when this + # authorizer is triggered, on publishing or subscribing or both. + # All other given parameters are evaluated as channels this authorizer shall include rules for. + # An authorizer is only triggered if it contains the requested channel and if apply_on fits to the requested mode + def initialize(apply_on, *channels) + raise 'apply_on must be :publish, :subscribe or :publish_and_subscribe' unless [:publish, :subscribe, :publish_and_subscribe].include? apply_on + + raise 'a minimum of one channel is needed' if channels.nil? || channels.size == 0 + + @apply_on_publishing = @apply_on_subscribing = false + apply_on_publishing if apply_on == :publish + apply_on_subscribing if apply_on == :subscribe + apply_on_publishing_and_subscribing if apply_on == :publish_and_subscribe + + self.channels = channels + + reinitialize_rules! + end + + # Reinitializes all rules, setting @allow_if to [] + def reinitialize_rules! + @allow_if = [] + return self + end + + # Makes channel/apply_on combination accessible for everyone + def make_public! + reinitialize_rules! + allow_if do + true + end + return self + end + + # Makes channel/apply_on combination accessible for noone + def make_private! + reinitialize_rules! + allow_if do + false + end + return self + end + + # Checks if for the given context (which should be a task instance / instance of MessageBusTasks) + # all contained rules return true + def authorized?(task_context) + # If there is no rule, action is unauthorized + return false if @allow_if.empty? + + # else, check all rules and return false if any of them did not return true + # do not check all rules if unnecessary + @allow_if.each do |rule| + return false if rule.call(task_context) != true + end + + true + end + + # Adds another rule to this authorizer + # Just pass a block to this method, this block will be called with + # the task_context as a parameter on an authorization request + # Your block has to return true if it should authorize the action, + # all other return values are interpreted as false + def allow_if(&block) + @allow_if << block + return self + end + + def channels=(channels) + self.class.remove_from channels, self + @channels = channels + self.class.add_to channels, self + end + + # Applies authorizer to publishing requests + def apply_on_publishing + @apply_on_publishing = true + return self + end + + # Applies authorizer to subscribing requests + def apply_on_subscribing + @apply_on_subscribing = true + return self + end + + # Unapplies authorizer to publishing requests + def unapply_on_publishing + @apply_on_publishing = false + return self + end + + # Unapplies authorizer to publishing requests + def unapply_on_subscribing + @apply_on_subscribing = false + return self + end + + def apply_on_publishing? + @apply_on_publishing + end + + def apply_on_subscribing? + @apply_on_subscribing + end + + # Applies authorizer to publishing and subscribing requests + def apply_on_publishing_and_subscribing + apply_on_subscribing + apply_on_publishing + return self + end + + # Unapplies authorizer to publishing and subscribing requests (=> disables it) + def unapply_publishing_and_subscribing + unapply_on_subscribing + unapply_on_publishing + return self + end + + # Adds an authorizer to the global authorizer collection by building a hasmap + # based on the authorizer's channels + def self.add_to(channels, authorizer) + @authorizers ||= {} + channels.each do |channel| + @authorizers[channel] ||= [] + @authorizers[channel] << authorizer + end + end + + # Opposite of #add_to: removes authorizer from a given list of channels in the + # authorizer collection + def self.remove_from(channels, authorizer) + channels.each do |channel| + if @authorizers && @authorizers[channel] + @authorizers[channel].delete authorizer + end + end + end + + # Returns all authorizers (including namespace authorizers) which are associated to + # the given channel + def self.find_authorizers(channel) + return [] if @authorizers.nil? + result = [] + + # 1) add exact rules for channel name + result = result + @authorizers[channel] if @authorizers[channel] + + # 2) look for namespace rules + if channel.include?(":") + namespace_channel = channel.split(":").first + ":*" + result = result + @authorizers[namespace_channel] if @authorizers[namespace_channel] + end + + result + end + + # Filters a given list of authorizers, returns filtered list with authorizers which do all + # apply on the given mode (:publish or :subscribe) + def self.filter_appliance(authorizers, mode) + raise 'mode must be :publish or :subscribe' unless [:publish, :subscribe].include? mode + + filter_method = (mode == :publish) ? :apply_on_publishing? : :apply_on_subscribing? + authorizers.select{|authorizer| authorizer.send filter_method} + end + + # Checks if :publish or :subscribe to a given channel is allowed by evaluating all authorizers + # Needs task_context, which is in anstance of MessageBusTasks + # This method is used in MessageBusTasks to check the client's rights to publish/subscribe to a channel + def self.authorized?(task_context, mode, channel) + raise 'mode must be :publish or :subscribe' unless [:publish, :subscribe].include? mode + + # Find all fitting authorizers + authorizers = find_authorizers channel + authorizers = filter_appliance authorizers, mode + + # If there is no authorizer for this mode, action is unauthorized + # this means, per default, all actions are unauthorized + return false if authorizers.empty? + + # Else, loop through all authorizers, return false if any of them + # did not return true + authorizers.each do |authorizer| + return false if authorizer.authorized?(task_context) != true + end + + # If all checks passed, authorize action + true + end + end + end +end + + +# Default rule: Make everything public for all channel names starting with 'public:' +Volt::MessageBus::ClientAuthorizer.new(:publish_and_subscribe, + 'public:*').make_public! \ No newline at end of file From 2ea38c9dcf5c30797b849fda3c25d613a644a2b6 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Wed, 6 Jan 2016 14:22:49 +0100 Subject: [PATCH 5/8] changes all volt message bus channels to live in the 'volt:' namespace --- app/volt/tasks/live_query/live_query_pool.rb | 2 +- app/volt/tasks/message_bus_tasks.rb | 1 - lib/volt/volt/server_setup/app.rb | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/volt/tasks/live_query/live_query_pool.rb b/app/volt/tasks/live_query/live_query_pool.rb index c2b55e69..71ce0919 100644 --- a/app/volt/tasks/live_query/live_query_pool.rb +++ b/app/volt/tasks/live_query/live_query_pool.rb @@ -26,7 +26,7 @@ def updated_collection(collection, skip_channel, from_message_bus=false) msg_bus = @volt_app.message_bus if !from_message_bus && collection != 'active_volt_instances' && msg_bus - msg_bus.publish('volt_collection_update', collection) + msg_bus.publish('volt:collection_update', collection) end end diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb index 2e5c91c0..4531f917 100644 --- a/app/volt/tasks/message_bus_tasks.rb +++ b/app/volt/tasks/message_bus_tasks.rb @@ -75,7 +75,6 @@ def subscribing_allowed?(*channels) end # todo: enable usage in views - # todo: use 'volt:' as a channel prefix for all volt internals? private diff --git a/lib/volt/volt/server_setup/app.rb b/lib/volt/volt/server_setup/app.rb index 42c29827..cf4d0f5b 100644 --- a/lib/volt/volt/server_setup/app.rb +++ b/lib/volt/volt/server_setup/app.rb @@ -142,7 +142,7 @@ def start_message_bus Thread.new do # Handle incoming messages in a new thread - @message_bus.subscribe('volt_collection_update') do |collection_name| + @message_bus.subscribe('volt:collection_update') do |collection_name| # update a collection, don't resend since we're coming from # the message bus. live_query_pool.updated_collection(collection_name, nil, true) From 03c2b9380ca455b8ff6887fbd455a11cc873dc1c Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Wed, 6 Jan 2016 19:29:22 +0100 Subject: [PATCH 6/8] removes todo comment concerning view usage after a short discussion with ryan --- app/volt/tasks/message_bus_tasks.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/volt/tasks/message_bus_tasks.rb b/app/volt/tasks/message_bus_tasks.rb index 4531f917..5a18dd7e 100644 --- a/app/volt/tasks/message_bus_tasks.rb +++ b/app/volt/tasks/message_bus_tasks.rb @@ -74,8 +74,6 @@ def subscribing_allowed?(*channels) is_allowed? :subscribe, *channels end - # todo: enable usage in views - private # informs subscriber about new message in channel From 32da79bb6bf88cfb1a2bbbfd9c8dfec6cabc94e2 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Sat, 9 Jan 2016 00:28:40 +0100 Subject: [PATCH 7/8] adds early concept version of message bus controller helpers, for supporting purpose --- lib/volt/controllers/message_bus_helpers.rb | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 lib/volt/controllers/message_bus_helpers.rb diff --git a/lib/volt/controllers/message_bus_helpers.rb b/lib/volt/controllers/message_bus_helpers.rb new file mode 100644 index 00000000..5e2f22b7 --- /dev/null +++ b/lib/volt/controllers/message_bus_helpers.rb @@ -0,0 +1,52 @@ +# Current status of this: concept.. + +# Todo: How to add this to controller lifecycle management (see method todo's) +# Adds a method 'message_bus_subscription' to your controller which makes it easier to +# subscribe to message bus +# Usage: message_bus_subscription :my_event, :my_method +# You can also pass a proc instead of a method. The instance method or proc will be called +# every time the event is fired. The listeners will be removed automatically as soon as the +# controller is not needed anymore. + +module Volt + module MessageBusHelpers + module ClassMethods + def message_bus_subscription event, callback + callback = callback.to_sym unless callback.is_a?(Proc) + @message_bus_subscriptions ||= [] + @message_bus_subscriptions << {event: event, callback: callback} + end + end + + def self.included(base) + base.extend ClassMethods + end + + # todo: Call this method automatically on controller startup, but only once! + # before_action won't fit here, and hook in initialize either: + # the block is executed many times (5x) (why?) on start up / with a test on main_controller + def register_message_bus + @message_bus_listeners = [] + subscriptions = self.class.instance_variable_get :@message_bus_subscriptions + subscriptions ||= [] + + subscriptions.each do |subscription| + @message_bus_listeners << Volt.current_app.message_bus.on(subscription[:event]) do |*params| + case subscription[:callback] + when Symbol + send(subscription[:callback]) + when Proc + instance_eval(&subscription[:callback]) + end + end + end + end + + # todo: call this automatically once controller is not needed anymore + # how to integrate this into controller lifecycle management? + def remove_message_bus_listeners + return if @message_bus_listeners.nil? + @message_bus_listeners.each &:remove + end + end +end \ No newline at end of file From 85caadcef3edb632c6b06d4684e30180ee511af7 Mon Sep 17 00:00:00 2001 From: Antonius Ostermann Date: Tue, 19 Jan 2016 13:09:23 +0100 Subject: [PATCH 8/8] renames message_bus_client_proxy to message_bus_client_adapter --- ...ge_bus_client_proxy.rb => message_bus_client_adapter.rb} | 2 +- lib/volt/volt/client_setup/app.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/volt/page/{message_bus_client_proxy.rb => message_bus_client_adapter.rb} (97%) diff --git a/lib/volt/page/message_bus_client_proxy.rb b/lib/volt/page/message_bus_client_adapter.rb similarity index 97% rename from lib/volt/page/message_bus_client_proxy.rb rename to lib/volt/page/message_bus_client_adapter.rb index b824a3c6..1b8ae645 100644 --- a/lib/volt/page/message_bus_client_proxy.rb +++ b/lib/volt/page/message_bus_client_adapter.rb @@ -2,7 +2,7 @@ require 'volt/server/message_bus/base_message_bus' module Volt - class MessageBusClientProxy < MessageBus::BaseMessageBus + class MessageBusClientAdapter < MessageBus::BaseMessageBus include Eventable # Custom listener class, proxying Eventable#Listener since diff --git a/lib/volt/volt/client_setup/app.rb b/lib/volt/volt/client_setup/app.rb index 4dba2a7a..6fc6acdc 100644 --- a/lib/volt/volt/client_setup/app.rb +++ b/lib/volt/volt/client_setup/app.rb @@ -1,15 +1,15 @@ # The following setup handles setting up the app on the client. # Currently, this only sets up the message bus for client usage -require 'volt/page/message_bus_client_proxy' +require 'volt/page/message_bus_client_adapter' module Volt module ClientSetup module App - # Registers app.message_bus as a proxyfied websocket endpoint to the message bus + # Registers app.message_bus as a websocket endpoint / adapter to the message bus def start_message_bus - @message_bus = MessageBusClientProxy.new(self) + @message_bus = MessageBusClientAdapter.new(self) end end end