actioncable 5.0.7.2 → 5.1.0.beta1
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 +5 -5
- data/CHANGELOG.md +13 -169
- data/MIT-LICENSE +1 -1
- data/README.md +18 -15
- data/lib/action_cable.rb +9 -9
- data/lib/action_cable/channel/base.rb +17 -19
- data/lib/action_cable/channel/broadcasting.rb +2 -2
- data/lib/action_cable/channel/callbacks.rb +1 -1
- data/lib/action_cable/channel/naming.rb +2 -1
- data/lib/action_cable/channel/periodic_timers.rb +1 -1
- data/lib/action_cable/channel/streams.rb +7 -7
- data/lib/action_cable/connection.rb +0 -2
- data/lib/action_cable/connection/authorization.rb +6 -6
- data/lib/action_cable/connection/base.rb +20 -21
- data/lib/action_cable/connection/client_socket.rb +16 -16
- data/lib/action_cable/connection/identification.rb +1 -1
- data/lib/action_cable/connection/internal_channel.rb +2 -2
- data/lib/action_cable/connection/message_buffer.rb +2 -0
- data/lib/action_cable/connection/stream.rb +5 -5
- data/lib/action_cable/connection/stream_event_loop.rb +2 -2
- data/lib/action_cable/connection/subscriptions.rb +12 -10
- data/lib/action_cable/connection/tagged_logger_proxy.rb +2 -2
- data/lib/action_cable/connection/web_socket.rb +5 -3
- data/lib/action_cable/engine.rb +4 -4
- data/lib/action_cable/gem_version.rb +3 -3
- data/lib/action_cable/helpers/action_cable_helper.rb +1 -1
- data/lib/action_cable/remote_connections.rb +2 -2
- data/lib/action_cable/server.rb +1 -1
- data/lib/action_cable/server/base.rb +5 -5
- data/lib/action_cable/server/broadcasting.rb +7 -3
- data/lib/action_cable/server/configuration.rb +3 -19
- data/lib/action_cable/server/worker.rb +3 -3
- data/lib/action_cable/subscription_adapter.rb +1 -0
- data/lib/action_cable/subscription_adapter/async.rb +1 -1
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +26 -0
- data/lib/action_cable/subscription_adapter/evented_redis.rb +13 -5
- data/lib/action_cable/subscription_adapter/postgresql.rb +4 -4
- data/lib/action_cable/subscription_adapter/redis.rb +9 -7
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +1 -1
- data/lib/action_cable/version.rb +1 -1
- data/lib/assets/compiled/action_cable.js +554 -567
- data/lib/rails/generators/channel/USAGE +2 -2
- data/lib/rails/generators/channel/channel_generator.rb +9 -9
- data/lib/rails/generators/channel/templates/assets/cable.js +1 -1
- metadata +13 -33
- data/lib/action_cable/connection/faye_client_socket.rb +0 -48
- data/lib/action_cable/connection/faye_event_loop.rb +0 -44
@@ -1,11 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require "websocket/driver"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
5
5
|
# Wrap the real socket to minimize the externally-presented API
|
6
6
|
class WebSocket
|
7
|
-
def initialize(env, event_target, event_loop,
|
8
|
-
@websocket = ::WebSocket::Driver.websocket?(env) ?
|
7
|
+
def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols])
|
8
|
+
@websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil
|
9
9
|
end
|
10
10
|
|
11
11
|
def possible?
|
@@ -32,6 +32,8 @@ module ActionCable
|
|
32
32
|
websocket.rack_response
|
33
33
|
end
|
34
34
|
|
35
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
36
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
35
37
|
protected
|
36
38
|
attr_reader :websocket
|
37
39
|
end
|
data/lib/action_cable/engine.rb
CHANGED
@@ -22,7 +22,7 @@ module ActionCable
|
|
22
22
|
|
23
23
|
initializer "action_cable.set_configs" do |app|
|
24
24
|
options = app.config.action_cable
|
25
|
-
options.allowed_request_origins ||=
|
25
|
+
options.allowed_request_origins ||= /https?:\/\/localhost:\d+/ if ::Rails.env.development?
|
26
26
|
|
27
27
|
app.paths.add "config/cable", with: "config/cable.yml"
|
28
28
|
|
@@ -31,10 +31,10 @@ module ActionCable
|
|
31
31
|
self.cable = Rails.application.config_for(config_path).with_indifferent_access
|
32
32
|
end
|
33
33
|
|
34
|
-
previous_connection_class =
|
35
|
-
self.connection_class = -> {
|
34
|
+
previous_connection_class = connection_class
|
35
|
+
self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }
|
36
36
|
|
37
|
-
options.each { |k,v| send("#{k}=", v) }
|
37
|
+
options.each { |k, v| send("#{k}=", v) }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -6,7 +6,7 @@ module ActionCable
|
|
6
6
|
#
|
7
7
|
# <head>
|
8
8
|
# <%= action_cable_meta_tag %>
|
9
|
-
# <%= javascript_include_tag 'application', 'data-turbolinks-track' =>
|
9
|
+
# <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
|
10
10
|
# </head>
|
11
11
|
#
|
12
12
|
# This is then used by Action Cable to determine the URL of your WebSocket server.
|
@@ -41,7 +41,7 @@ module ActionCable
|
|
41
41
|
|
42
42
|
# Uses the internal channel to disconnect the connection.
|
43
43
|
def disconnect
|
44
|
-
server.broadcast internal_channel, type:
|
44
|
+
server.broadcast internal_channel, type: "disconnect"
|
45
45
|
end
|
46
46
|
|
47
47
|
# Returns all the identifiers that were applied to this connection.
|
@@ -54,7 +54,7 @@ module ActionCable
|
|
54
54
|
|
55
55
|
def set_identifier_instance_vars(ids)
|
56
56
|
raise InvalidIdentifiersError unless valid_identifiers?(ids)
|
57
|
-
ids.each { |k,v| instance_variable_set("@#{k}", v) }
|
57
|
+
ids.each { |k, v| instance_variable_set("@#{k}", v) }
|
58
58
|
end
|
59
59
|
|
60
60
|
def valid_identifiers?(ids)
|
data/lib/action_cable/server.rb
CHANGED
@@ -9,7 +9,7 @@ module ActionCable
|
|
9
9
|
autoload :Configuration
|
10
10
|
|
11
11
|
autoload :Worker
|
12
|
-
autoload :ActiveRecordConnectionManagement,
|
12
|
+
autoload :ActiveRecordConnectionManagement, "action_cable/server/worker/active_record_connection_management"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "monitor"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Server
|
@@ -53,20 +53,20 @@ module ActionCable
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def event_loop
|
56
|
-
@event_loop || @mutex.synchronize { @event_loop ||=
|
56
|
+
@event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new }
|
57
57
|
end
|
58
58
|
|
59
59
|
# The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
|
60
60
|
# The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
|
61
|
-
# at 4 worker threads by default. Tune the size yourself with config.action_cable.worker_pool_size
|
61
|
+
# at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>.
|
62
62
|
#
|
63
63
|
# Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
|
64
64
|
# Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
|
65
65
|
# connections.
|
66
66
|
#
|
67
67
|
# Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
|
68
|
-
# the
|
69
|
-
#
|
68
|
+
# the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
|
69
|
+
# database connection pool instead.
|
70
70
|
def worker_pool
|
71
71
|
@worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
|
72
72
|
end
|
@@ -38,9 +38,13 @@ module ActionCable
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def broadcast(message)
|
41
|
-
server.logger.
|
42
|
-
|
43
|
-
|
41
|
+
server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
|
42
|
+
|
43
|
+
payload = { broadcasting: broadcasting, message: message, coder: coder }
|
44
|
+
ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
|
45
|
+
encoded = coder ? coder.encode(message) : message
|
46
|
+
server.pubsub.broadcast broadcasting, encoded
|
47
|
+
end
|
44
48
|
end
|
45
49
|
end
|
46
50
|
end
|
@@ -4,7 +4,7 @@ module ActionCable
|
|
4
4
|
# in a Rails config initializer.
|
5
5
|
class Configuration
|
6
6
|
attr_accessor :logger, :log_tags
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :connection_class, :worker_pool_size
|
8
8
|
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
|
9
9
|
attr_accessor :cable, :url, :mount_path
|
10
10
|
|
@@ -22,7 +22,7 @@ module ActionCable
|
|
22
22
|
# If the adapter cannot be found, this will default to the Redis adapter.
|
23
23
|
# Also makes sure proper dependencies are required.
|
24
24
|
def pubsub_adapter
|
25
|
-
adapter = (cable.fetch(
|
25
|
+
adapter = (cable.fetch("adapter") { "redis" })
|
26
26
|
path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
|
27
27
|
begin
|
28
28
|
require path_to_adapter
|
@@ -33,25 +33,9 @@ module ActionCable
|
|
33
33
|
end
|
34
34
|
|
35
35
|
adapter = adapter.camelize
|
36
|
-
adapter =
|
36
|
+
adapter = "PostgreSQL" if adapter == "Postgresql"
|
37
37
|
"ActionCable::SubscriptionAdapter::#{adapter}".constantize
|
38
38
|
end
|
39
|
-
|
40
|
-
def event_loop_class
|
41
|
-
if use_faye
|
42
|
-
ActionCable::Connection::FayeEventLoop
|
43
|
-
else
|
44
|
-
ActionCable::Connection::StreamEventLoop
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def client_socket_class
|
49
|
-
if use_faye
|
50
|
-
ActionCable::Connection::FayeClientSocket
|
51
|
-
else
|
52
|
-
ActionCable::Connection::ClientSocket
|
53
|
-
end
|
54
|
-
end
|
55
39
|
end
|
56
40
|
end
|
57
41
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "active_support/callbacks"
|
2
|
+
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
3
|
+
require "concurrent"
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module Server
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActionCable
|
2
|
+
module SubscriptionAdapter
|
3
|
+
module ChannelPrefix # :nodoc:
|
4
|
+
def broadcast(channel, payload)
|
5
|
+
channel = channel_with_prefix(channel)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def subscribe(channel, callback, success_callback = nil)
|
10
|
+
channel = channel_with_prefix(channel)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def unsubscribe(channel, callback)
|
15
|
+
channel = channel_with_prefix(channel)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
# Returns the channel name, including channel_prefix specified in cable.yml
|
21
|
+
def channel_with_prefix(channel)
|
22
|
+
[@server.config.cable[:channel_prefix], channel].compact.join(":")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "thread"
|
2
2
|
|
3
|
-
gem
|
4
|
-
gem
|
5
|
-
require
|
6
|
-
require
|
3
|
+
gem "em-hiredis", "~> 0.3.0"
|
4
|
+
gem "redis", "~> 3.0"
|
5
|
+
require "em-hiredis"
|
6
|
+
require "redis"
|
7
7
|
|
8
8
|
EventMachine.epoll if EventMachine.epoll?
|
9
9
|
EventMachine.kqueue if EventMachine.kqueue?
|
@@ -11,6 +11,8 @@ EventMachine.kqueue if EventMachine.kqueue?
|
|
11
11
|
module ActionCable
|
12
12
|
module SubscriptionAdapter
|
13
13
|
class EventedRedis < Base # :nodoc:
|
14
|
+
prepend ChannelPrefix
|
15
|
+
|
14
16
|
@@mutex = Mutex.new
|
15
17
|
|
16
18
|
# Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
|
@@ -22,6 +24,12 @@ module ActionCable
|
|
22
24
|
cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
|
23
25
|
|
24
26
|
def initialize(*)
|
27
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
28
|
+
The "evented_redis" subscription adapter is deprecated and
|
29
|
+
will be removed in Rails 5.2. Please use the "redis" adapter
|
30
|
+
instead.
|
31
|
+
MSG
|
32
|
+
|
25
33
|
super
|
26
34
|
@redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil
|
27
35
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
gem "pg", "
|
2
|
-
require
|
3
|
-
require
|
1
|
+
gem "pg", "~> 0.18"
|
2
|
+
require "pg"
|
3
|
+
require "thread"
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module SubscriptionAdapter
|
@@ -33,7 +33,7 @@ module ActionCable
|
|
33
33
|
pg_conn = ar_conn.raw_connection
|
34
34
|
|
35
35
|
unless pg_conn.is_a?(PG::Connection)
|
36
|
-
raise
|
36
|
+
raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter"
|
37
37
|
end
|
38
38
|
|
39
39
|
yield pg_conn
|
@@ -1,11 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require "thread"
|
2
2
|
|
3
|
-
gem
|
4
|
-
require
|
3
|
+
gem "redis", "~> 3.0"
|
4
|
+
require "redis"
|
5
5
|
|
6
6
|
module ActionCable
|
7
7
|
module SubscriptionAdapter
|
8
8
|
class Redis < Base # :nodoc:
|
9
|
+
prepend ChannelPrefix
|
10
|
+
|
9
11
|
# Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.
|
10
12
|
# This is needed, for example, when using Makara proxies for distributed Redis.
|
11
13
|
cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
|
@@ -72,7 +74,7 @@ module ActionCable
|
|
72
74
|
conn.without_reconnect do
|
73
75
|
original_client = conn.client
|
74
76
|
|
75
|
-
conn.subscribe(
|
77
|
+
conn.subscribe("_action_cable_internal") do |on|
|
76
78
|
on.subscribe do |chan, count|
|
77
79
|
@subscription_lock.synchronize do
|
78
80
|
if count == 1
|
@@ -111,7 +113,7 @@ module ActionCable
|
|
111
113
|
return if @thread.nil?
|
112
114
|
|
113
115
|
when_connected do
|
114
|
-
send_command(
|
116
|
+
send_command("unsubscribe")
|
115
117
|
@raw_client = nil
|
116
118
|
end
|
117
119
|
end
|
@@ -123,13 +125,13 @@ module ActionCable
|
|
123
125
|
@subscription_lock.synchronize do
|
124
126
|
ensure_listener_running
|
125
127
|
@subscribe_callbacks[channel] << on_success
|
126
|
-
when_connected { send_command(
|
128
|
+
when_connected { send_command("subscribe", channel) }
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
130
132
|
def remove_channel(channel)
|
131
133
|
@subscription_lock.synchronize do
|
132
|
-
when_connected { send_command(
|
134
|
+
when_connected { send_command("unsubscribe", channel) }
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
data/lib/action_cable/version.rb
CHANGED
@@ -1,597 +1,584 @@
|
|
1
1
|
(function() {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
}).call(this);
|
58
|
-
}).call(this);
|
59
|
-
|
60
|
-
var ActionCable = this.ActionCable;
|
61
|
-
|
62
|
-
(function() {
|
63
|
-
(function() {
|
64
|
-
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
65
|
-
|
66
|
-
ActionCable.ConnectionMonitor = (function() {
|
67
|
-
var clamp, now, secondsSince;
|
68
|
-
|
69
|
-
ConnectionMonitor.pollInterval = {
|
70
|
-
min: 3,
|
71
|
-
max: 30
|
72
|
-
};
|
2
|
+
var slice = [].slice;
|
3
|
+
|
4
|
+
this.ActionCable = {
|
5
|
+
INTERNAL: {
|
6
|
+
"message_types": {
|
7
|
+
"welcome": "welcome",
|
8
|
+
"ping": "ping",
|
9
|
+
"confirmation": "confirm_subscription",
|
10
|
+
"rejection": "reject_subscription"
|
11
|
+
},
|
12
|
+
"default_mount_path": "/cable",
|
13
|
+
"protocols": ["actioncable-v1-json", "actioncable-unsupported"]
|
14
|
+
},
|
15
|
+
WebSocket: window.WebSocket,
|
16
|
+
logger: window.console,
|
17
|
+
createConsumer: function(url) {
|
18
|
+
var ref;
|
19
|
+
if (url == null) {
|
20
|
+
url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
|
21
|
+
}
|
22
|
+
return new ActionCable.Consumer(this.createWebSocketURL(url));
|
23
|
+
},
|
24
|
+
getConfig: function(name) {
|
25
|
+
var element;
|
26
|
+
element = document.head.querySelector("meta[name='action-cable-" + name + "']");
|
27
|
+
return element != null ? element.getAttribute("content") : void 0;
|
28
|
+
},
|
29
|
+
createWebSocketURL: function(url) {
|
30
|
+
var a;
|
31
|
+
if (url && !/^wss?:/i.test(url)) {
|
32
|
+
a = document.createElement("a");
|
33
|
+
a.href = url;
|
34
|
+
a.href = a.href;
|
35
|
+
a.protocol = a.protocol.replace("http", "ws");
|
36
|
+
return a.href;
|
37
|
+
} else {
|
38
|
+
return url;
|
39
|
+
}
|
40
|
+
},
|
41
|
+
startDebugging: function() {
|
42
|
+
return this.debugging = true;
|
43
|
+
},
|
44
|
+
stopDebugging: function() {
|
45
|
+
return this.debugging = null;
|
46
|
+
},
|
47
|
+
log: function() {
|
48
|
+
var messages, ref;
|
49
|
+
messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
50
|
+
if (this.debugging) {
|
51
|
+
messages.push(Date.now());
|
52
|
+
return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
|
53
|
+
}
|
54
|
+
}
|
55
|
+
};
|
73
56
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
57
|
+
}).call(this);
|
58
|
+
(function() {
|
59
|
+
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
60
|
+
|
61
|
+
ActionCable.ConnectionMonitor = (function() {
|
62
|
+
var clamp, now, secondsSince;
|
63
|
+
|
64
|
+
ConnectionMonitor.pollInterval = {
|
65
|
+
min: 3,
|
66
|
+
max: 30
|
67
|
+
};
|
68
|
+
|
69
|
+
ConnectionMonitor.staleThreshold = 6;
|
70
|
+
|
71
|
+
function ConnectionMonitor(connection) {
|
72
|
+
this.connection = connection;
|
73
|
+
this.visibilityDidChange = bind(this.visibilityDidChange, this);
|
74
|
+
this.reconnectAttempts = 0;
|
75
|
+
}
|
76
|
+
|
77
|
+
ConnectionMonitor.prototype.start = function() {
|
78
|
+
if (!this.isRunning()) {
|
79
|
+
this.startedAt = now();
|
80
|
+
delete this.stoppedAt;
|
81
|
+
this.startPolling();
|
82
|
+
document.addEventListener("visibilitychange", this.visibilityDidChange);
|
83
|
+
return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
ConnectionMonitor.prototype.stop = function() {
|
88
|
+
if (this.isRunning()) {
|
89
|
+
this.stoppedAt = now();
|
90
|
+
this.stopPolling();
|
91
|
+
document.removeEventListener("visibilitychange", this.visibilityDidChange);
|
92
|
+
return ActionCable.log("ConnectionMonitor stopped");
|
93
|
+
}
|
94
|
+
};
|
95
|
+
|
96
|
+
ConnectionMonitor.prototype.isRunning = function() {
|
97
|
+
return (this.startedAt != null) && (this.stoppedAt == null);
|
98
|
+
};
|
99
|
+
|
100
|
+
ConnectionMonitor.prototype.recordPing = function() {
|
101
|
+
return this.pingedAt = now();
|
102
|
+
};
|
103
|
+
|
104
|
+
ConnectionMonitor.prototype.recordConnect = function() {
|
105
|
+
this.reconnectAttempts = 0;
|
106
|
+
this.recordPing();
|
107
|
+
delete this.disconnectedAt;
|
108
|
+
return ActionCable.log("ConnectionMonitor recorded connect");
|
109
|
+
};
|
110
|
+
|
111
|
+
ConnectionMonitor.prototype.recordDisconnect = function() {
|
112
|
+
this.disconnectedAt = now();
|
113
|
+
return ActionCable.log("ConnectionMonitor recorded disconnect");
|
114
|
+
};
|
115
|
+
|
116
|
+
ConnectionMonitor.prototype.startPolling = function() {
|
117
|
+
this.stopPolling();
|
118
|
+
return this.poll();
|
119
|
+
};
|
120
|
+
|
121
|
+
ConnectionMonitor.prototype.stopPolling = function() {
|
122
|
+
return clearTimeout(this.pollTimeout);
|
123
|
+
};
|
124
|
+
|
125
|
+
ConnectionMonitor.prototype.poll = function() {
|
126
|
+
return this.pollTimeout = setTimeout((function(_this) {
|
127
|
+
return function() {
|
128
|
+
_this.reconnectIfStale();
|
129
|
+
return _this.poll();
|
130
|
+
};
|
131
|
+
})(this), this.getPollInterval());
|
132
|
+
};
|
133
|
+
|
134
|
+
ConnectionMonitor.prototype.getPollInterval = function() {
|
135
|
+
var interval, max, min, ref;
|
136
|
+
ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
|
137
|
+
interval = 5 * Math.log(this.reconnectAttempts + 1);
|
138
|
+
return Math.round(clamp(interval, min, max) * 1000);
|
139
|
+
};
|
140
|
+
|
141
|
+
ConnectionMonitor.prototype.reconnectIfStale = function() {
|
142
|
+
if (this.connectionIsStale()) {
|
143
|
+
ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
|
144
|
+
this.reconnectAttempts++;
|
145
|
+
if (this.disconnectedRecently()) {
|
146
|
+
return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
|
147
|
+
} else {
|
148
|
+
ActionCable.log("ConnectionMonitor reopening");
|
149
|
+
return this.connection.reopen();
|
80
150
|
}
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
ConnectionMonitor.prototype.isRunning = function() {
|
102
|
-
return (this.startedAt != null) && (this.stoppedAt == null);
|
103
|
-
};
|
104
|
-
|
105
|
-
ConnectionMonitor.prototype.recordPing = function() {
|
106
|
-
return this.pingedAt = now();
|
107
|
-
};
|
108
|
-
|
109
|
-
ConnectionMonitor.prototype.recordConnect = function() {
|
110
|
-
this.reconnectAttempts = 0;
|
111
|
-
this.recordPing();
|
112
|
-
delete this.disconnectedAt;
|
113
|
-
return ActionCable.log("ConnectionMonitor recorded connect");
|
114
|
-
};
|
115
|
-
|
116
|
-
ConnectionMonitor.prototype.recordDisconnect = function() {
|
117
|
-
this.disconnectedAt = now();
|
118
|
-
return ActionCable.log("ConnectionMonitor recorded disconnect");
|
119
|
-
};
|
120
|
-
|
121
|
-
ConnectionMonitor.prototype.startPolling = function() {
|
122
|
-
this.stopPolling();
|
123
|
-
return this.poll();
|
124
|
-
};
|
125
|
-
|
126
|
-
ConnectionMonitor.prototype.stopPolling = function() {
|
127
|
-
return clearTimeout(this.pollTimeout);
|
128
|
-
};
|
129
|
-
|
130
|
-
ConnectionMonitor.prototype.poll = function() {
|
131
|
-
return this.pollTimeout = setTimeout((function(_this) {
|
132
|
-
return function() {
|
133
|
-
_this.reconnectIfStale();
|
134
|
-
return _this.poll();
|
135
|
-
};
|
136
|
-
})(this), this.getPollInterval());
|
137
|
-
};
|
138
|
-
|
139
|
-
ConnectionMonitor.prototype.getPollInterval = function() {
|
140
|
-
var interval, max, min, ref;
|
141
|
-
ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
|
142
|
-
interval = 5 * Math.log(this.reconnectAttempts + 1);
|
143
|
-
return Math.round(clamp(interval, min, max) * 1000);
|
144
|
-
};
|
145
|
-
|
146
|
-
ConnectionMonitor.prototype.reconnectIfStale = function() {
|
147
|
-
if (this.connectionIsStale()) {
|
148
|
-
ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
|
149
|
-
this.reconnectAttempts++;
|
150
|
-
if (this.disconnectedRecently()) {
|
151
|
-
return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
|
152
|
-
} else {
|
153
|
-
ActionCable.log("ConnectionMonitor reopening");
|
154
|
-
return this.connection.reopen();
|
151
|
+
}
|
152
|
+
};
|
153
|
+
|
154
|
+
ConnectionMonitor.prototype.connectionIsStale = function() {
|
155
|
+
var ref;
|
156
|
+
return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
|
157
|
+
};
|
158
|
+
|
159
|
+
ConnectionMonitor.prototype.disconnectedRecently = function() {
|
160
|
+
return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
|
161
|
+
};
|
162
|
+
|
163
|
+
ConnectionMonitor.prototype.visibilityDidChange = function() {
|
164
|
+
if (document.visibilityState === "visible") {
|
165
|
+
return setTimeout((function(_this) {
|
166
|
+
return function() {
|
167
|
+
if (_this.connectionIsStale() || !_this.connection.isOpen()) {
|
168
|
+
ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
|
169
|
+
return _this.connection.reopen();
|
155
170
|
}
|
156
|
-
}
|
157
|
-
};
|
158
|
-
|
159
|
-
|
160
|
-
var ref;
|
161
|
-
return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
|
162
|
-
};
|
163
|
-
|
164
|
-
ConnectionMonitor.prototype.disconnectedRecently = function() {
|
165
|
-
return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
|
166
|
-
};
|
167
|
-
|
168
|
-
ConnectionMonitor.prototype.visibilityDidChange = function() {
|
169
|
-
if (document.visibilityState === "visible") {
|
170
|
-
return setTimeout((function(_this) {
|
171
|
-
return function() {
|
172
|
-
if (_this.connectionIsStale() || !_this.connection.isOpen()) {
|
173
|
-
ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
|
174
|
-
return _this.connection.reopen();
|
175
|
-
}
|
176
|
-
};
|
177
|
-
})(this), 200);
|
178
|
-
}
|
179
|
-
};
|
180
|
-
|
181
|
-
now = function() {
|
182
|
-
return new Date().getTime();
|
183
|
-
};
|
184
|
-
|
185
|
-
secondsSince = function(time) {
|
186
|
-
return (now() - time) / 1000;
|
187
|
-
};
|
188
|
-
|
189
|
-
clamp = function(number, min, max) {
|
190
|
-
return Math.max(min, Math.min(max, number));
|
191
|
-
};
|
192
|
-
|
193
|
-
return ConnectionMonitor;
|
171
|
+
};
|
172
|
+
})(this), 200);
|
173
|
+
}
|
174
|
+
};
|
194
175
|
|
195
|
-
|
176
|
+
now = function() {
|
177
|
+
return new Date().getTime();
|
178
|
+
};
|
196
179
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
slice = [].slice,
|
201
|
-
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
202
|
-
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
180
|
+
secondsSince = function(time) {
|
181
|
+
return (now() - time) / 1000;
|
182
|
+
};
|
203
183
|
|
204
|
-
|
184
|
+
clamp = function(number, min, max) {
|
185
|
+
return Math.max(min, Math.min(max, number));
|
186
|
+
};
|
205
187
|
|
206
|
-
|
188
|
+
return ConnectionMonitor;
|
207
189
|
|
208
|
-
|
209
|
-
Connection.reopenDelay = 500;
|
190
|
+
})();
|
210
191
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
192
|
+
}).call(this);
|
193
|
+
(function() {
|
194
|
+
var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
|
195
|
+
slice = [].slice,
|
196
|
+
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
197
|
+
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
198
|
+
|
199
|
+
ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
|
200
|
+
|
201
|
+
supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
|
202
|
+
|
203
|
+
ActionCable.Connection = (function() {
|
204
|
+
Connection.reopenDelay = 500;
|
205
|
+
|
206
|
+
function Connection(consumer) {
|
207
|
+
this.consumer = consumer;
|
208
|
+
this.open = bind(this.open, this);
|
209
|
+
this.subscriptions = this.consumer.subscriptions;
|
210
|
+
this.monitor = new ActionCable.ConnectionMonitor(this);
|
211
|
+
this.disconnected = true;
|
212
|
+
}
|
213
|
+
|
214
|
+
Connection.prototype.send = function(data) {
|
215
|
+
if (this.isOpen()) {
|
216
|
+
this.webSocket.send(JSON.stringify(data));
|
217
|
+
return true;
|
218
|
+
} else {
|
219
|
+
return false;
|
220
|
+
}
|
221
|
+
};
|
222
|
+
|
223
|
+
Connection.prototype.open = function() {
|
224
|
+
if (this.isActive()) {
|
225
|
+
ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
|
226
|
+
return false;
|
227
|
+
} else {
|
228
|
+
ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
|
229
|
+
if (this.webSocket != null) {
|
230
|
+
this.uninstallEventHandlers();
|
217
231
|
}
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
this.monitor.stop();
|
251
|
-
}
|
252
|
-
if (this.isActive()) {
|
253
|
-
return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
|
254
|
-
}
|
255
|
-
};
|
256
|
-
|
257
|
-
Connection.prototype.reopen = function() {
|
258
|
-
var error;
|
259
|
-
ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
|
260
|
-
if (this.isActive()) {
|
261
|
-
try {
|
262
|
-
return this.close();
|
263
|
-
} catch (error1) {
|
264
|
-
error = error1;
|
265
|
-
return ActionCable.log("Failed to reopen WebSocket", error);
|
266
|
-
} finally {
|
267
|
-
ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
|
268
|
-
setTimeout(this.open, this.constructor.reopenDelay);
|
269
|
-
}
|
270
|
-
} else {
|
271
|
-
return this.open();
|
272
|
-
}
|
273
|
-
};
|
274
|
-
|
275
|
-
Connection.prototype.getProtocol = function() {
|
276
|
-
var ref1;
|
277
|
-
return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
|
278
|
-
};
|
279
|
-
|
280
|
-
Connection.prototype.isOpen = function() {
|
281
|
-
return this.isState("open");
|
282
|
-
};
|
283
|
-
|
284
|
-
Connection.prototype.isActive = function() {
|
285
|
-
return this.isState("open", "connecting");
|
286
|
-
};
|
287
|
-
|
288
|
-
Connection.prototype.isProtocolSupported = function() {
|
289
|
-
var ref1;
|
290
|
-
return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
|
291
|
-
};
|
292
|
-
|
293
|
-
Connection.prototype.isState = function() {
|
294
|
-
var ref1, states;
|
295
|
-
states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
296
|
-
return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
|
297
|
-
};
|
298
|
-
|
299
|
-
Connection.prototype.getState = function() {
|
300
|
-
var ref1, state, value;
|
301
|
-
for (state in WebSocket) {
|
302
|
-
value = WebSocket[state];
|
303
|
-
if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
|
304
|
-
return state.toLowerCase();
|
305
|
-
}
|
306
|
-
}
|
307
|
-
return null;
|
308
|
-
};
|
309
|
-
|
310
|
-
Connection.prototype.installEventHandlers = function() {
|
311
|
-
var eventName, handler;
|
312
|
-
for (eventName in this.events) {
|
313
|
-
handler = this.events[eventName].bind(this);
|
314
|
-
this.webSocket["on" + eventName] = handler;
|
315
|
-
}
|
316
|
-
};
|
317
|
-
|
318
|
-
Connection.prototype.uninstallEventHandlers = function() {
|
319
|
-
var eventName;
|
320
|
-
for (eventName in this.events) {
|
321
|
-
this.webSocket["on" + eventName] = function() {};
|
322
|
-
}
|
323
|
-
};
|
324
|
-
|
325
|
-
Connection.prototype.events = {
|
326
|
-
message: function(event) {
|
327
|
-
var identifier, message, ref1, type;
|
328
|
-
if (!this.isProtocolSupported()) {
|
329
|
-
return;
|
330
|
-
}
|
331
|
-
ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
|
332
|
-
switch (type) {
|
333
|
-
case message_types.welcome:
|
334
|
-
this.monitor.recordConnect();
|
335
|
-
return this.subscriptions.reload();
|
336
|
-
case message_types.ping:
|
337
|
-
return this.monitor.recordPing();
|
338
|
-
case message_types.confirmation:
|
339
|
-
return this.subscriptions.notify(identifier, "connected");
|
340
|
-
case message_types.rejection:
|
341
|
-
return this.subscriptions.reject(identifier);
|
342
|
-
default:
|
343
|
-
return this.subscriptions.notify(identifier, "received", message);
|
344
|
-
}
|
345
|
-
},
|
346
|
-
open: function() {
|
347
|
-
ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
|
348
|
-
this.disconnected = false;
|
349
|
-
if (!this.isProtocolSupported()) {
|
350
|
-
ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
|
351
|
-
return this.close({
|
352
|
-
allowReconnect: false
|
353
|
-
});
|
354
|
-
}
|
355
|
-
},
|
356
|
-
close: function(event) {
|
357
|
-
ActionCable.log("WebSocket onclose event");
|
358
|
-
if (this.disconnected) {
|
359
|
-
return;
|
360
|
-
}
|
361
|
-
this.disconnected = true;
|
362
|
-
this.monitor.recordDisconnect();
|
363
|
-
return this.subscriptions.notifyAll("disconnected", {
|
364
|
-
willAttemptReconnect: this.monitor.isRunning()
|
365
|
-
});
|
366
|
-
},
|
367
|
-
error: function() {
|
368
|
-
return ActionCable.log("WebSocket onerror event");
|
369
|
-
}
|
370
|
-
};
|
371
|
-
|
372
|
-
return Connection;
|
373
|
-
|
374
|
-
})();
|
375
|
-
|
376
|
-
}).call(this);
|
377
|
-
(function() {
|
378
|
-
var slice = [].slice;
|
379
|
-
|
380
|
-
ActionCable.Subscriptions = (function() {
|
381
|
-
function Subscriptions(consumer) {
|
382
|
-
this.consumer = consumer;
|
383
|
-
this.subscriptions = [];
|
232
|
+
this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
|
233
|
+
this.installEventHandlers();
|
234
|
+
this.monitor.start();
|
235
|
+
return true;
|
236
|
+
}
|
237
|
+
};
|
238
|
+
|
239
|
+
Connection.prototype.close = function(arg) {
|
240
|
+
var allowReconnect, ref1;
|
241
|
+
allowReconnect = (arg != null ? arg : {
|
242
|
+
allowReconnect: true
|
243
|
+
}).allowReconnect;
|
244
|
+
if (!allowReconnect) {
|
245
|
+
this.monitor.stop();
|
246
|
+
}
|
247
|
+
if (this.isActive()) {
|
248
|
+
return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
|
249
|
+
}
|
250
|
+
};
|
251
|
+
|
252
|
+
Connection.prototype.reopen = function() {
|
253
|
+
var error;
|
254
|
+
ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
|
255
|
+
if (this.isActive()) {
|
256
|
+
try {
|
257
|
+
return this.close();
|
258
|
+
} catch (error1) {
|
259
|
+
error = error1;
|
260
|
+
return ActionCable.log("Failed to reopen WebSocket", error);
|
261
|
+
} finally {
|
262
|
+
ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
|
263
|
+
setTimeout(this.open, this.constructor.reopenDelay);
|
384
264
|
}
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
469
|
-
ref = this.subscriptions;
|
470
|
-
results = [];
|
471
|
-
for (i = 0, len = ref.length; i < len; i++) {
|
472
|
-
subscription = ref[i];
|
473
|
-
results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
|
474
|
-
}
|
475
|
-
return results;
|
476
|
-
};
|
477
|
-
|
478
|
-
Subscriptions.prototype.notify = function() {
|
479
|
-
var args, callbackName, i, len, results, subscription, subscriptions;
|
480
|
-
subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
|
481
|
-
if (typeof subscription === "string") {
|
482
|
-
subscriptions = this.findAll(subscription);
|
483
|
-
} else {
|
484
|
-
subscriptions = [subscription];
|
485
|
-
}
|
486
|
-
results = [];
|
487
|
-
for (i = 0, len = subscriptions.length; i < len; i++) {
|
488
|
-
subscription = subscriptions[i];
|
489
|
-
results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
|
490
|
-
}
|
491
|
-
return results;
|
492
|
-
};
|
493
|
-
|
494
|
-
Subscriptions.prototype.sendCommand = function(subscription, command) {
|
495
|
-
var identifier;
|
496
|
-
identifier = subscription.identifier;
|
497
|
-
return this.consumer.send({
|
498
|
-
command: command,
|
499
|
-
identifier: identifier
|
265
|
+
} else {
|
266
|
+
return this.open();
|
267
|
+
}
|
268
|
+
};
|
269
|
+
|
270
|
+
Connection.prototype.getProtocol = function() {
|
271
|
+
var ref1;
|
272
|
+
return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
|
273
|
+
};
|
274
|
+
|
275
|
+
Connection.prototype.isOpen = function() {
|
276
|
+
return this.isState("open");
|
277
|
+
};
|
278
|
+
|
279
|
+
Connection.prototype.isActive = function() {
|
280
|
+
return this.isState("open", "connecting");
|
281
|
+
};
|
282
|
+
|
283
|
+
Connection.prototype.isProtocolSupported = function() {
|
284
|
+
var ref1;
|
285
|
+
return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
|
286
|
+
};
|
287
|
+
|
288
|
+
Connection.prototype.isState = function() {
|
289
|
+
var ref1, states;
|
290
|
+
states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
291
|
+
return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
|
292
|
+
};
|
293
|
+
|
294
|
+
Connection.prototype.getState = function() {
|
295
|
+
var ref1, state, value;
|
296
|
+
for (state in WebSocket) {
|
297
|
+
value = WebSocket[state];
|
298
|
+
if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
|
299
|
+
return state.toLowerCase();
|
300
|
+
}
|
301
|
+
}
|
302
|
+
return null;
|
303
|
+
};
|
304
|
+
|
305
|
+
Connection.prototype.installEventHandlers = function() {
|
306
|
+
var eventName, handler;
|
307
|
+
for (eventName in this.events) {
|
308
|
+
handler = this.events[eventName].bind(this);
|
309
|
+
this.webSocket["on" + eventName] = handler;
|
310
|
+
}
|
311
|
+
};
|
312
|
+
|
313
|
+
Connection.prototype.uninstallEventHandlers = function() {
|
314
|
+
var eventName;
|
315
|
+
for (eventName in this.events) {
|
316
|
+
this.webSocket["on" + eventName] = function() {};
|
317
|
+
}
|
318
|
+
};
|
319
|
+
|
320
|
+
Connection.prototype.events = {
|
321
|
+
message: function(event) {
|
322
|
+
var identifier, message, ref1, type;
|
323
|
+
if (!this.isProtocolSupported()) {
|
324
|
+
return;
|
325
|
+
}
|
326
|
+
ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
|
327
|
+
switch (type) {
|
328
|
+
case message_types.welcome:
|
329
|
+
this.monitor.recordConnect();
|
330
|
+
return this.subscriptions.reload();
|
331
|
+
case message_types.ping:
|
332
|
+
return this.monitor.recordPing();
|
333
|
+
case message_types.confirmation:
|
334
|
+
return this.subscriptions.notify(identifier, "connected");
|
335
|
+
case message_types.rejection:
|
336
|
+
return this.subscriptions.reject(identifier);
|
337
|
+
default:
|
338
|
+
return this.subscriptions.notify(identifier, "received", message);
|
339
|
+
}
|
340
|
+
},
|
341
|
+
open: function() {
|
342
|
+
ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
|
343
|
+
this.disconnected = false;
|
344
|
+
if (!this.isProtocolSupported()) {
|
345
|
+
ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
|
346
|
+
return this.close({
|
347
|
+
allowReconnect: false
|
500
348
|
});
|
501
|
-
};
|
502
|
-
|
503
|
-
return Subscriptions;
|
504
|
-
|
505
|
-
})();
|
506
|
-
|
507
|
-
}).call(this);
|
508
|
-
(function() {
|
509
|
-
ActionCable.Subscription = (function() {
|
510
|
-
var extend;
|
511
|
-
|
512
|
-
function Subscription(consumer, params, mixin) {
|
513
|
-
this.consumer = consumer;
|
514
|
-
if (params == null) {
|
515
|
-
params = {};
|
516
|
-
}
|
517
|
-
this.identifier = JSON.stringify(params);
|
518
|
-
extend(this, mixin);
|
519
349
|
}
|
350
|
+
},
|
351
|
+
close: function(event) {
|
352
|
+
ActionCable.log("WebSocket onclose event");
|
353
|
+
if (this.disconnected) {
|
354
|
+
return;
|
355
|
+
}
|
356
|
+
this.disconnected = true;
|
357
|
+
this.monitor.recordDisconnect();
|
358
|
+
return this.subscriptions.notifyAll("disconnected", {
|
359
|
+
willAttemptReconnect: this.monitor.isRunning()
|
360
|
+
});
|
361
|
+
},
|
362
|
+
error: function() {
|
363
|
+
return ActionCable.log("WebSocket onerror event");
|
364
|
+
}
|
365
|
+
};
|
520
366
|
|
521
|
-
|
522
|
-
if (data == null) {
|
523
|
-
data = {};
|
524
|
-
}
|
525
|
-
data.action = action;
|
526
|
-
return this.send(data);
|
527
|
-
};
|
528
|
-
|
529
|
-
Subscription.prototype.send = function(data) {
|
530
|
-
return this.consumer.send({
|
531
|
-
command: "message",
|
532
|
-
identifier: this.identifier,
|
533
|
-
data: JSON.stringify(data)
|
534
|
-
});
|
535
|
-
};
|
367
|
+
return Connection;
|
536
368
|
|
537
|
-
|
538
|
-
return this.consumer.subscriptions.remove(this);
|
539
|
-
};
|
369
|
+
})();
|
540
370
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
371
|
+
}).call(this);
|
372
|
+
(function() {
|
373
|
+
var slice = [].slice;
|
374
|
+
|
375
|
+
ActionCable.Subscriptions = (function() {
|
376
|
+
function Subscriptions(consumer) {
|
377
|
+
this.consumer = consumer;
|
378
|
+
this.subscriptions = [];
|
379
|
+
}
|
380
|
+
|
381
|
+
Subscriptions.prototype.create = function(channelName, mixin) {
|
382
|
+
var channel, params, subscription;
|
383
|
+
channel = channelName;
|
384
|
+
params = typeof channel === "object" ? channel : {
|
385
|
+
channel: channel
|
386
|
+
};
|
387
|
+
subscription = new ActionCable.Subscription(this.consumer, params, mixin);
|
388
|
+
return this.add(subscription);
|
389
|
+
};
|
390
|
+
|
391
|
+
Subscriptions.prototype.add = function(subscription) {
|
392
|
+
this.subscriptions.push(subscription);
|
393
|
+
this.consumer.ensureActiveConnection();
|
394
|
+
this.notify(subscription, "initialized");
|
395
|
+
this.sendCommand(subscription, "subscribe");
|
396
|
+
return subscription;
|
397
|
+
};
|
398
|
+
|
399
|
+
Subscriptions.prototype.remove = function(subscription) {
|
400
|
+
this.forget(subscription);
|
401
|
+
if (!this.findAll(subscription.identifier).length) {
|
402
|
+
this.sendCommand(subscription, "unsubscribe");
|
403
|
+
}
|
404
|
+
return subscription;
|
405
|
+
};
|
406
|
+
|
407
|
+
Subscriptions.prototype.reject = function(identifier) {
|
408
|
+
var i, len, ref, results, subscription;
|
409
|
+
ref = this.findAll(identifier);
|
410
|
+
results = [];
|
411
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
412
|
+
subscription = ref[i];
|
413
|
+
this.forget(subscription);
|
414
|
+
this.notify(subscription, "rejected");
|
415
|
+
results.push(subscription);
|
416
|
+
}
|
417
|
+
return results;
|
418
|
+
};
|
419
|
+
|
420
|
+
Subscriptions.prototype.forget = function(subscription) {
|
421
|
+
var s;
|
422
|
+
this.subscriptions = (function() {
|
423
|
+
var i, len, ref, results;
|
424
|
+
ref = this.subscriptions;
|
425
|
+
results = [];
|
426
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
427
|
+
s = ref[i];
|
428
|
+
if (s !== subscription) {
|
429
|
+
results.push(s);
|
548
430
|
}
|
549
|
-
|
550
|
-
|
431
|
+
}
|
432
|
+
return results;
|
433
|
+
}).call(this);
|
434
|
+
return subscription;
|
435
|
+
};
|
436
|
+
|
437
|
+
Subscriptions.prototype.findAll = function(identifier) {
|
438
|
+
var i, len, ref, results, s;
|
439
|
+
ref = this.subscriptions;
|
440
|
+
results = [];
|
441
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
442
|
+
s = ref[i];
|
443
|
+
if (s.identifier === identifier) {
|
444
|
+
results.push(s);
|
445
|
+
}
|
446
|
+
}
|
447
|
+
return results;
|
448
|
+
};
|
449
|
+
|
450
|
+
Subscriptions.prototype.reload = function() {
|
451
|
+
var i, len, ref, results, subscription;
|
452
|
+
ref = this.subscriptions;
|
453
|
+
results = [];
|
454
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
455
|
+
subscription = ref[i];
|
456
|
+
results.push(this.sendCommand(subscription, "subscribe"));
|
457
|
+
}
|
458
|
+
return results;
|
459
|
+
};
|
460
|
+
|
461
|
+
Subscriptions.prototype.notifyAll = function() {
|
462
|
+
var args, callbackName, i, len, ref, results, subscription;
|
463
|
+
callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
464
|
+
ref = this.subscriptions;
|
465
|
+
results = [];
|
466
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
467
|
+
subscription = ref[i];
|
468
|
+
results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
|
469
|
+
}
|
470
|
+
return results;
|
471
|
+
};
|
472
|
+
|
473
|
+
Subscriptions.prototype.notify = function() {
|
474
|
+
var args, callbackName, i, len, results, subscription, subscriptions;
|
475
|
+
subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
|
476
|
+
if (typeof subscription === "string") {
|
477
|
+
subscriptions = this.findAll(subscription);
|
478
|
+
} else {
|
479
|
+
subscriptions = [subscription];
|
480
|
+
}
|
481
|
+
results = [];
|
482
|
+
for (i = 0, len = subscriptions.length; i < len; i++) {
|
483
|
+
subscription = subscriptions[i];
|
484
|
+
results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
|
485
|
+
}
|
486
|
+
return results;
|
487
|
+
};
|
488
|
+
|
489
|
+
Subscriptions.prototype.sendCommand = function(subscription, command) {
|
490
|
+
var identifier;
|
491
|
+
identifier = subscription.identifier;
|
492
|
+
return this.consumer.send({
|
493
|
+
command: command,
|
494
|
+
identifier: identifier
|
495
|
+
});
|
496
|
+
};
|
497
|
+
|
498
|
+
return Subscriptions;
|
499
|
+
|
500
|
+
})();
|
551
501
|
|
552
|
-
|
502
|
+
}).call(this);
|
503
|
+
(function() {
|
504
|
+
ActionCable.Subscription = (function() {
|
505
|
+
var extend;
|
506
|
+
|
507
|
+
function Subscription(consumer, params, mixin) {
|
508
|
+
this.consumer = consumer;
|
509
|
+
if (params == null) {
|
510
|
+
params = {};
|
511
|
+
}
|
512
|
+
this.identifier = JSON.stringify(params);
|
513
|
+
extend(this, mixin);
|
514
|
+
}
|
515
|
+
|
516
|
+
Subscription.prototype.perform = function(action, data) {
|
517
|
+
if (data == null) {
|
518
|
+
data = {};
|
519
|
+
}
|
520
|
+
data.action = action;
|
521
|
+
return this.send(data);
|
522
|
+
};
|
523
|
+
|
524
|
+
Subscription.prototype.send = function(data) {
|
525
|
+
return this.consumer.send({
|
526
|
+
command: "message",
|
527
|
+
identifier: this.identifier,
|
528
|
+
data: JSON.stringify(data)
|
529
|
+
});
|
530
|
+
};
|
531
|
+
|
532
|
+
Subscription.prototype.unsubscribe = function() {
|
533
|
+
return this.consumer.subscriptions.remove(this);
|
534
|
+
};
|
535
|
+
|
536
|
+
extend = function(object, properties) {
|
537
|
+
var key, value;
|
538
|
+
if (properties != null) {
|
539
|
+
for (key in properties) {
|
540
|
+
value = properties[key];
|
541
|
+
object[key] = value;
|
542
|
+
}
|
543
|
+
}
|
544
|
+
return object;
|
545
|
+
};
|
553
546
|
|
554
|
-
|
547
|
+
return Subscription;
|
555
548
|
|
556
|
-
|
557
|
-
(function() {
|
558
|
-
ActionCable.Consumer = (function() {
|
559
|
-
function Consumer(url) {
|
560
|
-
this.url = url;
|
561
|
-
this.subscriptions = new ActionCable.Subscriptions(this);
|
562
|
-
this.connection = new ActionCable.Connection(this);
|
563
|
-
}
|
549
|
+
})();
|
564
550
|
|
565
|
-
|
566
|
-
|
567
|
-
|
551
|
+
}).call(this);
|
552
|
+
(function() {
|
553
|
+
ActionCable.Consumer = (function() {
|
554
|
+
function Consumer(url) {
|
555
|
+
this.url = url;
|
556
|
+
this.subscriptions = new ActionCable.Subscriptions(this);
|
557
|
+
this.connection = new ActionCable.Connection(this);
|
558
|
+
}
|
568
559
|
|
569
|
-
|
570
|
-
|
571
|
-
|
560
|
+
Consumer.prototype.send = function(data) {
|
561
|
+
return this.connection.send(data);
|
562
|
+
};
|
572
563
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
});
|
577
|
-
};
|
564
|
+
Consumer.prototype.connect = function() {
|
565
|
+
return this.connection.open();
|
566
|
+
};
|
578
567
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
568
|
+
Consumer.prototype.disconnect = function() {
|
569
|
+
return this.connection.close({
|
570
|
+
allowReconnect: false
|
571
|
+
});
|
572
|
+
};
|
584
573
|
|
585
|
-
|
574
|
+
Consumer.prototype.ensureActiveConnection = function() {
|
575
|
+
if (!this.connection.isActive()) {
|
576
|
+
return this.connection.open();
|
577
|
+
}
|
578
|
+
};
|
586
579
|
|
587
|
-
|
580
|
+
return Consumer;
|
588
581
|
|
589
|
-
|
590
|
-
}).call(this);
|
582
|
+
})();
|
591
583
|
|
592
|
-
if (typeof module === "object" && module.exports) {
|
593
|
-
module.exports = ActionCable;
|
594
|
-
} else if (typeof define === "function" && define.amd) {
|
595
|
-
define(ActionCable);
|
596
|
-
}
|
597
584
|
}).call(this);
|