anycable-rails 0.6.5 → 1.0.0.preview1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -5
- data/README.md +17 -5
- data/lib/action_cable/subscription_adapter/any_cable.rb +2 -1
- data/lib/anycable/rails.rb +1 -0
- data/lib/anycable/rails/actioncable/connection.rb +47 -16
- data/lib/anycable/rails/actioncable/connection/persistent_session.rb +30 -0
- data/lib/anycable/rails/compatibility.rb +4 -4
- data/lib/anycable/rails/compatibility/rubocop/config/default.yml +0 -1
- data/lib/anycable/rails/config.rb +8 -4
- data/lib/anycable/rails/rack.rb +56 -0
- data/lib/anycable/rails/railtie.rb +8 -1
- data/lib/anycable/rails/refinements/subscriptions.rb +0 -5
- data/lib/anycable/rails/session_proxy.rb +62 -0
- data/lib/anycable/rails/version.rb +1 -1
- data/lib/rails/generators/anycable/setup/setup_generator.rb +267 -0
- data/lib/rails/generators/anycable/setup/templates/Procfile +2 -0
- data/lib/rails/generators/anycable/setup/templates/Procfile.dev +3 -0
- data/lib/rails/generators/anycable/setup/templates/bin/heroku-web +7 -0
- data/lib/rails/generators/anycable/setup/templates/config/anycable.yml.tt +32 -0
- data/lib/rails/generators/anycable/setup/templates/config/cable.yml.tt +11 -0
- data/lib/rails/generators/anycable/setup/templates/config/initializers/anycable.rb +9 -0
- metadata +56 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38f23428dcd1556e40cdd060257acfd1f1987fbde5b567e8ede65d25b8adde5c
|
4
|
+
data.tar.gz: 0b03ddc51e2cd9f9f807fa2aea0f086d302501fbc585e6aa83079f93a4a24864
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb3ede8fe975ca5d7e20cf4f3b51eeb37e1fc0ce94a356bbf21b73c64a212cdc026684b371750280f3bf99176b219f201cf596fb54ebbaaa5eeda4476e8a8fce
|
7
|
+
data.tar.gz: e8bb32e3c81fdd83851cc7d0a494b645c0b2407128fc380c11178623f7aef1372971ed391ed72b79bfc0521ce8f70b7e25d97ec75cb8a66a2e614b38947b5aed
|
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,23 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
-
##
|
3
|
+
## 🚧 1.0.0 (_coming soon_)
|
4
4
|
|
5
|
-
|
5
|
+
- **Ruby 2.5+ is required**. ([@palkan][])
|
6
6
|
|
7
|
-
-
|
7
|
+
- Support `disconnect` messages. ([@palkan][])
|
8
8
|
|
9
|
-
|
10
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
```
|
81
|
+
```sh
|
70
82
|
$ bundle exec anycable
|
71
83
|
|
72
84
|
# don't forget to provide Rails env
|
data/lib/anycable/rails.rb
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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
|
-
|
166
|
-
ActionCable::
|
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
|
-
|
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
|
-
|
39
|
-
|
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
|
-
|
55
|
+
"Disconnecting remote clients is not supported by AnyCable yet"
|
56
56
|
end
|
57
57
|
end)
|
58
58
|
end
|
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
require "anycable/config"
|
4
4
|
|
5
|
-
# Extend AnyCable configuration with
|
6
|
-
# `access_logs_disabled`
|
7
|
-
|
8
|
-
AnyCable::Config.
|
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
|
-
|
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
|
@@ -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(¤t_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,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.
|
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-
|
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.
|
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.
|
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:
|
42
|
+
name: ammeter
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
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.
|
54
|
+
version: '1.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
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: '
|
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.
|
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.
|
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.
|
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:
|
214
|
+
version: 1.3.1
|
177
215
|
requirements: []
|
178
216
|
rubygems_version: 3.0.6
|
179
217
|
signing_key:
|