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,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