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.
Files changed (291) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +49 -0
  3. data/CODE_OF_CONDUCT.md +49 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +324 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +708 -0
  8. data/Rakefile +4 -0
  9. data/examples/chat-app/.gitignore +21 -0
  10. data/examples/chat-app/Gemfile +57 -0
  11. data/examples/chat-app/Gemfile.lock +282 -0
  12. data/examples/chat-app/README.md +3 -0
  13. data/examples/chat-app/Rakefile +6 -0
  14. data/examples/chat-app/app/assets/config/manifest.js +3 -0
  15. data/examples/chat-app/app/assets/images/.keep +0 -0
  16. data/examples/chat-app/app/assets/javascripts/application.js +3 -0
  17. data/examples/chat-app/app/assets/javascripts/cable.js +13 -0
  18. data/examples/chat-app/app/assets/javascripts/channels/.keep +0 -0
  19. data/examples/chat-app/app/assets/stylesheets/application.css +15 -0
  20. data/examples/chat-app/app/channels/application_cable/channel.rb +4 -0
  21. data/examples/chat-app/app/channels/application_cable/connection.rb +4 -0
  22. data/examples/chat-app/app/controllers/application_controller.rb +3 -0
  23. data/examples/chat-app/app/controllers/concerns/.keep +0 -0
  24. data/examples/chat-app/app/controllers/home_controller.rb +5 -0
  25. data/examples/chat-app/app/helpers/application_helper.rb +2 -0
  26. data/examples/chat-app/app/hyperloop/components/app.rb +11 -0
  27. data/examples/chat-app/app/hyperloop/components/formatted_div.rb +13 -0
  28. data/examples/chat-app/app/hyperloop/components/input_box.rb +29 -0
  29. data/examples/chat-app/app/hyperloop/components/message.rb +29 -0
  30. data/examples/chat-app/app/hyperloop/components/messages.rb +9 -0
  31. data/examples/chat-app/app/hyperloop/components/nav.rb +30 -0
  32. data/examples/chat-app/app/hyperloop/operations/operations.rb +56 -0
  33. data/examples/chat-app/app/hyperloop/stores/message_store.rb +23 -0
  34. data/examples/chat-app/app/models/application_record.rb +3 -0
  35. data/examples/chat-app/app/models/concerns/.keep +0 -0
  36. data/examples/chat-app/app/models/models.rb +2 -0
  37. data/examples/chat-app/app/models/public/.keep +0 -0
  38. data/examples/chat-app/app/models/public/announcement.rb +8 -0
  39. data/examples/chat-app/app/policies/application_policy.rb +5 -0
  40. data/examples/chat-app/app/views/layouts/application.html.erb +51 -0
  41. data/examples/chat-app/bin/bundle +3 -0
  42. data/examples/chat-app/bin/rails +9 -0
  43. data/examples/chat-app/bin/rake +9 -0
  44. data/examples/chat-app/bin/setup +34 -0
  45. data/examples/chat-app/bin/spring +17 -0
  46. data/examples/chat-app/bin/update +29 -0
  47. data/examples/chat-app/config.ru +5 -0
  48. data/examples/chat-app/config/application.rb +13 -0
  49. data/examples/chat-app/config/boot.rb +3 -0
  50. data/examples/chat-app/config/cable.yml +9 -0
  51. data/examples/chat-app/config/database.yml +25 -0
  52. data/examples/chat-app/config/environment.rb +5 -0
  53. data/examples/chat-app/config/environments/development.rb +56 -0
  54. data/examples/chat-app/config/environments/production.rb +86 -0
  55. data/examples/chat-app/config/environments/test.rb +42 -0
  56. data/examples/chat-app/config/initializers/application_controller_renderer.rb +6 -0
  57. data/examples/chat-app/config/initializers/assets.rb +11 -0
  58. data/examples/chat-app/config/initializers/backtrace_silencers.rb +7 -0
  59. data/examples/chat-app/config/initializers/cookies_serializer.rb +5 -0
  60. data/examples/chat-app/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/examples/chat-app/config/initializers/hyperloop.rb +4 -0
  62. data/examples/chat-app/config/initializers/inflections.rb +16 -0
  63. data/examples/chat-app/config/initializers/mime_types.rb +4 -0
  64. data/examples/chat-app/config/initializers/new_framework_defaults.rb +24 -0
  65. data/examples/chat-app/config/initializers/session_store.rb +3 -0
  66. data/examples/chat-app/config/initializers/wrap_parameters.rb +14 -0
  67. data/examples/chat-app/config/locales/en.yml +23 -0
  68. data/examples/chat-app/config/puma.rb +47 -0
  69. data/examples/chat-app/config/routes.rb +5 -0
  70. data/examples/chat-app/config/secrets.yml +22 -0
  71. data/examples/chat-app/config/spring.rb +6 -0
  72. data/examples/chat-app/db/seeds.rb +7 -0
  73. data/examples/chat-app/lib/assets/.keep +0 -0
  74. data/examples/chat-app/lib/tasks/.keep +0 -0
  75. data/examples/chat-app/log/.keep +0 -0
  76. data/examples/chat-app/public/404.html +67 -0
  77. data/examples/chat-app/public/422.html +67 -0
  78. data/examples/chat-app/public/500.html +66 -0
  79. data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
  80. data/examples/chat-app/public/apple-touch-icon.png +0 -0
  81. data/examples/chat-app/public/favicon.ico +0 -0
  82. data/examples/chat-app/public/robots.txt +5 -0
  83. data/examples/chat-app/test/controllers/.keep +0 -0
  84. data/examples/chat-app/test/fixtures/.keep +0 -0
  85. data/examples/chat-app/test/fixtures/files/.keep +0 -0
  86. data/examples/chat-app/test/helpers/.keep +0 -0
  87. data/examples/chat-app/test/integration/.keep +0 -0
  88. data/examples/chat-app/test/mailers/.keep +0 -0
  89. data/examples/chat-app/test/models/.keep +0 -0
  90. data/examples/chat-app/test/test_helper.rb +10 -0
  91. data/examples/chat-app/tmp/.keep +0 -0
  92. data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
  93. data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
  94. data/examples/five-letter-word-game/.gitignore +21 -0
  95. data/examples/five-letter-word-game/Gemfile +57 -0
  96. data/examples/five-letter-word-game/Gemfile.lock +282 -0
  97. data/examples/five-letter-word-game/README.md +24 -0
  98. data/examples/five-letter-word-game/Rakefile +6 -0
  99. data/examples/five-letter-word-game/app/assets/config/manifest.js +3 -0
  100. data/examples/five-letter-word-game/app/assets/images/.keep +0 -0
  101. data/examples/five-letter-word-game/app/assets/javascripts/application.js +3 -0
  102. data/examples/five-letter-word-game/app/assets/javascripts/cable.js +13 -0
  103. data/examples/five-letter-word-game/app/assets/javascripts/channels/.keep +0 -0
  104. data/examples/five-letter-word-game/app/assets/stylesheets/application.css +15 -0
  105. data/examples/five-letter-word-game/app/channels/application_cable/channel.rb +4 -0
  106. data/examples/five-letter-word-game/app/channels/application_cable/connection.rb +4 -0
  107. data/examples/five-letter-word-game/app/controllers/application_controller.rb +14 -0
  108. data/examples/five-letter-word-game/app/controllers/concerns/.keep +0 -0
  109. data/examples/five-letter-word-game/app/controllers/home_controller.rb +5 -0
  110. data/examples/five-letter-word-game/app/helpers/application_helper.rb +2 -0
  111. data/examples/five-letter-word-game/app/hyperloop/components/app.rb +65 -0
  112. data/examples/five-letter-word-game/app/hyperloop/components/guesses.rb +8 -0
  113. data/examples/five-letter-word-game/app/hyperloop/components/input_word.rb +13 -0
  114. data/examples/five-letter-word-game/app/hyperloop/models/user.rb +27 -0
  115. data/examples/five-letter-word-game/app/hyperloop/operations/ops.rb +115 -0
  116. data/examples/five-letter-word-game/app/hyperloop/stores/store.rb +120 -0
  117. data/examples/five-letter-word-game/app/jobs/application_job.rb +2 -0
  118. data/examples/five-letter-word-game/app/mailers/application_mailer.rb +4 -0
  119. data/examples/five-letter-word-game/app/models/application_record.rb +3 -0
  120. data/examples/five-letter-word-game/app/models/concerns/.keep +0 -0
  121. data/examples/five-letter-word-game/app/policies/user_policy.rb +4 -0
  122. data/examples/five-letter-word-game/app/views/layouts/application.html.erb +14 -0
  123. data/examples/five-letter-word-game/app/views/layouts/mailer.html.erb +13 -0
  124. data/examples/five-letter-word-game/app/views/layouts/mailer.text.erb +1 -0
  125. data/examples/five-letter-word-game/bin/bundle +3 -0
  126. data/examples/five-letter-word-game/bin/rails +9 -0
  127. data/examples/five-letter-word-game/bin/rake +9 -0
  128. data/examples/five-letter-word-game/bin/setup +34 -0
  129. data/examples/five-letter-word-game/bin/spring +17 -0
  130. data/examples/five-letter-word-game/bin/update +29 -0
  131. data/examples/five-letter-word-game/config.ru +5 -0
  132. data/examples/five-letter-word-game/config/application.rb +12 -0
  133. data/examples/five-letter-word-game/config/boot.rb +3 -0
  134. data/examples/five-letter-word-game/config/cable.yml +9 -0
  135. data/examples/five-letter-word-game/config/database.yml +25 -0
  136. data/examples/five-letter-word-game/config/environment.rb +5 -0
  137. data/examples/five-letter-word-game/config/environments/development.rb +56 -0
  138. data/examples/five-letter-word-game/config/environments/production.rb +86 -0
  139. data/examples/five-letter-word-game/config/environments/test.rb +42 -0
  140. data/examples/five-letter-word-game/config/initializers/application_controller_renderer.rb +6 -0
  141. data/examples/five-letter-word-game/config/initializers/assets.rb +11 -0
  142. data/examples/five-letter-word-game/config/initializers/backtrace_silencers.rb +7 -0
  143. data/examples/five-letter-word-game/config/initializers/cookies_serializer.rb +5 -0
  144. data/examples/five-letter-word-game/config/initializers/filter_parameter_logging.rb +4 -0
  145. data/examples/five-letter-word-game/config/initializers/hyperloop.rb +3 -0
  146. data/examples/five-letter-word-game/config/initializers/inflections.rb +16 -0
  147. data/examples/five-letter-word-game/config/initializers/mime_types.rb +4 -0
  148. data/examples/five-letter-word-game/config/initializers/new_framework_defaults.rb +24 -0
  149. data/examples/five-letter-word-game/config/initializers/session_store.rb +3 -0
  150. data/examples/five-letter-word-game/config/initializers/wrap_parameters.rb +14 -0
  151. data/examples/five-letter-word-game/config/locales/en.yml +23 -0
  152. data/examples/five-letter-word-game/config/puma.rb +47 -0
  153. data/examples/five-letter-word-game/config/routes.rb +5 -0
  154. data/examples/five-letter-word-game/config/secrets.yml +22 -0
  155. data/examples/five-letter-word-game/config/spring.rb +6 -0
  156. data/examples/five-letter-word-game/db/schema.rb +28 -0
  157. data/examples/five-letter-word-game/db/seeds.rb +7 -0
  158. data/examples/five-letter-word-game/lib/assets/.keep +0 -0
  159. data/examples/five-letter-word-game/lib/tasks/.keep +0 -0
  160. data/examples/five-letter-word-game/log/.keep +0 -0
  161. data/examples/five-letter-word-game/public/404.html +67 -0
  162. data/examples/five-letter-word-game/public/422.html +67 -0
  163. data/examples/five-letter-word-game/public/500.html +66 -0
  164. data/examples/five-letter-word-game/public/apple-touch-icon-precomposed.png +0 -0
  165. data/examples/five-letter-word-game/public/apple-touch-icon.png +0 -0
  166. data/examples/five-letter-word-game/public/favicon.ico +0 -0
  167. data/examples/five-letter-word-game/public/robots.txt +5 -0
  168. data/examples/five-letter-word-game/test/controllers/.keep +0 -0
  169. data/examples/five-letter-word-game/test/fixtures/.keep +0 -0
  170. data/examples/five-letter-word-game/test/fixtures/files/.keep +0 -0
  171. data/examples/five-letter-word-game/test/helpers/.keep +0 -0
  172. data/examples/five-letter-word-game/test/integration/.keep +0 -0
  173. data/examples/five-letter-word-game/test/mailers/.keep +0 -0
  174. data/examples/five-letter-word-game/test/models/.keep +0 -0
  175. data/examples/five-letter-word-game/test/test_helper.rb +10 -0
  176. data/examples/five-letter-word-game/tmp/.keep +0 -0
  177. data/examples/five-letter-word-game/vendor/assets/javascripts/.keep +0 -0
  178. data/examples/five-letter-word-game/vendor/assets/stylesheets/.keep +0 -0
  179. data/examples/smoke_test/.gitignore +21 -0
  180. data/examples/smoke_test/Gemfile +59 -0
  181. data/examples/smoke_test/Gemfile.lock +289 -0
  182. data/examples/smoke_test/README.md +24 -0
  183. data/examples/smoke_test/Rakefile +6 -0
  184. data/examples/smoke_test/app/assets/config/manifest.js +3 -0
  185. data/examples/smoke_test/app/assets/images/.keep +0 -0
  186. data/examples/smoke_test/app/assets/javascripts/application.js +15 -0
  187. data/examples/smoke_test/app/assets/javascripts/cable.js +13 -0
  188. data/examples/smoke_test/app/assets/javascripts/channels/.keep +0 -0
  189. data/examples/smoke_test/app/assets/stylesheets/application.css +15 -0
  190. data/examples/smoke_test/app/channels/application_cable/channel.rb +4 -0
  191. data/examples/smoke_test/app/channels/application_cable/connection.rb +4 -0
  192. data/examples/smoke_test/app/controllers/app_controller.rb +5 -0
  193. data/examples/smoke_test/app/controllers/application_controller.rb +3 -0
  194. data/examples/smoke_test/app/helpers/application_helper.rb +2 -0
  195. data/examples/smoke_test/app/hyperloop/components/hello.rb +25 -0
  196. data/examples/smoke_test/app/hyperloop/operations/operations/nested_send_to_all.rb +7 -0
  197. data/examples/smoke_test/app/hyperloop/operations/send_to_all.rb +6 -0
  198. data/examples/smoke_test/app/hyperloop/stores/messages.rb +4 -0
  199. data/examples/smoke_test/app/jobs/application_job.rb +2 -0
  200. data/examples/smoke_test/app/mailers/application_mailer.rb +4 -0
  201. data/examples/smoke_test/app/models/application_record.rb +3 -0
  202. data/examples/smoke_test/app/models/concerns/.keep +0 -0
  203. data/examples/smoke_test/app/policies/application_policy.rb +8 -0
  204. data/examples/smoke_test/app/views/layouts/application.html.erb +14 -0
  205. data/examples/smoke_test/app/views/layouts/mailer.html.erb +13 -0
  206. data/examples/smoke_test/app/views/layouts/mailer.text.erb +1 -0
  207. data/examples/smoke_test/bin/bundle +3 -0
  208. data/examples/smoke_test/bin/rails +9 -0
  209. data/examples/smoke_test/bin/rake +9 -0
  210. data/examples/smoke_test/bin/setup +34 -0
  211. data/examples/smoke_test/bin/spring +17 -0
  212. data/examples/smoke_test/bin/update +29 -0
  213. data/examples/smoke_test/config.ru +5 -0
  214. data/examples/smoke_test/config/application.rb +15 -0
  215. data/examples/smoke_test/config/boot.rb +3 -0
  216. data/examples/smoke_test/config/cable.yml +9 -0
  217. data/examples/smoke_test/config/database.yml +25 -0
  218. data/examples/smoke_test/config/environment.rb +5 -0
  219. data/examples/smoke_test/config/environments/development.rb +54 -0
  220. data/examples/smoke_test/config/environments/production.rb +86 -0
  221. data/examples/smoke_test/config/environments/test.rb +42 -0
  222. data/examples/smoke_test/config/initializers/application_controller_renderer.rb +6 -0
  223. data/examples/smoke_test/config/initializers/assets.rb +11 -0
  224. data/examples/smoke_test/config/initializers/backtrace_silencers.rb +7 -0
  225. data/examples/smoke_test/config/initializers/cookies_serializer.rb +5 -0
  226. data/examples/smoke_test/config/initializers/filter_parameter_logging.rb +4 -0
  227. data/examples/smoke_test/config/initializers/hyperloop.rb +32 -0
  228. data/examples/smoke_test/config/initializers/inflections.rb +16 -0
  229. data/examples/smoke_test/config/initializers/mime_types.rb +4 -0
  230. data/examples/smoke_test/config/initializers/new_framework_defaults.rb +24 -0
  231. data/examples/smoke_test/config/initializers/session_store.rb +3 -0
  232. data/examples/smoke_test/config/initializers/wrap_parameters.rb +14 -0
  233. data/examples/smoke_test/config/locales/en.yml +23 -0
  234. data/examples/smoke_test/config/puma.rb +47 -0
  235. data/examples/smoke_test/config/routes.rb +5 -0
  236. data/examples/smoke_test/config/secrets.yml +22 -0
  237. data/examples/smoke_test/config/spring.rb +6 -0
  238. data/examples/smoke_test/db/seeds.rb +7 -0
  239. data/examples/smoke_test/lib/assets/.keep +0 -0
  240. data/examples/smoke_test/lib/tasks/.keep +0 -0
  241. data/examples/smoke_test/log/.keep +0 -0
  242. data/examples/smoke_test/public/404.html +67 -0
  243. data/examples/smoke_test/public/422.html +67 -0
  244. data/examples/smoke_test/public/500.html +66 -0
  245. data/examples/smoke_test/public/apple-touch-icon-precomposed.png +0 -0
  246. data/examples/smoke_test/public/apple-touch-icon.png +0 -0
  247. data/examples/smoke_test/public/favicon.ico +0 -0
  248. data/examples/smoke_test/public/robots.txt +5 -0
  249. data/examples/smoke_test/test/controllers/.keep +0 -0
  250. data/examples/smoke_test/test/fixtures/.keep +0 -0
  251. data/examples/smoke_test/test/fixtures/files/.keep +0 -0
  252. data/examples/smoke_test/test/helpers/.keep +0 -0
  253. data/examples/smoke_test/test/integration/.keep +0 -0
  254. data/examples/smoke_test/test/mailers/.keep +0 -0
  255. data/examples/smoke_test/test/models/.keep +0 -0
  256. data/examples/smoke_test/test/test_helper.rb +10 -0
  257. data/examples/smoke_test/tmp/.keep +0 -0
  258. data/examples/smoke_test/vendor/assets/javascripts/.keep +0 -0
  259. data/examples/smoke_test/vendor/assets/stylesheets/.keep +0 -0
  260. data/hyper-operation.gemspec +44 -0
  261. data/lib/hyper-operation.rb +59 -0
  262. data/lib/hyper-operation/api.rb +154 -0
  263. data/lib/hyper-operation/boot.rb +24 -0
  264. data/lib/hyper-operation/call_by_class_name.rb +60 -0
  265. data/lib/hyper-operation/delay_and_interval.rb +9 -0
  266. data/lib/hyper-operation/engine.rb +11 -0
  267. data/lib/hyper-operation/exception.rb +13 -0
  268. data/lib/hyper-operation/filters/acting_user.rb +19 -0
  269. data/lib/hyper-operation/filters/outbound_filter.rb +9 -0
  270. data/lib/hyper-operation/filters/simple_hash.rb +22 -0
  271. data/lib/hyper-operation/opal_patches.rb +39 -0
  272. data/lib/hyper-operation/promise.rb +347 -0
  273. data/lib/hyper-operation/railway.rb +5 -0
  274. data/lib/hyper-operation/railway/dispatcher.rb +31 -0
  275. data/lib/hyper-operation/railway/operation_wrapper.rb +106 -0
  276. data/lib/hyper-operation/railway/params_wrapper.rb +124 -0
  277. data/lib/hyper-operation/railway/run.rb +140 -0
  278. data/lib/hyper-operation/railway/validations.rb +63 -0
  279. data/lib/hyper-operation/server_op.rb +98 -0
  280. data/lib/hyper-operation/transport/acting_user.rb +6 -0
  281. data/lib/hyper-operation/transport/action_cable.rb +39 -0
  282. data/lib/hyper-operation/transport/active_record.rb +15 -0
  283. data/lib/hyper-operation/transport/client_drivers.rb +210 -0
  284. data/lib/hyper-operation/transport/connection.rb +170 -0
  285. data/lib/hyper-operation/transport/hyperloop.rb +165 -0
  286. data/lib/hyper-operation/transport/hyperloop_controller.rb +184 -0
  287. data/lib/hyper-operation/transport/pluck.rb +6 -0
  288. data/lib/hyper-operation/transport/policy.rb +535 -0
  289. data/lib/hyper-operation/version.rb +5 -0
  290. data/lib/sources/hyperloop/pusher.js +98 -0
  291. metadata +628 -0
@@ -0,0 +1,98 @@
1
+ module Hyperloop
2
+ class ServerOp < Operation
3
+
4
+ class << self
5
+ def run(*args)
6
+ hash = _Railway.params_wrapper.combine_arg_array(args)
7
+ hash = serialize_params(hash)
8
+ HTTP.post(
9
+ "#{`window.HyperloopEnginePath`}/execute_remote",
10
+ payload: {json: {operation: name, params: hash}.to_json},
11
+ headers: {'X-CSRF-Token' => Hyperloop::ClientDrivers.opts[:form_authenticity_token] }
12
+ )
13
+ .then do |response|
14
+ deserialize_response response.json[:response]
15
+ end.fail do |response|
16
+ Exception.new response.json[:error]
17
+ end
18
+ end if RUBY_ENGINE == 'opal'
19
+
20
+ def run_from_client(security_param, operation, params)
21
+ operation.constantize.class_eval do
22
+ raise AccessViolation unless _Railway.params_wrapper.method_defined? security_param
23
+ run(params)
24
+ .then { |r| return { json: { response: serialize_response(r) } } }
25
+ .fail { |e| return { json: { error: e}, status: 500 } }
26
+ end
27
+ rescue Exception => e
28
+ { json: {error: e}, status: 500 }
29
+ end
30
+
31
+ def remote(path, *args)
32
+ promise = Promise.new
33
+ uri = URI("#{path}execute_remote_api")
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
36
+ if uri.scheme == 'https'
37
+ http.use_ssl = true
38
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
+ end
40
+ request.body = {
41
+ operation: name,
42
+ params: Hyperloop::Operation::ParamsWrapper.combine_arg_array(args)
43
+ }.to_json
44
+ promise.resolve http.request(request)
45
+ rescue Exception => e
46
+ promise.reject e
47
+ end
48
+
49
+ def serialize_params(hash)
50
+ hash
51
+ end
52
+
53
+ def deserialize_params(hash)
54
+ hash
55
+ end
56
+
57
+ def serialize_response(hash)
58
+ hash
59
+ end
60
+
61
+ def deserialize_response(hash)
62
+ hash
63
+ end
64
+
65
+ def serialize_dispatch(hash)
66
+ hash
67
+ end
68
+
69
+ def deserialize_dispatch(hash)
70
+ hash
71
+ end
72
+
73
+ def dispatch_to(*args, &regulation)
74
+ _dispatch_to(nil, args, &regulation) if RUBY_ENGINE != 'opal'
75
+ end
76
+
77
+ def _dispatch_to(context, args=[], &regulation)
78
+ if args.count == 0 && regulation.nil?
79
+ raise "must provide either a list of channel classes or a block to regulate_dispatch"
80
+ elsif args.count > 0 && regulation
81
+ raise "cannot provide both a list of channel classes and a block to regulate_dispatch"
82
+ end
83
+ regulation ||= proc { args }
84
+ on_dispatch do |params, operation|
85
+ serialized_params = serialize_dispatch(params.to_h)
86
+ [operation.instance_exec(*context, &regulation)].flatten.compact.uniq.each do |channel|
87
+ Hyperloop.dispatch(channel: Hyperloop::InternalPolicy.channel_to_string(channel), operation: operation.class.name, params: serialized_params)
88
+ end
89
+ end
90
+ end if RUBY_ENGINE != 'opal'
91
+
92
+ def dispatch_from_server(params_hash)
93
+ params = _Railway.params_wrapper.new(deserialize_dispatch(params_hash)).lock
94
+ _Railway.receivers.each { |receiver| receiver.call params }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,6 @@
1
+ class ActionController::Base
2
+
3
+ def acting_user
4
+ end
5
+
6
+ end
@@ -0,0 +1,39 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base; end
3
+ class Connection < ActionCable::Connection::Base; end
4
+ end
5
+
6
+ module Hyperloop
7
+ class ActionCableChannel < ApplicationCable::Channel
8
+ class << self
9
+ def subscriptions
10
+ @subscriptions ||= Hash.new { |h, k| h[k] = 0 }
11
+ end
12
+ end
13
+
14
+ def inc_subscription
15
+ self.class.subscriptions[params[:hyperloop_channel]] =
16
+ self.class.subscriptions[params[:hyperloop_channel]] + 1
17
+ end
18
+
19
+ def dec_subscription
20
+ self.class.subscriptions[params[:hyperloop_channel]] =
21
+ self.class.subscriptions[params[:hyperloop_channel]] - 1
22
+ end
23
+
24
+ def subscribed
25
+ session_id = params["client_id"]
26
+ authorization = Hyperloop.authorization(params["salt"], params["hyperloop_channel"], session_id)
27
+ if params["authorization"] == authorization
28
+ inc_subscription
29
+ stream_from "hyperloop-#{params[:hyperloop_channel]}"
30
+ else
31
+ reject
32
+ end
33
+ end
34
+
35
+ def unsubscribed
36
+ Hyperloop::Connection.disconnect(params[:hyperloop_channel]) if dec_subscription == 0
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ class Base
3
+
4
+ class << self
5
+
6
+ def do_not_synchronize
7
+ @do_not_synchronize = true
8
+ end
9
+
10
+ def do_not_synchronize?
11
+ @do_not_synchronize
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,210 @@
1
+ #### this is going to need some refactoring so that HyperMesh can add its methods in here...
2
+
3
+ module Hyperloop
4
+ # Client side handling of synchronization messages
5
+ # When a synchronization message comes in, the client will sync_dispatch
6
+ # We use ERB to determine the configuration and implement the appropriate
7
+ # client interface to sync_change or sync_destroy
8
+
9
+ class Application
10
+ def self.acting_user_id
11
+ ClientDrivers.opts[:acting_user_id]
12
+ end
13
+ end
14
+
15
+
16
+ if RUBY_ENGINE == 'opal'
17
+ def self.connect(*channels)
18
+ channels.each do |channel|
19
+ if channel.is_a? Class
20
+ IncomingBroadcast.connect_to(channel.name)
21
+ elsif channel.is_a?(String) || channel.is_a?(Array)
22
+ IncomingBroadcast.connect_to(*channel)
23
+ elsif channel.id
24
+ IncomingBroadcast.connect_to(channel.class.name, channel.id)
25
+ else
26
+ raise "cannot connect to model before it has been saved"
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.action_cable_consumer
32
+ ClientDrivers.opts[:action_cable_consumer]
33
+ end
34
+
35
+ class IncomingBroadcast
36
+
37
+ def self.open_channels
38
+ @open_channels ||= Set.new
39
+ end
40
+
41
+ def self.add_connection(channel_name, id = nil)
42
+ channel_string = "#{channel_name}#{'-'+id.to_s if id}"
43
+ open_channels << channel_string
44
+ channel_string
45
+ end
46
+
47
+ def self.connect_to(channel_name, id = nil)
48
+ channel_string = add_connection(channel_name, id)
49
+ if ClientDrivers.opts[:transport] == :pusher
50
+ channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
51
+ %x{
52
+ var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')});
53
+ channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]})
54
+ channel.bind('pusher:subscription_succeeded', #{lambda {ClientDrivers.get_queued_data("connect-to-transport", channel_string)}})
55
+ }
56
+ elsif ClientDrivers.opts[:transport] == :action_cable
57
+ channel = "#{ClientDrivers.opts[:channel]}-#{channel_string}"
58
+ HTTP.post(ClientDrivers.polling_path('action-cable-auth', channel)).then do |response|
59
+ %x{
60
+ #{Hyperloop.action_cable_consumer}.subscriptions.create(
61
+ {
62
+ channel: "Hyperloop::ActionCableChannel",
63
+ client_id: #{ClientDrivers.opts[:id]},
64
+ hyperloop_channel: #{channel_string},
65
+ authorization: #{response.json[:authorization]},
66
+ salt: #{response.json[:salt]}
67
+ },
68
+ {
69
+ connected: function() {
70
+ #{ClientDrivers.get_queued_data("connect-to-transport", channel_string)}
71
+ },
72
+ received: function(data) {
73
+ #{ClientDrivers.sync_dispatch(JSON.parse(`JSON.stringify(data)`)['data'])}
74
+ }
75
+ }
76
+ )
77
+ }
78
+ end
79
+ else
80
+ HTTP.get(ClientDrivers.polling_path(:subscribe, channel_string))
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class ClientDrivers
87
+ include React::IsomorphicHelpers
88
+
89
+ def self.sync_dispatch(data)
90
+ # TODO old synchromesh double checked at this point to make sure that this client
91
+ # expected to recieve from the channel the data was sent on. Was that really needed?
92
+ data[:operation].constantize.dispatch_from_server(data[:params])
93
+ end
94
+
95
+ # save the configuration info needed in window.HyperloopOpts
96
+
97
+ # we keep a list of all channels by session with connections in progress
98
+ # for each broadcast we check the list, and add the message to a queue for that
99
+ # session. When the client is informed that the connection has completed
100
+ # we call the server, this will return ALL the broadcasts (cool) and
101
+ # will remove the session from the list.
102
+
103
+ prerender_footer do |controller|
104
+ if defined?(PusherFake)
105
+ path = ::Rails.application.routes.routes.detect do |route|
106
+ route.app == Hyperloop::Engine ||
107
+ (route.app.respond_to?(:app) && route.app.app == Hyperloop::Engine)
108
+ end.path.spec
109
+ pusher_fake_js = PusherFake.javascript(
110
+ auth: { headers: { 'X-CSRF-Token' => controller.send(:form_authenticity_token) } },
111
+ authEndpoint: "#{path}/hyperloop-pusher-auth"
112
+ )
113
+ end
114
+ controller.session.delete 'hyperloop-dummy-init' unless controller.session.id
115
+ id = "#{SecureRandom.uuid}-#{controller.session.id}"
116
+ config_hash = {
117
+ transport: Hyperloop.transport,
118
+ id: id,
119
+ acting_user_id: (controller.acting_user && controller.acting_user.id),
120
+ client_logging: Hyperloop.client_logging,
121
+ pusher_fake_js: pusher_fake_js,
122
+ key: Hyperloop.key,
123
+ encrypted: Hyperloop.encrypted,
124
+ channel: Hyperloop.channel,
125
+ form_authenticity_token: controller.send(:form_authenticity_token),
126
+ seconds_between_poll: Hyperloop.seconds_between_poll,
127
+ auto_connect: Hyperloop::AutoConnect.channels(id, controller.acting_user)
128
+ }
129
+ path = ::Rails.application.routes.routes.detect do |route|
130
+ # not sure why the second check is needed. It happens in the test app
131
+ route.app == Hyperloop::Engine or (route.app.respond_to?(:app) and route.app.app == Hyperloop::Engine)
132
+ end
133
+ raise 'Hyperloop::Engine mount point not found. Check your config/routes.rb file' unless path
134
+ path = path.path.spec
135
+ "<script type='text/javascript'>\n"\
136
+ "window.HyperloopEnginePath = '#{path}';\n"\
137
+ "window.HyperloopOpts = #{config_hash.to_json}\n"\
138
+ "</script>\n"
139
+ end if RUBY_ENGINE != 'opal'
140
+
141
+ class << self
142
+ attr_reader :opts
143
+ end
144
+
145
+ def self.get_queued_data(operation, channel = nil, opts = {})
146
+ HTTP.get(polling_path(operation, channel), opts).then do |response|
147
+ response.json.each do |data|
148
+ #send "sync_#{update[0]}", update[1]
149
+ sync_dispatch(data[1])
150
+ end
151
+ end
152
+ end
153
+
154
+ # called from ReactiveRecord::Base before_first_mount hook
155
+ # to insure this is done first.
156
+
157
+ ####### TODO TODO TODO make sure to provide a hook that calls reactive record base AFTER this.. (hey use the boot hook)
158
+ def self.initialize_client_drivers_on_boot
159
+
160
+ if RUBY_ENGINE == 'opal'
161
+ @opts = Hash.new(`window.HyperloopOpts`)
162
+ end
163
+
164
+ if on_opal_client?
165
+
166
+ if opts[:transport] == :pusher
167
+
168
+ opts[:dispatch] = lambda do |data|
169
+ sync_dispatch JSON.parse(`JSON.stringify(#{data})`)
170
+ end
171
+
172
+ if opts[:client_logging] && `window.console && window.console.log`
173
+ `Pusher.log = function(message) {window.console.log(message);}`
174
+ end
175
+ if opts[:pusher_fake_js]
176
+ opts[:pusher_api] = `eval(#{opts[:pusher_fake_js]})`
177
+ else
178
+ h = nil
179
+ pusher_api = nil
180
+ %x{
181
+ h = {
182
+ encrypted: #{opts[:encrypted]},
183
+ authEndpoint: window.HyperloopEnginePath+'/hyperloop-pusher-auth',
184
+ auth: {headers: {'X-CSRF-Token': #{opts[:form_authenticity_token]}}}
185
+ };
186
+ pusher_api = new Pusher(#{opts[:key]}, h)
187
+ }
188
+ opts[:pusher_api] = pusher_api
189
+ end
190
+ Hyperloop.connect(*opts[:auto_connect])
191
+ elsif opts[:transport] == :action_cable
192
+ opts[:action_cable_consumer] =
193
+ `ActionCable.createConsumer.apply(ActionCable, #{[*opts[:action_cable_consumer_url]]})`
194
+ Hyperloop.connect(*opts[:auto_connect])
195
+ elsif opts[:transport] == :simple_poller
196
+ opts[:auto_connect].each { |channel| IncomingBroadcast.add_connection(*channel) }
197
+ every(opts[:seconds_between_poll]) do
198
+ get_queued_data(:read, nil, headers: {'X-HYPERLOOP-SILENT-REQUEST' => true })
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def self.polling_path(to, id = nil)
205
+ s = "#{`window.HyperloopEnginePath`}/hyperloop-#{to}/#{opts[:id]}"
206
+ s = "#{s}/#{id}" if id
207
+ s
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,170 @@
1
+ module Hyperloop
2
+ module AutoCreate
3
+ def needs_init?
4
+ return true unless connection.tables.include?(table_name)
5
+ return false unless Hyperloop.on_server?
6
+ return true if defined?(Rails::Server)
7
+ return true unless Connection.root_path
8
+ uri = URI("#{Connection.root_path}server_up")
9
+ http = Net::HTTP.new(uri.host, uri.port)
10
+ request = Net::HTTP::Get.new(uri.path)
11
+ if uri.scheme == 'https'
12
+ http.use_ssl = true
13
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
14
+ end
15
+ http.request(request) && return rescue true
16
+ end
17
+
18
+ def create_table(*args, &block)
19
+ connection.create_table(table_name, *args, &block) if needs_init?
20
+ end
21
+ end
22
+
23
+ class Connection < ActiveRecord::Base
24
+ class QueuedMessage < ActiveRecord::Base
25
+
26
+ extend AutoCreate
27
+
28
+ self.table_name = 'hyperloop_queued_messages'
29
+
30
+ do_not_synchronize
31
+
32
+ serialize :data
33
+
34
+ belongs_to :hyperloop_connection,
35
+ class_name: 'Hyperloop::Connection',
36
+ foreign_key: 'connection_id'
37
+
38
+ scope :for_session,
39
+ ->(session) { joins(:hyperloop_connection).where('session = ?', session) }
40
+
41
+ # For simplicity we use QueuedMessage with connection_id 0
42
+ # to store the current path which is used by consoles to
43
+ # communicate back to the server
44
+
45
+ default_scope { where('connection_id IS NULL OR connection_id != 0') }
46
+
47
+ def self.root_path=(path)
48
+ unscoped.find_or_create_by(connection_id: 0).update(data: path)
49
+ end
50
+
51
+ def self.root_path
52
+ unscoped.find_or_create_by(connection_id: 0).data
53
+ end
54
+ end
55
+
56
+ extend AutoCreate
57
+
58
+ def self.build_tables
59
+ create_table(force: true) do |t|
60
+ t.string :channel
61
+ t.string :session
62
+ t.datetime :created_at
63
+ t.datetime :expires_at
64
+ t.datetime :refresh_at
65
+ end
66
+ QueuedMessage.create_table(force: true) do |t|
67
+ t.text :data
68
+ t.integer :connection_id
69
+ end
70
+ end
71
+
72
+ do_not_synchronize
73
+
74
+ self.table_name = 'hyperloop_connections'
75
+
76
+ has_many :messages,
77
+ foreign_key: 'connection_id',
78
+ class_name: 'Hyperloop::Connection::QueuedMessage',
79
+ dependent: :destroy
80
+ scope :expired,
81
+ -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) }
82
+ scope :pending_for,
83
+ ->(channel) { where(channel: channel).where('session IS NOT NULL') }
84
+ scope :inactive,
85
+ -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) }
86
+
87
+ def self.needs_refresh?
88
+ exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now])
89
+ end
90
+
91
+ def transport
92
+ self.class.transport
93
+ end
94
+
95
+ before_create do
96
+ if session
97
+ self.expires_at = Time.now + transport.expire_new_connection_in
98
+ elsif transport.refresh_channels_every != :never
99
+ self.refresh_at = Time.now + transport.refresh_channels_every
100
+ end
101
+ end
102
+
103
+ class << self
104
+ attr_accessor :transport
105
+
106
+ def active
107
+ if Hyperloop.on_server?
108
+ expired.delete_all
109
+ refresh_connections if needs_refresh?
110
+ end
111
+ all.pluck(:channel).uniq
112
+ end
113
+
114
+ def open(channel, session = nil, root_path = nil)
115
+ self.root_path = root_path
116
+ find_or_create_by(channel: channel, session: session)
117
+ end
118
+
119
+ def send_to_channel(channel, data)
120
+ pending_for(channel).each do |connection|
121
+ QueuedMessage.create(data: data, hyperloop_connection: connection)
122
+ end
123
+ transport.send_data(channel, data) if exists?(channel: channel, session: nil)
124
+ end
125
+
126
+ def read(session, root_path)
127
+ self.root_path = root_path
128
+ where(session: session)
129
+ .update_all(expires_at: Time.now + transport.expire_polled_connection_in)
130
+ QueuedMessage.for_session(session).destroy_all.pluck(:data)
131
+ end
132
+
133
+ def connect_to_transport(channel, session, root_path)
134
+ self.root_path = root_path
135
+ if (connection = find_by(channel: channel, session: session))
136
+ messages = connection.messages.pluck(:data)
137
+ connection.destroy
138
+ else
139
+ messages = []
140
+ end
141
+ open(channel)
142
+ messages
143
+ end
144
+
145
+ def disconnect(channel)
146
+ find_by(channel: channel, session: nil).destroy
147
+ end
148
+
149
+ def root_path=(path)
150
+ QueuedMessage.root_path = path if path
151
+ end
152
+
153
+ def root_path
154
+ QueuedMessage.root_path
155
+ rescue
156
+ nil
157
+ end
158
+
159
+ def refresh_connections
160
+ refresh_started_at = Time.zone.now
161
+ channels = transport.refresh_channels
162
+ next_refresh = refresh_started_at + transport.refresh_channels_every
163
+ channels.each do |channel|
164
+ find_by(channel: channel, session: nil).update(refresh_at: next_refresh)
165
+ end
166
+ inactive.delete_all
167
+ end
168
+ end
169
+ end
170
+ end