anycable-rails 0.6.5 → 1.0.0.preview1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d538541df27b5712aadd225d788d43c7d6de563a15dcbb85dd9ef51ff1b13be4
4
- data.tar.gz: e7e5c055860a07f2554af925cee5076a07f97cfcdfa7d540891cb187bbc32f72
3
+ metadata.gz: 38f23428dcd1556e40cdd060257acfd1f1987fbde5b567e8ede65d25b8adde5c
4
+ data.tar.gz: 0b03ddc51e2cd9f9f807fa2aea0f086d302501fbc585e6aa83079f93a4a24864
5
5
  SHA512:
6
- metadata.gz: 007b964f71b796feb189e76d82f2ae3e16c679c56811b9a1b7105bc5663e5e432bcebe4f9a16a971e3819be658f2fcfb8198576d7c9d75495dcc9dcd9db7949e
7
- data.tar.gz: 257f761cfdd1fc3b05d8e1446297a7b31119c667e88e73db1a72485252f5f57e672ee6ac3c39b72e32286b4994d7b62f30f888c4aa12039202c8ccc93fb8df38
6
+ metadata.gz: fb3ede8fe975ca5d7e20cf4f3b51eeb37e1fc0ce94a356bbf21b73c64a212cdc026684b371750280f3bf99176b219f201cf596fb54ebbaaa5eeda4476e8a8fce
7
+ data.tar.gz: e8bb32e3c81fdd83851cc7d0a494b645c0b2407128fc380c11178623f7aef1372971ed391ed72b79bfc0521ce8f70b7e25d97ec75cb8a66a2e614b38947b5aed
@@ -1,13 +1,23 @@
1
1
  # Change log
2
2
 
3
- ## master
3
+ ## 🚧 1.0.0 (_coming soon_)
4
4
 
5
- ## 0.6.5 (2020-02-05)
5
+ - **Ruby 2.5+ is required**. ([@palkan][])
6
6
 
7
- - Make channel not found exception more descriptive. ([@palkan][])
7
+ - Support `disconnect` messages. ([@palkan][])
8
8
 
9
- Previusly, we returned `Undefined method handle_subscribe for nil` when Action Cable
10
- couldn't resolve the channel class. Now we return `Channel not found: <name>`.
9
+ Added in Rails 6 (see [PR#34194](https://github.com/rails/rails/pull/34194)).
10
+
11
+ - Add ability to persist _dirty_ `request.session` between RPC calls. ([@palkan][])
12
+
13
+ This feature emulates the Action Cable behaviour where it's possible to use `request.session` as a shared Hash-like store.
14
+ This could be used by some applications (e.g., [StimulusReflex](https://github.com/hopsoft/stimulus_reflex)-based).
15
+
16
+ You must turn this feature on by setting `persistent_session_enabled: true` in the AnyCable configuration.
17
+
18
+ - Add ability to use Rack middlewares when build a request for a connection. ([@bibendi][])
19
+
20
+ - Add set up generator to configure a Rails application by running `bin/rails g anycable:setup`. ([@bibendi][])
11
21
 
12
22
  - Require a minimum version of Ruby when installing the gem. ([@bibendi][])
13
23
 
data/README.md CHANGED
@@ -20,7 +20,7 @@ You can even use Action Cable in development and not be afraid of [compatibility
20
20
 
21
21
  ## Requirements
22
22
 
23
- - Ruby >= 2.4; **NOTE:** for Ruby 2.6 use RC version of `google-protobuf` gem. In your Gemfile: `gem "google-protobuf", '>=3.7.0.rc.2'`
23
+ - Ruby >= 2.5
24
24
  - Rails >= 5.0;
25
25
  - Redis (see [other options]() for broadcasting)
26
26
 
@@ -30,7 +30,6 @@ You can even use Action Cable in development and not be afraid of [compatibility
30
30
 
31
31
  ## Usage
32
32
 
33
-
34
33
  Add `anycable-rails` gem to your Gemfile:
35
34
 
36
35
  ```ruby
@@ -42,12 +41,25 @@ gem "redis", ">= 4.0"
42
41
 
43
42
  (and don't forget to run `bundle install`).
44
43
 
45
- Next, specify AnyCable subscription adapter for Action Cable:
44
+ ### Interactive set up
45
+
46
+ After the gem was installed, you can run an interactive wizard to configure your Rails application for using with AnyCable by running a generator:
47
+
48
+ ```sh
49
+ bin/rails g anycable:setup
50
+ ```
51
+
52
+ ### Manual set up
53
+
54
+ Specify AnyCable subscription adapter for Action Cable:
46
55
 
47
56
  ```yml
48
57
  # config/cable.yml
49
- production:
58
+ development:
50
59
  adapter: any_cable # or anycable
60
+
61
+ production:
62
+ adapter: any_cable
51
63
  ```
52
64
 
53
65
  and specify AnyCable WebSocket server URL:
@@ -66,7 +78,7 @@ config.action_cable.url = "wss://ws.example.com/cable"
66
78
 
67
79
  Then, run AnyCable RPC server:
68
80
 
69
- ```ruby
81
+ ```sh
70
82
  $ bundle exec anycable
71
83
 
72
84
  # don't forget to provide Rails env
@@ -7,7 +7,8 @@ module ActionCable
7
7
  # AnyCable subscription adapter delegates broadcasts
8
8
  # to AnyCable
9
9
  class AnyCable < Base
10
- def initialize(*); end
10
+ def initialize(*)
11
+ end
11
12
 
12
13
  def broadcast(channel, payload)
13
14
  ::AnyCable.broadcast(channel, payload)
@@ -3,6 +3,7 @@
3
3
  require "anycable"
4
4
  require "anycable/rails/version"
5
5
  require "anycable/rails/config"
6
+ require "anycable/rails/rack"
6
7
 
7
8
  module AnyCable
8
9
  # Rails handler for AnyCable
@@ -3,12 +3,13 @@
3
3
  require "action_cable/connection"
4
4
  require "anycable/rails/refinements/subscriptions"
5
5
  require "anycable/rails/actioncable/channel"
6
+ require "anycable/rails/session_proxy"
6
7
 
7
8
  module ActionCable
8
9
  module Connection
9
10
  # rubocop: disable Metrics/ClassLength
10
11
  class Base # :nodoc:
11
- # We store logger tags in identifiers to be able
12
+ # We store logger tags in the connection state to be able
12
13
  # to re-use them in the subsequent calls
13
14
  LOG_TAGS_IDENTIFIER = "__ltags__"
14
15
 
@@ -16,6 +17,8 @@ module ActionCable
16
17
 
17
18
  attr_reader :socket
18
19
 
20
+ delegate :env, :session, to: :request
21
+
19
22
  class << self
20
23
  def call(socket, **options)
21
24
  new(socket, options)
@@ -34,10 +37,12 @@ module ActionCable
34
37
  def initialize(socket, identifiers: "{}", subscriptions: [])
35
38
  @ids = ActiveSupport::JSON.decode(identifiers)
36
39
 
37
- @ltags = ids.delete(LOG_TAGS_IDENTIFIER)
40
+ @ltags = socket.cstate.read(LOG_TAGS_IDENTIFIER).yield_self do |raw_tags|
41
+ next unless raw_tags
42
+ ActiveSupport::JSON.decode(raw_tags)
43
+ end
38
44
 
39
45
  @cached_ids = {}
40
- @env = socket.env
41
46
  @coder = ActiveSupport::JSON
42
47
  @socket = socket
43
48
  @subscriptions = ActionCable::Connection::Subscriptions.new(self)
@@ -49,12 +54,17 @@ module ActionCable
49
54
  def handle_open
50
55
  logger.info started_request_message if access_logs?
51
56
 
52
- verify_origin!
57
+ verify_origin! || return
53
58
 
54
59
  connect if respond_to?(:connect)
60
+
61
+ socket.cstate.write(LOG_TAGS_IDENTIFIER, fetch_ltags.to_json)
62
+
55
63
  send_welcome_message
56
64
  rescue ActionCable::Connection::Authorization::UnauthorizedError
57
- reject_request
65
+ reject_request(
66
+ ActionCable::INTERNAL[:disconnect_reasons]&.[](:unauthorized) || "unauthorized"
67
+ )
58
68
  end
59
69
 
60
70
  def handle_close
@@ -84,7 +94,12 @@ module ActionCable
84
94
  end
85
95
  # rubocop:enable Metrics/MethodLength
86
96
 
87
- def close
97
+ def close(reason: nil, reconnect: nil)
98
+ transmit(
99
+ type: ActionCable::INTERNAL[:message_types].fetch(:disconnect, "disconnect"),
100
+ reason: reason,
101
+ reconnect: reconnect
102
+ )
88
103
  socket.close
89
104
  end
90
105
 
@@ -95,9 +110,7 @@ module ActionCable
95
110
  # Generate identifiers info.
96
111
  # Converts GlobalID compatible vars to corresponding global IDs params.
97
112
  def identifiers_hash
98
- obj = { LOG_TAGS_IDENTIFIER => fetch_ltags }
99
-
100
- identifiers.each_with_object(obj) do |id, acc|
113
+ identifiers.each_with_object({}) do |id, acc|
101
114
  obj = instance_variable_get("@#{id}")
102
115
  next unless obj
103
116
 
@@ -123,6 +136,10 @@ module ActionCable
123
136
  @logger ||= TaggedLoggerProxy.new(AnyCable.logger, tags: ltags || [])
124
137
  end
125
138
 
139
+ def request
140
+ @request ||= build_rack_request
141
+ end
142
+
126
143
  private
127
144
 
128
145
  attr_reader :ids, :ltags
@@ -158,19 +175,33 @@ module ActionCable
158
175
  end
159
176
 
160
177
  def verify_origin!
161
- return unless socket.env.key?("HTTP_ORIGIN")
178
+ return true unless socket.env.key?("HTTP_ORIGIN")
162
179
 
163
- return if allow_request_origin?
180
+ return true if allow_request_origin?
164
181
 
165
- raise(
166
- ActionCable::Connection::Authorization::UnauthorizedError,
167
- "Origin is not allowed"
182
+ reject_request(
183
+ ActionCable::INTERNAL[:disconnect_reasons]&.[](:invalid_request) || "invalid_request"
168
184
  )
185
+ false
169
186
  end
170
187
 
171
- def reject_request
188
+ def reject_request(reason, reconnect = false)
172
189
  logger.info finished_request_message("Rejected") if access_logs?
173
- close
190
+ close(
191
+ reason: reason,
192
+ reconnect: reconnect
193
+ )
194
+ end
195
+
196
+ def build_rack_request
197
+ environment = Rails.application.env_config.merge(socket.env)
198
+ AnyCable::Rails::Rack.app.call(environment)
199
+
200
+ ActionDispatch::Request.new(environment)
201
+ end
202
+
203
+ def request_loaded?
204
+ instance_variable_defined?(:@request)
174
205
  end
175
206
  end
176
207
  # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionCable
4
+ module Connection
5
+ module PersistentSession
6
+ def handle_open
7
+ super.tap { commit_session! }
8
+ end
9
+
10
+ def handle_channel_command(*)
11
+ super.tap { commit_session! }
12
+ end
13
+
14
+ def build_rack_request
15
+ return super unless socket.session
16
+
17
+ super.tap do |req|
18
+ req.env[Rack::RACK_SESSION] =
19
+ AnyCable::Rails::SessionProxy.new(req.env[Rack::RACK_SESSION], socket.session)
20
+ end
21
+ end
22
+
23
+ def commit_session!
24
+ return unless request_loaded? && request.session.loaded?
25
+
26
+ socket.session = request.session.to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -12,7 +12,7 @@ module AnyCable
12
12
 
13
13
  if callback.present? || block_given?
14
14
  raise AnyCable::CompatibilityError,
15
- "Custom stream callbacks are not supported by AnyCable"
15
+ "Custom stream callbacks are not supported by AnyCable"
16
16
  end
17
17
 
18
18
  super
@@ -35,8 +35,8 @@ module AnyCable
35
35
 
36
36
  unless diff.empty?
37
37
  raise AnyCable::CompatibilityError,
38
- "Channel instance variables are not supported by AnyCable, " \
39
- "but were set: #{diff.join(', ')}"
38
+ "Channel instance variables are not supported by AnyCable, " \
39
+ "but were set: #{diff.join(", ")}"
40
40
  end
41
41
 
42
42
  res
@@ -52,7 +52,7 @@ module AnyCable
52
52
  ActionCable::RemoteConnections::RemoteConnection.prepend(Module.new do
53
53
  def disconnect
54
54
  raise AnyCable::CompatibilityError,
55
- "Disconnecting remote clients is not supported by AnyCable yet"
55
+ "Disconnecting remote clients is not supported by AnyCable yet"
56
56
  end
57
57
  end)
58
58
  end
@@ -9,4 +9,3 @@ AnyCable/StreamFrom:
9
9
  AnyCable/PeriodicalTimers:
10
10
  Include:
11
11
  - "**/channels/**/*.rb"
12
-
@@ -2,7 +2,11 @@
2
2
 
3
3
  require "anycable/config"
4
4
 
5
- # Extend AnyCable configuration with
6
- # `access_logs_disabled` options (defaults to true)
7
- AnyCable::Config.attr_config access_logs_disabled: true
8
- AnyCable::Config.ignore_options :access_logs_disabled
5
+ # Extend AnyCable configuration with:
6
+ # - `access_logs_disabled` (defaults to true) — whether to print Started/Finished logs
7
+ # - `persistent_session_enabled` (defaults to false) — whether to store session changes in the connection state
8
+ AnyCable::Config.attr_config(
9
+ access_logs_disabled: true,
10
+ persistent_session_enabled: false
11
+ )
12
+ AnyCable::Config.ignore_options :access_logs_disabled, :persistent_session_enabled
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/configuration"
4
+ require "action_dispatch/middleware/stack"
5
+
6
+ module AnyCable
7
+ module Rails
8
+ # Rack middleware stack to modify the HTTP request object.
9
+ #
10
+ # AnyCable Websocket server does not use Rack middleware processing mechanism (which Rails uses
11
+ # when Action Cable is mounted into the main app).
12
+ #
13
+ # Some middlewares could enhance request env with useful information.
14
+ #
15
+ # For instance, consider the Rails session middleware: it's responsible for restoring the
16
+ # session data from cookies.
17
+ #
18
+ # AnyCable adds session middelware by default to its own stack.
19
+ #
20
+ # You can also use any Rack/Rails middleware you want. For example, to enable Devise/Warden
21
+ # you can add the following code to an initializer or any other configuration file:
22
+ #
23
+ # AnyCable::Rails::Rack.middleware.use Warden::Manager do |config|
24
+ # Devise.warden_config = config
25
+ # end
26
+ module Rack
27
+ def self.app_build_lock
28
+ @app_build_lock
29
+ end
30
+
31
+ @app_build_lock = Mutex.new
32
+
33
+ def self.middleware
34
+ @middleware ||= ::Rails::Configuration::MiddlewareStackProxy.new
35
+ end
36
+
37
+ def self.default_middleware_stack
38
+ config = ::Rails.application.config
39
+
40
+ ActionDispatch::MiddlewareStack.new do |middleware|
41
+ middleware.use(config.session_store, config.session_options)
42
+ end
43
+ end
44
+
45
+ def self.app
46
+ @rack_app || app_build_lock.synchronize do
47
+ @rack_app ||= begin
48
+ stack = default_middleware_stack
49
+ @middleware = middleware.merge_into(stack)
50
+ middleware.build { [-1, {}, []] }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -16,7 +16,7 @@ module AnyCable
16
16
 
17
17
  # Broadcast server logs to STDOUT in development
18
18
  if ::Rails.env.development? &&
19
- !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDOUT)
19
+ !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDOUT)
20
20
  AnyCable.configure_server do
21
21
  console = ActiveSupport::Logger.new(STDOUT)
22
22
  console.formatter = ::Rails.logger.formatter
@@ -46,6 +46,13 @@ module AnyCable
46
46
  if AnyCable::Rails.compatible_adapter?(adapter)
47
47
  require "anycable/rails/actioncable/connection"
48
48
 
49
+ if AnyCable.config.persistent_session_enabled
50
+ require "anycable/rails/actioncable/connection/persistent_session"
51
+ ::ActionCable::Connection::Base.prepend(
52
+ ::ActionCable::Connection::PersistentSession
53
+ )
54
+ end
55
+
49
56
  app.config.to_prepare do
50
57
  AnyCable.connection_factory = ActionCable.server.config.connection_class.call
51
58
  end
@@ -7,11 +7,6 @@ module AnyCable
7
7
  # Find or add a subscription to the list
8
8
  def fetch(identifier)
9
9
  add("identifier" => identifier) unless subscriptions[identifier]
10
-
11
- unless subscriptions[identifier]
12
- raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch('channel')}"
13
- end
14
-
15
10
  subscriptions[identifier]
16
11
  end
17
12
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ # Wrap `request.session` to lazily load values provided
6
+ # in the RPC call (set by the previous calls)
7
+ class SessionProxy
8
+ delegate_missing_to :@rack_session
9
+
10
+ attr_reader :rack_session, :socket_session
11
+
12
+ def initialize(rack_session, socket_session)
13
+ @rack_session = rack_session
14
+ @socket_session = JSON.parse(socket_session).with_indifferent_access
15
+ end
16
+
17
+ %i[has_key? [] []= fetch delete dig].each do |mid|
18
+ class_eval <<~CODE, __FILE__, __LINE__ + 1
19
+ def #{mid}(*args, **kwargs, &block)
20
+ restore_key! args.first
21
+ rack_session.#{mid}(*args, **kwargs, &block)
22
+ end
23
+ CODE
24
+ end
25
+
26
+ alias include? has_key?
27
+ alias key? has_key?
28
+
29
+ %i[update merge! to_hash].each do |mid|
30
+ class_eval <<~CODE, __FILE__, __LINE__ + 1
31
+ def #{mid}(*args, **kwargs, &block)
32
+ restore!
33
+ rack_session.#{mid}(*args, **kwargs, &block)
34
+ end
35
+ CODE
36
+ end
37
+
38
+ alias to_h to_hash
39
+
40
+ def keys
41
+ rack_session.keys + socket_session.keys
42
+ end
43
+
44
+ private
45
+
46
+ def restore!
47
+ socket_session.keys.each(&method(:restore_key!))
48
+ end
49
+
50
+ def restore_key!(key)
51
+ return unless socket_session.key?(key)
52
+ val = socket_session.delete(key)
53
+ rack_session[key] =
54
+ if val.is_a?(String)
55
+ GlobalID::Locator.locate(val) || val
56
+ else
57
+ val
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AnyCable
4
4
  module Rails
5
- VERSION = "0.6.5"
5
+ VERSION = "1.0.0.preview1"
6
6
  end
7
7
  end
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCableRailsGenerators
4
+ # Entry point for interactive installation
5
+ class SetupGenerator < ::Rails::Generators::Base
6
+ namespace "anycable:setup"
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ METHODS = %w[skip local docker].freeze
10
+ SERVER_VERSION = "v0.6.4"
11
+ OS_NAMES = %w[linux darwin freebsd win].freeze
12
+ CPU_NAMES = %w[amd64 arm64 386 arm].freeze
13
+ SERVER_SOURCES = %w[skip brew binary].freeze
14
+ DEFAULT_BIN_PATH = "/usr/local/bin"
15
+
16
+ class_option :method,
17
+ type: :string,
18
+ desc: "Select your development environment (options: #{METHODS.join(", ")})"
19
+ class_option :source,
20
+ type: :string,
21
+ desc: "Choose a way of installing AnyCable-Go server (options: #{SERVER_SOURCES.join(", ")})"
22
+ class_option :bin_path,
23
+ type: :string,
24
+ desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
25
+ class_option :os,
26
+ type: :string,
27
+ desc: "Specify the OS for AnyCable-Go server binary (options: #{OS_NAMES.join(", ")})"
28
+ class_option :cpu,
29
+ type: :string,
30
+ desc: "Specify the CPU architecturefor AnyCable-Go server binary (options: #{CPU_NAMES.join(", ")})"
31
+ class_option :skip_heroku,
32
+ type: :boolean,
33
+ desc: "Do not copy Heroku configs"
34
+ class_option :skip_procfile_dev,
35
+ type: :boolean,
36
+ desc: "Do not create Procfile.dev"
37
+
38
+ def welcome
39
+ say "👋 Welcome to AnyCable interactive installer."
40
+ end
41
+
42
+ def configs
43
+ inside("config") do
44
+ template "cable.yml"
45
+ template "anycable.yml"
46
+ end
47
+ end
48
+
49
+ def cable_url
50
+ environment(nil, env: :development) do
51
+ <<~SNIPPET
52
+ # Specify AnyCable WebSocket server URL to use by JS client
53
+ config.action_cable.url = ENV.fetch("CABLE_URL", "ws://localhost:3334/cable").presence
54
+ SNIPPET
55
+ end
56
+
57
+ environment(nil, env: :production) do
58
+ <<~SNIPPET
59
+ # Specify AnyCable WebSocket server URL to use by JS client
60
+ config.action_cable.url = ENV["CABLE_URL"].presence
61
+ SNIPPET
62
+ end
63
+
64
+ say_status :info, "✅ 'config.action_cable.url' has been configured"
65
+ say_status :help, "⚠️ If you're using JS client make sure you have " \
66
+ "`action_cable_meta_tag` included before any <script> tag in your application.html"
67
+ end
68
+
69
+ def development_method
70
+ answer = METHODS.index(options[:method]) || 99
71
+
72
+ until METHODS[answer.to_i]
73
+ answer = ask "Which environment do you use for development? (1) Local, (2) Docker, (0) Skip"
74
+ end
75
+
76
+ case env = METHODS[answer.to_i]
77
+ when "skip"
78
+ say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
79
+ else
80
+ send "install_for_#{env}"
81
+ end
82
+ end
83
+
84
+ def heroku
85
+ if options[:skip_heroku].nil?
86
+ return unless yes? "Do you use Heroku for deployment?"
87
+ elsif options[:skip_heroku]
88
+ return
89
+ end
90
+
91
+ template "Procfile"
92
+ inside("bin") { template "heroku-web" }
93
+
94
+ say_status :help, "️️⚠️ Please, read the required steps to configure Heroku applications 👉 https://docs.anycable.io/#/deployment/heroku", :yellow
95
+ end
96
+
97
+ def devise
98
+ in_root do
99
+ return unless File.file?("config/initializers/devise.rb")
100
+ end
101
+
102
+ inside("config/initializers") do
103
+ template "anycable.rb"
104
+ end
105
+
106
+ say_status :info, "✅ config/initializers/anycable.rb with Devise configuration has been added"
107
+ end
108
+
109
+ def finish
110
+ say_status :info, "✅ AnyCable has been configured successfully!"
111
+ end
112
+
113
+ private
114
+
115
+ def stimulus_reflex?
116
+ !gemfile_lock&.match?(/^\s+stimulus_reflex\b/).nil?
117
+ end
118
+
119
+ def gemfile_lock
120
+ @gemfile_lock ||= begin
121
+ res = nil
122
+ in_root do
123
+ next unless File.file?("Gemfile.lock")
124
+ res = File.read("Gemfile.lock")
125
+ end
126
+ res
127
+ end
128
+ end
129
+
130
+ def install_for_docker
131
+ say_status :help, "️️⚠️ Docker development configuration could vary", :yellow
132
+
133
+ say "Here is an example snippet for docker-compose.yml:"
134
+ say <<~YML
135
+ ─────────────────────────────────────────
136
+ anycable-ws:
137
+ image: anycable/anycable-go:v0.6.4
138
+ ports:
139
+ - '3334:3334'
140
+ environment:
141
+ PORT: 3334
142
+ ANYCABLE_REDIS_URL: redis://redis:6379/0
143
+ ANYCABLE_RPC_HOST: anycable-rpc:50051
144
+ depends_on:
145
+ - anycable-rpc
146
+ - redis
147
+
148
+ anycable-rpc:
149
+ <<: *backend
150
+ command: bundle exec anycable
151
+ environment:
152
+ <<: *backend_environment
153
+ ANYCABLE_REDIS_URL: redis://redis:6379/0
154
+ ANYCABLE_RPC_HOST: 0.0.0.0:50051
155
+ ports:
156
+ - '50051'
157
+ ─────────────────────────────────────────
158
+ YML
159
+ end
160
+
161
+ def install_for_local
162
+ install_server
163
+ template_proc_files
164
+ end
165
+
166
+ def install_server
167
+ answer = SERVER_SOURCES.index(options[:source]) || 99
168
+
169
+ until SERVER_SOURCES[answer.to_i]
170
+ answer = ask "How do you want to install AnyCable-Go WebSocket server? (1) Homebrew, (2) Download binary, (0) Skip"
171
+ end
172
+
173
+ case answer.to_i
174
+ when 0
175
+ say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
176
+ return
177
+ else
178
+ return unless send("install_from_#{SERVER_SOURCES[answer.to_i]}")
179
+ end
180
+
181
+ say_status :info, "✅ AnyCable-Go WebSocket server has been successfully installed"
182
+ end
183
+
184
+ def template_proc_files
185
+ file_name = "Procfile.dev"
186
+
187
+ if File.exist?(file_name)
188
+ append_file file_name, 'anycable: bundle exec anycable --server-command "anycable-go --port 3334"'
189
+ else
190
+ say_status :help, "💡 We recommend using Hivemind to manage multiple processes in development 👉 https://github.com/DarthSim/hivemind", :yellow
191
+
192
+ if options[:skip_procfile_dev].nil?
193
+ return unless yes? "Do you want to create a '#{file_name}' file?"
194
+ elsif options[:skip_procfile_dev]
195
+ return
196
+ end
197
+
198
+ template file_name
199
+ end
200
+ end
201
+
202
+ def install_from_brew
203
+ run "brew install anycable-go", abort_on_failure: true
204
+ run "anycable-go -v", abort_on_failure: true
205
+ end
206
+
207
+ def install_from_binary
208
+ out = options[:bin_path] if options[:bin_path]
209
+ out ||= "/usr/local/bin" if options[:method] # User don't want interactive mode
210
+ out ||= ask "Please, enter the path to download the AnyCable-Go binary to", default: DEFAULT_BIN_PATH, path: true
211
+
212
+ os_name = options[:os] ||
213
+ OS_NAMES.find(&Gem::Platform.local.os.method(:==)) ||
214
+ ask("What is your OS name?", limited_to: OS_NAMES)
215
+
216
+ cpu_name = options[:cpu] ||
217
+ CPU_NAMES.find(&current_cpu.method(:==)) ||
218
+ ask("What is your CPU architecture?", limited_to: CPU_NAMES)
219
+
220
+ download_exe(
221
+ "https://github.com/anycable/anycable-go/releases/download/#{SERVER_VERSION}/" \
222
+ "anycable-go-#{SERVER_VERSION}-#{os_name}-#{cpu_name}",
223
+ to: out,
224
+ file_name: "anycable-go"
225
+ )
226
+
227
+ true
228
+ end
229
+
230
+ def download_exe(url, to:, file_name:)
231
+ file_path = File.join(to, file_name)
232
+
233
+ run "#{sudo(to)}curl -L #{url} -o #{file_path}", abort_on_failure: true
234
+ run "#{sudo(to)}chmod +x #{file_path}", abort_on_failure: true
235
+ run "#{file_path} -v", abort_on_failure: true
236
+ end
237
+
238
+ def sudo!(path)
239
+ sudo = ""
240
+ unless File.writable?(path)
241
+ if yes? "Path is not writable 😕. Do you have sudo privileges?"
242
+ sudo = "sudo "
243
+ else
244
+ say_status :error, "❌ Failed to install AnyCable-Go WebSocket server", :red
245
+ raise StandardError, "Path #{path} is not writable!"
246
+ end
247
+ end
248
+
249
+ sudo
250
+ end
251
+
252
+ def current_cpu
253
+ case Gem::Platform.local.cpu
254
+ when "x86_64", "x64"
255
+ "amd64"
256
+ when "x86_32", "x86", "i386", "i486", "i686"
257
+ "i386"
258
+ when "aarch64", "aarch64_be", /armv8/
259
+ "arm64"
260
+ when "arm", /armv7/, /armv6/
261
+ "arm"
262
+ else
263
+ "unknown"
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,2 @@
1
+ web: bin/heroku-web
2
+ release: bundle exec rails db:migrate
@@ -0,0 +1,3 @@
1
+ server: bin/rails server
2
+ assets: bin/webpack-dev-server
3
+ anycable: bundle exec anycable --server-command "anycable-go --port 3334"
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ if [ "$ANYCABLE_DEPLOYMENT" == "true" ]; then
4
+ bundle exec anycable --server-command="anycable-go"
5
+ else
6
+ bundle exec rails server -p $PORT -b 0.0.0.0
7
+ fi
@@ -0,0 +1,32 @@
1
+ # This file contains per-environment settings for AnyCable.
2
+ #
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: https://docs.anycable.io/#/ruby/configuration
11
+ #
12
+ default: &default
13
+ # Turn on/off access logs ("Started..." and "Finished...")
14
+ access_logs_disabled: false
15
+ # Persist "dirty" session between RPC calls (might be required for StimulusReflex apps)
16
+ persistent_session_enabled: <%= stimulus_reflex? %>
17
+ # This is the host and the port to run AnyCable RPC server on.
18
+ # You must configure your WebSocket server to connect to it, e.g.:
19
+ # $ anycable-go --rpc-host="<rpc hostname>:50051"
20
+ rpc_host: "127.0.0.1:50051"
21
+ # Whether to enable gRPC level logging or not
22
+ log_grpc: false
23
+ # Use the same channel name for WebSocket server, e.g.:
24
+ # $ anycable-go --redis-channel="__anycable__"
25
+ redis_channel: "__anycable__"
26
+
27
+ development:
28
+ <<: *default
29
+ redis_url: "redis://localhost:6379/1"
30
+
31
+ production:
32
+ <<: *default
@@ -0,0 +1,11 @@
1
+ # Make it possible to switch adapters by passing the CABLE_ADAPTER env variable.
2
+ # For example, you can use it fallback to the standard Action Cable in staging/review
3
+ # environments (by setting `CABLE_ADAPTER=redis`).
4
+ development:
5
+ adapter: <%%= ENV.fetch("CABLE_ADAPTER", "any_cable") %>
6
+
7
+ test:
8
+ adapter: test
9
+
10
+ production:
11
+ adapter: <%%= ENV.fetch("CABLE_ADAPTER", "any_cable") %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add Warden middkeware to AnyCable stack to allow accessing
4
+ # Devise current user via `env["warden"].user`.
5
+ #
6
+ # See http://docs.anycable.io/#/ruby/authentication
7
+ AnyCable::Rails::Rack.middleware.use Warden::Manager do |config|
8
+ Devise.warden_config = config
9
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 1.0.0.preview1
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-05 00:00:00.000000000 Z
11
+ date: 2020-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.6.0
19
+ version: 1.0.0.preview1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.6.0
26
+ version: 1.0.0.preview1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,33 +39,33 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: ammeter
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.10'
47
+ version: '1.1'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.10'
54
+ version: '1.1'
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry-byebug
56
+ name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '1.10'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '1.10'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -95,19 +95,19 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.4'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rubocop
98
+ name: rubocop-md
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.67.0
103
+ version: '0.3'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.67.0
110
+ version: '0.3'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,34 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.3.8
125
+ - !ruby/object:Gem::Dependency
126
+ name: standard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.1.7
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.1.7
139
+ - !ruby/object:Gem::Dependency
140
+ name: warden
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  description: Rails adapter for AnyCable
126
154
  email:
127
155
  - dementiev.vm@gmail.com
@@ -138,6 +166,7 @@ files:
138
166
  - lib/anycable/rails.rb
139
167
  - lib/anycable/rails/actioncable/channel.rb
140
168
  - lib/anycable/rails/actioncable/connection.rb
169
+ - lib/anycable/rails/actioncable/connection/persistent_session.rb
141
170
  - lib/anycable/rails/compatibility.rb
142
171
  - lib/anycable/rails/compatibility/rubocop.rb
143
172
  - lib/anycable/rails/compatibility/rubocop/config/default.yml
@@ -148,9 +177,18 @@ files:
148
177
  - lib/anycable/rails/config.rb
149
178
  - lib/anycable/rails/middlewares/executor.rb
150
179
  - lib/anycable/rails/middlewares/log_tagging.rb
180
+ - lib/anycable/rails/rack.rb
151
181
  - lib/anycable/rails/railtie.rb
152
182
  - lib/anycable/rails/refinements/subscriptions.rb
183
+ - lib/anycable/rails/session_proxy.rb
153
184
  - lib/anycable/rails/version.rb
185
+ - lib/rails/generators/anycable/setup/setup_generator.rb
186
+ - lib/rails/generators/anycable/setup/templates/Procfile
187
+ - lib/rails/generators/anycable/setup/templates/Procfile.dev
188
+ - lib/rails/generators/anycable/setup/templates/bin/heroku-web
189
+ - lib/rails/generators/anycable/setup/templates/config/anycable.yml.tt
190
+ - lib/rails/generators/anycable/setup/templates/config/cable.yml.tt
191
+ - lib/rails/generators/anycable/setup/templates/config/initializers/anycable.rb
154
192
  homepage: http://github.com/anycable/anycable-rails
155
193
  licenses:
156
194
  - MIT
@@ -168,12 +206,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
206
  requirements:
169
207
  - - ">="
170
208
  - !ruby/object:Gem::Version
171
- version: '2.4'
209
+ version: '2.5'
172
210
  required_rubygems_version: !ruby/object:Gem::Requirement
173
211
  requirements:
174
- - - ">="
212
+ - - ">"
175
213
  - !ruby/object:Gem::Version
176
- version: '0'
214
+ version: 1.3.1
177
215
  requirements: []
178
216
  rubygems_version: 3.0.6
179
217
  signing_key: