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 +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:
|