hyper-operation 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,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,6 @@
1
+ # Add pluck to enumerable... its already done for us in rails 5+
2
+ module Enumerable
3
+ def pluck(key)
4
+ map { |element| element[key] }
5
+ end
6
+ end unless Enumerable.method_defined? :pluck
@@ -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, &regulation)
19
+ regulate(ClassConnectionRegulation, :class_connection, args, &regulation)
20
+ end
21
+
22
+ def always_allow_connection(*args)
23
+ regulate(ClassConnectionRegulation, nil, args) { true }
24
+ end
25
+
26
+ def regulate_instance_connections(*args, &regulation)
27
+ regulate(InstanceConnectionRegulation, :instance_connections, args, &regulation)
28
+ end
29
+
30
+ def regulate_all_broadcasts(*args, &regulation)
31
+ regulate(ChannelBroadcastRegulation, :all_broadcasts, args, &regulation)
32
+ end
33
+
34
+ def regulate_broadcast(*args, &regulation)
35
+ regulate(InstanceBroadcastRegulation, :broadcast, args, &regulation)
36
+ end
37
+
38
+ def regulate_dispatches_from(*args, &regulation)
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(&regulation) }
44
+ end
45
+ end
46
+
47
+ def always_dispatch_from(*args, &regulation)
48
+ regulate_dispatches_from(*args) { true }
49
+ end
50
+
51
+ def dispatch_to(*args, &regulation)
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, &regulation)
58
+ end
59
+
60
+ CHANGE_POLICIES = [:create, :update, :destroy]
61
+
62
+ def self.allow_policy(policy, method)
63
+ define_method "allow_#{policy}" do |*args, &regulation|
64
+ process_args(policy, [], args, regulation) do |model|
65
+ get_ar_model(model).class_eval { define_method("#{method}_permitted?", &regulation) }
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, &regulation)
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?", &regulation) }
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, &regulation)
92
+ process_args(policy, regulation_klass.allowed_opts, args, regulation) do |regulated_klass, opts|
93
+ regulation_klass.add_regulation regulated_klass, opts, &regulation
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={}, &regulation)
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(&regulation)
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={}, &regulation)
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={}, &regulation)
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), &regulation
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