actioncable-next 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|