hkroger-websocket-rails 0.7.1
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 +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +237 -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 +42 -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 +63 -0
- data/lib/hkroger-websocket-rails.rb +1 -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 +113 -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 +197 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +169 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -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 +189 -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 +182 -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.ru +4 -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/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/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 +69 -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 +274 -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 +247 -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
data/Rakefile
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'rake'
|
|
4
|
+
begin
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
require 'rdoc/task'
|
|
12
|
+
rescue LoadError
|
|
13
|
+
require 'rdoc/rdoc'
|
|
14
|
+
require 'rake/rdoctask'
|
|
15
|
+
RDoc::Task = Rake::RDocTask
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Bundler::GemHelper.install_tasks
|
|
19
|
+
|
|
20
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
22
|
+
rdoc.title = 'websocket-rails'
|
|
23
|
+
rdoc.options << '--line-numbers'
|
|
24
|
+
rdoc.rdoc_files.include('README.md')
|
|
25
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require 'rspec/core/rake_task'
|
|
29
|
+
|
|
30
|
+
desc 'Default: run RSpec and Jasmine specs.'
|
|
31
|
+
task :default => :spec_and_jasmine
|
|
32
|
+
|
|
33
|
+
desc "Run specs"
|
|
34
|
+
RSpec::Core::RakeTask.new do |t|
|
|
35
|
+
t.pattern = "./spec/**/*_spec.rb"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Run rspec and jasmine:ci at the same time"
|
|
39
|
+
task :spec_and_jasmine do
|
|
40
|
+
Rake::Task["spec"].execute
|
|
41
|
+
Rake::Task["jasmine:ci:headless"].execute
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "Generate code coverage"
|
|
45
|
+
task :coverage do
|
|
46
|
+
ENV['COVERAGE'] = 'true'
|
|
47
|
+
Rake::Task["spec"].execute
|
|
48
|
+
`open coverage/index.html`
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
begin
|
|
52
|
+
require 'jasmine'
|
|
53
|
+
load 'jasmine/tasks/jasmine.rake'
|
|
54
|
+
rescue LoadError
|
|
55
|
+
task :jasmine do
|
|
56
|
+
abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
require 'headless'
|
|
61
|
+
require 'selenium-webdriver'
|
|
62
|
+
|
|
63
|
+
namespace :jasmine do
|
|
64
|
+
namespace :ci do
|
|
65
|
+
desc "Run Jasmine CI build headlessly"
|
|
66
|
+
task :headless do
|
|
67
|
+
ENV['DISPLAY'] = ':99.0'
|
|
68
|
+
puts "Running Jasmine Headlessly"
|
|
69
|
+
Rake::Task['jasmine:ci'].invoke
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -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,42 @@
|
|
|
1
|
+
###
|
|
2
|
+
The Event object stores all the relevant event information.
|
|
3
|
+
###
|
|
4
|
+
|
|
5
|
+
class WebSocketRails.Event
|
|
6
|
+
|
|
7
|
+
constructor: (data, @success_callback, @failure_callback) ->
|
|
8
|
+
@name = data[0]
|
|
9
|
+
attr = data[1]
|
|
10
|
+
if attr?
|
|
11
|
+
@id = if attr['id']? then attr['id'] else (((1+Math.random())*0x10000)|0)
|
|
12
|
+
@channel = if attr.channel? then attr.channel
|
|
13
|
+
@data = if attr.data? then attr.data else attr
|
|
14
|
+
@token = if attr.token? then attr.token
|
|
15
|
+
@connection_id = data[2]
|
|
16
|
+
if attr.success?
|
|
17
|
+
@result = true
|
|
18
|
+
@success = attr.success
|
|
19
|
+
|
|
20
|
+
is_channel: ->
|
|
21
|
+
@channel?
|
|
22
|
+
|
|
23
|
+
is_result: ->
|
|
24
|
+
typeof @result != 'undefined'
|
|
25
|
+
|
|
26
|
+
is_ping: ->
|
|
27
|
+
@name == 'websocket_rails.ping'
|
|
28
|
+
|
|
29
|
+
serialize: ->
|
|
30
|
+
JSON.stringify [@name, @attributes()]
|
|
31
|
+
|
|
32
|
+
attributes: ->
|
|
33
|
+
id: @id,
|
|
34
|
+
channel: @channel,
|
|
35
|
+
data: @data
|
|
36
|
+
token: @token
|
|
37
|
+
|
|
38
|
+
run_callbacks: (@success, @result) ->
|
|
39
|
+
if @success == true
|
|
40
|
+
@success_callback?(@result)
|
|
41
|
+
else
|
|
42
|
+
@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 deprecated"
|
|
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
|