anycable-rails 1.0.7 → 1.3.4

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.
@@ -1,222 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_cable/connection"
4
- require "anycable/rails/actioncable/connection/serializable_identification"
5
- require "anycable/rails/refinements/subscriptions"
6
- require "anycable/rails/actioncable/channel"
7
- require "anycable/rails/actioncable/remote_connections"
8
- require "anycable/rails/session_proxy"
9
-
10
- module ActionCable
11
- module Connection
12
- # rubocop: disable Metrics/ClassLength
13
- class Base # :nodoc:
14
- # We store logger tags in the connection state to be able
15
- # to re-use them in the subsequent calls
16
- LOG_TAGS_IDENTIFIER = "__ltags__"
17
-
18
- using AnyCable::Refinements::Subscriptions
19
-
20
- include SerializableIdentification
21
-
22
- attr_reader :socket
23
-
24
- alias_method :anycable_socket, :socket
25
-
26
- delegate :env, :session, to: :request
27
-
28
- class << self
29
- def call(socket, **options)
30
- new(socket, nil, **options)
31
- end
32
- end
33
-
34
- def initialize(socket, env, identifiers: "{}", subscriptions: nil)
35
- if env
36
- # If env is set, then somehow we're in the context of Action Cable
37
- # Return and print a warning in #process
38
- @request = ActionDispatch::Request.new(env)
39
- return
40
- end
41
-
42
- @ids = ActiveSupport::JSON.decode(identifiers)
43
-
44
- @ltags = socket.cstate.read(LOG_TAGS_IDENTIFIER).yield_self do |raw_tags|
45
- next unless raw_tags
46
- ActiveSupport::JSON.decode(raw_tags)
47
- end
48
-
49
- @cached_ids = {}
50
- @coder = ActiveSupport::JSON
51
- @socket = socket
52
- @subscriptions = ActionCable::Connection::Subscriptions.new(self)
53
-
54
- return unless subscriptions
55
-
56
- # Initialize channels (for disconnect)
57
- subscriptions.each do |id|
58
- channel = @subscriptions.fetch(id)
59
- next unless socket.istate[id]
60
-
61
- channel.__istate__ = ActiveSupport::JSON.decode(socket.istate[id])
62
- end
63
- end
64
-
65
- def process
66
- # Use Rails logger here to print to stdout in development
67
- logger.error invalid_request_message
68
- logger.info finished_request_message
69
- [404, {"Content-Type" => "text/plain"}, ["Page not found"]]
70
- end
71
-
72
- def invalid_request_message
73
- "You're trying to connect to Action Cable server while using AnyCable. " \
74
- "See https://docs.anycable.io/#/troubleshooting?id=server-raises-an-argumenterror-exception-when-client-tries-to-connect"
75
- end
76
-
77
- def handle_open
78
- logger.info started_request_message if access_logs?
79
-
80
- verify_origin! || return
81
-
82
- connect if respond_to?(:connect)
83
-
84
- socket.cstate.write(LOG_TAGS_IDENTIFIER, fetch_ltags.to_json)
85
-
86
- send_welcome_message
87
- rescue ActionCable::Connection::Authorization::UnauthorizedError
88
- reject_request(
89
- ActionCable::INTERNAL[:disconnect_reasons]&.[](:unauthorized) || "unauthorized"
90
- )
91
- end
92
-
93
- def handle_close
94
- logger.info finished_request_message if access_logs?
95
-
96
- subscriptions.unsubscribe_from_all
97
- disconnect if respond_to?(:disconnect)
98
- true
99
- end
100
-
101
- # rubocop:disable Metrics/MethodLength
102
- def handle_channel_command(identifier, command, data)
103
- channel = subscriptions.fetch(identifier)
104
- case command
105
- when "subscribe"
106
- channel.handle_subscribe
107
- !channel.subscription_rejected?
108
- when "unsubscribe"
109
- subscriptions.remove_subscription(channel)
110
- true
111
- when "message"
112
- channel.perform_action ActiveSupport::JSON.decode(data)
113
- true
114
- else
115
- false
116
- end
117
- end
118
- # rubocop:enable Metrics/MethodLength
119
-
120
- def close(reason: nil, reconnect: nil)
121
- transmit(
122
- type: ActionCable::INTERNAL[:message_types].fetch(:disconnect, "disconnect"),
123
- reason: reason,
124
- reconnect: reconnect
125
- )
126
- socket.close
127
- end
128
-
129
- def transmit(cable_message)
130
- socket.transmit encode(cable_message)
131
- end
132
-
133
- def logger
134
- @logger ||= TaggedLoggerProxy.new(AnyCable.logger, tags: ltags || [])
135
- end
136
-
137
- def request
138
- @request ||= build_rack_request
139
- end
140
-
141
- private
142
-
143
- attr_reader :ids, :ltags
144
-
145
- def started_request_message
146
- format(
147
- 'Started "%s"%s for %s at %s',
148
- request.filtered_path, " [AnyCable]", request.ip, Time.now.to_s
149
- )
150
- end
151
-
152
- def finished_request_message(reason = "Closed")
153
- format(
154
- 'Finished "%s"%s for %s at %s (%s)',
155
- request.filtered_path, " [AnyCable]", request.ip, Time.now.to_s, reason
156
- )
157
- end
158
-
159
- def access_logs?
160
- AnyCable.config.access_logs_disabled == false
161
- end
162
-
163
- def fetch_ltags
164
- if instance_variable_defined?(:@logger)
165
- logger.tags
166
- else
167
- ltags
168
- end
169
- end
170
-
171
- def server
172
- ActionCable.server
173
- end
174
-
175
- def verify_origin!
176
- return true unless socket.env.key?("HTTP_ORIGIN")
177
-
178
- return true if allow_request_origin?
179
-
180
- reject_request(
181
- ActionCable::INTERNAL[:disconnect_reasons]&.[](:invalid_request) || "invalid_request"
182
- )
183
- false
184
- end
185
-
186
- def reject_request(reason, reconnect = false)
187
- logger.info finished_request_message("Rejected") if access_logs?
188
- close(
189
- reason: reason,
190
- reconnect: reconnect
191
- )
192
- end
193
-
194
- def build_rack_request
195
- environment = Rails.application.env_config.merge(socket.env)
196
- AnyCable::Rails::Rack.app.call(environment)
197
-
198
- ActionDispatch::Request.new(environment)
199
- end
200
-
201
- def request_loaded?
202
- instance_variable_defined?(:@request)
203
- end
204
- end
205
- # rubocop:enable Metrics/ClassLength
206
- end
207
- end
208
-
209
- # Support rescue_from
210
- # https://github.com/rails/rails/commit/d2571e560c62116f60429c933d0c41a0e249b58b
211
- if ActionCable::Connection::Base.respond_to?(:rescue_from)
212
- ActionCable::Connection::Base.prepend(Module.new do
213
- def handle_channel_command(*)
214
- super
215
- rescue Exception => e # rubocop:disable Lint/RescueException
216
- rescue_with_handler(e) || raise
217
- false
218
- end
219
- end)
220
- end
221
-
222
- require "anycable/rails/actioncable/testing" if ::Rails.env.test?
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This file contains patches to Action Cable testing modules
4
-
5
- # Trigger autoload (if constant is defined)
6
- begin
7
- ActionCable::Channel::TestCase # rubocop:disable Lint/Void
8
- ActionCable::Connection::TestCase # rubocop:disable Lint/Void
9
- rescue NameError
10
- return
11
- end
12
-
13
- ActionCable::Channel::ChannelStub.prepend(Module.new do
14
- def subscribe_to_channel
15
- handle_subscribe
16
- end
17
- end)
18
-
19
- ActionCable::Channel::ConnectionStub.prepend(Module.new do
20
- def socket
21
- @socket ||= AnyCable::Socket.new(env: {})
22
- end
23
-
24
- alias_method :anycable_socket, :socket
25
- end)
26
-
27
- ActionCable::Connection::TestConnection.prepend(Module.new do
28
- def initialize(request)
29
- @request = request
30
- @cached_ids = {}
31
- super
32
- end
33
- end)
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AnyCable
4
- module Refinements
5
- module Subscriptions # :nodoc:
6
- refine ActionCable::Connection::Subscriptions do
7
- # Find or add a subscription to the list
8
- def fetch(identifier)
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
- subscriptions[identifier]
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,79 +0,0 @@
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
- attr_reader :rack_session, :socket_session
9
-
10
- def initialize(rack_session, socket_session)
11
- @rack_session = rack_session
12
- @socket_session = JSON.parse(socket_session).with_indifferent_access
13
- end
14
-
15
- %i[has_key? [] []= fetch delete dig].each do |mid|
16
- class_eval <<~CODE, __FILE__, __LINE__ + 1
17
- def #{mid}(*args, **kwargs, &block)
18
- restore_key! args.first
19
- rack_session.#{mid}(*args, **kwargs, &block)
20
- end
21
- CODE
22
- end
23
-
24
- alias_method :include?, :has_key?
25
- alias_method :key?, :has_key?
26
-
27
- %i[update merge! to_hash].each do |mid|
28
- class_eval <<~CODE, __FILE__, __LINE__ + 1
29
- def #{mid}(*args, **kwargs, &block)
30
- restore!
31
- rack_session.#{mid}(*args, **kwargs, &block)
32
- end
33
- CODE
34
- end
35
-
36
- alias_method :to_h, :to_hash
37
-
38
- def keys
39
- rack_session.keys + socket_session.keys
40
- end
41
-
42
- # Delegate both publuc and private methods to rack_session
43
- def respond_to_missing?(name, include_private = false)
44
- return false if name == :marshal_dump || name == :_dump
45
- rack_session.respond_to?(name, include_private) || super
46
- end
47
-
48
- def method_missing(method, *args, &block)
49
- if rack_session.respond_to?(method, true)
50
- rack_session.send(method, *args, &block)
51
- else
52
- super
53
- end
54
- end
55
-
56
- # This method is used by StimulusReflex to obtain `@by`
57
- def instance_variable_get(name)
58
- super || rack_session.instance_variable_get(name)
59
- end
60
-
61
- private
62
-
63
- def restore!
64
- socket_session.keys.each(&method(:restore_key!))
65
- end
66
-
67
- def restore_key!(key)
68
- return unless socket_session.key?(key)
69
- val = socket_session.delete(key)
70
- rack_session[key] =
71
- if val.is_a?(String)
72
- GlobalID::Locator.locate(val) || val
73
- else
74
- val
75
- end
76
- end
77
- end
78
- end
79
- end