hyper-operation 0.5.0
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 +7 -0
- data/.gitignore +49 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +324 -0
- data/LICENSE.txt +21 -0
- data/README.md +708 -0
- data/Rakefile +4 -0
- data/examples/chat-app/.gitignore +21 -0
- data/examples/chat-app/Gemfile +57 -0
- data/examples/chat-app/Gemfile.lock +282 -0
- data/examples/chat-app/README.md +3 -0
- data/examples/chat-app/Rakefile +6 -0
- data/examples/chat-app/app/assets/config/manifest.js +3 -0
- data/examples/chat-app/app/assets/images/.keep +0 -0
- data/examples/chat-app/app/assets/javascripts/application.js +3 -0
- data/examples/chat-app/app/assets/javascripts/cable.js +13 -0
- data/examples/chat-app/app/assets/javascripts/channels/.keep +0 -0
- data/examples/chat-app/app/assets/stylesheets/application.css +15 -0
- data/examples/chat-app/app/channels/application_cable/channel.rb +4 -0
- data/examples/chat-app/app/channels/application_cable/connection.rb +4 -0
- data/examples/chat-app/app/controllers/application_controller.rb +3 -0
- data/examples/chat-app/app/controllers/concerns/.keep +0 -0
- data/examples/chat-app/app/controllers/home_controller.rb +5 -0
- data/examples/chat-app/app/helpers/application_helper.rb +2 -0
- data/examples/chat-app/app/hyperloop/components/app.rb +11 -0
- data/examples/chat-app/app/hyperloop/components/formatted_div.rb +13 -0
- data/examples/chat-app/app/hyperloop/components/input_box.rb +29 -0
- data/examples/chat-app/app/hyperloop/components/message.rb +29 -0
- data/examples/chat-app/app/hyperloop/components/messages.rb +9 -0
- data/examples/chat-app/app/hyperloop/components/nav.rb +30 -0
- data/examples/chat-app/app/hyperloop/operations/operations.rb +56 -0
- data/examples/chat-app/app/hyperloop/stores/message_store.rb +23 -0
- data/examples/chat-app/app/models/application_record.rb +3 -0
- data/examples/chat-app/app/models/concerns/.keep +0 -0
- data/examples/chat-app/app/models/models.rb +2 -0
- data/examples/chat-app/app/models/public/.keep +0 -0
- data/examples/chat-app/app/models/public/announcement.rb +8 -0
- data/examples/chat-app/app/policies/application_policy.rb +5 -0
- data/examples/chat-app/app/views/layouts/application.html.erb +51 -0
- data/examples/chat-app/bin/bundle +3 -0
- data/examples/chat-app/bin/rails +9 -0
- data/examples/chat-app/bin/rake +9 -0
- data/examples/chat-app/bin/setup +34 -0
- data/examples/chat-app/bin/spring +17 -0
- data/examples/chat-app/bin/update +29 -0
- data/examples/chat-app/config.ru +5 -0
- data/examples/chat-app/config/application.rb +13 -0
- data/examples/chat-app/config/boot.rb +3 -0
- data/examples/chat-app/config/cable.yml +9 -0
- data/examples/chat-app/config/database.yml +25 -0
- data/examples/chat-app/config/environment.rb +5 -0
- data/examples/chat-app/config/environments/development.rb +56 -0
- data/examples/chat-app/config/environments/production.rb +86 -0
- data/examples/chat-app/config/environments/test.rb +42 -0
- data/examples/chat-app/config/initializers/application_controller_renderer.rb +6 -0
- data/examples/chat-app/config/initializers/assets.rb +11 -0
- data/examples/chat-app/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/chat-app/config/initializers/cookies_serializer.rb +5 -0
- data/examples/chat-app/config/initializers/filter_parameter_logging.rb +4 -0
- data/examples/chat-app/config/initializers/hyperloop.rb +4 -0
- data/examples/chat-app/config/initializers/inflections.rb +16 -0
- data/examples/chat-app/config/initializers/mime_types.rb +4 -0
- data/examples/chat-app/config/initializers/new_framework_defaults.rb +24 -0
- data/examples/chat-app/config/initializers/session_store.rb +3 -0
- data/examples/chat-app/config/initializers/wrap_parameters.rb +14 -0
- data/examples/chat-app/config/locales/en.yml +23 -0
- data/examples/chat-app/config/puma.rb +47 -0
- data/examples/chat-app/config/routes.rb +5 -0
- data/examples/chat-app/config/secrets.yml +22 -0
- data/examples/chat-app/config/spring.rb +6 -0
- data/examples/chat-app/db/seeds.rb +7 -0
- data/examples/chat-app/lib/assets/.keep +0 -0
- data/examples/chat-app/lib/tasks/.keep +0 -0
- data/examples/chat-app/log/.keep +0 -0
- data/examples/chat-app/public/404.html +67 -0
- data/examples/chat-app/public/422.html +67 -0
- data/examples/chat-app/public/500.html +66 -0
- data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/chat-app/public/apple-touch-icon.png +0 -0
- data/examples/chat-app/public/favicon.ico +0 -0
- data/examples/chat-app/public/robots.txt +5 -0
- data/examples/chat-app/test/controllers/.keep +0 -0
- data/examples/chat-app/test/fixtures/.keep +0 -0
- data/examples/chat-app/test/fixtures/files/.keep +0 -0
- data/examples/chat-app/test/helpers/.keep +0 -0
- data/examples/chat-app/test/integration/.keep +0 -0
- data/examples/chat-app/test/mailers/.keep +0 -0
- data/examples/chat-app/test/models/.keep +0 -0
- data/examples/chat-app/test/test_helper.rb +10 -0
- data/examples/chat-app/tmp/.keep +0 -0
- data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
- data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
- data/examples/five-letter-word-game/.gitignore +21 -0
- data/examples/five-letter-word-game/Gemfile +57 -0
- data/examples/five-letter-word-game/Gemfile.lock +282 -0
- data/examples/five-letter-word-game/README.md +24 -0
- data/examples/five-letter-word-game/Rakefile +6 -0
- data/examples/five-letter-word-game/app/assets/config/manifest.js +3 -0
- data/examples/five-letter-word-game/app/assets/images/.keep +0 -0
- data/examples/five-letter-word-game/app/assets/javascripts/application.js +3 -0
- data/examples/five-letter-word-game/app/assets/javascripts/cable.js +13 -0
- data/examples/five-letter-word-game/app/assets/javascripts/channels/.keep +0 -0
- data/examples/five-letter-word-game/app/assets/stylesheets/application.css +15 -0
- data/examples/five-letter-word-game/app/channels/application_cable/channel.rb +4 -0
- data/examples/five-letter-word-game/app/channels/application_cable/connection.rb +4 -0
- data/examples/five-letter-word-game/app/controllers/application_controller.rb +14 -0
- data/examples/five-letter-word-game/app/controllers/concerns/.keep +0 -0
- data/examples/five-letter-word-game/app/controllers/home_controller.rb +5 -0
- data/examples/five-letter-word-game/app/helpers/application_helper.rb +2 -0
- data/examples/five-letter-word-game/app/hyperloop/components/app.rb +65 -0
- data/examples/five-letter-word-game/app/hyperloop/components/guesses.rb +8 -0
- data/examples/five-letter-word-game/app/hyperloop/components/input_word.rb +13 -0
- data/examples/five-letter-word-game/app/hyperloop/models/user.rb +27 -0
- data/examples/five-letter-word-game/app/hyperloop/operations/ops.rb +115 -0
- data/examples/five-letter-word-game/app/hyperloop/stores/store.rb +120 -0
- data/examples/five-letter-word-game/app/jobs/application_job.rb +2 -0
- data/examples/five-letter-word-game/app/mailers/application_mailer.rb +4 -0
- data/examples/five-letter-word-game/app/models/application_record.rb +3 -0
- data/examples/five-letter-word-game/app/models/concerns/.keep +0 -0
- data/examples/five-letter-word-game/app/policies/user_policy.rb +4 -0
- data/examples/five-letter-word-game/app/views/layouts/application.html.erb +14 -0
- data/examples/five-letter-word-game/app/views/layouts/mailer.html.erb +13 -0
- data/examples/five-letter-word-game/app/views/layouts/mailer.text.erb +1 -0
- data/examples/five-letter-word-game/bin/bundle +3 -0
- data/examples/five-letter-word-game/bin/rails +9 -0
- data/examples/five-letter-word-game/bin/rake +9 -0
- data/examples/five-letter-word-game/bin/setup +34 -0
- data/examples/five-letter-word-game/bin/spring +17 -0
- data/examples/five-letter-word-game/bin/update +29 -0
- data/examples/five-letter-word-game/config.ru +5 -0
- data/examples/five-letter-word-game/config/application.rb +12 -0
- data/examples/five-letter-word-game/config/boot.rb +3 -0
- data/examples/five-letter-word-game/config/cable.yml +9 -0
- data/examples/five-letter-word-game/config/database.yml +25 -0
- data/examples/five-letter-word-game/config/environment.rb +5 -0
- data/examples/five-letter-word-game/config/environments/development.rb +56 -0
- data/examples/five-letter-word-game/config/environments/production.rb +86 -0
- data/examples/five-letter-word-game/config/environments/test.rb +42 -0
- data/examples/five-letter-word-game/config/initializers/application_controller_renderer.rb +6 -0
- data/examples/five-letter-word-game/config/initializers/assets.rb +11 -0
- data/examples/five-letter-word-game/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/five-letter-word-game/config/initializers/cookies_serializer.rb +5 -0
- data/examples/five-letter-word-game/config/initializers/filter_parameter_logging.rb +4 -0
- data/examples/five-letter-word-game/config/initializers/hyperloop.rb +3 -0
- data/examples/five-letter-word-game/config/initializers/inflections.rb +16 -0
- data/examples/five-letter-word-game/config/initializers/mime_types.rb +4 -0
- data/examples/five-letter-word-game/config/initializers/new_framework_defaults.rb +24 -0
- data/examples/five-letter-word-game/config/initializers/session_store.rb +3 -0
- data/examples/five-letter-word-game/config/initializers/wrap_parameters.rb +14 -0
- data/examples/five-letter-word-game/config/locales/en.yml +23 -0
- data/examples/five-letter-word-game/config/puma.rb +47 -0
- data/examples/five-letter-word-game/config/routes.rb +5 -0
- data/examples/five-letter-word-game/config/secrets.yml +22 -0
- data/examples/five-letter-word-game/config/spring.rb +6 -0
- data/examples/five-letter-word-game/db/schema.rb +28 -0
- data/examples/five-letter-word-game/db/seeds.rb +7 -0
- data/examples/five-letter-word-game/lib/assets/.keep +0 -0
- data/examples/five-letter-word-game/lib/tasks/.keep +0 -0
- data/examples/five-letter-word-game/log/.keep +0 -0
- data/examples/five-letter-word-game/public/404.html +67 -0
- data/examples/five-letter-word-game/public/422.html +67 -0
- data/examples/five-letter-word-game/public/500.html +66 -0
- data/examples/five-letter-word-game/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/five-letter-word-game/public/apple-touch-icon.png +0 -0
- data/examples/five-letter-word-game/public/favicon.ico +0 -0
- data/examples/five-letter-word-game/public/robots.txt +5 -0
- data/examples/five-letter-word-game/test/controllers/.keep +0 -0
- data/examples/five-letter-word-game/test/fixtures/.keep +0 -0
- data/examples/five-letter-word-game/test/fixtures/files/.keep +0 -0
- data/examples/five-letter-word-game/test/helpers/.keep +0 -0
- data/examples/five-letter-word-game/test/integration/.keep +0 -0
- data/examples/five-letter-word-game/test/mailers/.keep +0 -0
- data/examples/five-letter-word-game/test/models/.keep +0 -0
- data/examples/five-letter-word-game/test/test_helper.rb +10 -0
- data/examples/five-letter-word-game/tmp/.keep +0 -0
- data/examples/five-letter-word-game/vendor/assets/javascripts/.keep +0 -0
- data/examples/five-letter-word-game/vendor/assets/stylesheets/.keep +0 -0
- data/examples/smoke_test/.gitignore +21 -0
- data/examples/smoke_test/Gemfile +59 -0
- data/examples/smoke_test/Gemfile.lock +289 -0
- data/examples/smoke_test/README.md +24 -0
- data/examples/smoke_test/Rakefile +6 -0
- data/examples/smoke_test/app/assets/config/manifest.js +3 -0
- data/examples/smoke_test/app/assets/images/.keep +0 -0
- data/examples/smoke_test/app/assets/javascripts/application.js +15 -0
- data/examples/smoke_test/app/assets/javascripts/cable.js +13 -0
- data/examples/smoke_test/app/assets/javascripts/channels/.keep +0 -0
- data/examples/smoke_test/app/assets/stylesheets/application.css +15 -0
- data/examples/smoke_test/app/channels/application_cable/channel.rb +4 -0
- data/examples/smoke_test/app/channels/application_cable/connection.rb +4 -0
- data/examples/smoke_test/app/controllers/app_controller.rb +5 -0
- data/examples/smoke_test/app/controllers/application_controller.rb +3 -0
- data/examples/smoke_test/app/helpers/application_helper.rb +2 -0
- data/examples/smoke_test/app/hyperloop/components/hello.rb +25 -0
- data/examples/smoke_test/app/hyperloop/operations/operations/nested_send_to_all.rb +7 -0
- data/examples/smoke_test/app/hyperloop/operations/send_to_all.rb +6 -0
- data/examples/smoke_test/app/hyperloop/stores/messages.rb +4 -0
- data/examples/smoke_test/app/jobs/application_job.rb +2 -0
- data/examples/smoke_test/app/mailers/application_mailer.rb +4 -0
- data/examples/smoke_test/app/models/application_record.rb +3 -0
- data/examples/smoke_test/app/models/concerns/.keep +0 -0
- data/examples/smoke_test/app/policies/application_policy.rb +8 -0
- data/examples/smoke_test/app/views/layouts/application.html.erb +14 -0
- data/examples/smoke_test/app/views/layouts/mailer.html.erb +13 -0
- data/examples/smoke_test/app/views/layouts/mailer.text.erb +1 -0
- data/examples/smoke_test/bin/bundle +3 -0
- data/examples/smoke_test/bin/rails +9 -0
- data/examples/smoke_test/bin/rake +9 -0
- data/examples/smoke_test/bin/setup +34 -0
- data/examples/smoke_test/bin/spring +17 -0
- data/examples/smoke_test/bin/update +29 -0
- data/examples/smoke_test/config.ru +5 -0
- data/examples/smoke_test/config/application.rb +15 -0
- data/examples/smoke_test/config/boot.rb +3 -0
- data/examples/smoke_test/config/cable.yml +9 -0
- data/examples/smoke_test/config/database.yml +25 -0
- data/examples/smoke_test/config/environment.rb +5 -0
- data/examples/smoke_test/config/environments/development.rb +54 -0
- data/examples/smoke_test/config/environments/production.rb +86 -0
- data/examples/smoke_test/config/environments/test.rb +42 -0
- data/examples/smoke_test/config/initializers/application_controller_renderer.rb +6 -0
- data/examples/smoke_test/config/initializers/assets.rb +11 -0
- data/examples/smoke_test/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/smoke_test/config/initializers/cookies_serializer.rb +5 -0
- data/examples/smoke_test/config/initializers/filter_parameter_logging.rb +4 -0
- data/examples/smoke_test/config/initializers/hyperloop.rb +32 -0
- data/examples/smoke_test/config/initializers/inflections.rb +16 -0
- data/examples/smoke_test/config/initializers/mime_types.rb +4 -0
- data/examples/smoke_test/config/initializers/new_framework_defaults.rb +24 -0
- data/examples/smoke_test/config/initializers/session_store.rb +3 -0
- data/examples/smoke_test/config/initializers/wrap_parameters.rb +14 -0
- data/examples/smoke_test/config/locales/en.yml +23 -0
- data/examples/smoke_test/config/puma.rb +47 -0
- data/examples/smoke_test/config/routes.rb +5 -0
- data/examples/smoke_test/config/secrets.yml +22 -0
- data/examples/smoke_test/config/spring.rb +6 -0
- data/examples/smoke_test/db/seeds.rb +7 -0
- data/examples/smoke_test/lib/assets/.keep +0 -0
- data/examples/smoke_test/lib/tasks/.keep +0 -0
- data/examples/smoke_test/log/.keep +0 -0
- data/examples/smoke_test/public/404.html +67 -0
- data/examples/smoke_test/public/422.html +67 -0
- data/examples/smoke_test/public/500.html +66 -0
- data/examples/smoke_test/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/smoke_test/public/apple-touch-icon.png +0 -0
- data/examples/smoke_test/public/favicon.ico +0 -0
- data/examples/smoke_test/public/robots.txt +5 -0
- data/examples/smoke_test/test/controllers/.keep +0 -0
- data/examples/smoke_test/test/fixtures/.keep +0 -0
- data/examples/smoke_test/test/fixtures/files/.keep +0 -0
- data/examples/smoke_test/test/helpers/.keep +0 -0
- data/examples/smoke_test/test/integration/.keep +0 -0
- data/examples/smoke_test/test/mailers/.keep +0 -0
- data/examples/smoke_test/test/models/.keep +0 -0
- data/examples/smoke_test/test/test_helper.rb +10 -0
- data/examples/smoke_test/tmp/.keep +0 -0
- data/examples/smoke_test/vendor/assets/javascripts/.keep +0 -0
- data/examples/smoke_test/vendor/assets/stylesheets/.keep +0 -0
- data/hyper-operation.gemspec +44 -0
- data/lib/hyper-operation.rb +59 -0
- data/lib/hyper-operation/api.rb +154 -0
- data/lib/hyper-operation/boot.rb +24 -0
- data/lib/hyper-operation/call_by_class_name.rb +60 -0
- data/lib/hyper-operation/delay_and_interval.rb +9 -0
- data/lib/hyper-operation/engine.rb +11 -0
- data/lib/hyper-operation/exception.rb +13 -0
- data/lib/hyper-operation/filters/acting_user.rb +19 -0
- data/lib/hyper-operation/filters/outbound_filter.rb +9 -0
- data/lib/hyper-operation/filters/simple_hash.rb +22 -0
- data/lib/hyper-operation/opal_patches.rb +39 -0
- data/lib/hyper-operation/promise.rb +347 -0
- data/lib/hyper-operation/railway.rb +5 -0
- data/lib/hyper-operation/railway/dispatcher.rb +31 -0
- data/lib/hyper-operation/railway/operation_wrapper.rb +106 -0
- data/lib/hyper-operation/railway/params_wrapper.rb +124 -0
- data/lib/hyper-operation/railway/run.rb +140 -0
- data/lib/hyper-operation/railway/validations.rb +63 -0
- data/lib/hyper-operation/server_op.rb +98 -0
- data/lib/hyper-operation/transport/acting_user.rb +6 -0
- data/lib/hyper-operation/transport/action_cable.rb +39 -0
- data/lib/hyper-operation/transport/active_record.rb +15 -0
- data/lib/hyper-operation/transport/client_drivers.rb +210 -0
- data/lib/hyper-operation/transport/connection.rb +170 -0
- data/lib/hyper-operation/transport/hyperloop.rb +165 -0
- data/lib/hyper-operation/transport/hyperloop_controller.rb +184 -0
- data/lib/hyper-operation/transport/pluck.rb +6 -0
- data/lib/hyper-operation/transport/policy.rb +535 -0
- data/lib/hyper-operation/version.rb +5 -0
- data/lib/sources/hyperloop/pusher.js +98 -0
- metadata +628 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
# Provides the configuration and the two basic routines for the server
|
2
|
+
# to indicate that records have changed: after_change and after_destroy
|
3
|
+
module Hyperloop
|
4
|
+
|
5
|
+
def self.initialize_policies
|
6
|
+
reset_operations unless @config_reset_called
|
7
|
+
end
|
8
|
+
|
9
|
+
on_config_reset do
|
10
|
+
reset_operations
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.reset_operations
|
14
|
+
@config_reset_called = true
|
15
|
+
Rails.configuration.tap do |config|
|
16
|
+
# config.eager_load_paths += %W(#{config.root}/app/hyperloop/models)
|
17
|
+
# config.autoload_paths += %W(#{config.root}/app/hyperloop/models)
|
18
|
+
# config.assets.paths << ::Rails.root.join('app', 'hyperloop').to_s
|
19
|
+
config.after_initialize { Connection.build_tables }
|
20
|
+
end
|
21
|
+
Object.send(:remove_const, :Application) if @fake_application_defined
|
22
|
+
policy = begin
|
23
|
+
Object.const_get 'ApplicationPolicy'
|
24
|
+
rescue Exception => e
|
25
|
+
#raise e unless e.is_a?(NameError) && e.message == "uninitialized constant ApplicationPolicy"
|
26
|
+
rescue LoadError
|
27
|
+
end
|
28
|
+
application = begin
|
29
|
+
Object.const_get('Application')
|
30
|
+
rescue LoadError
|
31
|
+
rescue Exception => e
|
32
|
+
#raise e unless e.is_a?(NameError) && e.message == "uninitialized constant Application"
|
33
|
+
end if policy
|
34
|
+
if policy && !application
|
35
|
+
Object.const_set 'Application', Class.new
|
36
|
+
@fake_application_defined = true
|
37
|
+
end
|
38
|
+
@pusher = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
define_setting(:transport, :none) do |transport|
|
42
|
+
if transport == :action_cable
|
43
|
+
require 'hyper-operation/transport/action_cable'
|
44
|
+
opts[:refresh_channels_every] = :never
|
45
|
+
import 'action_cable', client_only: true if Rails.configuration.hyperloop.auto_config
|
46
|
+
elsif transport == :pusher
|
47
|
+
require 'pusher'
|
48
|
+
import 'hyperloop/pusher', client_only: true if Rails.configuration.hyperloop.auto_config
|
49
|
+
opts[:refresh_channels_every] = nil if opts[:refresh_channels_every] == :never
|
50
|
+
else
|
51
|
+
opts[:refresh_channels_every] = nil if opts[:refresh_channels_every] == :never
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
define_setting :opts, {}
|
56
|
+
define_setting :channel_prefix, 'synchromesh'
|
57
|
+
define_setting :client_logging, true
|
58
|
+
|
59
|
+
def self.app_id
|
60
|
+
opts[:app_id] || Pusher.app_id if transport == :pusher
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.key
|
64
|
+
opts[:key] || Pusher.key if transport == :pusher
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.secret
|
68
|
+
opts[:secret] || Pusher.secret if transport == :pusher
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.encrypted
|
72
|
+
opts.key?(:encrypted) ? opts[:encrypted] : true
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.expire_polled_connection_in
|
76
|
+
opts[:expire_polled_connection_in] || (5 * 60)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.seconds_between_poll
|
80
|
+
opts[:seconds_between_poll] || 0.5
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.expire_new_connection_in
|
84
|
+
opts[:expire_new_connection_in] || 10.seconds
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.refresh_channels_timeout
|
88
|
+
opts[:refresh_channels_timeout] || 5.seconds
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.refresh_channels_every
|
92
|
+
opts[:refresh_channels_every] || 2.minutes
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.refresh_channels
|
96
|
+
new_channels = pusher.channels[:channels].collect do |channel, _etc|
|
97
|
+
channel.gsub(/^#{Regexp.quote(Hyperloop.channel)}\-/, '').gsub('==', '::')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.send_data(channel, data)
|
102
|
+
if !on_server?
|
103
|
+
send_to_server(channel, data)
|
104
|
+
elsif transport == :pusher
|
105
|
+
pusher.trigger("#{Hyperloop.channel}-#{data[1][:channel].gsub('::', '==')}", *data)
|
106
|
+
elsif transport == :action_cable
|
107
|
+
ActionCable.server.broadcast("hyperloop-#{channel}", message: data[0], data: data[1])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.on_server?
|
112
|
+
Rails.const_defined? 'Server'
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.pusher
|
116
|
+
unless @pusher
|
117
|
+
unless channel_prefix
|
118
|
+
self.transport = nil
|
119
|
+
raise '******** NO CHANNEL PREFIX SET ***************'
|
120
|
+
end
|
121
|
+
@pusher = Pusher::Client.new(
|
122
|
+
opts || { app_id: app_id, key: key, secret: secret }
|
123
|
+
)
|
124
|
+
end
|
125
|
+
@pusher
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.channel
|
129
|
+
"private-#{channel_prefix}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.authorization(salt, channel, session_id)
|
133
|
+
secret_key = Rails.application.secrets[:secret_key_base]
|
134
|
+
Digest::SHA1.hexdigest(
|
135
|
+
"salt: #{salt}, channel: #{channel}, session_id: #{session_id}, secret_key: #{secret_key}"
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.send_to_server(channel, data) # TODO this should work the same/similar to HyperMesh / Models way of sending to console
|
140
|
+
salt = SecureRandom.hex
|
141
|
+
authorization = authorization(salt, channel, data[1][:broadcast_id])
|
142
|
+
raise 'no server running' unless Connection.root_path
|
143
|
+
uri = URI("#{Connection.root_path}console_update")
|
144
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
145
|
+
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
146
|
+
if uri.scheme == 'https'
|
147
|
+
http.use_ssl = true
|
148
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
149
|
+
end
|
150
|
+
request.body = {
|
151
|
+
channel: channel, data: data, salt: salt, authorization: authorization
|
152
|
+
}.to_json
|
153
|
+
http.request(request)
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.dispatch(data)
|
157
|
+
if !Hyperloop.on_server? && Connection.root_path
|
158
|
+
Hyperloop.send_to_server(data[:channel], [:dispatch, data])
|
159
|
+
else
|
160
|
+
Connection.send_to_channel(data[:channel], [:dispatch, data])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
Connection.transport = self
|
165
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Hyperloop
|
2
|
+
::Hyperloop::Engine.routes.append do
|
3
|
+
Hyperloop.initialize_policies
|
4
|
+
|
5
|
+
module ::WebConsole
|
6
|
+
class Middleware
|
7
|
+
private
|
8
|
+
def acceptable_content_type?(headers)
|
9
|
+
Mime::Type.parse(headers['Content-Type'] || '').first == Mime[:html]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end if defined? ::WebConsole::Middleware
|
13
|
+
|
14
|
+
module ::Rails
|
15
|
+
module Rack
|
16
|
+
class Logger < ActiveSupport::LogSubscriber
|
17
|
+
unless method_defined? :pre_hyperloop_call
|
18
|
+
alias pre_hyperloop_call call
|
19
|
+
def call(env)
|
20
|
+
if !Hyperloop.opts[:noisy] && env['HTTP_X_HYPERLOOP_SILENT_REQUEST']
|
21
|
+
Rails.logger.silence do
|
22
|
+
pre_hyperloop_call(env)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
pre_hyperloop_call(env)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end if defined?(::Rails::Rack::Logger)
|
32
|
+
|
33
|
+
class HyperloopController < ::ApplicationController
|
34
|
+
|
35
|
+
protect_from_forgery except: [:console_update, :execute_remote_api]
|
36
|
+
|
37
|
+
def client_id
|
38
|
+
params[:client_id]
|
39
|
+
end
|
40
|
+
|
41
|
+
before_action do
|
42
|
+
session.delete 'hyperloop-dummy-init' unless session.id
|
43
|
+
end
|
44
|
+
|
45
|
+
def channels(user = acting_user, session_id = session.id)
|
46
|
+
Hyperloop::AutoConnect.channels(session_id, user)
|
47
|
+
end
|
48
|
+
|
49
|
+
def can_connect?(channel, user = acting_user)
|
50
|
+
Hyperloop::InternalPolicy.regulate_connection(
|
51
|
+
user,
|
52
|
+
Hyperloop::InternalPolicy.channel_to_string(channel)
|
53
|
+
)
|
54
|
+
true
|
55
|
+
rescue
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def view_permitted?(model, attr, user = acting_user)
|
60
|
+
!!model.check_permission_with_acting_user(user, :view_permitted?, attr)
|
61
|
+
rescue
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def viewable_attributes(model, user = acting_user)
|
66
|
+
model.attributes.select { |attr| view_permitted?(model, attr, user) }
|
67
|
+
end
|
68
|
+
|
69
|
+
[:create, :update, :destroy].each do |op|
|
70
|
+
define_method "#{op}_permitted?" do |model, user = acting_user|
|
71
|
+
begin
|
72
|
+
!!model.check_permission_with_acting_user(user, "#{op}_permitted?".to_sym)
|
73
|
+
rescue
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def debug_console
|
80
|
+
if Rails.env.development?
|
81
|
+
render inline: "<style>div#console {height: 100% !important;}</style>\n".html_safe
|
82
|
+
# "<div>additional helper methods: channels, can_connect? "\
|
83
|
+
# "viewable_attributes, view_permitted?, create_permitted?, "\
|
84
|
+
# "update_permitted? and destroy_permitted?</div>\n".html_safe
|
85
|
+
console
|
86
|
+
else
|
87
|
+
head :unauthorized
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def subscribe
|
92
|
+
channel = params[:channel].gsub('==', '::')
|
93
|
+
Hyperloop::InternalPolicy.regulate_connection(try(:acting_user), channel)
|
94
|
+
root_path = request.original_url.gsub(/hyperloop-subscribe.*$/, '')
|
95
|
+
Hyperloop::Connection.open(channel, client_id, root_path)
|
96
|
+
head :ok
|
97
|
+
rescue Exception
|
98
|
+
head :unauthorized
|
99
|
+
end
|
100
|
+
|
101
|
+
def read
|
102
|
+
root_path = request.original_url.gsub(/hyperloop-read.*$/, '')
|
103
|
+
data = Hyperloop::Connection.read(client_id, root_path)
|
104
|
+
render json: data
|
105
|
+
end
|
106
|
+
|
107
|
+
def pusher_auth
|
108
|
+
channel = params[:channel_name].gsub(/^#{Regexp.quote(Hyperloop.channel)}\-/,'').gsub('==', '::')
|
109
|
+
Hyperloop::InternalPolicy.regulate_connection(acting_user, channel)
|
110
|
+
response = Hyperloop.pusher.authenticate(params[:channel_name], params[:socket_id])
|
111
|
+
render json: response
|
112
|
+
rescue Exception => e
|
113
|
+
head :unauthorized
|
114
|
+
end
|
115
|
+
|
116
|
+
def action_cable_auth
|
117
|
+
channel = params[:channel_name].gsub(/^#{Regexp.quote(Hyperloop.channel)}\-/,'')
|
118
|
+
Hyperloop::InternalPolicy.regulate_connection(acting_user, channel)
|
119
|
+
salt = SecureRandom.hex
|
120
|
+
authorization = Hyperloop.authorization(salt, channel, client_id)
|
121
|
+
render json: {authorization: authorization, salt: salt}
|
122
|
+
rescue Exception
|
123
|
+
head :unauthorized
|
124
|
+
end
|
125
|
+
|
126
|
+
def connect_to_transport
|
127
|
+
root_path = request.original_url.gsub(/hyperloop-connect-to-transport.*$/, '')
|
128
|
+
render json: Hyperloop::Connection.connect_to_transport(params[:channel], client_id, root_path)
|
129
|
+
end
|
130
|
+
|
131
|
+
def execute_remote
|
132
|
+
parsed_params = JSON.parse(params[:json]).symbolize_keys
|
133
|
+
render ServerOp.run_from_client(
|
134
|
+
:acting_user, parsed_params[:operation], parsed_params[:params].merge(acting_user: acting_user))
|
135
|
+
end
|
136
|
+
|
137
|
+
def execute_remote_api
|
138
|
+
parsed_params = params[:params].symbolize_keys
|
139
|
+
raise AccessViolation unless parsed_params[:authorization]
|
140
|
+
render ServerOp.run_from_client(:authorization, params[:operation], parsed_params)
|
141
|
+
end
|
142
|
+
|
143
|
+
def console_update # TODO this should just become an execute-remote-api call
|
144
|
+
authorization = Hyperloop.authorization(params[:salt], params[:channel], params[:data][1][:broadcast_id]) #params[:data].to_json)
|
145
|
+
return head :unauthorized if authorization != params[:authorization]
|
146
|
+
Hyperloop::Connection.send_to_channel(params[:channel], params[:data])
|
147
|
+
head :no_content
|
148
|
+
rescue
|
149
|
+
head :unauthorized
|
150
|
+
end
|
151
|
+
|
152
|
+
def server_up
|
153
|
+
head :no_content
|
154
|
+
end
|
155
|
+
|
156
|
+
end unless defined? HyperloopController
|
157
|
+
|
158
|
+
match 'execute_remote',
|
159
|
+
to: 'hyperloop#execute_remote', via: :post
|
160
|
+
match 'execute_remote_api',
|
161
|
+
to: 'hyperloop#execute_remote_api', via: :post
|
162
|
+
|
163
|
+
# match 'hyperloop-subscribe',
|
164
|
+
# to: 'hyperloop#subscribe', via: :get
|
165
|
+
# match 'hyperloop-read/:subscriber',
|
166
|
+
# to: 'hyperloop#read', via: :get
|
167
|
+
match 'hyperloop-subscribe/:client_id/:channel',
|
168
|
+
to: 'hyperloop#subscribe', via: :get
|
169
|
+
match 'hyperloop-read/:client_id',
|
170
|
+
to: 'hyperloop#read', via: :get
|
171
|
+
match 'hyperloop-pusher-auth',
|
172
|
+
to: 'hyperloop#pusher_auth', via: :post
|
173
|
+
match 'hyperloop-action-cable-auth/:client_id/:channel_name',
|
174
|
+
to: 'hyperloop#action_cable_auth', via: :post
|
175
|
+
match 'hyperloop-connect-to-transport/:client_id/:channel',
|
176
|
+
to: 'hyperloop#connect_to_transport', via: :get
|
177
|
+
match 'console',
|
178
|
+
to: 'hyperloop#debug_console', via: :get
|
179
|
+
match 'console_update',
|
180
|
+
to: 'hyperloop#console_update', via: :post
|
181
|
+
match 'server_up',
|
182
|
+
to: 'hyperloop#server_up', via: :get
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,535 @@
|
|
1
|
+
module Hyperloop
|
2
|
+
|
3
|
+
class InternalClassPolicy
|
4
|
+
|
5
|
+
def initialize(regulated_klass)
|
6
|
+
@regulated_klass = regulated_klass
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :regulated_klass
|
10
|
+
|
11
|
+
EXPOSED_METHODS = [
|
12
|
+
:regulate_class_connection, :always_allow_connection, :regulate_instance_connections,
|
13
|
+
:regulate_all_broadcasts, :regulate_broadcast,
|
14
|
+
:dispatch_to, :regulate_dispatches_from, :always_dispatch_from,
|
15
|
+
:allow_change, :allow_create, :allow_read, :allow_update, :allow_destroy
|
16
|
+
]
|
17
|
+
|
18
|
+
def regulate_class_connection(*args, ®ulation)
|
19
|
+
regulate(ClassConnectionRegulation, :class_connection, args, ®ulation)
|
20
|
+
end
|
21
|
+
|
22
|
+
def always_allow_connection(*args)
|
23
|
+
regulate(ClassConnectionRegulation, nil, args) { true }
|
24
|
+
end
|
25
|
+
|
26
|
+
def regulate_instance_connections(*args, ®ulation)
|
27
|
+
regulate(InstanceConnectionRegulation, :instance_connections, args, ®ulation)
|
28
|
+
end
|
29
|
+
|
30
|
+
def regulate_all_broadcasts(*args, ®ulation)
|
31
|
+
regulate(ChannelBroadcastRegulation, :all_broadcasts, args, ®ulation)
|
32
|
+
end
|
33
|
+
|
34
|
+
def regulate_broadcast(*args, ®ulation)
|
35
|
+
regulate(InstanceBroadcastRegulation, :broadcast, args, ®ulation)
|
36
|
+
end
|
37
|
+
|
38
|
+
def regulate_dispatches_from(*args, ®ulation)
|
39
|
+
args.each do |klass|
|
40
|
+
unless klass.respond_to? :dispatch_to
|
41
|
+
raise 'you can only regulate_dispatches_from Operation classes'
|
42
|
+
end
|
43
|
+
klass._dispatch_to(self) { |sself| sself.regulated_klass if instance_eval(®ulation) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def always_dispatch_from(*args, ®ulation)
|
48
|
+
regulate_dispatches_from(*args) { true }
|
49
|
+
end
|
50
|
+
|
51
|
+
def dispatch_to(*args, ®ulation)
|
52
|
+
actual_klass = regulated_klass.is_a?(Class) ? regulated_klass : regulated_klass.constantize rescue nil
|
53
|
+
actual_klass.dispatch_to(actual_klass) if actual_klass.respond_to? :dispatch_to
|
54
|
+
unless actual_klass.respond_to? :dispatch_to
|
55
|
+
raise 'you can only dispatch_to Operation classes'
|
56
|
+
end
|
57
|
+
actual_klass.dispatch_to(*args, ®ulation)
|
58
|
+
end
|
59
|
+
|
60
|
+
CHANGE_POLICIES = [:create, :update, :destroy]
|
61
|
+
|
62
|
+
def self.allow_policy(policy, method)
|
63
|
+
define_method "allow_#{policy}" do |*args, ®ulation|
|
64
|
+
process_args(policy, [], args, regulation) do |model|
|
65
|
+
get_ar_model(model).class_eval { define_method("#{method}_permitted?", ®ulation) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
CHANGE_POLICIES.each { |policy| allow_policy policy, policy }
|
71
|
+
|
72
|
+
allow_policy(:read, :view)
|
73
|
+
|
74
|
+
def allow_change(*args, ®ulation)
|
75
|
+
process_args('change', [:on], args, regulation) do |model, opts|
|
76
|
+
model = get_ar_model(model)
|
77
|
+
opts[:on] ||= CHANGE_POLICIES
|
78
|
+
opts[:on].each do |policy|
|
79
|
+
check_valid_on_option policy
|
80
|
+
model.class_eval { define_method("#{policy}_permitted?", ®ulation) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_ar_model(str)
|
86
|
+
str.is_a?(Class) ? str : Object.const_get(str)
|
87
|
+
rescue
|
88
|
+
raise "#{str} is not a class"
|
89
|
+
end
|
90
|
+
|
91
|
+
def regulate(regulation_klass, policy, args, ®ulation)
|
92
|
+
process_args(policy, regulation_klass.allowed_opts, args, regulation) do |regulated_klass, opts|
|
93
|
+
regulation_klass.add_regulation regulated_klass, opts, ®ulation
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_args(policy, allowed_opts, args, regulation)
|
98
|
+
raise "you must provide a block to the regulate_#{policy} method" unless regulation
|
99
|
+
*args, opts = args if args.last.is_a? Hash
|
100
|
+
opts ||= {}
|
101
|
+
args = process_to_opt(allowed_opts, opts, args)
|
102
|
+
args.each do |regulated_klass|
|
103
|
+
yield regulated_klass, opts
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_to_opt(allowed_opts, opts, args)
|
108
|
+
opts.each do |opt, value|
|
109
|
+
unless opt == :to || allowed_opts.include?(opt)
|
110
|
+
raise "Unknown ':#{opt} => #{value}' option in policy definition"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
if (to = opts[:to])
|
114
|
+
raise "option to: :#{to} is not recognized in allow_#{policy}" unless to == :all
|
115
|
+
raise "option to: :all cannot be used with other classes" unless args.empty?
|
116
|
+
[ActiveRecord::Base]
|
117
|
+
elsif args.empty?
|
118
|
+
[@regulated_klass]
|
119
|
+
else
|
120
|
+
args
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def check_valid_on_option(policy)
|
125
|
+
unless CHANGE_POLICIES.include? policy
|
126
|
+
valid_policies = CHANGE_POLICIES.collect { |s| ":{s}" }.to_sentence
|
127
|
+
raise "only #{valid_policies} are allowed on the regulate_access :on option"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
class Regulation
|
134
|
+
|
135
|
+
class << self
|
136
|
+
|
137
|
+
def add_regulation(klass, opts={}, ®ulation)
|
138
|
+
klass = regulations[klass]
|
139
|
+
klass.opts.merge! opts
|
140
|
+
klass.regulations << regulation
|
141
|
+
klass
|
142
|
+
end
|
143
|
+
|
144
|
+
def regulations
|
145
|
+
@regulations ||= Hash.new do |hash, klass|
|
146
|
+
if klass.is_a? String
|
147
|
+
hash[klass] = new(klass)
|
148
|
+
elsif klass.is_a?(Class) && klass.name
|
149
|
+
hash[klass.name]
|
150
|
+
else
|
151
|
+
hash[klass.class.name]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def wrap_policy(policy, regulation)
|
157
|
+
begin
|
158
|
+
policy_klass = regulation.binding.receiver
|
159
|
+
rescue
|
160
|
+
raise "Could not determine the class when regulating. This is probably caused by doing something like &:send_all"
|
161
|
+
end
|
162
|
+
wrapped_policy = policy_klass.new(nil, nil)
|
163
|
+
wrapped_policy.hyperloop_internal_policy_object = policy
|
164
|
+
wrapped_policy
|
165
|
+
end
|
166
|
+
|
167
|
+
def allowed_opts
|
168
|
+
[]
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
attr_reader :klass
|
174
|
+
|
175
|
+
def opts
|
176
|
+
@opts ||= {}
|
177
|
+
end
|
178
|
+
|
179
|
+
def regulations
|
180
|
+
@regulations ||= []
|
181
|
+
end
|
182
|
+
|
183
|
+
def regulate_for(acting_user)
|
184
|
+
Enumerator.new do |y|
|
185
|
+
regulations.each do |regulation|
|
186
|
+
y << acting_user.instance_eval(®ulation)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def auto_connect_disabled?
|
192
|
+
opts.has_key?(:auto_connect) && !opts[:auto_connect]
|
193
|
+
end
|
194
|
+
|
195
|
+
def initialize(klass)
|
196
|
+
@klass = klass
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
class ClassConnectionRegulation < Regulation
|
202
|
+
|
203
|
+
def self.add_regulation(klass, opts={}, ®ulation)
|
204
|
+
actual_klass = klass.is_a?(Class) ? klass : klass.constantize rescue nil
|
205
|
+
actual_klass.dispatch_to(actual_klass) if actual_klass.respond_to? :dispatch_to rescue nil
|
206
|
+
super
|
207
|
+
end
|
208
|
+
|
209
|
+
def connectable?(acting_user)
|
210
|
+
regulate_for(acting_user).all? unless regulations.empty? rescue nil
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.connect(channel, acting_user)
|
214
|
+
raise "connection failed" unless regulations[channel].connectable?(acting_user)
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.connections_for(acting_user, auto_connections_only)
|
218
|
+
regulations.collect do |channel, regulation|
|
219
|
+
next if auto_connections_only && regulation.auto_connect_disabled?
|
220
|
+
next if !regulation.connectable?(acting_user)
|
221
|
+
channel
|
222
|
+
end.compact
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.allowed_opts
|
226
|
+
[:auto_connect]
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
class InstanceConnectionRegulation < Regulation
|
232
|
+
|
233
|
+
def connectable_to(acting_user, auto_connections_only)
|
234
|
+
return [] if auto_connections_only && auto_connect_disabled?
|
235
|
+
regulate_for(acting_user).entries.compact.flatten(1) rescue []
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.connect(instance, acting_user)
|
239
|
+
unless regulations[instance].connectable_to(acting_user, false).include? instance
|
240
|
+
raise "connection failed"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.connections_for(acting_user, auto_connections_only)
|
245
|
+
regulations.collect do |_channel, regulation|
|
246
|
+
# the following was bizarelly passing true for auto_connections_only????
|
247
|
+
regulation.connectable_to(acting_user, auto_connections_only).collect do |obj|
|
248
|
+
if false && auto_connections_only # false added to try to get channel instances to connect???
|
249
|
+
[obj.class.name, obj.id]
|
250
|
+
else
|
251
|
+
InternalPolicy.channel_to_string obj
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end.flatten(1)
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.allowed_opts
|
258
|
+
[:auto_connect]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class ChannelBroadcastRegulation < Regulation
|
263
|
+
class << self
|
264
|
+
def add_regulation(channel, opts={}, ®ulation)
|
265
|
+
regulations_to_channels[regulation] << super
|
266
|
+
end
|
267
|
+
|
268
|
+
def broadcast(policy)
|
269
|
+
regulations_to_channels.each do |regulation, channels|
|
270
|
+
wrapped_policy = wrap_policy(policy, regulation)
|
271
|
+
channels.each do |channel|
|
272
|
+
regulation.call wrapped_policy
|
273
|
+
policy.send_unassigned_sets_to channel.klass
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def regulations_to_channels
|
279
|
+
@regulations_to_channels ||= Hash.new { |hash, key| hash[key] = [] }
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
class InstanceBroadcastRegulation < Regulation
|
285
|
+
def self.broadcast(instance, policy)
|
286
|
+
regulations[instance].regulations.each do |regulation|
|
287
|
+
instance.instance_exec wrap_policy(policy, regulation), ®ulation
|
288
|
+
end
|
289
|
+
if policy.has_unassigned_sets?
|
290
|
+
raise "#{instance.class.name} instance broadcast policy not sent to any channel"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
module AutoConnect
|
296
|
+
def self.channels(session, acting_user)
|
297
|
+
channels = ClassConnectionRegulation.connections_for(acting_user, true) +
|
298
|
+
InstanceConnectionRegulation.connections_for(acting_user, true)
|
299
|
+
channels = channels.uniq
|
300
|
+
channels.each do |channel|
|
301
|
+
Connection.open(channel, session)
|
302
|
+
end
|
303
|
+
channels
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class InternalPolicy
|
308
|
+
|
309
|
+
EXPOSED_METHODS = [:send_all, :send_all_but, :send_only, :obj]
|
310
|
+
|
311
|
+
def send_all
|
312
|
+
SendSet.new(self)
|
313
|
+
end
|
314
|
+
|
315
|
+
def send_all_but(*execeptions)
|
316
|
+
SendSet.new(self, exclude: execeptions)
|
317
|
+
end
|
318
|
+
|
319
|
+
def send_only(*white_list)
|
320
|
+
SendSet.new(self, white_list: white_list)
|
321
|
+
end
|
322
|
+
|
323
|
+
def obj
|
324
|
+
@obj
|
325
|
+
end
|
326
|
+
|
327
|
+
def self.regulate_connection(acting_user, channel_string)
|
328
|
+
channel = channel_string.split("-")
|
329
|
+
if channel.length > 1
|
330
|
+
id = channel[1..-1].join("-")
|
331
|
+
object = Object.const_get(channel[0]).find(id)
|
332
|
+
InstanceConnectionRegulation.connect(object, acting_user)
|
333
|
+
else
|
334
|
+
ClassConnectionRegulation.connect(channel[0], acting_user)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.regulate_broadcast(model, &block)
|
339
|
+
internal_policy = InternalPolicy.new(
|
340
|
+
model, model.attribute_names, Connection.active
|
341
|
+
)
|
342
|
+
ChannelBroadcastRegulation.broadcast(internal_policy)
|
343
|
+
InstanceBroadcastRegulation.broadcast(model, internal_policy)
|
344
|
+
internal_policy.broadcast &block
|
345
|
+
end
|
346
|
+
|
347
|
+
def initialize(obj, attribute_names, available_channels)
|
348
|
+
@obj = obj
|
349
|
+
attribute_names = attribute_names.map(&:to_sym).to_set
|
350
|
+
@unassigned_send_sets = []
|
351
|
+
@channel_sets = Hash.new { |hash, key| hash[key] = attribute_names }
|
352
|
+
@available_channels = available_channels
|
353
|
+
end
|
354
|
+
|
355
|
+
def channel_available?(channel)
|
356
|
+
channel && @available_channels.include?(channel_to_string(channel))
|
357
|
+
end
|
358
|
+
|
359
|
+
def id
|
360
|
+
@id ||= "#{self.object_id}-#{Time.now.to_f}"
|
361
|
+
end
|
362
|
+
|
363
|
+
def has_unassigned_sets?
|
364
|
+
!@unassigned_send_sets.empty?
|
365
|
+
end
|
366
|
+
|
367
|
+
def send_unassigned_sets_to(channel)
|
368
|
+
if channel_available?(channel) && has_unassigned_sets?
|
369
|
+
@channel_sets[channel] = @unassigned_send_sets.inject(@channel_sets[channel]) do |set, send_set|
|
370
|
+
send_set.merge(set)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
@unassigned_send_sets = []
|
374
|
+
end
|
375
|
+
|
376
|
+
def add_unassigned_send_set(send_set)
|
377
|
+
@unassigned_send_sets << send_set
|
378
|
+
end
|
379
|
+
|
380
|
+
def send_set_to(send_set, channels)
|
381
|
+
channels.flatten(1).each do |channel|
|
382
|
+
merge_set(send_set, channel) if channel_available? channel
|
383
|
+
@unassigned_send_sets.delete(send_set)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def merge_set(send_set, channel)
|
388
|
+
return unless channel
|
389
|
+
channel = channel.name if channel.is_a?(Class) && channel.name
|
390
|
+
@channel_sets[channel] = send_set.merge(@channel_sets[channel])
|
391
|
+
end
|
392
|
+
|
393
|
+
def channel_list
|
394
|
+
@channel_sets.collect { |channel, _value| channel_to_string channel }
|
395
|
+
end
|
396
|
+
|
397
|
+
def self.channel_to_string(channel)
|
398
|
+
if channel.is_a?(Class) && channel.name
|
399
|
+
channel.name
|
400
|
+
elsif channel.is_a? String
|
401
|
+
channel
|
402
|
+
else
|
403
|
+
"#{channel.class.name}-#{channel.id}"
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def channel_to_string(channel)
|
408
|
+
self.class.channel_to_string(channel)
|
409
|
+
end
|
410
|
+
|
411
|
+
def filter(h, attribute_set)
|
412
|
+
r = {}
|
413
|
+
h.each do |key, value|
|
414
|
+
r[key.to_sym] = value if attribute_set.member?(key.to_sym) || (key.to_sym == :id)
|
415
|
+
end unless attribute_set.empty?
|
416
|
+
#model_id = h[:id] || h["id"]
|
417
|
+
#r[:id] = model_id if model_id && !attribute_set.empty?
|
418
|
+
r
|
419
|
+
end
|
420
|
+
|
421
|
+
def send_message(header, channel, attribute_set, &block)
|
422
|
+
record = filter(@obj.react_serializer, attribute_set)
|
423
|
+
previous_changes = filter(@obj.previous_changes, attribute_set)
|
424
|
+
return if record.empty? && previous_changes.empty?
|
425
|
+
message = header.merge(
|
426
|
+
channel: channel_to_string(channel),
|
427
|
+
record: record,
|
428
|
+
previous_changes: previous_changes
|
429
|
+
)
|
430
|
+
yield message
|
431
|
+
end
|
432
|
+
|
433
|
+
def broadcast(&block)
|
434
|
+
klass = @obj.class
|
435
|
+
header = {broadcast_id: id, channels: channel_list, klass: klass.name}
|
436
|
+
@channel_sets.each do |channel, attribute_set|
|
437
|
+
send_message header, channel, attribute_set, &block
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class SendSet
|
443
|
+
|
444
|
+
def to(*channels)
|
445
|
+
@policy.send_set_to(self, channels)
|
446
|
+
end
|
447
|
+
|
448
|
+
def initialize(policy, exclude: nil, white_list: nil)
|
449
|
+
@policy = policy
|
450
|
+
@policy.add_unassigned_send_set(self)
|
451
|
+
@excluded = exclude.map &:to_sym if exclude
|
452
|
+
@white_list = white_list.map &:to_sym if white_list
|
453
|
+
end
|
454
|
+
|
455
|
+
def merge(set)
|
456
|
+
set = set.difference(@excluded) if @excluded
|
457
|
+
set = set.intersection(@white_list) if @white_list
|
458
|
+
set
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
463
|
+
module ClassPolicyMethods
|
464
|
+
def hyperloop_internal_policy_object
|
465
|
+
@hyperloop_internal_policy_object ||= InternalClassPolicy.new(name || self)
|
466
|
+
end
|
467
|
+
InternalClassPolicy::EXPOSED_METHODS.each do |policy_method|
|
468
|
+
define_method policy_method do |*klasses, &block|
|
469
|
+
hyperloop_internal_policy_object.send policy_method, *klasses, &block
|
470
|
+
end unless respond_to? policy_method
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
module PolicyMethods
|
475
|
+
def self.included(base)
|
476
|
+
base.class_eval do
|
477
|
+
extend ClassPolicyMethods
|
478
|
+
end
|
479
|
+
end
|
480
|
+
attr_accessor :hyperloop_internal_policy_object
|
481
|
+
InternalPolicy::EXPOSED_METHODS.each do |method|
|
482
|
+
define_method method do |*args, &block|
|
483
|
+
hyperloop_internal_policy_object.send method, *args, &block
|
484
|
+
end unless respond_to? method
|
485
|
+
end
|
486
|
+
define_method :initialize do |*args|
|
487
|
+
end unless instance_methods(false).include?(:initialize)
|
488
|
+
end
|
489
|
+
|
490
|
+
module PolicyAutoLoader
|
491
|
+
def self.load(name, value)
|
492
|
+
const_get("#{name}Policy") if name && !(name =~ /Policy$/) && value.is_a?(Class)
|
493
|
+
rescue Exception => e
|
494
|
+
raise e if e.is_a?(LoadError) && e.message =~ /Unable to autoload constant #{name}Policy/
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class Module
|
500
|
+
alias pre_hyperloop_const_set const_set
|
501
|
+
|
502
|
+
def const_set(name, value)
|
503
|
+
pre_hyperloop_const_set(name, value).tap do
|
504
|
+
Hyperloop::PolicyAutoLoader.load(name, value)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
class Class
|
510
|
+
|
511
|
+
alias pre_hyperloop_inherited inherited
|
512
|
+
|
513
|
+
def inherited(child_class)
|
514
|
+
pre_hyperloop_inherited(child_class).tap do
|
515
|
+
Hyperloop::PolicyAutoLoader.load(child_class.name, child_class)
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
|
520
|
+
define_method method do |*args, &block|
|
521
|
+
if name =~ /Policy$/
|
522
|
+
@hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.gsub(/Policy$/,""))
|
523
|
+
include Hyperloop::PolicyMethods
|
524
|
+
send method, *args, &block
|
525
|
+
else
|
526
|
+
class << self
|
527
|
+
Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
|
528
|
+
undef_method method
|
529
|
+
end
|
530
|
+
end
|
531
|
+
method_missing(method, *args, &block)
|
532
|
+
end
|
533
|
+
end unless respond_to? method
|
534
|
+
end
|
535
|
+
end
|