websocket-rails 0.6.2 → 0.7.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 +32 -0
- data/Gemfile +2 -1
- data/README.md +29 -34
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +34 -17
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +13 -11
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +44 -45
- data/lib/assets/javascripts/websocket_rails/main.js +1 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +20 -34
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +60 -15
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +15 -0
- data/lib/rails/config/routes.rb +1 -1
- data/lib/rails/tasks/websocket_rails.tasks +6 -2
- data/lib/websocket_rails/channel.rb +28 -2
- data/lib/websocket_rails/channel_manager.rb +16 -0
- data/lib/websocket_rails/configuration.rb +26 -1
- data/lib/websocket_rails/connection_adapters/http.rb +7 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +3 -1
- data/lib/websocket_rails/connection_manager.rb +1 -1
- data/lib/websocket_rails/controller_factory.rb +1 -1
- data/lib/websocket_rails/event.rb +9 -2
- data/lib/websocket_rails/logging.rb +0 -1
- data/lib/websocket_rails/synchronization.rb +11 -7
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/javascripts/generated/assets/abstract_connection.js +71 -0
- data/spec/javascripts/generated/assets/channel.js +58 -34
- data/spec/javascripts/generated/assets/event.js +12 -16
- data/spec/javascripts/generated/assets/http_connection.js +67 -65
- data/spec/javascripts/generated/assets/websocket_connection.js +36 -51
- data/spec/javascripts/generated/assets/websocket_rails.js +68 -21
- data/spec/javascripts/generated/specs/channel_spec.js +102 -19
- data/spec/javascripts/generated/specs/helpers.js +17 -0
- data/spec/javascripts/generated/specs/websocket_connection_spec.js +72 -19
- data/spec/javascripts/generated/specs/websocket_rails_spec.js +146 -47
- data/spec/javascripts/support/jasmine.yml +10 -2
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +66 -12
- data/spec/javascripts/websocket_rails/event_spec.coffee +7 -7
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +53 -15
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +108 -25
- data/spec/unit/base_controller_spec.rb +41 -0
- data/spec/unit/channel_manager_spec.rb +21 -0
- data/spec/unit/channel_spec.rb +43 -3
- data/spec/unit/connection_adapters/http_spec.rb +24 -3
- data/spec/unit/connection_adapters_spec.rb +2 -2
- data/spec/unit/connection_manager_spec.rb +1 -1
- data/spec/unit/event_spec.rb +25 -1
- data/spec/unit/logging_spec.rb +1 -1
- metadata +57 -67
- data/spec/javascripts/support/jasmine_config.rb +0 -63
@@ -1,43 +1,29 @@
|
|
1
1
|
###
|
2
2
|
WebSocket Interface for the WebSocketRails client.
|
3
3
|
###
|
4
|
-
class WebSocketRails.WebSocketConnection
|
5
|
-
|
6
|
-
|
4
|
+
class WebSocketRails.WebSocketConnection extends WebSocketRails.AbstractConnection
|
5
|
+
connection_type: 'websocket'
|
6
|
+
|
7
|
+
constructor: (@url, @dispatcher) ->
|
8
|
+
super
|
7
9
|
if @url.match(/^wss?:\/\//)
|
8
10
|
console.log "WARNING: Using connection urls with protocol specified is depricated"
|
9
|
-
else if window.location.protocol == '
|
10
|
-
@url = "ws://#{@url}"
|
11
|
-
else
|
11
|
+
else if window.location.protocol == 'https:'
|
12
12
|
@url = "wss://#{@url}"
|
13
|
-
|
14
|
-
@message_queue = []
|
15
|
-
@_conn = new WebSocket(@url)
|
16
|
-
@_conn.onmessage = @on_message
|
17
|
-
@_conn.onclose = @on_close
|
18
|
-
@_conn.onerror = @on_error
|
19
|
-
|
20
|
-
trigger: (event) =>
|
21
|
-
if @dispatcher.state != 'connected'
|
22
|
-
@message_queue.push event
|
23
13
|
else
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@dispatcher.dispatch close_event
|
14
|
+
@url = "ws://#{@url}"
|
15
|
+
@_conn = new WebSocket(@url)
|
16
|
+
@_conn.onmessage = (event) =>
|
17
|
+
event_data = JSON.parse event.data
|
18
|
+
@on_message(event_data)
|
19
|
+
@_conn.onclose = (event) =>
|
20
|
+
@on_close(event)
|
21
|
+
@_conn.onerror = (event) =>
|
22
|
+
@on_error(event)
|
34
23
|
|
35
|
-
|
36
|
-
|
37
|
-
@dispatcher.state = 'disconnected'
|
38
|
-
@dispatcher.dispatch error_event
|
24
|
+
close: ->
|
25
|
+
@_conn.close()
|
39
26
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@message_queue = []
|
27
|
+
send_event: (event) ->
|
28
|
+
super
|
29
|
+
@_conn.send event.serialize()
|
@@ -18,24 +18,55 @@ Listening for new events from the server
|
|
18
18
|
###
|
19
19
|
class @WebSocketRails
|
20
20
|
constructor: (@url, @use_websockets = true) ->
|
21
|
-
@state = 'connecting'
|
22
21
|
@callbacks = {}
|
23
22
|
@channels = {}
|
24
23
|
@queue = {}
|
25
24
|
|
25
|
+
@connect()
|
26
|
+
|
27
|
+
connect: ->
|
28
|
+
@state = 'connecting'
|
29
|
+
|
26
30
|
unless @supports_websockets() and @use_websockets
|
27
|
-
@_conn = new WebSocketRails.HttpConnection url, @
|
31
|
+
@_conn = new WebSocketRails.HttpConnection @url, @
|
28
32
|
else
|
29
|
-
@_conn = new WebSocketRails.WebSocketConnection url, @
|
33
|
+
@_conn = new WebSocketRails.WebSocketConnection @url, @
|
30
34
|
|
31
35
|
@_conn.new_message = @new_message
|
32
36
|
|
37
|
+
disconnect: ->
|
38
|
+
if @_conn
|
39
|
+
@_conn.close()
|
40
|
+
delete @_conn._conn
|
41
|
+
delete @_conn
|
42
|
+
|
43
|
+
@state = 'disconnected'
|
44
|
+
|
45
|
+
# Reconnects the whole connection,
|
46
|
+
# keeping the messages queue and its' connected channels.
|
47
|
+
#
|
48
|
+
# After successfull connection, this will:
|
49
|
+
# - reconnect to all channels, that were active while disconnecting
|
50
|
+
# - resend all events from which we haven't received any response yet
|
51
|
+
reconnect: =>
|
52
|
+
old_connection_id = @_conn?.connection_id
|
53
|
+
|
54
|
+
@disconnect()
|
55
|
+
@connect()
|
56
|
+
|
57
|
+
# Resend all unfinished events from the previous connection.
|
58
|
+
for id, event of @queue
|
59
|
+
if event.connection_id == old_connection_id && !event.is_result()
|
60
|
+
@trigger_event event
|
61
|
+
|
62
|
+
@reconnect_channels()
|
63
|
+
|
33
64
|
new_message: (data) =>
|
34
65
|
for socket_message in data
|
35
66
|
event = new WebSocketRails.Event( socket_message )
|
36
67
|
if event.is_result()
|
37
68
|
@queue[event.id]?.run_callbacks(event.success, event.data)
|
38
|
-
@queue[event.id]
|
69
|
+
delete @queue[event.id]
|
39
70
|
else if event.is_channel()
|
40
71
|
@dispatch_channel event
|
41
72
|
else if event.is_ping()
|
@@ -48,8 +79,8 @@ class @WebSocketRails
|
|
48
79
|
|
49
80
|
connection_established: (data) =>
|
50
81
|
@state = 'connected'
|
51
|
-
@
|
52
|
-
@_conn.flush_queue
|
82
|
+
@_conn.setConnectionId(data.connection_id)
|
83
|
+
@_conn.flush_queue()
|
53
84
|
if @on_open?
|
54
85
|
@on_open(data)
|
55
86
|
|
@@ -58,30 +89,30 @@ class @WebSocketRails
|
|
58
89
|
@callbacks[event_name].push callback
|
59
90
|
|
60
91
|
trigger: (event_name, data, success_callback, failure_callback) =>
|
61
|
-
event = new WebSocketRails.Event( [event_name, data, @connection_id], success_callback, failure_callback )
|
62
|
-
@
|
63
|
-
@_conn.trigger event
|
92
|
+
event = new WebSocketRails.Event( [event_name, data, @_conn?.connection_id], success_callback, failure_callback )
|
93
|
+
@trigger_event event
|
64
94
|
|
65
95
|
trigger_event: (event) =>
|
66
96
|
@queue[event.id] ?= event # Prevent replacing an event that has callbacks stored
|
67
|
-
@_conn.trigger event
|
97
|
+
@_conn.trigger event if @_conn
|
98
|
+
event
|
68
99
|
|
69
100
|
dispatch: (event) =>
|
70
101
|
return unless @callbacks[event.name]?
|
71
102
|
for callback in @callbacks[event.name]
|
72
103
|
callback event.data
|
73
104
|
|
74
|
-
subscribe: (channel_name) =>
|
105
|
+
subscribe: (channel_name, success_callback, failure_callback) =>
|
75
106
|
unless @channels[channel_name]?
|
76
|
-
channel = new WebSocketRails.Channel channel_name,
|
107
|
+
channel = new WebSocketRails.Channel channel_name, @, false, success_callback, failure_callback
|
77
108
|
@channels[channel_name] = channel
|
78
109
|
channel
|
79
110
|
else
|
80
111
|
@channels[channel_name]
|
81
112
|
|
82
|
-
subscribe_private: (channel_name) =>
|
113
|
+
subscribe_private: (channel_name, success_callback, failure_callback) =>
|
83
114
|
unless @channels[channel_name]?
|
84
|
-
channel = new WebSocketRails.Channel channel_name, @, true
|
115
|
+
channel = new WebSocketRails.Channel channel_name, @, true, success_callback, failure_callback
|
85
116
|
@channels[channel_name] = channel
|
86
117
|
channel
|
87
118
|
else
|
@@ -100,8 +131,22 @@ class @WebSocketRails
|
|
100
131
|
(typeof(WebSocket) == "function" or typeof(WebSocket) == "object")
|
101
132
|
|
102
133
|
pong: =>
|
103
|
-
pong = new WebSocketRails.Event( ['websocket_rails.pong',{}
|
134
|
+
pong = new WebSocketRails.Event( ['websocket_rails.pong', {}, @_conn?.connection_id] )
|
104
135
|
@_conn.trigger pong
|
105
136
|
|
106
137
|
connection_stale: =>
|
107
138
|
@state != 'connected'
|
139
|
+
|
140
|
+
# Destroy and resubscribe to all existing @channels.
|
141
|
+
reconnect_channels: ->
|
142
|
+
for name, channel of @channels
|
143
|
+
callbacks = channel._callbacks
|
144
|
+
channel.destroy()
|
145
|
+
delete @channels[name]
|
146
|
+
|
147
|
+
channel = if channel.is_private
|
148
|
+
@subscribe_private name
|
149
|
+
else
|
150
|
+
@subscribe name
|
151
|
+
channel._callbacks = callbacks
|
152
|
+
channel
|
@@ -22,6 +22,9 @@ WebsocketRails.setup do |config|
|
|
22
22
|
# * Requires Redis.
|
23
23
|
config.synchronize = false
|
24
24
|
|
25
|
+
# Prevent Thin from daemonizing (default is true)
|
26
|
+
# config.daemonize = false
|
27
|
+
|
25
28
|
# Uncomment and edit to point to a different redis instance.
|
26
29
|
# Will not be used unless standalone or synchronization mode
|
27
30
|
# is enabled.
|
@@ -33,6 +36,13 @@ WebsocketRails.setup do |config|
|
|
33
36
|
# when making it private, set the following to true.
|
34
37
|
# config.keep_subscribers_when_private = false
|
35
38
|
|
39
|
+
# Set to true if you wish to broadcast channel subscriber_join and
|
40
|
+
# subscriber_part events. All subscribers of a channel will be
|
41
|
+
# notified when other clients join and part the channel. If you are
|
42
|
+
# using the UserManager, the current_user object will be sent along
|
43
|
+
# with the event.
|
44
|
+
# config.broadcast_subscriber_events = true
|
45
|
+
|
36
46
|
# Used as the key for the WebsocketRails.users Hash. This method
|
37
47
|
# will be called on the `current_user` object in your controller
|
38
48
|
# if one exists. If `current_user` does not exist or does not
|
@@ -45,4 +55,9 @@ WebsocketRails.setup do |config|
|
|
45
55
|
# jobs using the WebsocketRails.users UserManager.
|
46
56
|
# config.user_class = User
|
47
57
|
|
58
|
+
# Supporting HTTP streaming on Internet Explorer versions 8 & 9
|
59
|
+
# requires CORS to be enabled for GET "/websocket" request.
|
60
|
+
# List here the origin domains allowed to perform the request.
|
61
|
+
# config.allowed_origins = ['http://localhost:3000']
|
62
|
+
|
48
63
|
end
|
data/lib/rails/config/routes.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
2
|
if Rails.version >= '4.0.0'
|
3
|
-
|
3
|
+
match "/websocket", :to => WebsocketRails::ConnectionManager.new, via: [:get, :post]
|
4
4
|
else
|
5
5
|
match "/websocket", :to => WebsocketRails::ConnectionManager.new
|
6
6
|
end
|
@@ -9,8 +9,12 @@ namespace :websocket_rails do
|
|
9
9
|
|
10
10
|
warn_if_standalone_not_enabled!
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
if options[:daemonize]
|
13
|
+
fork do
|
14
|
+
Thin::Controllers::Controller.new(options).start
|
15
|
+
end
|
16
|
+
else
|
17
|
+
Thin::Controllers::Controller.new(options).start
|
14
18
|
end
|
15
19
|
|
16
20
|
puts "Websocket Rails Standalone Server listening on port #{options[:port]}"
|
@@ -3,7 +3,7 @@ module WebsocketRails
|
|
3
3
|
|
4
4
|
include Logging
|
5
5
|
|
6
|
-
delegate :config, :to => WebsocketRails
|
6
|
+
delegate :config, :channel_tokens, :channel_manager, :to => WebsocketRails
|
7
7
|
|
8
8
|
attr_reader :name, :subscribers
|
9
9
|
|
@@ -15,17 +15,20 @@ module WebsocketRails
|
|
15
15
|
|
16
16
|
def subscribe(connection)
|
17
17
|
info "#{connection} subscribed to channel #{name}"
|
18
|
+
trigger 'subscriber_join', connection.user if config.broadcast_subscriber_events?
|
18
19
|
@subscribers << connection
|
20
|
+
send_token connection
|
19
21
|
end
|
20
22
|
|
21
23
|
def unsubscribe(connection)
|
22
24
|
return unless @subscribers.include? connection
|
23
25
|
info "#{connection} unsubscribed from channel #{name}"
|
24
26
|
@subscribers.delete connection
|
27
|
+
trigger 'subscriber_part', connection.user if config.broadcast_subscriber_events?
|
25
28
|
end
|
26
29
|
|
27
30
|
def trigger(event_name,data={},options={})
|
28
|
-
options.merge! :channel => name
|
31
|
+
options.merge! :channel => name, :token => token
|
29
32
|
options[:data] = data
|
30
33
|
|
31
34
|
event = Event.new event_name, options
|
@@ -35,6 +38,7 @@ module WebsocketRails
|
|
35
38
|
end
|
36
39
|
|
37
40
|
def trigger_event(event)
|
41
|
+
return if event.token != token
|
38
42
|
info "[#{name}] #{event.data.inspect}"
|
39
43
|
send_data event
|
40
44
|
end
|
@@ -50,8 +54,30 @@ module WebsocketRails
|
|
50
54
|
@private
|
51
55
|
end
|
52
56
|
|
57
|
+
def token
|
58
|
+
@token ||= channel_tokens[@name] ||= generate_unique_token
|
59
|
+
end
|
60
|
+
|
53
61
|
private
|
54
62
|
|
63
|
+
def generate_unique_token
|
64
|
+
begin
|
65
|
+
new_token = SecureRandom.uuid
|
66
|
+
end while channel_tokens.values.include?(new_token)
|
67
|
+
|
68
|
+
new_token
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_token(connection)
|
72
|
+
options = {
|
73
|
+
:channel => @name,
|
74
|
+
:data => {:token => token},
|
75
|
+
:connection => connection
|
76
|
+
}
|
77
|
+
info 'sending token'
|
78
|
+
Event.new('websocket_rails.channel_token', options).trigger
|
79
|
+
end
|
80
|
+
|
55
81
|
def send_data(event)
|
56
82
|
if WebsocketRails.synchronize? && event.server_token.nil?
|
57
83
|
Synchronization.publish event
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'redis-objects'
|
2
|
+
|
1
3
|
module WebsocketRails
|
2
4
|
|
3
5
|
class << self
|
@@ -10,6 +12,10 @@ module WebsocketRails
|
|
10
12
|
channel_manager[channel]
|
11
13
|
end
|
12
14
|
|
15
|
+
def channel_tokens
|
16
|
+
channel_manager.channel_tokens
|
17
|
+
end
|
18
|
+
|
13
19
|
end
|
14
20
|
|
15
21
|
class ChannelManager
|
@@ -20,6 +26,16 @@ module WebsocketRails
|
|
20
26
|
@channels = {}.with_indifferent_access
|
21
27
|
end
|
22
28
|
|
29
|
+
def channel_tokens
|
30
|
+
@channel_tokens ||= begin
|
31
|
+
if WebsocketRails.synchronize?
|
32
|
+
::Redis::HashKey.new('websocket_rails.channel_tokens', Synchronization.redis)
|
33
|
+
else
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
23
39
|
def [](channel)
|
24
40
|
@channels[channel] ||= Channel.new channel
|
25
41
|
end
|
@@ -25,6 +25,23 @@ module WebsocketRails
|
|
25
25
|
@keep_subscribers_when_private = value
|
26
26
|
end
|
27
27
|
|
28
|
+
def allowed_origins
|
29
|
+
# allows the value to be string or array
|
30
|
+
[@allowed_origins].flatten.compact.uniq ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def allowed_origins=(value)
|
34
|
+
@allowed_origins = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def broadcast_subscriber_events?
|
38
|
+
@broadcast_subscriber_events ||= false
|
39
|
+
end
|
40
|
+
|
41
|
+
def broadcast_subscriber_events=(value)
|
42
|
+
@broadcast_subscriber_events = value
|
43
|
+
end
|
44
|
+
|
28
45
|
def route_block=(routes)
|
29
46
|
@event_routes = routes
|
30
47
|
end
|
@@ -73,6 +90,14 @@ module WebsocketRails
|
|
73
90
|
@log_internal_events = value
|
74
91
|
end
|
75
92
|
|
93
|
+
def daemonize?
|
94
|
+
@daemonize.nil? ? true : @daemonize
|
95
|
+
end
|
96
|
+
|
97
|
+
def daemonize=(value)
|
98
|
+
@daemonize = value
|
99
|
+
end
|
100
|
+
|
76
101
|
def synchronize
|
77
102
|
@synchronize ||= false
|
78
103
|
end
|
@@ -125,7 +150,7 @@ module WebsocketRails
|
|
125
150
|
:tag => 'websocket_rails',
|
126
151
|
:rackup => "#{Rails.root}/config.ru",
|
127
152
|
:threaded => false,
|
128
|
-
:daemonize =>
|
153
|
+
:daemonize => daemonize?,
|
129
154
|
:dirname => Rails.root,
|
130
155
|
:max_persistent_conns => 1024,
|
131
156
|
:max_conns => 1024
|
@@ -21,6 +21,13 @@ module WebsocketRails
|
|
21
21
|
|
22
22
|
define_deferrable_callbacks
|
23
23
|
|
24
|
+
origin = "#{request.protocol}#{request.raw_host_with_port}"
|
25
|
+
@headers.merge!({'Access-Control-Allow-Origin' => origin}) if WebsocketRails.config.allowed_origins.include?(origin)
|
26
|
+
# IE < 10.0 hack
|
27
|
+
# XDomainRequest will not bubble up notifications of download progress in the first 2kb of the response
|
28
|
+
# http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx
|
29
|
+
@body.chunk(encode_chunk(" " * 2048))
|
30
|
+
|
24
31
|
EM.next_tick do
|
25
32
|
@env['async.callback'].call [200, @headers, @body]
|
26
33
|
on_open
|
@@ -63,7 +63,7 @@ module WebsocketRails
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def parse_incoming_event(params)
|
66
|
-
connection = find_connection_by_id(params["client_id"]
|
66
|
+
connection = find_connection_by_id(params["client_id"])
|
67
67
|
connection.on_message params["data"]
|
68
68
|
SuccessfulResponse
|
69
69
|
end
|