actioncable-next 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/MIT-LICENSE +20 -0
- data/README.md +17 -0
- data/lib/action_cable/channel/base.rb +335 -0
- data/lib/action_cable/channel/broadcasting.rb +50 -0
- data/lib/action_cable/channel/callbacks.rb +76 -0
- data/lib/action_cable/channel/naming.rb +28 -0
- data/lib/action_cable/channel/periodic_timers.rb +81 -0
- data/lib/action_cable/channel/streams.rb +213 -0
- data/lib/action_cable/channel/test_case.rb +329 -0
- data/lib/action_cable/connection/authorization.rb +18 -0
- data/lib/action_cable/connection/base.rb +165 -0
- data/lib/action_cable/connection/callbacks.rb +57 -0
- data/lib/action_cable/connection/identification.rb +51 -0
- data/lib/action_cable/connection/internal_channel.rb +50 -0
- data/lib/action_cable/connection/subscriptions.rb +124 -0
- data/lib/action_cable/connection/test_case.rb +294 -0
- data/lib/action_cable/deprecator.rb +9 -0
- data/lib/action_cable/engine.rb +98 -0
- data/lib/action_cable/gem_version.rb +19 -0
- data/lib/action_cable/helpers/action_cable_helper.rb +45 -0
- data/lib/action_cable/remote_connections.rb +82 -0
- data/lib/action_cable/server/base.rb +163 -0
- data/lib/action_cable/server/broadcasting.rb +62 -0
- data/lib/action_cable/server/configuration.rb +75 -0
- data/lib/action_cable/server/connections.rb +44 -0
- data/lib/action_cable/server/socket/client_socket.rb +159 -0
- data/lib/action_cable/server/socket/message_buffer.rb +56 -0
- data/lib/action_cable/server/socket/stream.rb +117 -0
- data/lib/action_cable/server/socket/web_socket.rb +47 -0
- data/lib/action_cable/server/socket.rb +180 -0
- data/lib/action_cable/server/stream_event_loop.rb +119 -0
- data/lib/action_cable/server/tagged_logger_proxy.rb +46 -0
- data/lib/action_cable/server/worker/active_record_connection_management.rb +23 -0
- data/lib/action_cable/server/worker.rb +75 -0
- data/lib/action_cable/subscription_adapter/async.rb +14 -0
- data/lib/action_cable/subscription_adapter/base.rb +39 -0
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +30 -0
- data/lib/action_cable/subscription_adapter/inline.rb +40 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +130 -0
- data/lib/action_cable/subscription_adapter/redis.rb +257 -0
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +80 -0
- data/lib/action_cable/subscription_adapter/test.rb +41 -0
- data/lib/action_cable/test_case.rb +13 -0
- data/lib/action_cable/test_helper.rb +163 -0
- data/lib/action_cable/version.rb +12 -0
- data/lib/action_cable.rb +81 -0
- data/lib/actioncable-next.rb +5 -0
- data/lib/rails/generators/channel/USAGE +19 -0
- data/lib/rails/generators/channel/channel_generator.rb +127 -0
- data/lib/rails/generators/channel/templates/application_cable/channel.rb.tt +4 -0
- data/lib/rails/generators/channel/templates/application_cable/connection.rb.tt +4 -0
- data/lib/rails/generators/channel/templates/channel.rb.tt +16 -0
- data/lib/rails/generators/channel/templates/javascript/channel.js.tt +20 -0
- data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
- data/lib/rails/generators/channel/templates/javascript/index.js.tt +1 -0
- data/lib/rails/generators/test_unit/channel_generator.rb +22 -0
- data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
- metadata +191 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "websocket/driver"
|
6
|
+
|
7
|
+
module ActionCable
|
8
|
+
module Server
|
9
|
+
class Socket
|
10
|
+
# # Action Cable Connection WebSocket
|
11
|
+
#
|
12
|
+
# Wrap the real socket to minimize the externally-presented API
|
13
|
+
class WebSocket # :nodoc:
|
14
|
+
def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols])
|
15
|
+
@websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def possible?
|
19
|
+
websocket
|
20
|
+
end
|
21
|
+
|
22
|
+
def alive?
|
23
|
+
websocket&.alive?
|
24
|
+
end
|
25
|
+
|
26
|
+
def transmit(...)
|
27
|
+
websocket&.transmit(...)
|
28
|
+
end
|
29
|
+
|
30
|
+
def close(...)
|
31
|
+
websocket&.close(...)
|
32
|
+
end
|
33
|
+
|
34
|
+
def protocol
|
35
|
+
websocket&.protocol
|
36
|
+
end
|
37
|
+
|
38
|
+
def rack_response
|
39
|
+
websocket&.rack_response
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
attr_reader :websocket
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch"
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module Server
|
7
|
+
# This class encapsulates all the low-level logic of working with the underlying WebSocket conenctions
|
8
|
+
# and delegate all the business-logic to the user-level connection object (e.g., ApplicationCable::Connection).
|
9
|
+
# This connection object is also responsible for handling encoding and decoding of messages, so the user-level
|
10
|
+
# connection object shouldn't know about such details.
|
11
|
+
class Socket
|
12
|
+
attr_reader :server, :env, :protocol, :logger, :connection
|
13
|
+
private attr_reader :worker_pool
|
14
|
+
|
15
|
+
delegate :event_loop, :pubsub, :config, to: :server
|
16
|
+
|
17
|
+
def initialize(server, env, coder: ActiveSupport::JSON)
|
18
|
+
@server, @env, @coder = server, env, coder
|
19
|
+
|
20
|
+
@worker_pool = server.worker_pool
|
21
|
+
@logger = server.new_tagged_logger { request }
|
22
|
+
|
23
|
+
@websocket = WebSocket.new(env, self, event_loop)
|
24
|
+
@message_buffer = MessageBuffer.new(self)
|
25
|
+
|
26
|
+
@protocol = nil
|
27
|
+
@connection = config.connection_class.call.new(server, self)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Called by the server when a new WebSocket connection is established.
|
31
|
+
def process # :nodoc:
|
32
|
+
logger.info started_request_message
|
33
|
+
|
34
|
+
if websocket.possible? && server.allow_request_origin?(env)
|
35
|
+
respond_to_successful_request
|
36
|
+
else
|
37
|
+
respond_to_invalid_request
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Methods used by the delegate (i.e., an application connection)
|
42
|
+
|
43
|
+
# Send a non-serialized message over the WebSocket connection.
|
44
|
+
def transmit(cable_message)
|
45
|
+
return unless websocket.alive?
|
46
|
+
|
47
|
+
websocket.transmit encode(cable_message)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Close the WebSocket connection.
|
51
|
+
def close(...)
|
52
|
+
websocket.close(...) if websocket.alive?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Invoke a method on the connection asynchronously through the pool of thread workers.
|
56
|
+
def perform_work(receiver, method, *args)
|
57
|
+
worker_pool.async_invoke(receiver, method, *args, connection: self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_async(method, *arguments)
|
61
|
+
worker_pool.async_invoke(self, method, *arguments)
|
62
|
+
end
|
63
|
+
|
64
|
+
# The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
|
65
|
+
def request
|
66
|
+
@request ||= begin
|
67
|
+
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
|
68
|
+
ActionDispatch::Request.new(environment || env)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Decodes WebSocket messages and dispatches them to subscribed channels.
|
73
|
+
# WebSocket message transfer encoding is always JSON.
|
74
|
+
def receive(websocket_message) # :nodoc:
|
75
|
+
send_async :dispatch_websocket_message, websocket_message
|
76
|
+
end
|
77
|
+
|
78
|
+
def dispatch_websocket_message(websocket_message) # :nodoc:
|
79
|
+
if websocket.alive?
|
80
|
+
@connection.handle_incoming decode(websocket_message)
|
81
|
+
else
|
82
|
+
logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
|
83
|
+
end
|
84
|
+
rescue Exception => e
|
85
|
+
logger.error "Could not handle incoming message: #{websocket_message.inspect} [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_open # :nodoc:
|
89
|
+
send_async :handle_open
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_message(message) # :nodoc:
|
93
|
+
message_buffer.append message
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_error(message) # :nodoc:
|
97
|
+
# log errors to make diagnosing socket errors easier
|
98
|
+
logger.error "WebSocket error occurred: #{message}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_close(reason, code) # :nodoc:
|
102
|
+
send_async :handle_close
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect # :nodoc:
|
106
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
attr_reader :websocket
|
111
|
+
attr_reader :message_buffer
|
112
|
+
|
113
|
+
def encode(cable_message)
|
114
|
+
@coder.encode cable_message
|
115
|
+
end
|
116
|
+
|
117
|
+
def decode(websocket_message)
|
118
|
+
@coder.decode websocket_message
|
119
|
+
end
|
120
|
+
|
121
|
+
def handle_open
|
122
|
+
@protocol = websocket.protocol
|
123
|
+
|
124
|
+
@connection.handle_open
|
125
|
+
|
126
|
+
message_buffer.process!
|
127
|
+
server.add_connection(@connection)
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_close
|
131
|
+
logger.info finished_request_message
|
132
|
+
|
133
|
+
server.remove_connection(@connection)
|
134
|
+
@connection.handle_close
|
135
|
+
end
|
136
|
+
|
137
|
+
def respond_to_successful_request
|
138
|
+
logger.info successful_request_message
|
139
|
+
websocket.rack_response
|
140
|
+
end
|
141
|
+
|
142
|
+
def respond_to_invalid_request
|
143
|
+
close if websocket.alive?
|
144
|
+
|
145
|
+
logger.error invalid_request_message
|
146
|
+
logger.info finished_request_message
|
147
|
+
[ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
|
148
|
+
end
|
149
|
+
|
150
|
+
def started_request_message
|
151
|
+
'Started %s "%s"%s for %s at %s' % [
|
152
|
+
request.request_method,
|
153
|
+
request.filtered_path,
|
154
|
+
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
155
|
+
request.ip,
|
156
|
+
Time.now.to_s ]
|
157
|
+
end
|
158
|
+
|
159
|
+
def finished_request_message
|
160
|
+
'Finished "%s"%s for %s at %s' % [
|
161
|
+
request.filtered_path,
|
162
|
+
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
163
|
+
request.ip,
|
164
|
+
Time.now.to_s ]
|
165
|
+
end
|
166
|
+
|
167
|
+
def invalid_request_message
|
168
|
+
"Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
|
169
|
+
env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
|
170
|
+
]
|
171
|
+
end
|
172
|
+
|
173
|
+
def successful_request_message
|
174
|
+
"Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
|
175
|
+
env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
|
176
|
+
]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "nio"
|
6
|
+
|
7
|
+
module ActionCable
|
8
|
+
module Server
|
9
|
+
class StreamEventLoop
|
10
|
+
def initialize
|
11
|
+
@nio = @thread = nil
|
12
|
+
@map = {}
|
13
|
+
@stopping = false
|
14
|
+
@todo = Queue.new
|
15
|
+
|
16
|
+
@spawn_mutex = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def attach(io, stream)
|
20
|
+
@todo << lambda do
|
21
|
+
@map[io] = @nio.register(io, :r)
|
22
|
+
@map[io].value = stream
|
23
|
+
end
|
24
|
+
wakeup
|
25
|
+
end
|
26
|
+
|
27
|
+
def detach(io, stream)
|
28
|
+
@todo << lambda do
|
29
|
+
@nio.deregister io
|
30
|
+
@map.delete io
|
31
|
+
io.close
|
32
|
+
end
|
33
|
+
wakeup
|
34
|
+
end
|
35
|
+
|
36
|
+
def writes_pending(io)
|
37
|
+
@todo << lambda do
|
38
|
+
if monitor = @map[io]
|
39
|
+
monitor.interests = :rw
|
40
|
+
end
|
41
|
+
end
|
42
|
+
wakeup
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
@stopping = true
|
47
|
+
wakeup if @nio
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def spawn
|
52
|
+
return if @thread && @thread.status
|
53
|
+
|
54
|
+
@spawn_mutex.synchronize do
|
55
|
+
return if @thread && @thread.status
|
56
|
+
|
57
|
+
@nio ||= NIO::Selector.new
|
58
|
+
|
59
|
+
@thread = Thread.new { run }
|
60
|
+
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def wakeup
|
66
|
+
spawn || @nio.wakeup
|
67
|
+
end
|
68
|
+
|
69
|
+
def run
|
70
|
+
loop do
|
71
|
+
if @stopping
|
72
|
+
@nio.close
|
73
|
+
break
|
74
|
+
end
|
75
|
+
|
76
|
+
until @todo.empty?
|
77
|
+
@todo.pop(true).call
|
78
|
+
end
|
79
|
+
|
80
|
+
next unless monitors = @nio.select
|
81
|
+
|
82
|
+
monitors.each do |monitor|
|
83
|
+
io = monitor.io
|
84
|
+
stream = monitor.value
|
85
|
+
|
86
|
+
begin
|
87
|
+
if monitor.writable?
|
88
|
+
if stream.flush_write_buffer
|
89
|
+
monitor.interests = :r
|
90
|
+
end
|
91
|
+
next unless monitor.readable?
|
92
|
+
end
|
93
|
+
|
94
|
+
incoming = io.read_nonblock(4096, exception: false)
|
95
|
+
case incoming
|
96
|
+
when :wait_readable
|
97
|
+
next
|
98
|
+
when nil
|
99
|
+
stream.close
|
100
|
+
else
|
101
|
+
stream.receive incoming
|
102
|
+
end
|
103
|
+
rescue
|
104
|
+
# We expect one of EOFError or Errno::ECONNRESET in normal operation (when the
|
105
|
+
# client goes away). But if anything else goes wrong, this is still the best way
|
106
|
+
# to handle it.
|
107
|
+
begin
|
108
|
+
stream.close
|
109
|
+
rescue
|
110
|
+
@nio.deregister io
|
111
|
+
@map.delete io
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module Server
|
7
|
+
# # Action Cable Connection TaggedLoggerProxy
|
8
|
+
#
|
9
|
+
# Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional
|
10
|
+
# ActiveSupport::TaggedLogging enhanced Rails.logger, as that logger will reset the tags between requests.
|
11
|
+
# The connection is long-lived, so it needs its own set of tags for its independent duration.
|
12
|
+
class TaggedLoggerProxy
|
13
|
+
attr_reader :tags
|
14
|
+
|
15
|
+
def initialize(logger, tags:)
|
16
|
+
@logger = logger
|
17
|
+
@tags = tags.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_tags(*tags)
|
21
|
+
@tags += tags.flatten
|
22
|
+
@tags = @tags.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def tag(logger, &block)
|
26
|
+
if logger.respond_to?(:tagged)
|
27
|
+
current_tags = tags - logger.formatter.current_tags
|
28
|
+
logger.tagged(*current_tags, &block)
|
29
|
+
else
|
30
|
+
yield
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%i( debug info warn error fatal unknown ).each do |severity|
|
35
|
+
define_method(severity) do |message = nil, &block|
|
36
|
+
log severity, message, &block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def log(type, message, &block) # :doc:
|
42
|
+
tag(@logger) { @logger.send type, message, &block }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module Server
|
7
|
+
class Worker
|
8
|
+
module ActiveRecordConnectionManagement
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
if defined?(ActiveRecord::Base)
|
13
|
+
set_callback :work, :around, :with_database_connections
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_database_connections(&block)
|
18
|
+
connection.logger.tag(ActiveRecord::Base.logger, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/callbacks"
|
6
|
+
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
7
|
+
require "concurrent"
|
8
|
+
|
9
|
+
module ActionCable
|
10
|
+
module Server
|
11
|
+
# Worker used by Server.send_async to do connection work in threads.
|
12
|
+
class Worker # :nodoc:
|
13
|
+
include ActiveSupport::Callbacks
|
14
|
+
|
15
|
+
thread_mattr_accessor :connection
|
16
|
+
define_callbacks :work
|
17
|
+
include ActiveRecordConnectionManagement
|
18
|
+
|
19
|
+
attr_reader :executor
|
20
|
+
|
21
|
+
def initialize(max_size: 5)
|
22
|
+
@executor = Concurrent::ThreadPoolExecutor.new(
|
23
|
+
name: "ActionCable worker",
|
24
|
+
min_threads: 1,
|
25
|
+
max_threads: max_size,
|
26
|
+
max_queue: 0,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Stop processing work: any work that has not already started running will be
|
31
|
+
# discarded from the queue
|
32
|
+
def halt
|
33
|
+
@executor.shutdown
|
34
|
+
end
|
35
|
+
|
36
|
+
def stopping?
|
37
|
+
@executor.shuttingdown?
|
38
|
+
end
|
39
|
+
|
40
|
+
def work(connection, &block)
|
41
|
+
self.connection = connection
|
42
|
+
|
43
|
+
run_callbacks :work, &block
|
44
|
+
ensure
|
45
|
+
self.connection = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def async_exec(receiver, *args, connection:, &block)
|
49
|
+
async_invoke receiver, :instance_exec, *args, connection: connection, &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def async_invoke(receiver, method, *args, connection: receiver, &block)
|
53
|
+
@executor.post do
|
54
|
+
invoke(receiver, method, *args, connection: connection, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def invoke(receiver, method, *args, connection:, &block)
|
59
|
+
work(connection) do
|
60
|
+
receiver.send method, *args, &block
|
61
|
+
rescue Exception => e
|
62
|
+
logger.error "There was an exception - #{e.class}(#{e.message})"
|
63
|
+
logger.error e.backtrace.join("\n")
|
64
|
+
|
65
|
+
receiver.handle_exception if receiver.respond_to?(:handle_exception)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def logger
|
71
|
+
ActionCable.server.logger
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module SubscriptionAdapter
|
7
|
+
class Base
|
8
|
+
private attr_reader :executor
|
9
|
+
private attr_reader :config
|
10
|
+
|
11
|
+
delegate :logger, to: :config
|
12
|
+
|
13
|
+
def initialize(server)
|
14
|
+
@executor = server.executor
|
15
|
+
@config = server.config
|
16
|
+
end
|
17
|
+
|
18
|
+
def broadcast(channel, payload)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def subscribe(channel, message_callback, success_callback = nil)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def unsubscribe(channel, message_callback)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def shutdown
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def identifier
|
35
|
+
config.cable[:id] ||= "ActionCable-PID-#{$$}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module SubscriptionAdapter
|
7
|
+
module ChannelPrefix # :nodoc:
|
8
|
+
def broadcast(channel, payload)
|
9
|
+
channel = channel_with_prefix(channel)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def subscribe(channel, callback, success_callback = nil)
|
14
|
+
channel = channel_with_prefix(channel)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def unsubscribe(channel, callback)
|
19
|
+
channel = channel_with_prefix(channel)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
# Returns the channel name, including channel_prefix specified in cable.yml
|
25
|
+
def channel_with_prefix(channel)
|
26
|
+
[config.cable[:channel_prefix], channel].compact.join(":")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module SubscriptionAdapter
|
7
|
+
class Inline < Base # :nodoc:
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@subscriber_map = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def broadcast(channel, payload)
|
15
|
+
subscriber_map.broadcast(channel, payload)
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribe(channel, callback, success_callback = nil)
|
19
|
+
subscriber_map.add_subscriber(channel, callback, success_callback)
|
20
|
+
end
|
21
|
+
|
22
|
+
def unsubscribe(channel, callback)
|
23
|
+
subscriber_map.remove_subscriber(channel, callback)
|
24
|
+
end
|
25
|
+
|
26
|
+
def shutdown
|
27
|
+
# nothing to do
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def subscriber_map
|
32
|
+
@subscriber_map || @mutex.synchronize { @subscriber_map ||= new_subscriber_map }
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_subscriber_map
|
36
|
+
SubscriberMap.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|