[feedback appreciated] Expose message bus to client #343
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Hey folks,
on the last recent days, I have been working on exposing volt's message bus to the client. With this pull request, this should be possible, but before merging, I'd love to have some feedback. In addition, I still need to write all specs for it, just wanted to check first that I've done everything right. There are some comments introduced with 'todo:', which reference to questions or tasks you could pick up in discussion.
How does it work?
The interface of the message bus on the client is the same as on the server.
Volt.current_app.message_bus
is connected toMessageBusClientProxy
on the client, which supports theEventable
module and callsMessageBusTasks
for publish/subscribe. Of course, these methods return Promises on the client, but unless you do not explicitly want to wait until sth has finished, you don't really need to handle them. Because of proxying, you can even call.remove
the same way you would do on the server:Volt.current_app.message_bus.on('some-event') { |message| ... }.remove
.If you subscribe / publish to a channel,
MessageBusTasks
takes care of redirecting the subscription to the real MessageBus and pipelines everything up and down the websocket. It automatically matches local client listeners to message bus listeners using uuids.Authorization layer
information extracted from
app/volt/tasks/message_bus_tasks.rb
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 isexplained very well.
Volt uses channels starting with 'volt:' for internal stuff, so be aware of publishing / subscribing
to these channels (although you could do!).
Define custom authorization rules
information extracted from
lib/volt/server/message_bus/client_authorizer.rb
lib/volt/server/message_bus/client_authorizer.rb
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: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.
The authorization 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.
Using namespaces
information extracted from
lib/volt/server/message_bus/client_authorizer.rb
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 use channels with the prefix 'chat:' dynamically, without the need of adding additional authorizers.
To define an authorizer, which should apply to a namespace, use 'namespace:*' as channel name, for example:
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
lib/volt/server/message_bus/client_authorizer.rb
), which enables everyone topublish/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!