hyper-operation 0.5.12 → 0.99.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +4 -1
- data/.travis.yml +33 -0
- data/DOCS-POLICIES.md +93 -0
- data/DOCS.md +1 -1
- data/Gemfile +5 -1
- data/Gemfile.lock +379 -0
- data/README.md +10 -9
- data/Rakefile +10 -2
- data/hyper-operation.gemspec +34 -29
- data/lib/hyper-operation.rb +5 -4
- data/lib/hyper-operation/boot.rb +1 -1
- data/lib/hyper-operation/engine.rb +1 -1
- data/lib/hyper-operation/http.rb +309 -0
- data/lib/hyper-operation/railway/params_wrapper.rb +1 -0
- data/lib/hyper-operation/server_op.rb +83 -18
- data/lib/hyper-operation/transport/client_drivers.rb +71 -28
- data/lib/hyper-operation/transport/connection.rb +22 -20
- data/lib/hyper-operation/transport/hyperloop.rb +1 -1
- data/lib/hyper-operation/transport/hyperloop_controller.rb +6 -1
- data/lib/hyper-operation/transport/policy.rb +78 -13
- data/lib/hyper-operation/version.rb +1 -1
- metadata +95 -319
- data/CODE_OF_CONDUCT.md +0 -49
- data/examples/chat-app/.gitignore +0 -21
- data/examples/chat-app/Gemfile +0 -57
- data/examples/chat-app/Gemfile.lock +0 -283
- data/examples/chat-app/README.md +0 -3
- data/examples/chat-app/Rakefile +0 -6
- data/examples/chat-app/app/assets/config/manifest.js +0 -3
- data/examples/chat-app/app/assets/images/.keep +0 -0
- data/examples/chat-app/app/assets/javascripts/application.js +0 -3
- data/examples/chat-app/app/assets/javascripts/cable.js +0 -13
- data/examples/chat-app/app/assets/javascripts/channels/.keep +0 -0
- data/examples/chat-app/app/assets/stylesheets/application.css +0 -15
- data/examples/chat-app/app/channels/application_cable/channel.rb +0 -4
- data/examples/chat-app/app/channels/application_cable/connection.rb +0 -4
- data/examples/chat-app/app/controllers/application_controller.rb +0 -3
- data/examples/chat-app/app/controllers/concerns/.keep +0 -0
- data/examples/chat-app/app/controllers/home_controller.rb +0 -5
- data/examples/chat-app/app/helpers/application_helper.rb +0 -2
- data/examples/chat-app/app/hyperloop/components/app.rb +0 -11
- data/examples/chat-app/app/hyperloop/components/formatted_div.rb +0 -13
- data/examples/chat-app/app/hyperloop/components/input_box.rb +0 -29
- data/examples/chat-app/app/hyperloop/components/message.rb +0 -29
- data/examples/chat-app/app/hyperloop/components/messages.rb +0 -9
- data/examples/chat-app/app/hyperloop/components/nav.rb +0 -30
- data/examples/chat-app/app/hyperloop/operations/operations.rb +0 -56
- data/examples/chat-app/app/hyperloop/stores/message_store.rb +0 -23
- data/examples/chat-app/app/models/application_record.rb +0 -3
- data/examples/chat-app/app/models/concerns/.keep +0 -0
- data/examples/chat-app/app/models/models.rb +0 -2
- data/examples/chat-app/app/models/public/.keep +0 -0
- data/examples/chat-app/app/models/public/announcement.rb +0 -8
- data/examples/chat-app/app/policies/application_policy.rb +0 -5
- data/examples/chat-app/app/views/layouts/application.html.erb +0 -51
- data/examples/chat-app/bin/bundle +0 -3
- data/examples/chat-app/bin/rails +0 -9
- data/examples/chat-app/bin/rake +0 -9
- data/examples/chat-app/bin/setup +0 -34
- data/examples/chat-app/bin/spring +0 -17
- data/examples/chat-app/bin/update +0 -29
- data/examples/chat-app/config.ru +0 -5
- data/examples/chat-app/config/application.rb +0 -13
- data/examples/chat-app/config/boot.rb +0 -3
- data/examples/chat-app/config/cable.yml +0 -9
- data/examples/chat-app/config/database.yml +0 -25
- data/examples/chat-app/config/environment.rb +0 -5
- data/examples/chat-app/config/environments/development.rb +0 -56
- data/examples/chat-app/config/environments/production.rb +0 -86
- data/examples/chat-app/config/environments/test.rb +0 -42
- data/examples/chat-app/config/initializers/application_controller_renderer.rb +0 -6
- data/examples/chat-app/config/initializers/assets.rb +0 -11
- data/examples/chat-app/config/initializers/backtrace_silencers.rb +0 -7
- data/examples/chat-app/config/initializers/cookies_serializer.rb +0 -5
- data/examples/chat-app/config/initializers/filter_parameter_logging.rb +0 -4
- data/examples/chat-app/config/initializers/hyperloop.rb +0 -4
- data/examples/chat-app/config/initializers/inflections.rb +0 -16
- data/examples/chat-app/config/initializers/mime_types.rb +0 -4
- data/examples/chat-app/config/initializers/new_framework_defaults.rb +0 -24
- data/examples/chat-app/config/initializers/session_store.rb +0 -3
- data/examples/chat-app/config/initializers/wrap_parameters.rb +0 -14
- data/examples/chat-app/config/locales/en.yml +0 -23
- data/examples/chat-app/config/puma.rb +0 -47
- data/examples/chat-app/config/routes.rb +0 -5
- data/examples/chat-app/config/secrets.yml +0 -22
- data/examples/chat-app/config/spring.rb +0 -6
- data/examples/chat-app/db/seeds.rb +0 -7
- 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 +0 -67
- data/examples/chat-app/public/422.html +0 -67
- data/examples/chat-app/public/500.html +0 -66
- 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 +0 -5
- 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 +0 -10
- 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 +0 -21
- data/examples/five-letter-word-game/Gemfile +0 -62
- data/examples/five-letter-word-game/Gemfile.lock +0 -291
- data/examples/five-letter-word-game/README.md +0 -24
- data/examples/five-letter-word-game/Rakefile +0 -6
- data/examples/five-letter-word-game/app/assets/config/manifest.js +0 -3
- data/examples/five-letter-word-game/app/assets/images/.keep +0 -0
- data/examples/five-letter-word-game/app/assets/javascripts/application.js +0 -4
- data/examples/five-letter-word-game/app/assets/javascripts/cable.js +0 -13
- data/examples/five-letter-word-game/app/assets/javascripts/channels/.keep +0 -0
- data/examples/five-letter-word-game/app/assets/stylesheets/application.css +0 -15
- data/examples/five-letter-word-game/app/channels/application_cable/channel.rb +0 -4
- data/examples/five-letter-word-game/app/channels/application_cable/connection.rb +0 -4
- data/examples/five-letter-word-game/app/controllers/application_controller.rb +0 -14
- data/examples/five-letter-word-game/app/controllers/concerns/.keep +0 -0
- data/examples/five-letter-word-game/app/controllers/home_controller.rb +0 -5
- data/examples/five-letter-word-game/app/helpers/application_helper.rb +0 -2
- data/examples/five-letter-word-game/app/hyperloop/components/app.rb +0 -65
- data/examples/five-letter-word-game/app/hyperloop/components/guesses.rb +0 -8
- data/examples/five-letter-word-game/app/hyperloop/components/input_word.rb +0 -13
- data/examples/five-letter-word-game/app/hyperloop/models/user.rb +0 -27
- data/examples/five-letter-word-game/app/hyperloop/operations/ops.rb +0 -115
- data/examples/five-letter-word-game/app/hyperloop/stores/store.rb +0 -120
- data/examples/five-letter-word-game/app/jobs/application_job.rb +0 -2
- data/examples/five-letter-word-game/app/mailers/application_mailer.rb +0 -4
- data/examples/five-letter-word-game/app/models/application_record.rb +0 -3
- data/examples/five-letter-word-game/app/models/concerns/.keep +0 -0
- data/examples/five-letter-word-game/app/policies/hyperloop/application_policy.rb +0 -3
- data/examples/five-letter-word-game/app/policies/user_policy.rb +0 -4
- data/examples/five-letter-word-game/app/views/layouts/application.html.erb +0 -14
- data/examples/five-letter-word-game/app/views/layouts/mailer.html.erb +0 -13
- data/examples/five-letter-word-game/app/views/layouts/mailer.text.erb +0 -1
- data/examples/five-letter-word-game/bin/bundle +0 -3
- data/examples/five-letter-word-game/bin/rails +0 -9
- data/examples/five-letter-word-game/bin/rake +0 -9
- data/examples/five-letter-word-game/bin/setup +0 -34
- data/examples/five-letter-word-game/bin/spring +0 -17
- data/examples/five-letter-word-game/bin/update +0 -29
- data/examples/five-letter-word-game/config.ru +0 -5
- data/examples/five-letter-word-game/config/application.rb +0 -12
- data/examples/five-letter-word-game/config/boot.rb +0 -3
- data/examples/five-letter-word-game/config/cable.yml +0 -9
- data/examples/five-letter-word-game/config/database.yml +0 -46
- data/examples/five-letter-word-game/config/environment.rb +0 -5
- data/examples/five-letter-word-game/config/environments/development.rb +0 -56
- data/examples/five-letter-word-game/config/environments/production.rb +0 -86
- data/examples/five-letter-word-game/config/environments/test.rb +0 -42
- data/examples/five-letter-word-game/config/initializers/application_controller_renderer.rb +0 -6
- data/examples/five-letter-word-game/config/initializers/assets.rb +0 -15
- data/examples/five-letter-word-game/config/initializers/backtrace_silencers.rb +0 -7
- data/examples/five-letter-word-game/config/initializers/cookies_serializer.rb +0 -5
- data/examples/five-letter-word-game/config/initializers/filter_parameter_logging.rb +0 -4
- data/examples/five-letter-word-game/config/initializers/hyperloop.rb +0 -19
- data/examples/five-letter-word-game/config/initializers/inflections.rb +0 -16
- data/examples/five-letter-word-game/config/initializers/mime_types.rb +0 -4
- data/examples/five-letter-word-game/config/initializers/new_framework_defaults.rb +0 -24
- data/examples/five-letter-word-game/config/initializers/session_store.rb +0 -3
- data/examples/five-letter-word-game/config/initializers/wrap_parameters.rb +0 -14
- data/examples/five-letter-word-game/config/locales/en.yml +0 -23
- data/examples/five-letter-word-game/config/puma.rb +0 -47
- data/examples/five-letter-word-game/config/routes.rb +0 -5
- data/examples/five-letter-word-game/config/secrets.yml +0 -22
- data/examples/five-letter-word-game/config/spring.rb +0 -6
- data/examples/five-letter-word-game/db/schema.rb +0 -28
- data/examples/five-letter-word-game/db/seeds.rb +0 -7
- 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 +0 -67
- data/examples/five-letter-word-game/public/422.html +0 -67
- data/examples/five-letter-word-game/public/500.html +0 -66
- 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 +0 -5
- 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 +0 -10
- 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 +0 -21
- data/examples/smoke_test/Gemfile +0 -59
- data/examples/smoke_test/Gemfile.lock +0 -289
- data/examples/smoke_test/README.md +0 -24
- data/examples/smoke_test/Rakefile +0 -6
- data/examples/smoke_test/app/assets/config/manifest.js +0 -3
- data/examples/smoke_test/app/assets/images/.keep +0 -0
- data/examples/smoke_test/app/assets/javascripts/application.js +0 -15
- data/examples/smoke_test/app/assets/javascripts/cable.js +0 -13
- data/examples/smoke_test/app/assets/javascripts/channels/.keep +0 -0
- data/examples/smoke_test/app/assets/stylesheets/application.css +0 -15
- data/examples/smoke_test/app/channels/application_cable/channel.rb +0 -4
- data/examples/smoke_test/app/channels/application_cable/connection.rb +0 -4
- data/examples/smoke_test/app/controllers/app_controller.rb +0 -5
- data/examples/smoke_test/app/controllers/application_controller.rb +0 -3
- data/examples/smoke_test/app/helpers/application_helper.rb +0 -2
- data/examples/smoke_test/app/hyperloop/components/hello.rb +0 -25
- data/examples/smoke_test/app/hyperloop/operations/operations/nested_send_to_all.rb +0 -7
- data/examples/smoke_test/app/hyperloop/operations/send_to_all.rb +0 -6
- data/examples/smoke_test/app/hyperloop/stores/messages.rb +0 -4
- data/examples/smoke_test/app/jobs/application_job.rb +0 -2
- data/examples/smoke_test/app/mailers/application_mailer.rb +0 -4
- data/examples/smoke_test/app/models/application_record.rb +0 -3
- data/examples/smoke_test/app/models/concerns/.keep +0 -0
- data/examples/smoke_test/app/policies/application_policy.rb +0 -8
- data/examples/smoke_test/app/views/layouts/application.html.erb +0 -14
- data/examples/smoke_test/app/views/layouts/mailer.html.erb +0 -13
- data/examples/smoke_test/app/views/layouts/mailer.text.erb +0 -1
- data/examples/smoke_test/bin/bundle +0 -3
- data/examples/smoke_test/bin/rails +0 -9
- data/examples/smoke_test/bin/rake +0 -9
- data/examples/smoke_test/bin/setup +0 -34
- data/examples/smoke_test/bin/spring +0 -17
- data/examples/smoke_test/bin/update +0 -29
- data/examples/smoke_test/config.ru +0 -5
- data/examples/smoke_test/config/application.rb +0 -15
- data/examples/smoke_test/config/boot.rb +0 -3
- data/examples/smoke_test/config/cable.yml +0 -9
- data/examples/smoke_test/config/database.yml +0 -25
- data/examples/smoke_test/config/environment.rb +0 -5
- data/examples/smoke_test/config/environments/development.rb +0 -54
- data/examples/smoke_test/config/environments/production.rb +0 -86
- data/examples/smoke_test/config/environments/test.rb +0 -42
- data/examples/smoke_test/config/initializers/application_controller_renderer.rb +0 -6
- data/examples/smoke_test/config/initializers/assets.rb +0 -11
- data/examples/smoke_test/config/initializers/backtrace_silencers.rb +0 -7
- data/examples/smoke_test/config/initializers/cookies_serializer.rb +0 -5
- data/examples/smoke_test/config/initializers/filter_parameter_logging.rb +0 -4
- data/examples/smoke_test/config/initializers/hyperloop.rb +0 -32
- data/examples/smoke_test/config/initializers/inflections.rb +0 -16
- data/examples/smoke_test/config/initializers/mime_types.rb +0 -4
- data/examples/smoke_test/config/initializers/new_framework_defaults.rb +0 -24
- data/examples/smoke_test/config/initializers/session_store.rb +0 -3
- data/examples/smoke_test/config/initializers/wrap_parameters.rb +0 -14
- data/examples/smoke_test/config/locales/en.yml +0 -23
- data/examples/smoke_test/config/puma.rb +0 -47
- data/examples/smoke_test/config/routes.rb +0 -5
- data/examples/smoke_test/config/secrets.yml +0 -22
- data/examples/smoke_test/config/spring.rb +0 -6
- data/examples/smoke_test/db/seeds.rb +0 -7
- 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 +0 -67
- data/examples/smoke_test/public/422.html +0 -67
- data/examples/smoke_test/public/500.html +0 -66
- 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 +0 -5
- 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 +0 -10
- 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/lib/hyper-operation/call_by_class_name.rb +0 -60
@@ -1,37 +1,99 @@
|
|
1
|
+
require 'net/http' unless RUBY_ENGINE == 'opal'
|
2
|
+
|
1
3
|
module Hyperloop
|
2
4
|
class ServerOp < Operation
|
3
5
|
|
4
6
|
class << self
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
include React::IsomorphicHelpers
|
8
|
+
|
9
|
+
if RUBY_ENGINE == 'opal'
|
10
|
+
if on_opal_client?
|
11
|
+
def run(*args)
|
12
|
+
hash = _Railway.params_wrapper.combine_arg_array(args)
|
13
|
+
hash = serialize_params(hash)
|
14
|
+
Hyperloop::HTTP.post(
|
15
|
+
"#{`window.HyperloopEnginePath`}/execute_remote",
|
16
|
+
payload: {json: {operation: name, params: hash}.to_json},
|
17
|
+
headers: {'X-CSRF-Token' => Hyperloop::ClientDrivers.opts[:form_authenticity_token] }
|
18
|
+
)
|
19
|
+
.then do |response|
|
20
|
+
deserialize_response response.json[:response]
|
21
|
+
end
|
22
|
+
.fail do |response|
|
23
|
+
Exception.new response.json[:error]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
elsif on_opal_server?
|
27
|
+
def run(*args)
|
28
|
+
promise = Promise.new
|
29
|
+
response = internal_iso_run(name, args)
|
30
|
+
if response[:json][:response]
|
31
|
+
promise.resolve(response[:json][:response])
|
32
|
+
else
|
33
|
+
promise.reject Exception.new response[:json][:error]
|
34
|
+
end
|
35
|
+
promise
|
36
|
+
end
|
17
37
|
end
|
18
|
-
end
|
38
|
+
end
|
39
|
+
|
40
|
+
isomorphic_method(:internal_iso_run) do |f, klass_name, op_params|
|
41
|
+
f.send_to_server(klass_name, op_params)
|
42
|
+
f.when_on_server {
|
43
|
+
Hyperloop::ServerOp.run_from_client(:acting_user, controller, klass_name, *op_params)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def descendants_map_cache
|
48
|
+
# calling descendants alone may take 10ms in a complex app, so better cache it
|
49
|
+
@cached_descendants ||= Hyperloop::ServerOp.descendants.map(&:to_s)
|
50
|
+
end
|
19
51
|
|
20
52
|
def run_from_client(security_param, controller, operation, params)
|
53
|
+
if Rails.env.production?
|
54
|
+
# in production everything is eager loaded so ServerOp.descendants is filled and can be used to guard the .constantize
|
55
|
+
unless Hyperloop::ServerOp.descendants_map_cache.include?(operation)
|
56
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:illegal_remote_op_call, "Operation: #{operation} (in production)")
|
57
|
+
end
|
58
|
+
# however ...
|
59
|
+
else
|
60
|
+
# ... in development things are autoloaded on demand, thus ServerOp.descendants can be empty or partially filled and above guard
|
61
|
+
# would fail legal operations. To prevent this, the class has to be loaded first, what .const_get will take care of, and then
|
62
|
+
# its guarded, to achieve similar behaviour as in production. Doing the const_get first, before the guard,
|
63
|
+
# would not be safe for production and allow for potential remote code execution!
|
64
|
+
begin
|
65
|
+
const = Object.const_get(operation)
|
66
|
+
rescue NameError
|
67
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:illegal_remote_op_call, "Operation: #{operation} (const not found)")
|
68
|
+
end
|
69
|
+
unless const < Hyperloop::ServerOp
|
70
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:illegal_remote_op_call, "Operation: #{operation} (not a ServerOp subclass)")
|
71
|
+
end
|
72
|
+
end
|
21
73
|
operation.constantize.class_eval do
|
22
74
|
if _Railway.params_wrapper.method_defined?(:controller)
|
23
75
|
params[:controller] = controller
|
24
76
|
elsif !_Railway.params_wrapper.method_defined?(security_param)
|
25
77
|
raise AccessViolation
|
26
78
|
end
|
27
|
-
run(params)
|
79
|
+
run(deserialize_params(params))
|
28
80
|
.then { |r| return { json: { response: serialize_response(r) } } }
|
29
|
-
.fail { |e| return
|
81
|
+
.fail { |e| return handle_exception(e, operation, params) }
|
30
82
|
end
|
31
83
|
rescue Exception => e
|
32
|
-
|
84
|
+
handle_exception(e, operation, params)
|
33
85
|
end
|
34
86
|
|
87
|
+
def handle_exception(e, operation, params)
|
88
|
+
if defined? ::Rails
|
89
|
+
params.delete(:controller)
|
90
|
+
::Rails.logger.debug "\033[0;31;1mERROR: Hyperloop::ServerOp exception caught when running "\
|
91
|
+
"#{operation} with params \"#{params}\": #{e}\033[0;30;21m"
|
92
|
+
end
|
93
|
+
{ json: { error: e }, status: 500 }
|
94
|
+
end
|
95
|
+
|
96
|
+
|
35
97
|
def remote(path, *args)
|
36
98
|
promise = Promise.new
|
37
99
|
uri = URI("#{path}execute_remote_api")
|
@@ -39,7 +101,6 @@ module Hyperloop
|
|
39
101
|
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
40
102
|
if uri.scheme == 'https'
|
41
103
|
http.use_ssl = true
|
42
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
43
104
|
end
|
44
105
|
request.body = {
|
45
106
|
operation: name,
|
@@ -86,9 +147,13 @@ module Hyperloop
|
|
86
147
|
end
|
87
148
|
regulation ||= proc { args }
|
88
149
|
on_dispatch do |params, operation|
|
150
|
+
operation.instance_variable_set(:@_dispatched_channels, []) unless operation.instance_variable_get(:@_dispatched_channels)
|
89
151
|
serialized_params = serialize_dispatch(params.to_h)
|
90
152
|
[operation.instance_exec(*context, ®ulation)].flatten.compact.uniq.each do |channel|
|
91
|
-
|
153
|
+
unless operation.instance_variable_get(:@_dispatched_channels).include?(channel)
|
154
|
+
operation.instance_variable_set(:@_dispatched_channels, operation.instance_variable_get(:@_dispatched_channels) << channel)
|
155
|
+
Hyperloop.dispatch(channel: Hyperloop::InternalPolicy.channel_to_string(channel), operation: operation.class.name, params: serialized_params)
|
156
|
+
end
|
92
157
|
end
|
93
158
|
end
|
94
159
|
end if RUBY_ENGINE != 'opal'
|
@@ -7,8 +7,25 @@ module Hyperloop
|
|
7
7
|
# client interface to sync_change or sync_destroy
|
8
8
|
|
9
9
|
class Application
|
10
|
-
|
11
|
-
|
10
|
+
extend React::IsomorphicHelpers::ClassMethods
|
11
|
+
|
12
|
+
if on_opal_client?
|
13
|
+
def self.acting_user_id
|
14
|
+
ClientDrivers.opts[:acting_user_id]
|
15
|
+
end
|
16
|
+
else
|
17
|
+
def self.acting_user_id
|
18
|
+
ClientDrivers.client_drivers_get_acting_user_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.env
|
23
|
+
@env = ClientDrivers.env unless @env
|
24
|
+
@env
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.production?
|
28
|
+
env == 'production'
|
12
29
|
end
|
13
30
|
end
|
14
31
|
|
@@ -59,8 +76,9 @@ module Hyperloop
|
|
59
76
|
}
|
60
77
|
elsif ClientDrivers.opts[:transport] == :action_cable
|
61
78
|
channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
|
62
|
-
HTTP.post(ClientDrivers.polling_path('action-cable-auth', channel)).then do |response|
|
79
|
+
Hyperloop::HTTP.post(ClientDrivers.polling_path('action-cable-auth', channel), headers: { 'X-CSRF-Token' => ClientDrivers.opts[:form_authenticity_token] }).then do |response|
|
63
80
|
%x{
|
81
|
+
var fix_opal_0110 = 'return';
|
64
82
|
#{Hyperloop.action_cable_consumer}.subscriptions.create(
|
65
83
|
{
|
66
84
|
channel: "Hyperloop::ActionCableChannel",
|
@@ -71,9 +89,11 @@ module Hyperloop
|
|
71
89
|
},
|
72
90
|
{
|
73
91
|
connected: function() {
|
74
|
-
#{ClientDrivers.
|
92
|
+
if (#{ClientDrivers.env == 'development'}) { console.log("ActionCable connected to: ", channel_string); }
|
93
|
+
#{ClientDrivers.complete_connection(channel_string)}
|
75
94
|
},
|
76
95
|
received: function(data) {
|
96
|
+
if (#{ClientDrivers.env == 'development'}) { console.log("ActionCable received: ", data); }
|
77
97
|
#{ClientDrivers.sync_dispatch(JSON.parse(`JSON.stringify(data)`)['data'])}
|
78
98
|
}
|
79
99
|
}
|
@@ -81,7 +101,7 @@ module Hyperloop
|
|
81
101
|
}
|
82
102
|
end
|
83
103
|
else
|
84
|
-
HTTP.get(ClientDrivers.polling_path(:subscribe, channel_string))
|
104
|
+
Hyperloop::HTTP.get(ClientDrivers.polling_path(:subscribe, channel_string))
|
85
105
|
end
|
86
106
|
end
|
87
107
|
end
|
@@ -105,24 +125,26 @@ module Hyperloop
|
|
105
125
|
# will remove the session from the list.
|
106
126
|
|
107
127
|
prerender_footer do |controller|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
128
|
+
unless Hyperloop.transport == :none
|
129
|
+
if defined?(PusherFake)
|
130
|
+
path = ::Rails.application.routes.routes.detect do |route|
|
131
|
+
route.app == Hyperloop::Engine ||
|
132
|
+
(route.app.respond_to?(:app) && route.app.app == Hyperloop::Engine)
|
133
|
+
end.path.spec
|
134
|
+
pusher_fake_js = PusherFake.javascript(
|
135
|
+
auth: { headers: { 'X-CSRF-Token' => controller.send(:form_authenticity_token) } },
|
136
|
+
authEndpoint: "#{path}/hyperloop-pusher-auth"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
controller.session.delete 'hyperloop-dummy-init' unless controller.session.id
|
140
|
+
id = "#{SecureRandom.uuid}-#{controller.session.id}"
|
141
|
+
auto_connections = Hyperloop::AutoConnect.channels(id, controller.acting_user)
|
118
142
|
end
|
119
|
-
controller.session.delete 'hyperloop-dummy-init' unless controller.session.id
|
120
|
-
id = "#{SecureRandom.uuid}-#{controller.session.id}"
|
121
|
-
auto_connections = Hyperloop::AutoConnect.channels(id, controller.acting_user)
|
122
143
|
config_hash = {
|
123
144
|
transport: Hyperloop.transport,
|
124
145
|
id: id,
|
125
146
|
acting_user_id: (controller.acting_user && controller.acting_user.id),
|
147
|
+
env: ::Rails.env,
|
126
148
|
client_logging: Hyperloop.client_logging,
|
127
149
|
pusher_fake_js: pusher_fake_js,
|
128
150
|
key: Hyperloop.key,
|
@@ -149,9 +171,27 @@ module Hyperloop
|
|
149
171
|
attr_reader :opts
|
150
172
|
end
|
151
173
|
|
174
|
+
isomorphic_method(:client_drivers_get_acting_user_id) do |f|
|
175
|
+
f.send_to_server if RUBY_ENGINE == 'opal'
|
176
|
+
f.when_on_server { (controller.acting_user && controller.acting_user.id) }
|
177
|
+
end
|
178
|
+
|
179
|
+
isomorphic_method(:env) do |f|
|
180
|
+
f.when_on_client { opts[:env] }
|
181
|
+
f.send_to_server
|
182
|
+
f.when_on_server { ::Rails.env }
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.complete_connection(channel, retries = 10)
|
186
|
+
get_queued_data('connect-to-transport', channel).fail do
|
187
|
+
after(0.25) { complete_connection(channel, retries - 1) } unless retries.zero?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
152
191
|
def self.get_queued_data(operation, channel = nil, opts = {})
|
153
|
-
HTTP.get(polling_path(operation, channel), opts).then do |response|
|
192
|
+
Hyperloop::HTTP.get(polling_path(operation, channel), opts).then do |response|
|
154
193
|
response.json.each do |data|
|
194
|
+
`console.log("simple_poller received: ", data)` if ClientDrivers.env == 'development'
|
155
195
|
sync_dispatch(data[1])
|
156
196
|
end
|
157
197
|
end
|
@@ -161,21 +201,24 @@ module Hyperloop
|
|
161
201
|
|
162
202
|
if @initialized
|
163
203
|
# 1) skip initialization if already initialized
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
204
|
+
if on_opal_client? && Hyperloop.action_cable_consumer
|
205
|
+
# 2) if running action_cable make sure connection is up after pinging the server_up
|
206
|
+
# action cable closes the connection if files change on the server
|
207
|
+
Hyperloop::HTTP.get("#{`window.HyperloopEnginePath`}/server_up") do
|
208
|
+
`#{Hyperloop.action_cable_consumer}.connection.open()` if `#{Hyperloop.action_cable_consumer}.connection.disconnected`
|
209
|
+
end
|
210
|
+
end
|
169
211
|
return
|
170
212
|
end
|
171
213
|
|
172
214
|
@initialized = true
|
215
|
+
@opts = {}
|
216
|
+
|
217
|
+
if on_opal_client?
|
173
218
|
|
174
|
-
if RUBY_ENGINE == 'opal'
|
175
219
|
@opts = Hash.new(`window.HyperloopOpts`)
|
176
|
-
end
|
177
220
|
|
178
|
-
|
221
|
+
|
179
222
|
if opts[:transport] == :pusher
|
180
223
|
|
181
224
|
opts[:dispatch] = lambda do |data|
|
@@ -209,7 +252,7 @@ module Hyperloop
|
|
209
252
|
elsif opts[:transport] == :simple_poller
|
210
253
|
opts[:auto_connect].each { |channel| IncomingBroadcast.add_connection(*channel) }
|
211
254
|
every(opts[:seconds_between_poll]) do
|
212
|
-
get_queued_data(:read, nil
|
255
|
+
get_queued_data(:read, nil)
|
213
256
|
end
|
214
257
|
end
|
215
258
|
end
|
@@ -1,20 +1,16 @@
|
|
1
1
|
module Hyperloop
|
2
2
|
module AutoCreate
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return true unless Connection.root_path
|
10
|
-
uri = URI("#{Connection.root_path}server_up")
|
11
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
12
|
-
request = Net::HTTP::Get.new(uri.path)
|
13
|
-
if uri.scheme == 'https'
|
14
|
-
http.use_ssl = true
|
15
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
3
|
+
def table_exists?
|
4
|
+
# works with both rails 4 and 5 without deprecation warnings
|
5
|
+
if connection.respond_to?(:data_sources)
|
6
|
+
connection.data_sources.include?(table_name)
|
7
|
+
else
|
8
|
+
connection.tables.include?(table_name)
|
16
9
|
end
|
17
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
def needs_init?
|
13
|
+
Hyperloop.transport != :none && Hyperloop.on_server? && !table_exists?
|
18
14
|
end
|
19
15
|
|
20
16
|
def create_table(*args, &block)
|
@@ -58,14 +54,14 @@ module Hyperloop
|
|
58
54
|
extend AutoCreate
|
59
55
|
|
60
56
|
def self.build_tables
|
61
|
-
create_table(force:
|
57
|
+
create_table(force: :cascade) do |t|
|
62
58
|
t.string :channel
|
63
59
|
t.string :session
|
64
60
|
t.datetime :created_at
|
65
61
|
t.datetime :expires_at
|
66
62
|
t.datetime :refresh_at
|
67
63
|
end
|
68
|
-
QueuedMessage.create_table(force:
|
64
|
+
QueuedMessage.create_table(force: :cascade) do |t|
|
69
65
|
t.text :data
|
70
66
|
t.integer :connection_id
|
71
67
|
end
|
@@ -106,6 +102,10 @@ module Hyperloop
|
|
106
102
|
attr_accessor :transport
|
107
103
|
|
108
104
|
def active
|
105
|
+
# if table doesn't exist then we are either calling from within
|
106
|
+
# a migration or from a console before the server has ever started
|
107
|
+
# in these cases there are no channels so we return nothing
|
108
|
+
return [] unless table_exists?
|
109
109
|
if Hyperloop.on_server?
|
110
110
|
expired.delete_all
|
111
111
|
refresh_connections if needs_refresh?
|
@@ -153,9 +153,10 @@ module Hyperloop
|
|
153
153
|
end
|
154
154
|
|
155
155
|
def root_path
|
156
|
-
QueuedMessage
|
157
|
-
|
158
|
-
|
156
|
+
# if the QueuedMessage table doesn't exist then we are either calling from within
|
157
|
+
# a migration or from a console before the server has ever started
|
158
|
+
# in these cases there is no root path to the server
|
159
|
+
QueuedMessage.root_path if QueuedMessage.table_exists?
|
159
160
|
end
|
160
161
|
|
161
162
|
def refresh_connections
|
@@ -163,7 +164,8 @@ module Hyperloop
|
|
163
164
|
channels = transport.refresh_channels
|
164
165
|
next_refresh = refresh_started_at + transport.refresh_channels_every
|
165
166
|
channels.each do |channel|
|
166
|
-
find_by(channel: channel, session: nil)
|
167
|
+
connection = find_by(channel: channel, session: nil)
|
168
|
+
connection.update(refresh_at: next_refresh) if connection
|
167
169
|
end
|
168
170
|
inactive.delete_all
|
169
171
|
end
|
@@ -17,7 +17,7 @@ module Hyperloop
|
|
17
17
|
unless method_defined? :pre_hyperloop_call
|
18
18
|
alias pre_hyperloop_call call
|
19
19
|
def call(env)
|
20
|
-
if
|
20
|
+
if Hyperloop.transport == :simple_poller && env['PATH_INFO'] && env['PATH_INFO'].include?('/hyperloop-read/')
|
21
21
|
Rails.logger.silence do
|
22
22
|
pre_hyperloop_call(env)
|
23
23
|
end
|
@@ -115,6 +115,7 @@ module Hyperloop
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def pusher_auth
|
118
|
+
raise unless Hyperloop.transport == :pusher
|
118
119
|
channel = regulate params[:channel_name].gsub(/^#{Regexp.quote(Hyperloop.channel)}\-/,'').gsub('==', '::')
|
119
120
|
response = Hyperloop.pusher.authenticate(params[:channel_name], params[:socket_id])
|
120
121
|
render json: response
|
@@ -123,6 +124,7 @@ module Hyperloop
|
|
123
124
|
end
|
124
125
|
|
125
126
|
def action_cable_auth
|
127
|
+
raise unless Hyperloop.transport == :action_cable
|
126
128
|
channel = regulate params[:channel_name].gsub(/^#{Regexp.quote(Hyperloop.channel)}\-/,'')
|
127
129
|
salt = SecureRandom.hex
|
128
130
|
authorization = Hyperloop.authorization(salt, channel, client_id)
|
@@ -134,6 +136,8 @@ module Hyperloop
|
|
134
136
|
def connect_to_transport
|
135
137
|
root_path = request.original_url.gsub(/hyperloop-connect-to-transport.*$/, '')
|
136
138
|
render json: Hyperloop::Connection.connect_to_transport(params[:channel], client_id, root_path)
|
139
|
+
rescue Exception => e
|
140
|
+
render status: :service_unavailable, json: {error: e}
|
137
141
|
end
|
138
142
|
|
139
143
|
def execute_remote
|
@@ -154,6 +158,7 @@ module Hyperloop
|
|
154
158
|
end
|
155
159
|
|
156
160
|
def console_update # TODO this should just become an execute-remote-api call
|
161
|
+
raise unless Rails.env.development?
|
157
162
|
authorization = Hyperloop.authorization(params[:salt], params[:channel], params[:data][1][:broadcast_id]) #params[:data].to_json)
|
158
163
|
return head :unauthorized if authorization != params[:authorization]
|
159
164
|
Hyperloop::Connection.send_to_channel(params[:channel], params[:data])
|
@@ -3,6 +3,16 @@ module Hyperloop
|
|
3
3
|
class InternalClassPolicy
|
4
4
|
|
5
5
|
def initialize(regulated_klass)
|
6
|
+
unless regulated_klass.is_a?(Class)
|
7
|
+
# attempt to constantize the class in case eager_loading in production
|
8
|
+
# has loaded the policy before the class. THis will insure that if
|
9
|
+
# there is a class being regulated, it is loaded first.
|
10
|
+
begin
|
11
|
+
regulated_klass.constantize
|
12
|
+
rescue NameError
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
6
16
|
@regulated_klass = regulated_klass
|
7
17
|
end
|
8
18
|
|
@@ -49,11 +59,17 @@ module Hyperloop
|
|
49
59
|
end
|
50
60
|
|
51
61
|
def dispatch_to(*args, ®ulation)
|
52
|
-
actual_klass = regulated_klass.is_a?(Class)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
62
|
+
actual_klass = if regulated_klass.is_a?(Class)
|
63
|
+
regulated_klass
|
64
|
+
else
|
65
|
+
begin
|
66
|
+
regulated_klass.constantize
|
67
|
+
rescue NameError
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise 'you can only dispatch_to Operation classes' unless actual_klass.respond_to? :dispatch_to
|
72
|
+
actual_klass.dispatch_to(actual_klass)
|
57
73
|
actual_klass.dispatch_to(*args, ®ulation)
|
58
74
|
end
|
59
75
|
|
@@ -82,14 +98,40 @@ module Hyperloop
|
|
82
98
|
end
|
83
99
|
end
|
84
100
|
|
101
|
+
def self.ar_base_descendants_map_cache
|
102
|
+
@ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
|
103
|
+
end
|
104
|
+
|
85
105
|
def get_ar_model(str)
|
86
|
-
str.is_a?(Class)
|
87
|
-
|
88
|
-
|
106
|
+
if str.is_a?(Class)
|
107
|
+
unless str <= ActiveRecord::Base
|
108
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is not a subclass of ActiveRecord::Base")
|
109
|
+
end
|
110
|
+
str
|
111
|
+
else
|
112
|
+
# we used to cache this here, but during eager loading the cache may get partially filled and never updated
|
113
|
+
# so this guard will fail, now performance will be suckish, as this guard, required for security, takes some ms
|
114
|
+
# def self.ar_base_descendants_map_cache
|
115
|
+
# @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
|
116
|
+
# end
|
117
|
+
# if Rails.env.production? && !Hyperloop::InternalClassPolicy.ar_base_descendants_map_cache.include?(str)
|
118
|
+
if Rails.application.config.eager_load && !ActiveRecord::Base.descendants.map(&:name).include?(str)
|
119
|
+
# AR::Base.descendants is eager loaded in production -> this guard works.
|
120
|
+
# In development it may be empty or partially filled -> this guard may fail.
|
121
|
+
# Thus guarded here only in production.
|
122
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is either not defined or is not a subclass of ActiveRecord::Base")
|
123
|
+
end
|
124
|
+
Object.const_get(str)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.regulated_klasses
|
129
|
+
@regulated_klasses ||= Set.new
|
89
130
|
end
|
90
131
|
|
91
132
|
def regulate(regulation_klass, policy, args, ®ulation)
|
92
133
|
process_args(policy, regulation_klass.allowed_opts, args, regulation) do |regulated_klass, opts|
|
134
|
+
self.class.regulated_klasses << regulated_klass.to_s
|
93
135
|
regulation_klass.add_regulation regulated_klass, opts, ®ulation
|
94
136
|
end
|
95
137
|
end
|
@@ -201,8 +243,23 @@ module Hyperloop
|
|
201
243
|
class ClassConnectionRegulation < Regulation
|
202
244
|
|
203
245
|
def self.add_regulation(klass, opts={}, ®ulation)
|
204
|
-
actual_klass = klass.is_a?(Class)
|
205
|
-
|
246
|
+
actual_klass = if klass.is_a?(Class)
|
247
|
+
klass
|
248
|
+
else
|
249
|
+
begin
|
250
|
+
klass.constantize
|
251
|
+
rescue NameError
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
end
|
255
|
+
if actual_klass && actual_klass.respond_to?(:dispatch_to)
|
256
|
+
begin
|
257
|
+
actual_klass.dispatch_to(actual_klass)
|
258
|
+
rescue NoMethodError
|
259
|
+
# this is the case for ClassPolicy where the instance method :dispatch_to has been deleted.
|
260
|
+
nil
|
261
|
+
end
|
262
|
+
end
|
206
263
|
super
|
207
264
|
end
|
208
265
|
|
@@ -321,10 +378,18 @@ module Hyperloop
|
|
321
378
|
@obj
|
322
379
|
end
|
323
380
|
|
381
|
+
def self.raise_operation_access_violation(message, details)
|
382
|
+
Hyperloop.on_error(Hyperloop::AccessViolation, message, details)
|
383
|
+
raise Hyperloop::AccessViolation
|
384
|
+
end
|
385
|
+
|
324
386
|
def self.regulate_connection(acting_user, channel_string)
|
325
387
|
channel = channel_string.split("-")
|
326
388
|
if channel.length > 1
|
327
389
|
id = channel[1..-1].join("-")
|
390
|
+
unless Hyperloop::InternalClassPolicy.regulated_klasses.include?(channel[0])
|
391
|
+
Hyperloop::InternalPolicy.raise_operation_access_violation(:not_a_channel, "#{channel[0]} is not regulated channel class")
|
392
|
+
end
|
328
393
|
object = Object.const_get(channel[0]).find(id)
|
329
394
|
InstanceConnectionRegulation.connect(object, acting_user)
|
330
395
|
else
|
@@ -486,7 +551,7 @@ module Hyperloop
|
|
486
551
|
|
487
552
|
module PolicyAutoLoader
|
488
553
|
def self.load(name, value)
|
489
|
-
const_get("#{name}Policy") if name && !(
|
554
|
+
const_get("#{name}Policy") if name && !name.end_with?("Policy".freeze) && value.is_a?(Class)
|
490
555
|
rescue Exception => e
|
491
556
|
raise e if e.is_a?(LoadError) && e.message =~ /Unable to autoload constant #{name}Policy/
|
492
557
|
end
|
@@ -515,8 +580,8 @@ class Class
|
|
515
580
|
|
516
581
|
Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
|
517
582
|
define_method method do |*args, &block|
|
518
|
-
if name
|
519
|
-
@hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.
|
583
|
+
if name.end_with?("Policy".freeze)
|
584
|
+
@hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.sub(/Policy$/,""))
|
520
585
|
include Hyperloop::PolicyMethods
|
521
586
|
send method, *args, &block
|
522
587
|
else
|