anycable-rails-core 1.5.3 → 1.6.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/README.md +10 -75
  4. data/lib/anycable/rails/action_cable_ext/channel.rb +20 -11
  5. data/lib/anycable/rails/action_cable_ext/remote_connections.rb +4 -14
  6. data/lib/anycable/rails/channel/presence.rb +36 -0
  7. data/lib/anycable/rails/channel.rb +9 -0
  8. data/lib/anycable/rails/compatibility/rubocop.rb +2 -25
  9. data/lib/anycable/rails/compatibility.rb +2 -0
  10. data/lib/anycable/rails/config.rb +2 -0
  11. data/lib/anycable/rails/connection.rb +62 -33
  12. data/lib/anycable/rails/connection_factory.rb +20 -1
  13. data/lib/anycable/rails/connections/persistent_session.rb +5 -0
  14. data/lib/anycable/rails/connections/serializable_identification.rb +19 -0
  15. data/lib/anycable/rails/connections/session_proxy.rb +2 -2
  16. data/lib/anycable/rails/ext/jwt.rb +1 -1
  17. data/lib/anycable/rails/ext/signed_streams.rb +21 -0
  18. data/lib/anycable/rails/ext/whisper.rb +34 -0
  19. data/lib/anycable/rails/ext.rb +4 -1
  20. data/lib/anycable/rails/helper.rb +2 -2
  21. data/lib/anycable/rails/next/action_cable_ext/channel.rb +36 -0
  22. data/lib/anycable/rails/next/action_cable_ext/connection.rb +29 -0
  23. data/lib/anycable/rails/next/connection/persistent_session.rb +39 -0
  24. data/lib/anycable/rails/next/connection.rb +226 -0
  25. data/lib/anycable/rails/railtie.rb +4 -8
  26. data/lib/anycable/rails/{compatibility/rubocop → rubocop}/cops/anycable/instance_vars.rb +1 -1
  27. data/lib/anycable/rails/{compatibility/rubocop → rubocop}/cops/anycable/periodical_timers.rb +3 -8
  28. data/lib/anycable/rails/{compatibility/rubocop → rubocop}/cops/anycable/stream_from.rb +3 -5
  29. data/lib/anycable/rails/rubocop.rb +27 -0
  30. data/lib/anycable/rails/version.rb +1 -1
  31. data/lib/anycable/rails.rb +2 -1
  32. data/lib/generators/anycable/bin/templates/bin/anycable-go.tt +5 -0
  33. data/lib/generators/anycable/download/download_generator.rb +16 -5
  34. data/lib/generators/anycable/setup/setup_generator.rb +280 -38
  35. data/lib/generators/anycable/setup/templates/Procfile.dev.tt +2 -2
  36. data/lib/generators/anycable/setup/templates/anycable.toml.tt +81 -0
  37. data/lib/generators/anycable/setup/templates/bin/anycable-go.tt +5 -0
  38. data/lib/generators/anycable/setup/templates/bin/dev +17 -0
  39. data/lib/generators/anycable/setup/templates/config/anycable.yml.tt +20 -33
  40. data/lib/generators/anycable/setup/templates/config/cable.yml.tt +2 -5
  41. metadata +27 -16
  42. data/lib/anycable/rails/action_cable_ext/signed_streams.rb +0 -31
  43. /data/lib/anycable/rails/{compatibility/rubocop → rubocop}/config/default.yml +0 -0
@@ -9,11 +9,11 @@ module AnyCableRailsGenerators
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
11
  DOCS_ROOT = "https://docs.anycable.io"
12
- DEVELOPMENT_METHODS = %w[skip local docker].freeze
13
- DEPLOYMENT_METHODS = %w[other fly heroku anycable_plus].freeze
12
+ DEVELOPMENT_METHODS = %w[skip bin docker].freeze
13
+ DEPLOYMENT_METHODS = %w[skip thruster fly heroku anycable_plus].freeze
14
14
  RPC_IMPL = %w[none grpc http].freeze
15
15
 
16
- class_option :devenv,
16
+ class_option :development,
17
17
  type: :string,
18
18
  desc: "Select your development environment (options: #{DEVELOPMENT_METHODS.reverse.join(", ")})"
19
19
  class_option :rpc,
@@ -31,37 +31,85 @@ module AnyCableRailsGenerators
31
31
  say ""
32
32
  say "👋 Welcome to AnyCable interactive installer. We'll guide you through the process of installing AnyCable for your Rails application. Buckle up!"
33
33
  say ""
34
+ @todos = []
34
35
  end
35
36
 
36
37
  def rpc_implementation
37
- say "AnyCable connects to your Rails server to communicate with Action Cable channels (via RPC API). Learn more from the docs 👉 #{DOCS_ROOT}/anycable-go/rpc"
38
- say ""
38
+ if RPC_IMPL.include?(options[:rpc])
39
+ @rpc_impl = options[:rpc]
40
+ return
41
+ end
42
+
43
+ if hotwire? && !custom_channels?
44
+ say_status :info, <<~MSG
45
+ ⚡️ Hotwire application has been detected, installing AnyCable in a standalone mode.
46
+ MSG
47
+ @rpc_impl = "none"
48
+ return
49
+ end
50
+
51
+ if custom_channels?
52
+ answer = RPC_IMPL.index(options[:rpc]) || 99
53
+
54
+ unless RPC_IMPL[answer.to_i]
55
+ say ""
56
+ say <<~MSG
57
+ AnyCable connects to your Rails server to communicate with Action Cable channels either via HTTP or gRPC.
58
+
59
+ gRPC provides better performance and scalability but requires running
60
+ a separate component (a gRPC server).
39
61
 
40
- answer = RPC_IMPL.index(options[:rpc]) || 99
62
+ HTTP is a good option for a quick start or in case your deployment platform doesn't
63
+ support running multiple web services (e.g., Heroku).
41
64
 
42
- until RPC_IMPL[answer.to_i]
43
- answer = ask "Do you want to use gRPC or HTTP for AnyCable RPC? (1) gRPC, (2) HTTP, (0) None"
65
+ If you only use Action Cable for Turbo Streams, you don't need RPC at all.
66
+
67
+ Learn more from the docs 👉 #{DOCS_ROOT}/anycable-go/rpc
68
+ MSG
69
+ say ""
70
+ end
71
+
72
+ answer = ask "Which RPC implementation would you like to use?", limited_to: %w[grpc http none], default: "grpc"
73
+
74
+ @rpc_impl = answer
75
+ return
44
76
  end
45
77
 
46
- @rpc_impl = RPC_IMPL[answer.to_i]
78
+ # no Hotwire, no custom channels
79
+ say_status :info, "Looks like you don't have any real-time functionality yet. Let's start with a miminal AnyCable setup!"
80
+ @rpc_impl = "none"
47
81
  end
48
82
 
49
83
  def development_method
50
- answer = DEVELOPMENT_METHODS.index(options[:devenv]) || 99
51
-
52
- say ""
84
+ if DEVELOPMENT_METHODS.include?(options[:development])
85
+ @development = options[:development]
86
+ end
53
87
 
54
- until DEVELOPMENT_METHODS[answer.to_i]
55
- answer = ask "Do you want to run AnyCable server (anycable-go) locally or as a Docker container? (1) Local, (2) Docker, (0) Skip"
88
+ # Fast-track for local development
89
+ if file_exists?("bin/dev") && file_exists?("Procfile.dev")
90
+ @development = "bin"
56
91
  end
57
92
 
58
- @devenv = DEVELOPMENT_METHODS[answer.to_i]
93
+ unless @development
94
+ say ""
95
+ say <<~MSG
96
+ You can run AnyCable server locally (recommended for most cases) or as a Docker container (in case you develop in a containerized environment).
97
+
98
+ For a local installation, we provide a convenient binstub (`bin/anycable-go`) which automatically
99
+ installs AnyCable server for the current platform.
100
+ MSG
101
+ say ""
102
+
103
+ answer = ask "Which way to run AnyCable server locally would you prefer?", limited_to: %w[bin docker skip], default: "bin"
59
104
 
60
- case @devenv
105
+ @development = answer
106
+ end
107
+
108
+ case @development
61
109
  when "skip"
62
- say_status :help, "⚠️ Please, read this guide on how to install AnyCable server 👉 #{DOCS_ROOT}/anycable-go/getting_started", :yellow
110
+ @todos << "Install AnyCable server for local development: #{DOCS_ROOT}/anycable-go/getting_started"
63
111
  else
64
- send "install_for_#{@devenv}"
112
+ send "install_for_#{@development}"
65
113
  end
66
114
  end
67
115
 
@@ -70,6 +118,8 @@ module AnyCableRailsGenerators
70
118
  template "anycable.yml"
71
119
  end
72
120
 
121
+ template "anycable.toml"
122
+
73
123
  update_cable_yml
74
124
  end
75
125
 
@@ -78,22 +128,112 @@ module AnyCableRailsGenerators
78
128
 
79
129
  say_status :info, "🤖 Running static compatibility checks with RuboCop"
80
130
  res = run "bundle exec rubocop -r 'anycable/rails/compatibility/rubocop' --only AnyCable/InstanceVars,AnyCable/PeriodicalTimers,AnyCable/InstanceVars"
81
- say_status :help, "⚠️ Please, take a look at the icompatibilities above and fix them. See #{DOCS_ROOT}/rails/compatibility" unless res
131
+
132
+ unless res
133
+ say_status :help, "⚠️ Please, take a look at the incompatibilities above and fix them"
134
+
135
+ @todos << "Fix Action Cable compatibility issues (listed above): #{DOCS_ROOT}/rails/compatibility"
136
+ end
82
137
  end
83
138
 
84
139
  def cable_url_info
85
- say_status :help, "⚠️ If you're using JS client make sure you have " \
86
- "`action_cable_meta_tag` or `action_cable_with_jwt_meta_tag` included in your HTML layout"
140
+ meta_tag = norpc? ? "action_cable_with_jwt_meta_tag" : "action_cable_meta_tag"
141
+
142
+ begin
143
+ app_layout = nil
144
+ inside("app/views/layouts") do
145
+ next unless File.file?("application.html.erb")
146
+ app_layout = File.read("application.html.erb")
147
+ end
148
+ return if app_layout&.include?(meta_tag)
149
+
150
+ if norpc? && app_layout&.include?("action_cable_meta_tag")
151
+ gsub_file "app/views/layouts/application.html.erb", %r{^\s+<%= action_cable_meta_tag %>.*$} do |match|
152
+ match.sub("action_cable_meta_tag", "action_cable_with_jwt_meta_tag")
153
+ end
154
+ inform_jwt_identifiers("app/views/layouts/application.html.erb")
155
+ return
156
+ end
157
+
158
+ found = false
159
+ gsub_file "app/views/layouts/application.html.erb", %r{^\s+<%= csp_meta_tag %>.*$} do |match|
160
+ found = true
161
+ match << "\n <%= #{meta_tag} %>"
162
+ end
163
+ if found
164
+ inform_jwt_identifiers("app/views/layouts/application.html.erb") if norpc?
165
+ return
166
+ end
167
+ rescue Errno::ENOENT
168
+ end
169
+
170
+ @todos << "⚠️ Ensure you have `action_cable_meta_tag`\n" \
171
+ " or `action_cable_with_jwt_meta_tag` included in your HTML layout:\n" \
172
+ " 👉 https://docs.anycable.io/rails/getting_started"
87
173
  end
88
174
 
89
- def deployment_method
90
- say_status :info, "🚢 See our deployment guide to learn how to run AnyCable in production 👉 #{DOCS_ROOT}/deployment"
175
+ def action_cable_engine
176
+ return unless application_rb
177
+ return if application_rb.match?(/^require\s+['"](action_cable\/engine|rails\/all)['"]/)
178
+
179
+ found = false
180
+ gsub_file "config/application.rb", %r{^require ['"]rails['"].*$} do |match|
181
+ found = true
182
+ match << %(\nrequire "action_cable/engine")
183
+ end
91
184
 
92
- say_status :info, "Check out AnyCable+, our hosted AnyCable solution: https://plus.anycable.io"
185
+ return if found
186
+
187
+ @todos << "⚠️ Ensure Action Cable is loaded. Add `require \"action_cable/engine\"` to your `config/application.rb` file"
188
+ end
189
+
190
+ def anycable_client
191
+ if hotwire? && install_js_packages
192
+ gsub_file "app/javascript/application.js", /^import "@hotwired\/turbo-rails".*$/, <<~JS
193
+ import "@hotwired/turbo"
194
+ import { createCable } from "@anycable/web"
195
+ import { start } from "@anycable/turbo-stream"
196
+
197
+ // Use extended Action Cable protocol to support reliable streams and presence
198
+ // See https://github.com/anycable/anycable-client
199
+ const cable = createCable({ protocol: 'actioncable-v1-ext-json' })
200
+ // Prevent frequent resubscriptions during morphing or navigation
201
+ start(cable, { delayedUnsubscribe: true })
202
+ JS
203
+ return
204
+ end
205
+
206
+ @todos << "⚠️ Install AnyCable JS client to use advanced features (presence, reliable streams): 👉 https://github.com/anycable/anycable-client\n"
207
+ end
208
+
209
+ def turbo_verifier_key
210
+ return unless hotwire?
211
+ return if application_rb.include?("config.turbo.signed_stream_verifier_key = AnyCable.config.secret")
212
+
213
+ gsub_file "config/application.rb", %r{\s+end\nend} do |match|
214
+ "\n\n" \
215
+ " # Use AnyCable secret to sign Turbo Streams\n" \
216
+ " # #{DOCS_ROOT}/guides/hotwire?id=rails-applications\n" \
217
+ " config.turbo.signed_stream_verifier_key = AnyCable.config.secret#{match}"
218
+ end
219
+ end
220
+
221
+ def deployment_method
222
+ @todos << "🚢 Learn how to run AnyCable in production: 👉 #{DOCS_ROOT}/deployment\n" \
223
+ " For the quick start, consider using AnyCable+ (https://plus.anycable.io)\n" \
224
+ " or AnyCable Thruster (https://github.com/anycable/thruster)"
93
225
  end
94
226
 
95
227
  def finish
96
- say_status :info, "✅ AnyCable has been configured successfully!"
228
+ say_status :info, "✅ AnyCable has been configured"
229
+
230
+ if @todos.any?
231
+ say ""
232
+ say "📋 Please, check the following actions required to complete the setup:\n"
233
+ @todos.each do |todo|
234
+ say "- [ ] #{todo}"
235
+ end
236
+ end
97
237
  end
98
238
 
99
239
  private
@@ -115,7 +255,7 @@ module AnyCableRailsGenerators
115
255
  end
116
256
 
117
257
  def local?
118
- @devenv == "local"
258
+ @development == "local"
119
259
  end
120
260
 
121
261
  def grpc?
@@ -126,6 +266,26 @@ module AnyCableRailsGenerators
126
266
  @rpc_impl == "http"
127
267
  end
128
268
 
269
+ def norpc?
270
+ @rpc_impl == "none"
271
+ end
272
+
273
+ def hotwire?
274
+ !!gemfile_lock&.match?(/^\s+turbo-rails\b/) &&
275
+ application_js&.match?(/^import\s+"@hotwired\/turbo/)
276
+ end
277
+
278
+ def custom_channels?
279
+ @has_custom_channels ||= begin
280
+ res = nil
281
+ in_root do
282
+ next unless File.directory?("app/channels")
283
+ res = Dir["app/channels/*_channel.rb"].any?
284
+ end
285
+ res
286
+ end
287
+ end
288
+
129
289
  def gemfile_lock
130
290
  @gemfile_lock ||= begin
131
291
  res = nil
@@ -137,6 +297,28 @@ module AnyCableRailsGenerators
137
297
  end
138
298
  end
139
299
 
300
+ def application_rb
301
+ @application_rb ||= begin
302
+ res = nil
303
+ in_root do
304
+ next unless File.file?("config/application.rb")
305
+ res = File.read("config/application.rb")
306
+ end
307
+ res
308
+ end
309
+ end
310
+
311
+ def application_js
312
+ @application_js ||= begin
313
+ res = nil
314
+ in_root do
315
+ next unless File.file?("app/javascript/application.js")
316
+ res = File.read("app/javascript/application.js")
317
+ end
318
+ res
319
+ end
320
+ end
321
+
140
322
  def install_for_docker
141
323
  say_status :help, "️️⚠️ Docker development configuration could vary", :yellow
142
324
 
@@ -159,7 +341,7 @@ module AnyCableRailsGenerators
159
341
  condition: service_started
160
342
 
161
343
  ws:
162
- image: anycable/anycable-go:1.5
344
+ image: anycable/anycable-go:1.6
163
345
  ports:
164
346
  - '8080:8080'
165
347
  - '8090'
@@ -168,6 +350,7 @@ module AnyCableRailsGenerators
168
350
  ANYCABLE_BROADCAST_ADAPTER: http
169
351
  ANYCABLE_RPC_HOST: anycable:50051
170
352
  ANYCABLE_DEBUG: ${ANYCABLE_DEBUG:-true}
353
+ ANYCABLE_SECRET: "anycable-local-secret"
171
354
 
172
355
  anycable:
173
356
  <<: *rails
@@ -208,16 +391,18 @@ module AnyCableRailsGenerators
208
391
  ANYCABLE_BROADCAST_ADAPTER: http
209
392
  ANYCABLE_RPC_HOST: http://rails:3000/_anycable
210
393
  ANYCABLE_DEBUG: ${ANYCABLE_DEBUG:-true}
394
+ ANYCABLE_SECRET: "anycable-local-secret"
211
395
  ─────────────────────────────────────────
212
396
  YML
213
397
  end
214
398
  end
215
399
 
216
- def install_for_local
400
+ def install_for_bin
217
401
  unless file_exists?("bin/anycable-go")
218
402
  generate "anycable:bin", "--version #{options[:version]}"
219
403
  end
220
404
  template_proc_files
405
+ update_bin_dev
221
406
  true
222
407
  end
223
408
 
@@ -230,7 +415,23 @@ module AnyCableRailsGenerators
230
415
  adapter = Regexp.last_match[1]
231
416
  next match if adapter == "test" || adapter.include?("any_cable")
232
417
 
233
- match.sub(adapter, %(<%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>))
418
+ match.sub(adapter, "any_cable")
419
+ end
420
+
421
+ # Try removing all lines contaning options for previous adapters,
422
+ # only keep aliases (<<:*), adapter and channel_prefix options.
423
+ new_clean_contents = new_contents.lines.select do |line|
424
+ line.match?(/^(\S|\s+adapter:|\s+channel_prefix:|\s+<<:)/) || line.match?(/^\s*$/)
425
+ end.join
426
+
427
+ # Verify new config
428
+ begin
429
+ clean_config = YAML.safe_load(new_clean_contents, aliases: true).deep_symbolize_keys
430
+ orig_config = YAML.safe_load(contents, aliases: true).deep_symbolize_keys
431
+
432
+ new_contents = new_clean_contents if clean_config.keys == orig_config.keys
433
+ rescue => _e
434
+ # something went wrong, keep older options
234
435
  end
235
436
 
236
437
  File.write "config/cable.yml", new_contents
@@ -248,8 +449,6 @@ module AnyCableRailsGenerators
248
449
  if file_exists?(file_name)
249
450
  update_procfile(file_name)
250
451
  else
251
- say_status :help, "💡 We recommend using Overmind to manage multiple processes in development 👉 https://github.com/DarthSim/overmind", :yellow
252
-
253
452
  return if options[:skip_procfile_dev]
254
453
 
255
454
  template file_name
@@ -260,22 +459,38 @@ module AnyCableRailsGenerators
260
459
  in_root do
261
460
  contents = File.read(file_name)
262
461
 
263
- unless http_rpc?
462
+ if grpc?
264
463
  unless contents.match?(/^anycable:\s/)
265
464
  append_file file_name, "anycable: bundle exec anycable\n", force: true
266
465
  end
267
466
  end
268
467
  unless contents.match?(/^ws:\s/)
269
- append_file file_name, "ws: bin/anycable-go #{anycable_go_options}", force: true
468
+ append_file file_name, "ws: bin/anycable-go --port 8080", force: true
270
469
  end
271
470
  end
272
471
  end
273
472
 
274
- def anycable_go_options
275
- opts = ["--port=8080"]
276
- opts << "--broadcast_adapter=http" unless redis?
277
- opts << "--rpc_host=http://localhost:3000/_anycable" if http_rpc?
278
- opts.join(" ")
473
+ def update_bin_dev
474
+ unless file_exists?("bin/dev")
475
+ template "bin/dev"
476
+ chmod "bin/dev", 0755, verbose: false # rubocop:disable Style/NumericLiteralPrefix
477
+
478
+ @todos << "Now you should use bin/dev to run your application with AnyCable services"
479
+ return
480
+ end
481
+
482
+ in_root do
483
+ contents = File.read("bin/dev")
484
+
485
+ return if contents.include?("Procfile.dev")
486
+
487
+ if contents.include?(%(exec "./bin/rails"))
488
+ template "bin/dev", force: true
489
+ chmod "bin/dev", 0755, verbose: false # rubocop:disable Style/NumericLiteralPrefix
490
+ else
491
+ @todos << "Please, check your bin/dev file and ensure it runs Procfile.dev with AnyCable services"
492
+ end
493
+ end
279
494
  end
280
495
 
281
496
  def file_exists?(name)
@@ -283,5 +498,32 @@ module AnyCableRailsGenerators
283
498
  return File.file?(name)
284
499
  end
285
500
  end
501
+
502
+ def inform_jwt_identifiers(path)
503
+ return unless file_exists?("app/channels/application_cable/connection.rb")
504
+
505
+ in_root do
506
+ contents = File.read("app/channels/application_cable/connection.rb")
507
+
508
+ if contents.match?(%r{^\s+identified_by\s})
509
+ @todos << "⚠️ Please, provide the correct connection identifiers to the #action_cable_with_jwt_meta_tag in #{path}. Read more: 👉 #{DOCS_ROOT}/rails/authentication?id=jwt-authentication"
510
+ end
511
+ end
512
+ end
513
+
514
+ def install_js_packages
515
+ if file_exists?("config/importmap.rb") && file_exists?("bin/importmap")
516
+ run "bin/importmap pin @hotwired/turbo @anycable/web @anycable/turbo-stream"
517
+ elsif file_exists?("yarn.lock")
518
+ run "yarn add @anycable/web @anycable/turbo-stream"
519
+ elsif file_exists?("package-json.lock")
520
+ run "npm install @anycable/web @anycable/turbo-stream"
521
+ else
522
+ false
523
+ end
524
+ rescue => e
525
+ say_status :warn, "Failed to install JS packages: #{e.message}. Skipping..."
526
+ false
527
+ end
286
528
  end
287
529
  end
@@ -1,5 +1,5 @@
1
- web: bin/rails s
1
+ web: bin/rails s -p 3000
2
2
  <%- unless http_rpc? -%>
3
3
  anycable: bundle exec anycable
4
4
  <%- end -%>
5
- ws: bin/anycable-go <%= anycable_go_options %>
5
+ ws: bin/anycable-go --port 8080
@@ -0,0 +1,81 @@
1
+ # AnyCable server configuration.
2
+ #
3
+ # Read more at https://docs.anycable.io/anycable-go/configuration
4
+
5
+ # Public mode disables connection authentication, pub/sub streams and broadcasts verification
6
+ # public = false
7
+
8
+ # The application secret key: it's used to verify JWT tokens, signed streams, etc.
9
+ # In development, it MUST match the value in the `config/anycable.yml`.
10
+ # IMPORTANT: Make sure you provided a secret in production via the ANYCABLE_SECRET env var
11
+ # if you use this configuration file.
12
+ secret = "anycable-local-secret"
13
+
14
+ # Enable AnyCable power features such as reliable streams, presence and resumable sessions.
15
+ # Read more:
16
+ # - https://docs.anycable.io/anycable-go/reliable_streams
17
+ # - https://docs.anycable.io/anycable-go/presence
18
+ presets = ["broker"]
19
+
20
+ # Broadcasting adapters for app-to-clients messages.
21
+ # You one of the specified adapters in the config/anycable.yml.
22
+ # Read more in https://docs.anycable.io/anycable-go/broadcasting.
23
+ <%- if redis? -%>
24
+ broadcast_adapters = ["http", "redisx"]
25
+ <%- elsif nats? -%>
26
+ broadcast_adapters = ["http", "nats"]
27
+ <%- else -%>
28
+ broadcast_adapters = ["http"]
29
+ <%- end -%>
30
+
31
+ # Pub/sub adapter for inter-node communication in a production cluster
32
+ # IMPORTANT: In production, when running AnyCable in a cluster mode,
33
+ # make sure to enable pub/sub adapter via the ANYCABLE_PUBSUB_ADAPTER env var
34
+ # pubsub_adapter = "redis" # or "nats"
35
+
36
+ [logging]
37
+ debug = true
38
+
39
+ # RPC is used to authorized subscriptions and perform channel actions.
40
+ # You only need it if you use custom Action Cable channel classes.
41
+ # Read more: https://docs.anycable.io/anycable-go/rpc
42
+ [rpc]
43
+ <%- if http_rpc? -%>
44
+ # IMPORTANT: In production, provide the URL of the RPC service via the ANYCABLE_RPC_HOST env variable
45
+ host = "http://localhost:3000/_anycable"
46
+ <%- elsif grpc? -%>
47
+ host = "localhost:50051"
48
+ <%- else -%>
49
+ # AnyCable is running in a standalone mode.
50
+ # Read more: https://docs.anycable.io/anycable-go/getting_started?id=standalone-mode-pubsub-only
51
+ implementation = "none"
52
+ <%- end -%>
53
+
54
+
55
+ # Signed and public streams configuration.
56
+ # Read more: https://docs.anycable.io/anycable-go/signed_streams
57
+ [streams]
58
+ # Enable public (unsigned) streams
59
+ # public = true
60
+ # Enable whispering support for pub/sub streams
61
+ whisper = true
62
+ #
63
+ # Authorize Turbo Streams at the AnyCable server side.
64
+ # IMPORTANT: Make sure your Turbo verifier key is the same as AnyCable secret.
65
+ # Add this line to your Rails configuration (if missing):
66
+ #
67
+ # config.turbo.signed_stream_verifier_key = AnyCable.config.secret
68
+ #
69
+ turbo = true
70
+
71
+ <%- if redis? -%>
72
+ [redis]
73
+ # IMPORTANT: In production, provide Redis address via the REDIS_URL or ANYCABLE_REDIS_URL env var
74
+ url = "redis://localhost:6379"
75
+ <%- end -%>
76
+
77
+ <%- if nats? -%>
78
+ [nats]
79
+ # IMPORTANT: In production, provide NATS address via the ANYCABLE_NATS_SERVERS env var
80
+ servers = "nats://127.0.0.1:4222"
81
+ <%- end -%>
@@ -10,6 +10,11 @@ if [ ! -f ./bin/dist/anycable-go ]; then
10
10
  ./bin/rails g anycable:download --version=$version --bin-path=./bin/dist
11
11
  fi
12
12
 
13
+ if [ -x ./bin/dist/anycable-go ]; then
14
+ echo "Setting execution permission for AnyCable server"
15
+ chmod +x ./bin/dist/anycable-go
16
+ fi
17
+
13
18
  curVersion=$(./bin/dist/anycable-go -v)
14
19
 
15
20
  if [[ "$version" != "latest" ]]; then
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # Let the debug gem allow remote connections,
4
+ # but avoid loading until `debugger` is called
5
+ export RUBY_DEBUG_OPEN="true"
6
+ export RUBY_DEBUG_LAZY="true"
7
+
8
+ if ! command -v overmind &> /dev/null; then
9
+ if ! gem list foreman -i --silent; then
10
+ echo "Installing foreman..."
11
+ gem install foreman
12
+ fi
13
+
14
+ exec foreman start -f Procfile.dev "$@"
15
+ else
16
+ exec overmind start -f Procfile.dev "$@"
17
+ fi
@@ -1,53 +1,40 @@
1
- # This file contains per-environment settings for AnyCable.
1
+ # This file contains per-environment settings for AnyCable Rails integration
2
+ # (NOT the AnyCable server).
2
3
  #
3
- # Since AnyCable config is based on anyway_config (https://github.com/palkan/anyway_config), all AnyCable settings
4
- # can be set or overridden through the corresponding environment variables.
5
- # E.g., `rpc_host` is overridden by ANYCABLE_RPC_HOST, `debug` by ANYCABLE_DEBUG etc.
6
- #
7
- # Note that AnyCable recognizes REDIS_URL env variable for Redis pub/sub adapter. If you want to
8
- # use another Redis instance for AnyCable, provide ANYCABLE_REDIS_URL variable.
9
- #
10
- # Read more about AnyCable configuration here: <%= DOCS_ROOT %>/ruby/configuration
4
+ # The settings specified here would be overriden by the corresponding values in Rails credentials (e.g., `anycable.secret`), or environment variables (e.g., ANYCABLE_SECRET), or any other configuration
5
+ # sources supported by Anyway Config (https://github.com/palkan/anyway_config).
11
6
  #
12
7
  default: &default
13
8
  # Turn on/off access logs ("Started..." and "Finished...")
14
9
  access_logs_disabled: false
15
10
  # Whether to enable gRPC level logging or not
16
11
  log_grpc: false
17
- <%- if redis? -%>
18
- # Use Redis to broadcast messages to AnyCable server
19
- broadcast_adapter: redis
20
- <%- elsif nats? -%>
21
- # Use NATS to broadcast messages to AnyCable server
22
- broadcast_adapter: nats
23
- <%- else -%>
24
- # Use HTTP broadcaster
12
+ # Use HTTP broadcaster by default
25
13
  broadcast_adapter: http
26
- <%- end -%>
27
- <%- if redis? -%>
28
- # You can use REDIS_URL env var to configure Redis URL.
29
- # Localhost is used by default.
30
- # redis_url: "redis://localhost:6379/1"
31
- # Use the same channel name for WebSocket server, e.g.:
32
- # $ anycable-go --redis_channel="__anycable__"
33
- # redis_channel: "__anycable__"
34
- <%- end -%>
35
14
  <%- if http_rpc? -%>
36
- # Use HTTP RPC mounted at the specified path of your web server
37
- # Read more about AnyCable RPC: <%= DOCS_ROOT %>/anycable-go/rpc
38
- http_rpc_mount_path: "/_anycable"
15
+ # Use HTTP RPC mounted at the "/_anycable" path of your web server
16
+ # Read more: https://docs.anycable.io/anycable-go/rpc?id=rpc-over-http
17
+ http_rpc: true
39
18
  <%- end -%>
19
+ # This values MUST be the same as in your AnyCable server configuration
20
+ secret: "anycable-local-secret"
21
+ # WebSocket endpoint of your AnyCable server for clients to connect to
22
+ # IMPORTANT: Make sure you have the `action_cable_meta_tag` in your HTML layout
23
+ # to propogate this value to the client app.
24
+ # Make sure your AnyCable server runs on port 8080 in development.
25
+ websocket_url: "ws://localhost:8080/cable"
40
26
 
41
27
  development:
42
28
  <<: *default
43
- # WebSocket endpoint of your AnyCable server for clients to connect to
44
- # Make sure you have the `action_cable_meta_tag` in your HTML layout
45
- # to propogate this value to the client app
46
- websocket_url: "ws://localhost:8080/cable"
47
29
 
30
+ # Read more about running AnyCable in test env:
31
+ # https://docs.anycable.io/rails/getting_started?id=testing-with-anycable
48
32
  test:
49
33
  <<: *default
50
34
 
51
35
  production:
52
36
  <<: *default
37
+ # Use ANYCABLE_WEBSOCKET_URL and ANYCABLE_SECRET environment variables
38
+ # or credentials or whatever.
53
39
  websocket_url: ~
40
+ secret: ~
@@ -1,11 +1,8 @@
1
- # Make it possible to switch adapters by passing the ACTION_CABLE_ADAPTER env variable.
2
- # For example, you can use it fallback to the standard Action Cable in staging/review
3
- # environments (by setting `ACTION_CABLE_ADAPTER=redis`).
4
1
  development:
5
- adapter: <%%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>
2
+ adapter: any_cable
6
3
 
7
4
  test:
8
5
  adapter: test
9
6
 
10
7
  production:
11
- adapter: <%%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>
8
+ adapter: any_cable