anycable-rails 1.0.7 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anycable/rails/connections/session_proxy"
4
+
5
+ module AnyCable
6
+ module Rails
7
+ module Connections
8
+ module PersistentSession
9
+ def handle_open
10
+ super.tap { commit_session! }
11
+ end
12
+
13
+ def handle_channel_command(*)
14
+ super.tap { commit_session! }
15
+ end
16
+
17
+ def build_rack_request(env)
18
+ return super unless socket.session
19
+
20
+ super.tap do |req|
21
+ req.env[::Rack::RACK_SESSION] =
22
+ SessionProxy.new(req.env[::Rack::RACK_SESSION], socket.session)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def commit_session!
29
+ return unless request_loaded? && request.session.respond_to?(:loaded?) && request.session.loaded?
30
+
31
+ socket.session = request.session.to_json
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ AnyCable::Rails::Connection.prepend(
39
+ AnyCable::Rails::Connections::PersistentSession
40
+ )
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module Connections
6
+ module SerializableIdentification
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def identified_by(*identifiers)
11
+ super
12
+ Array(identifiers).each do |identifier|
13
+ define_method(identifier) do
14
+ instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # Generate identifiers info.
21
+ # Converts GlobalID compatible vars to corresponding global IDs params.
22
+ def identifiers_hash
23
+ identifiers.each_with_object({}) do |id, acc|
24
+ obj = instance_variable_get("@#{id}")
25
+ next unless obj
26
+
27
+ acc[id] = AnyCable::Rails.serialize(obj)
28
+ end.compact
29
+ end
30
+
31
+ def identifiers_json
32
+ identifiers_hash.to_json
33
+ end
34
+
35
+ # Fetch identifier and deserialize if neccessary
36
+ def fetch_identifier(name)
37
+ return unless @cached_ids
38
+
39
+ @cached_ids[name] ||= @cached_ids.fetch(name) do
40
+ AnyCable::Rails.deserialize(@serialized_ids[name.to_s])
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module Connections
6
+ # Wrap `request.session` to lazily load values provided
7
+ # in the RPC call (set by the previous calls)
8
+ class SessionProxy
9
+ attr_reader :rack_session, :socket_session
10
+
11
+ def initialize(rack_session, socket_session)
12
+ @rack_session = rack_session
13
+ @socket_session = JSON.parse(socket_session).with_indifferent_access
14
+ end
15
+
16
+ %i[has_key? [] []= fetch delete dig].each do |mid|
17
+ class_eval <<~CODE, __FILE__, __LINE__ + 1
18
+ def #{mid}(*args, **kwargs, &block)
19
+ restore_key! args.first
20
+ rack_session.#{mid}(*args, **kwargs, &block)
21
+ end
22
+ CODE
23
+ end
24
+
25
+ alias_method :include?, :has_key?
26
+ alias_method :key?, :has_key?
27
+
28
+ %i[update merge! to_hash].each do |mid|
29
+ class_eval <<~CODE, __FILE__, __LINE__ + 1
30
+ def #{mid}(*args, **kwargs, &block)
31
+ restore!
32
+ rack_session.#{mid}(*args, **kwargs, &block)
33
+ end
34
+ CODE
35
+ end
36
+
37
+ alias_method :to_h, :to_hash
38
+
39
+ def keys
40
+ rack_session.keys + socket_session.keys
41
+ end
42
+
43
+ # Delegate both publuc and private methods to rack_session
44
+ def respond_to_missing?(name, include_private = false)
45
+ return false if name == :marshal_dump || name == :_dump
46
+ rack_session.respond_to?(name, include_private) || super
47
+ end
48
+
49
+ def method_missing(method, *args, &block)
50
+ if rack_session.respond_to?(method, true)
51
+ rack_session.send(method, *args, &block)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ # This method is used by StimulusReflex to obtain `@by`
58
+ def instance_variable_get(name)
59
+ super || rack_session.instance_variable_get(name)
60
+ end
61
+
62
+ private
63
+
64
+ def restore!
65
+ socket_session.keys.each(&method(:restore_key!))
66
+ end
67
+
68
+ def restore_key!(key)
69
+ return unless socket_session.key?(key)
70
+ val = socket_session.delete(key)
71
+ rack_session[key] =
72
+ if val.is_a?(String)
73
+ GlobalID::Locator.locate(val) || val
74
+ else
75
+ val
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -9,8 +9,8 @@ module AnyCable
9
9
  #
10
10
  # See https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md
11
11
  class LogTagging < AnyCable::Middleware
12
- def call(_request, call, _method)
13
- sid = call.metadata["sid"]
12
+ def call(_method, _request, metadata)
13
+ sid = metadata["sid"]
14
14
  return yield unless sid
15
15
 
16
16
  AnyCable.logger.tagged("AnyCable sid=#{sid}") { yield }
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "anycable/rails/action_cable_ext/connection"
4
+ require "anycable/rails/action_cable_ext/channel"
5
+ require "anycable/rails/action_cable_ext/remote_connections"
6
+
3
7
  require "anycable/rails/channel_state"
8
+ require "anycable/rails/connection_factory"
4
9
 
5
10
  module AnyCable
6
11
  module Rails
@@ -15,13 +20,14 @@ module AnyCable
15
20
  AnyCable.logger = ::ActionCable.server.config.logger
16
21
 
17
22
  AnyCable.configure_server do
18
- AnyCable.logger = ActiveSupport::TaggedLogging.new(::ActionCable.server.config.logger)
23
+ server_logger = AnyCable.logger = ::ActionCable.server.config.logger
24
+ AnyCable.logger = ActiveSupport::TaggedLogging.new(server_logger) if server_logger.is_a?(::Logger)
19
25
  # Broadcast server logs to STDOUT in development
20
26
  if ::Rails.env.development? &&
21
27
  !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
22
28
  console = ActiveSupport::Logger.new($stdout)
23
- console.formatter = ::Rails.logger.formatter
24
- console.level = ::Rails.logger.level
29
+ console.formatter = ::Rails.logger.formatter if ::Rails.logger.respond_to?(:formatter)
30
+ console.level = ::Rails.logger.level if ::Rails.logger.respond_to?(:level)
25
31
  AnyCable.logger.extend(ActiveSupport::Logger.broadcast(console))
26
32
  end
27
33
  end
@@ -43,22 +49,25 @@ module AnyCable
43
49
 
44
50
  initializer "anycable.connection_factory", after: "action_cable.set_configs" do |app|
45
51
  ActiveSupport.on_load(:action_cable) do
46
- # Add AnyCable patch method stub (we use it in ChannelState to distinguish between Action Cable and AnyCable)
47
- # NOTE: Method could be already defined if patch was loaded manually
48
- ActionCable::Connection::Base.attr_reader(:anycable_socket) unless ActionCable::Connection::Base.method_defined?(:anycable_socket)
49
-
50
52
  app.config.to_prepare do
51
- AnyCable.connection_factory = ActionCable.server.config.connection_class.call
53
+ AnyCable.connection_factory = AnyCable::Rails::ConnectionFactory.new
52
54
  end
53
55
 
54
- if AnyCable::Rails.enabled?
55
- require "anycable/rails/actioncable/connection"
56
- if AnyCable.config.persistent_session_enabled
57
- require "anycable/rails/actioncable/connection/persistent_session"
58
- end
56
+ if AnyCable.config.persistent_session_enabled?
57
+ require "anycable/rails/connections/persistent_session"
59
58
  end
60
59
  end
61
60
  end
61
+
62
+ # Since Rails 6.1
63
+ if respond_to?(:server)
64
+ server do
65
+ next unless AnyCable.config.embedded? && AnyCable::Rails.enabled?
66
+
67
+ require "anycable/cli"
68
+ AnyCable::CLI.embed!
69
+ end
70
+ end
62
71
  end
63
72
  end
64
73
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AnyCable
4
4
  module Rails
5
- VERSION = "1.0.7"
5
+ VERSION = "1.3.4"
6
6
  end
7
7
  end
@@ -48,6 +48,17 @@ module AnyCable
48
48
  val
49
49
  end
50
50
  end
51
+
52
+ module Extension
53
+ def broadcast(channel, payload)
54
+ super
55
+ ::AnyCable.broadcast(channel, payload)
56
+ end
57
+ end
58
+
59
+ def extend_adapter!(adapter)
60
+ adapter.extend(Extension)
61
+ end
51
62
  end
52
63
  end
53
64
  end
@@ -8,7 +8,7 @@ module AnyCableRailsGenerators
8
8
  namespace "anycable:setup"
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
- DOCS_ROOT = "https://docs.anycable.io/#"
11
+ DOCS_ROOT = "https://docs.anycable.io"
12
12
  DEVELOPMENT_METHODS = %w[skip local docker].freeze
13
13
  SERVER_SOURCES = %w[skip brew binary].freeze
14
14
 
@@ -24,6 +24,12 @@ module AnyCableRailsGenerators
24
24
  class_option :skip_procfile_dev,
25
25
  type: :boolean,
26
26
  desc: "Do not create Procfile.dev"
27
+ class_option :skip_jwt,
28
+ type: :boolean,
29
+ desc: "Do not install anycable-rails-jwt"
30
+ class_option :skip_install,
31
+ type: :boolean,
32
+ desc: "Do not run bundle install when adding new gems"
27
33
 
28
34
  include WithOSHelpers
29
35
 
@@ -59,7 +65,7 @@ module AnyCableRailsGenerators
59
65
  <<~SNIPPET
60
66
  # Specify AnyCable WebSocket server URL to use by JS client
61
67
  config.after_initialize do
62
- config.action_cable.url = ActionCable.server.config.url = ENV.fetch("CABLE_URL") if AnyCable::Rails.enabled?
68
+ config.action_cable.url = ActionCable.server.config.url = ENV.fetch("CABLE_URL", "/cable") if AnyCable::Rails.enabled?
63
69
  end
64
70
  SNIPPET
65
71
  end
@@ -118,7 +124,7 @@ module AnyCableRailsGenerators
118
124
  def stimulus_reflex
119
125
  return unless stimulus_reflex?
120
126
 
121
- say_status :help, "⚠️ Please, check out the documentation on using AnyCable with Stimulus Reflex: https://docs.anycable.io/#/rails/stimulus_reflex"
127
+ say_status :help, "⚠️ Please, check out the documentation on using AnyCable with Stimulus Reflex: #{DOCS_ROOT}/rails/stimulus_reflex"
122
128
  end
123
129
 
124
130
  def rubocop_compatibility
@@ -126,7 +132,17 @@ module AnyCableRailsGenerators
126
132
 
127
133
  say_status :info, "🤖 Running static compatibility checks with RuboCop"
128
134
  res = run "bundle exec rubocop -r 'anycable/rails/compatibility/rubocop' --only AnyCable/InstanceVars,AnyCable/PeriodicalTimers,AnyCable/InstanceVars"
129
- say_status :help, "⚠️ Please, take a look at the icompatibilities above and fix them. See https://docs.anycable.io/#/rails/compatibility" unless res
135
+ say_status :help, "⚠️ Please, take a look at the icompatibilities above and fix them. See #{DOCS_ROOT}/rails/compatibility" unless res
136
+ end
137
+
138
+ def jwt
139
+ return if options[:skip_jwt]
140
+
141
+ return unless options[:skip_jwt] == false || yes?("Do you want to use JWT for authentication? [Yn]")
142
+
143
+ opts = " --skip-install" if options[:skip_install]
144
+
145
+ run "bundle add anycable-rails-jwt#{opts}"
130
146
  end
131
147
 
132
148
  def finish
@@ -172,7 +188,7 @@ module AnyCableRailsGenerators
172
188
  say <<~YML
173
189
  ─────────────────────────────────────────
174
190
  ws:
175
- image: anycable/anycable-go:1.0
191
+ image: anycable/anycable-go:1.2
176
192
  ports:
177
193
  - '8080:8080'
178
194
  environment:
@@ -28,7 +28,7 @@ default: &default
28
28
  broadcast_adapter: http
29
29
  <%- end -%>
30
30
  # Use the same channel name for WebSocket server, e.g.:
31
- # $ anycable-go --redis-channel="__anycable__"
31
+ # $ anycable-go --redis_channel="__anycable__"
32
32
  redis_channel: "__anycable__"
33
33
  <%- if redis? -%>
34
34
  # You can use REDIS_URL env var to configure Redis URL.
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-05 00:00:00.000000000 Z
11
+ date: 2022-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.0
19
+ version: 1.2.0
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: 1.0.0
26
+ version: 1.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: actioncable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '5'
33
+ version: '6.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '5'
40
+ version: '6.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: globalid
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: activerecord
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: ammeter
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -164,12 +150,9 @@ files:
164
150
  - lib/action_cable/subscription_adapter/anycable.rb
165
151
  - lib/anycable-rails.rb
166
152
  - lib/anycable/rails.rb
167
- - lib/anycable/rails/actioncable/channel.rb
168
- - lib/anycable/rails/actioncable/connection.rb
169
- - lib/anycable/rails/actioncable/connection/persistent_session.rb
170
- - lib/anycable/rails/actioncable/connection/serializable_identification.rb
171
- - lib/anycable/rails/actioncable/remote_connections.rb
172
- - lib/anycable/rails/actioncable/testing.rb
153
+ - lib/anycable/rails/action_cable_ext/channel.rb
154
+ - lib/anycable/rails/action_cable_ext/connection.rb
155
+ - lib/anycable/rails/action_cable_ext/remote_connections.rb
173
156
  - lib/anycable/rails/channel_state.rb
174
157
  - lib/anycable/rails/compatibility.rb
175
158
  - lib/anycable/rails/compatibility/rubocop.rb
@@ -178,12 +161,15 @@ files:
178
161
  - lib/anycable/rails/compatibility/rubocop/cops/anycable/periodical_timers.rb
179
162
  - lib/anycable/rails/compatibility/rubocop/cops/anycable/stream_from.rb
180
163
  - lib/anycable/rails/config.rb
164
+ - lib/anycable/rails/connection.rb
165
+ - lib/anycable/rails/connection_factory.rb
166
+ - lib/anycable/rails/connections/persistent_session.rb
167
+ - lib/anycable/rails/connections/serializable_identification.rb
168
+ - lib/anycable/rails/connections/session_proxy.rb
181
169
  - lib/anycable/rails/middlewares/executor.rb
182
170
  - lib/anycable/rails/middlewares/log_tagging.rb
183
171
  - lib/anycable/rails/rack.rb
184
172
  - lib/anycable/rails/railtie.rb
185
- - lib/anycable/rails/refinements/subscriptions.rb
186
- - lib/anycable/rails/session_proxy.rb
187
173
  - lib/anycable/rails/version.rb
188
174
  - lib/generators/anycable/download/USAGE
189
175
  - lib/generators/anycable/download/download_generator.rb
@@ -203,7 +189,8 @@ metadata:
203
189
  documentation_uri: https://docs.anycable.io/#/using_with_rails
204
190
  homepage_uri: https://anycable.io/
205
191
  source_code_uri: http://github.com/anycable/anycable-rails
206
- post_install_message:
192
+ funding_uri: https://github.com/sponsors/anycable
193
+ post_install_message:
207
194
  rdoc_options: []
208
195
  require_paths:
209
196
  - lib
@@ -211,15 +198,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
198
  requirements:
212
199
  - - ">="
213
200
  - !ruby/object:Gem::Version
214
- version: '2.5'
201
+ version: '2.6'
215
202
  required_rubygems_version: !ruby/object:Gem::Requirement
216
203
  requirements:
217
204
  - - ">="
218
205
  - !ruby/object:Gem::Version
219
206
  version: '0'
220
207
  requirements: []
221
- rubygems_version: 3.0.6
222
- signing_key:
208
+ rubygems_version: 3.3.7
209
+ signing_key:
223
210
  specification_version: 4
224
211
  summary: Rails adapter for AnyCable
225
212
  test_files: []
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_cable/channel"
4
-
5
- module ActionCable
6
- module Channel
7
- class Base # :nodoc:
8
- alias_method :handle_subscribe, :subscribe_to_channel
9
-
10
- public :handle_subscribe, :subscription_rejected?
11
-
12
- # Action Cable calls this method from inside the Subscriptions#add
13
- # method, which we use to initialize the channel instance.
14
- # We don't want to invoke `subscribed` callbacks every time we do that.
15
- def subscribe_to_channel
16
- # noop
17
- end
18
-
19
- def start_periodic_timers
20
- # noop
21
- end
22
-
23
- def stop_periodic_timers
24
- # noop
25
- end
26
-
27
- def stream_from(broadcasting, _callback = nil, _options = {})
28
- connection.socket.subscribe identifier, broadcasting
29
- end
30
-
31
- def stop_stream_from(broadcasting)
32
- connection.socket.unsubscribe identifier, broadcasting
33
- end
34
-
35
- def stop_all_streams
36
- connection.socket.unsubscribe_from_all identifier
37
- end
38
- end
39
- end
40
- end
@@ -1,34 +0,0 @@
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.respond_to?(:loaded?) && request.session.loaded?
25
-
26
- socket.session = request.session.to_json
27
- end
28
- end
29
- end
30
- end
31
-
32
- ::ActionCable::Connection::Base.prepend(
33
- ::ActionCable::Connection::PersistentSession
34
- )
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionCable
4
- module Connection
5
- module SerializableIdentification
6
- extend ActiveSupport::Concern
7
-
8
- class_methods do
9
- def identified_by(*identifiers)
10
- super
11
- Array(identifiers).each do |identifier|
12
- define_method(identifier) do
13
- instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
14
- end
15
- end
16
- end
17
- end
18
-
19
- # Generate identifiers info.
20
- # Converts GlobalID compatible vars to corresponding global IDs params.
21
- def identifiers_hash
22
- identifiers.each_with_object({}) do |id, acc|
23
- obj = instance_variable_get("@#{id}")
24
- next unless obj
25
-
26
- acc[id] = AnyCable::Rails.serialize(obj)
27
- end.compact
28
- end
29
-
30
- def identifiers_json
31
- identifiers_hash.to_json
32
- end
33
-
34
- # Fetch identifier and deserialize if neccessary
35
- def fetch_identifier(name)
36
- @cached_ids[name] ||= @cached_ids.fetch(name) do
37
- AnyCable::Rails.deserialize(ids[name.to_s])
38
- end
39
- end
40
- end
41
- end
42
- end