wwl-websocket-rails 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +239 -0
- data/Rakefile +72 -0
- data/bin/thin-socketrails +45 -0
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +46 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
- data/lib/assets/javascripts/websocket_rails/main.js +6 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
- data/lib/config.ru +3 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +68 -0
- data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
- data/lib/rails/config/routes.rb +7 -0
- data/lib/rails/tasks/websocket_rails.tasks +42 -0
- data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
- data/lib/spec_helpers/matchers/trigger_matchers.rb +138 -0
- data/lib/spec_helpers/spec_helper_event.rb +34 -0
- data/lib/websocket-rails.rb +108 -0
- data/lib/websocket_rails/base_controller.rb +208 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +177 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +35 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_manager.rb +119 -0
- data/lib/websocket_rails/controller_factory.rb +80 -0
- data/lib/websocket_rails/data_store.rb +145 -0
- data/lib/websocket_rails/dispatcher.rb +129 -0
- data/lib/websocket_rails/engine.rb +26 -0
- data/lib/websocket_rails/event.rb +193 -0
- data/lib/websocket_rails/event_map.rb +184 -0
- data/lib/websocket_rails/event_queue.rb +33 -0
- data/lib/websocket_rails/internal_events.rb +37 -0
- data/lib/websocket_rails/logging.rb +133 -0
- data/lib/websocket_rails/spec_helpers.rb +3 -0
- data/lib/websocket_rails/synchronization.rb +178 -0
- data/lib/websocket_rails/user_manager.rb +276 -0
- data/lib/websocket_rails/version.rb +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/chat_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/events.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/log/test.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +202 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/connection_manager_spec.rb +135 -0
- data/spec/javascripts/support/jasmine.yml +52 -0
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +81 -0
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +273 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +358 -0
- data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/mock_web_socket.rb +41 -0
- data/spec/unit/base_controller_spec.rb +74 -0
- data/spec/unit/channel_manager_spec.rb +58 -0
- data/spec/unit/channel_spec.rb +169 -0
- data/spec/unit/connection_adapters/http_spec.rb +88 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
- data/spec/unit/connection_adapters_spec.rb +259 -0
- data/spec/unit/connection_manager_spec.rb +148 -0
- data/spec/unit/controller_factory_spec.rb +76 -0
- data/spec/unit/data_store_spec.rb +106 -0
- data/spec/unit/dispatcher_spec.rb +203 -0
- data/spec/unit/event_map_spec.rb +120 -0
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +181 -0
- data/spec/unit/logging_spec.rb +162 -0
- data/spec/unit/synchronization_spec.rb +150 -0
- data/spec/unit/target_validator_spec.rb +88 -0
- data/spec/unit/user_manager_spec.rb +165 -0
- metadata +320 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# * Borrowed from the <tt>websocket-rack</tt>
|
3
|
+
# Gem as a temporary workaround for the
|
4
|
+
# timeout problem.
|
5
|
+
#
|
6
|
+
# Modified Thin command line interface script.
|
7
|
+
# This is fallback for WebSocket-Rack.
|
8
|
+
# Use it when you have EventMachine version < 1.0.0
|
9
|
+
# Rationale:
|
10
|
+
# Older versions of EM have bug that prevent to
|
11
|
+
# clearing connection inactivity once it's set.
|
12
|
+
# This one will set connection timeout to 0 at
|
13
|
+
# default, so there will be no need to overwrite it.
|
14
|
+
# Be aware that this will also change inactivity
|
15
|
+
# timeout for "normal" connection, so it will be
|
16
|
+
# easy to make DoS attack.
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'thin'
|
20
|
+
|
21
|
+
puts <<END
|
22
|
+
*** Deprecation Notice***
|
23
|
+
|
24
|
+
The thin-socketrails executable is now deprecated and
|
25
|
+
will be removed in the next release.
|
26
|
+
|
27
|
+
You may use the regular thin executable to launch the
|
28
|
+
server now.
|
29
|
+
|
30
|
+
Other EventMachine based web servers should now be
|
31
|
+
supported but have not yet been tested.
|
32
|
+
|
33
|
+
*************************
|
34
|
+
END
|
35
|
+
|
36
|
+
if EM::VERSION < "1.0.0"
|
37
|
+
begin
|
38
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
39
|
+
::Thin::Server.const_set 'DEFAULT_TIMEOUT', 0
|
40
|
+
ensure
|
41
|
+
$VERBOSE = old_verbose
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Thin::Runner.new(ARGV).run!
|
@@ -0,0 +1,45 @@
|
|
1
|
+
###
|
2
|
+
Abstract Interface for the WebSocketRails client.
|
3
|
+
###
|
4
|
+
class WebSocketRails.AbstractConnection
|
5
|
+
|
6
|
+
constructor: (url, @dispatcher) ->
|
7
|
+
@message_queue = []
|
8
|
+
|
9
|
+
close: ->
|
10
|
+
|
11
|
+
trigger: (event) ->
|
12
|
+
if @dispatcher.state != 'connected'
|
13
|
+
@message_queue.push event
|
14
|
+
else
|
15
|
+
@send_event event
|
16
|
+
|
17
|
+
send_event: (event) ->
|
18
|
+
# Events queued before connecting do not have the correct
|
19
|
+
# connection_id set yet. We need to update it before dispatching.
|
20
|
+
event.connection_id = @connection_id if @connection_id?
|
21
|
+
|
22
|
+
# ...
|
23
|
+
|
24
|
+
on_close: (event) ->
|
25
|
+
if @dispatcher && @dispatcher._conn == @
|
26
|
+
close_event = new WebSocketRails.Event(['connection_closed', event])
|
27
|
+
@dispatcher.state = 'disconnected'
|
28
|
+
@dispatcher.dispatch close_event
|
29
|
+
|
30
|
+
on_error: (event) ->
|
31
|
+
if @dispatcher && @dispatcher._conn == @
|
32
|
+
error_event = new WebSocketRails.Event(['connection_error', event])
|
33
|
+
@dispatcher.state = 'disconnected'
|
34
|
+
@dispatcher.dispatch error_event
|
35
|
+
|
36
|
+
on_message: (event_data) ->
|
37
|
+
if @dispatcher && @dispatcher._conn == @
|
38
|
+
@dispatcher.new_message event_data
|
39
|
+
|
40
|
+
setConnectionId: (@connection_id) ->
|
41
|
+
|
42
|
+
flush_queue: ->
|
43
|
+
for event in @message_queue
|
44
|
+
@trigger event
|
45
|
+
@message_queue = []
|
@@ -0,0 +1,70 @@
|
|
1
|
+
###
|
2
|
+
The channel object is returned when you subscribe to a channel.
|
3
|
+
|
4
|
+
For instance:
|
5
|
+
var dispatcher = new WebSocketRails('localhost:3000/websocket');
|
6
|
+
var awesome_channel = dispatcher.subscribe('awesome_channel');
|
7
|
+
awesome_channel.bind('event', function(data) { console.log('channel event!'); });
|
8
|
+
awesome_channel.trigger('awesome_event', awesome_object);
|
9
|
+
|
10
|
+
If you want to unbind an event, you can use the unbind function :
|
11
|
+
awesome_channel.unbind('event')
|
12
|
+
###
|
13
|
+
class WebSocketRails.Channel
|
14
|
+
|
15
|
+
constructor: (@name, @_dispatcher, @is_private = false, @on_success, @on_failure) ->
|
16
|
+
@_callbacks = {}
|
17
|
+
@_token = undefined
|
18
|
+
@_queue = []
|
19
|
+
if @is_private
|
20
|
+
event_name = 'websocket_rails.subscribe_private'
|
21
|
+
else
|
22
|
+
event_name = 'websocket_rails.subscribe'
|
23
|
+
|
24
|
+
@connection_id = @_dispatcher._conn?.connection_id
|
25
|
+
event = new WebSocketRails.Event( [event_name, {data: {channel: @name}}, @connection_id], @_success_launcher, @_failure_launcher)
|
26
|
+
@_dispatcher.trigger_event event
|
27
|
+
|
28
|
+
destroy: ->
|
29
|
+
if @connection_id == @_dispatcher._conn?.connection_id
|
30
|
+
event_name = 'websocket_rails.unsubscribe'
|
31
|
+
event = new WebSocketRails.Event( [event_name, {data: {channel: @name}}, @connection_id] )
|
32
|
+
@_dispatcher.trigger_event event
|
33
|
+
@_callbacks = {}
|
34
|
+
|
35
|
+
bind: (event_name, callback) ->
|
36
|
+
@_callbacks[event_name] ?= []
|
37
|
+
@_callbacks[event_name].push callback
|
38
|
+
|
39
|
+
unbind: (event_name) ->
|
40
|
+
delete @_callbacks[event_name]
|
41
|
+
|
42
|
+
trigger: (event_name, message) ->
|
43
|
+
event = new WebSocketRails.Event( [event_name, {channel: @name, data: message, token: @_token}, @connection_id] )
|
44
|
+
if !@_token
|
45
|
+
@_queue.push event
|
46
|
+
else
|
47
|
+
@_dispatcher.trigger_event event
|
48
|
+
|
49
|
+
dispatch: (event_name, message) ->
|
50
|
+
if event_name == 'websocket_rails.channel_token'
|
51
|
+
@connection_id = @_dispatcher._conn?.connection_id
|
52
|
+
@_token = message['token']
|
53
|
+
@flush_queue()
|
54
|
+
else
|
55
|
+
return unless @_callbacks[event_name]?
|
56
|
+
for callback in @_callbacks[event_name]
|
57
|
+
callback message
|
58
|
+
|
59
|
+
# using this method because @on_success will not be defined when the constructor is executed
|
60
|
+
_success_launcher: (data) =>
|
61
|
+
@on_success(data) if @on_success?
|
62
|
+
|
63
|
+
# using this method because @on_failure will not be defined when the constructor is executed
|
64
|
+
_failure_launcher: (data) =>
|
65
|
+
@on_failure(data) if @on_failure?
|
66
|
+
|
67
|
+
flush_queue: ->
|
68
|
+
for event in @_queue
|
69
|
+
@_dispatcher.trigger_event event
|
70
|
+
@_queue = []
|
@@ -0,0 +1,46 @@
|
|
1
|
+
###
|
2
|
+
The Event object stores all the relevant event information.
|
3
|
+
###
|
4
|
+
|
5
|
+
class WebSocketRails.Event
|
6
|
+
|
7
|
+
SUCCEEDED: 0
|
8
|
+
FAILED: 1
|
9
|
+
FINISHED_WITHOUT_FAILURE: 2
|
10
|
+
|
11
|
+
constructor: (data, @success_callback, @failure_callback) ->
|
12
|
+
@name = data[0]
|
13
|
+
attr = data[1]
|
14
|
+
if attr?
|
15
|
+
@id = if attr['id']? then attr['id'] else (((1+Math.random())*0x10000)|0)
|
16
|
+
@channel = if attr.channel? then attr.channel
|
17
|
+
@data = if attr.data? then attr.data else attr
|
18
|
+
@token = if attr.token? then attr.token
|
19
|
+
@connection_id = data[2]
|
20
|
+
if attr.success?
|
21
|
+
@result = true
|
22
|
+
@success = attr.success
|
23
|
+
|
24
|
+
is_channel: ->
|
25
|
+
@channel?
|
26
|
+
|
27
|
+
is_result: ->
|
28
|
+
typeof @result != 'undefined'
|
29
|
+
|
30
|
+
is_ping: ->
|
31
|
+
@name == 'websocket_rails.ping'
|
32
|
+
|
33
|
+
serialize: ->
|
34
|
+
JSON.stringify [@name, @attributes()]
|
35
|
+
|
36
|
+
attributes: ->
|
37
|
+
id: @id,
|
38
|
+
channel: @channel,
|
39
|
+
data: @data
|
40
|
+
token: @token
|
41
|
+
|
42
|
+
run_callbacks: (@success, @result) ->
|
43
|
+
if @success == @SUCCEEDED
|
44
|
+
@success_callback?(@result)
|
45
|
+
else if @success == @FAILED
|
46
|
+
@failure_callback?(@result)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
###
|
2
|
+
HTTP Interface for the WebSocketRails client.
|
3
|
+
###
|
4
|
+
class WebSocketRails.HttpConnection extends WebSocketRails.AbstractConnection
|
5
|
+
connection_type: 'http'
|
6
|
+
|
7
|
+
_httpFactories: -> [
|
8
|
+
-> new XDomainRequest(),
|
9
|
+
-> new XMLHttpRequest(),
|
10
|
+
-> new ActiveXObject("Msxml2.XMLHTTP"),
|
11
|
+
-> new ActiveXObject("Msxml3.XMLHTTP"),
|
12
|
+
-> new ActiveXObject("Microsoft.XMLHTTP")
|
13
|
+
]
|
14
|
+
|
15
|
+
constructor: (url, @dispatcher) ->
|
16
|
+
super
|
17
|
+
@_url = "http://#{url}"
|
18
|
+
@_conn = @_createXMLHttpObject()
|
19
|
+
@last_pos = 0
|
20
|
+
try
|
21
|
+
@_conn.onreadystatechange = => @_parse_stream()
|
22
|
+
@_conn.addEventListener("load", @on_close, false)
|
23
|
+
catch e
|
24
|
+
@_conn.onprogress = => @_parse_stream()
|
25
|
+
@_conn.onload = @on_close
|
26
|
+
# set this as 3 always for parse_stream as the object does not have this property at all
|
27
|
+
@_conn.readyState = 3
|
28
|
+
@_conn.open "GET", @_url, true
|
29
|
+
@_conn.send()
|
30
|
+
|
31
|
+
close: ->
|
32
|
+
@_conn.abort()
|
33
|
+
|
34
|
+
send_event: (event) ->
|
35
|
+
super
|
36
|
+
@_post_data event.serialize()
|
37
|
+
|
38
|
+
_post_data: (payload) ->
|
39
|
+
$.ajax @_url,
|
40
|
+
type: 'POST'
|
41
|
+
data:
|
42
|
+
client_id: @connection_id
|
43
|
+
data: payload
|
44
|
+
success: ->
|
45
|
+
|
46
|
+
_createXMLHttpObject: ->
|
47
|
+
xmlhttp = false
|
48
|
+
factories = @_httpFactories()
|
49
|
+
for factory in factories
|
50
|
+
try
|
51
|
+
xmlhttp = factory()
|
52
|
+
catch e
|
53
|
+
continue
|
54
|
+
break
|
55
|
+
xmlhttp
|
56
|
+
|
57
|
+
_parse_stream: ->
|
58
|
+
if @_conn.readyState == 3
|
59
|
+
data = @_conn.responseText.substring @last_pos
|
60
|
+
@last_pos = @_conn.responseText.length
|
61
|
+
data = data.replace( /\]\]\[\[/g, "],[" )
|
62
|
+
try
|
63
|
+
event_data = JSON.parse data
|
64
|
+
@on_message(event_data)
|
65
|
+
catch e
|
66
|
+
# just ignore if it cannot be parsed, probably whitespace
|
@@ -0,0 +1,29 @@
|
|
1
|
+
###
|
2
|
+
WebSocket Interface for the WebSocketRails client.
|
3
|
+
###
|
4
|
+
class WebSocketRails.WebSocketConnection extends WebSocketRails.AbstractConnection
|
5
|
+
connection_type: 'websocket'
|
6
|
+
|
7
|
+
constructor: (@url, @dispatcher) ->
|
8
|
+
super
|
9
|
+
if @url.match(/^wss?:\/\//)
|
10
|
+
console.log "WARNING: Using connection urls with protocol specified is depricated"
|
11
|
+
else if window.location.protocol == 'https:'
|
12
|
+
@url = "wss://#{@url}"
|
13
|
+
else
|
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)
|
23
|
+
|
24
|
+
close: ->
|
25
|
+
@_conn.close()
|
26
|
+
|
27
|
+
send_event: (event) ->
|
28
|
+
super
|
29
|
+
@_conn.send event.serialize()
|
@@ -0,0 +1,158 @@
|
|
1
|
+
###
|
2
|
+
WebsocketRails JavaScript Client
|
3
|
+
|
4
|
+
Setting up the dispatcher:
|
5
|
+
var dispatcher = new WebSocketRails('localhost:3000/websocket');
|
6
|
+
dispatcher.on_open = function() {
|
7
|
+
// trigger a server event immediately after opening connection
|
8
|
+
dispatcher.trigger('new_user',{user_name: 'guest'});
|
9
|
+
})
|
10
|
+
|
11
|
+
Triggering a new event on the server
|
12
|
+
dispatcherer.trigger('event_name',object_to_be_serialized_to_json);
|
13
|
+
|
14
|
+
Listening for new events from the server
|
15
|
+
dispatcher.bind('event_name', function(data) {
|
16
|
+
console.log(data.user_name);
|
17
|
+
});
|
18
|
+
|
19
|
+
Stop listening for new events from the server
|
20
|
+
dispatcher.unbind('event')
|
21
|
+
###
|
22
|
+
class @WebSocketRails
|
23
|
+
constructor: (@url, @use_websockets = true) ->
|
24
|
+
@callbacks = {}
|
25
|
+
@channels = {}
|
26
|
+
@queue = {}
|
27
|
+
|
28
|
+
@connect()
|
29
|
+
|
30
|
+
connect: ->
|
31
|
+
@state = 'connecting'
|
32
|
+
|
33
|
+
unless @supports_websockets() and @use_websockets
|
34
|
+
@_conn = new WebSocketRails.HttpConnection @url, @
|
35
|
+
else
|
36
|
+
@_conn = new WebSocketRails.WebSocketConnection @url, @
|
37
|
+
|
38
|
+
@_conn.new_message = @new_message
|
39
|
+
|
40
|
+
disconnect: ->
|
41
|
+
if @_conn
|
42
|
+
@_conn.close()
|
43
|
+
delete @_conn._conn
|
44
|
+
delete @_conn
|
45
|
+
|
46
|
+
@state = 'disconnected'
|
47
|
+
|
48
|
+
# Reconnects the whole connection,
|
49
|
+
# keeping the messages queue and its' connected channels.
|
50
|
+
#
|
51
|
+
# After successfull connection, this will:
|
52
|
+
# - reconnect to all channels, that were active while disconnecting
|
53
|
+
# - resend all events from which we haven't received any response yet
|
54
|
+
reconnect: =>
|
55
|
+
old_connection_id = @_conn?.connection_id
|
56
|
+
|
57
|
+
@disconnect()
|
58
|
+
@connect()
|
59
|
+
|
60
|
+
# Resend all unfinished events from the previous connection.
|
61
|
+
for id, event of @queue
|
62
|
+
if event.connection_id == old_connection_id && !event.is_result()
|
63
|
+
@trigger_event event
|
64
|
+
|
65
|
+
@reconnect_channels()
|
66
|
+
|
67
|
+
new_message: (data) =>
|
68
|
+
for socket_message in data
|
69
|
+
event = new WebSocketRails.Event( socket_message )
|
70
|
+
if event.is_result()
|
71
|
+
@queue[event.id]?.run_callbacks(event.success, event.data)
|
72
|
+
delete @queue[event.id]
|
73
|
+
else if event.is_channel()
|
74
|
+
@dispatch_channel event
|
75
|
+
else if event.is_ping()
|
76
|
+
@pong()
|
77
|
+
else
|
78
|
+
@dispatch event
|
79
|
+
|
80
|
+
if @state == 'connecting' and event.name == 'client_connected'
|
81
|
+
@connection_established event.data
|
82
|
+
|
83
|
+
connection_established: (data) =>
|
84
|
+
@state = 'connected'
|
85
|
+
@_conn.setConnectionId(data.connection_id)
|
86
|
+
@_conn.flush_queue()
|
87
|
+
if @on_open?
|
88
|
+
@on_open(data)
|
89
|
+
|
90
|
+
bind: (event_name, callback) =>
|
91
|
+
@callbacks[event_name] ?= []
|
92
|
+
@callbacks[event_name].push callback
|
93
|
+
|
94
|
+
unbind: (event_name) =>
|
95
|
+
delete @callbacks[event_name]
|
96
|
+
|
97
|
+
trigger: (event_name, data, success_callback, failure_callback) =>
|
98
|
+
event = new WebSocketRails.Event( [event_name, data, @_conn?.connection_id], success_callback, failure_callback )
|
99
|
+
@trigger_event event
|
100
|
+
|
101
|
+
trigger_event: (event) =>
|
102
|
+
@queue[event.id] ?= event # Prevent replacing an event that has callbacks stored
|
103
|
+
@_conn.trigger event if @_conn
|
104
|
+
event
|
105
|
+
|
106
|
+
dispatch: (event) =>
|
107
|
+
return unless @callbacks[event.name]?
|
108
|
+
for callback in @callbacks[event.name]
|
109
|
+
callback event.data
|
110
|
+
|
111
|
+
subscribe: (channel_name, success_callback, failure_callback) =>
|
112
|
+
unless @channels[channel_name]?
|
113
|
+
channel = new WebSocketRails.Channel channel_name, @, false, success_callback, failure_callback
|
114
|
+
@channels[channel_name] = channel
|
115
|
+
channel
|
116
|
+
else
|
117
|
+
@channels[channel_name]
|
118
|
+
|
119
|
+
subscribe_private: (channel_name, success_callback, failure_callback) =>
|
120
|
+
unless @channels[channel_name]?
|
121
|
+
channel = new WebSocketRails.Channel channel_name, @, true, success_callback, failure_callback
|
122
|
+
@channels[channel_name] = channel
|
123
|
+
channel
|
124
|
+
else
|
125
|
+
@channels[channel_name]
|
126
|
+
|
127
|
+
unsubscribe: (channel_name) =>
|
128
|
+
return unless @channels[channel_name]?
|
129
|
+
@channels[channel_name].destroy()
|
130
|
+
delete @channels[channel_name]
|
131
|
+
|
132
|
+
dispatch_channel: (event) =>
|
133
|
+
return unless @channels[event.channel]?
|
134
|
+
@channels[event.channel].dispatch event.name, event.data
|
135
|
+
|
136
|
+
supports_websockets: =>
|
137
|
+
(typeof(WebSocket) == "function" or typeof(WebSocket) == "object")
|
138
|
+
|
139
|
+
pong: =>
|
140
|
+
pong = new WebSocketRails.Event( ['websocket_rails.pong', {}, @_conn?.connection_id] )
|
141
|
+
@_conn.trigger pong
|
142
|
+
|
143
|
+
connection_stale: =>
|
144
|
+
@state != 'connected'
|
145
|
+
|
146
|
+
# Destroy and resubscribe to all existing @channels.
|
147
|
+
reconnect_channels: ->
|
148
|
+
for name, channel of @channels
|
149
|
+
callbacks = channel._callbacks
|
150
|
+
channel.destroy()
|
151
|
+
delete @channels[name]
|
152
|
+
|
153
|
+
channel = if channel.is_private
|
154
|
+
@subscribe_private name
|
155
|
+
else
|
156
|
+
@subscribe name
|
157
|
+
channel._callbacks = callbacks
|
158
|
+
channel
|
data/lib/config.ru
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
desc "Create the events.rb initializer and require the JS client in the application.js manifest."
|
9
|
+
|
10
|
+
class_option :manifest, :type => :string, :aliases => "-m", :default => 'application.js',
|
11
|
+
:desc => "Javascript manifest file to modify (or create)"
|
12
|
+
|
13
|
+
def create_events_initializer_file
|
14
|
+
template 'events.rb', File.join('config', 'events.rb')
|
15
|
+
template 'websocket_rails.rb', File.join('config', 'initializers', 'websocket_rails.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
def inject_websocket_rails_client
|
19
|
+
manifest = options[:manifest]
|
20
|
+
js_path = "app/assets/javascripts"
|
21
|
+
|
22
|
+
create_file("#{js_path}/#{manifest}") unless File.exists?("#{js_path}/#{manifest}")
|
23
|
+
|
24
|
+
append_to_file "#{js_path}/#{manifest}" do
|
25
|
+
out = ""
|
26
|
+
out << "//= require websocket_rails/main"
|
27
|
+
out << "\n"
|
28
|
+
out << "\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
WebsocketRails::EventMap.describe do
|
2
|
+
# You can use this file to map incoming events to controller actions.
|
3
|
+
# One event can be mapped to any number of controller actions. The
|
4
|
+
# actions will be executed in the order they were subscribed.
|
5
|
+
#
|
6
|
+
# Uncomment and edit the next line to handle the client connected event:
|
7
|
+
# subscribe :client_connected, :to => Controller, :with_method => :method_name
|
8
|
+
#
|
9
|
+
# Here is an example of mapping namespaced events:
|
10
|
+
# namespace :product do
|
11
|
+
# subscribe :new, :to => ProductController, :with_method => :new_product
|
12
|
+
# end
|
13
|
+
# The above will handle an event triggered on the client like `product.new`.
|
14
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
WebsocketRails.setup do |config|
|
2
|
+
|
3
|
+
# Uncomment to override the default log level. The log level can be
|
4
|
+
# any of the standard Logger log levels. By default it will mirror the
|
5
|
+
# current Rails environment log level.
|
6
|
+
# config.log_level = :debug
|
7
|
+
|
8
|
+
# Uncomment to change the default log file path.
|
9
|
+
# config.log_path = "#{Rails.root}/log/websocket_rails.log"
|
10
|
+
|
11
|
+
# Set to true if you wish to log the internal websocket_rails events
|
12
|
+
# such as the keepalive `websocket_rails.ping` event.
|
13
|
+
# config.log_internal_events = false
|
14
|
+
|
15
|
+
# Change to true to enable standalone server mode
|
16
|
+
# Start the standalone server with rake websocket_rails:start_server
|
17
|
+
# * Requires Redis
|
18
|
+
config.standalone = false
|
19
|
+
|
20
|
+
# Change to true to enable channel synchronization between
|
21
|
+
# multiple server instances.
|
22
|
+
# * Requires Redis.
|
23
|
+
config.synchronize = false
|
24
|
+
|
25
|
+
# Prevent Thin from daemonizing (default is true)
|
26
|
+
# config.daemonize = false
|
27
|
+
|
28
|
+
# Uncomment and edit to point to a different redis instance.
|
29
|
+
# Will not be used unless standalone or synchronization mode
|
30
|
+
# is enabled.
|
31
|
+
# config.redis_options = {:host => 'localhost', :port => '6379'}
|
32
|
+
|
33
|
+
# By default, all subscribers in to a channel will be removed
|
34
|
+
# when that channel is made private. If you don't wish active
|
35
|
+
# subscribers to be removed from a previously public channel
|
36
|
+
# when making it private, set the following to true.
|
37
|
+
# config.keep_subscribers_when_private = false
|
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
|
+
|
46
|
+
# Used as the key for the WebsocketRails.users Hash. This method
|
47
|
+
# will be called on the `current_user` object in your controller
|
48
|
+
# if one exists. If `current_user` does not exist or does not
|
49
|
+
# respond to the identifier, the key will default to `connection.id`
|
50
|
+
# config.user_identifier = :id
|
51
|
+
|
52
|
+
# Uncomment and change this option to override the class associated
|
53
|
+
# with your `current_user` object. This class will be used when
|
54
|
+
# synchronization is enabled and you trigger events from background
|
55
|
+
# jobs using the WebsocketRails.users UserManager.
|
56
|
+
# config.user_class = User
|
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
|
+
|
63
|
+
# Uncomment this option to change the default behavior when
|
64
|
+
# trigger_success or trigger_failure are not called in the action
|
65
|
+
# of the WebsocketController. (default is true)
|
66
|
+
# confid.trigger_success_by_default = false
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module WebsocketRails
|
2
|
+
# This class provides a means for accessing the Rails
|
3
|
+
# controller helper methods defined in a user's application
|
4
|
+
# or in gems that the user has added to the project.
|
5
|
+
#
|
6
|
+
# Each active connection creates and stores it's own
|
7
|
+
# instance with the correct @_request and @_env objects
|
8
|
+
# set. WebsocketRails::BaseController sends missing
|
9
|
+
# methods to the active connection's delegation controller
|
10
|
+
# instance.
|
11
|
+
class DelegationController < ApplicationController
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
namespace :websocket_rails do
|
2
|
+
desc 'Start the WebsocketRails standalone server.'
|
3
|
+
task :start_server do
|
4
|
+
require "thin"
|
5
|
+
load "#{Rails.root}/config/initializers/websocket_rails.rb"
|
6
|
+
load "#{Rails.root}/config/events.rb"
|
7
|
+
|
8
|
+
options = WebsocketRails.config.thin_options
|
9
|
+
|
10
|
+
warn_if_standalone_not_enabled!
|
11
|
+
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Websocket Rails Standalone Server listening on port #{options[:port]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Stop the WebsocketRails standalone server.'
|
24
|
+
task :stop_server do
|
25
|
+
require "thin"
|
26
|
+
load "#{Rails.root}/config/initializers/websocket_rails.rb"
|
27
|
+
load "#{Rails.root}/config/events.rb"
|
28
|
+
|
29
|
+
options = WebsocketRails.config.thin_options
|
30
|
+
|
31
|
+
warn_if_standalone_not_enabled!
|
32
|
+
|
33
|
+
Thin::Controllers::Controller.new(options).stop
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def warn_if_standalone_not_enabled!
|
38
|
+
return if WebsocketRails.standalone?
|
39
|
+
puts "Fail!"
|
40
|
+
puts "You must enable standalone mode in your websocket_rails.rb initializer to use the standalone server."
|
41
|
+
exit 1
|
42
|
+
end
|