websocket-rails 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG.md +22 -0
  2. data/Gemfile +4 -0
  3. data/README.md +66 -159
  4. data/Rakefile +31 -4
  5. data/bin/thin-socketrails +16 -1
  6. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +23 -8
  7. data/lib/assets/javascripts/websocket_rails/event.js.coffee +40 -0
  8. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +18 -10
  9. data/lib/assets/javascripts/websocket_rails/main.js +1 -0
  10. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +15 -10
  11. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +41 -23
  12. data/lib/websocket-rails.rb +4 -4
  13. data/lib/websocket_rails/base_controller.rb +61 -29
  14. data/lib/websocket_rails/channel.rb +14 -5
  15. data/lib/websocket_rails/channel_manager.rb +3 -1
  16. data/lib/websocket_rails/connection_adapters.rb +34 -12
  17. data/lib/websocket_rails/connection_manager.rb +4 -0
  18. data/lib/websocket_rails/dispatcher.rb +27 -3
  19. data/lib/websocket_rails/engine.rb +2 -5
  20. data/lib/websocket_rails/event.rb +87 -42
  21. data/lib/websocket_rails/event_map.rb +70 -20
  22. data/lib/websocket_rails/event_queue.rb +4 -0
  23. data/lib/websocket_rails/internal_events.rb +21 -3
  24. data/lib/websocket_rails/logging.rb +18 -0
  25. data/lib/websocket_rails/version.rb +1 -1
  26. data/spec/dummy/log/test.log +0 -429
  27. data/spec/integration/connection_manager_spec.rb +3 -5
  28. data/spec/javascripts/generated/assets/channel.js +98 -0
  29. data/spec/javascripts/generated/assets/event.js +78 -0
  30. data/spec/javascripts/generated/assets/http_connection.js +108 -0
  31. data/spec/javascripts/generated/assets/websocket_connection.js +66 -0
  32. data/spec/javascripts/generated/assets/websocket_rails.js +180 -0
  33. data/spec/javascripts/generated/specs/channel_spec.js +66 -0
  34. data/spec/javascripts/generated/specs/event_spec.js +107 -0
  35. data/spec/javascripts/generated/specs/websocket_connection_spec.js +117 -0
  36. data/spec/javascripts/generated/specs/websocket_rails_spec.js +232 -0
  37. data/spec/javascripts/support/jasmine.yml +44 -0
  38. data/spec/javascripts/support/jasmine_config.rb +63 -0
  39. data/spec/javascripts/support/vendor/sinon-1.3.4.js +3555 -0
  40. data/spec/javascripts/websocket_rails/channel_spec.coffee +51 -0
  41. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  42. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +86 -0
  43. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +166 -0
  44. data/spec/support/helper_methods.rb +10 -1
  45. data/spec/unit/channel_spec.rb +28 -4
  46. data/spec/unit/connection_adapters_spec.rb +17 -0
  47. data/spec/unit/connection_manager_spec.rb +1 -1
  48. data/spec/unit/dispatcher_spec.rb +1 -1
  49. data/spec/unit/event_spec.rb +15 -11
  50. metadata +22 -4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # WebsocketRails Change Log
2
2
 
3
+ ## Version 0.1.6
4
+
5
+ July 17 2012
6
+
7
+ * Added private channel support - Thanks to @MhdSyrwan
8
+ * Added DSL method for marking channels as private.
9
+ * Added support for attaching success and failure callbacks to triggered
10
+ events on the JavaScript client.
11
+ * Fixed JSON parsing bug in HTTP streaming client when multiple events
12
+ were received together.
13
+ * Added connection keepalive ping/pong timers to ensure clients do not
14
+ disconnect automatically. Ensures HTTP streaming works well on Heroku.
15
+ * Removed the requirement of using the thin-socketrails executable. The
16
+ executable will be removed entirely in the next release.
17
+ * Added Jasmine specs for CoffeeScript client.
18
+ * Exceptions triggered in controller actions are now serialized and
19
+ passed to the failure callback on the client that triggered the
20
+ action.
21
+ * Events triggered on the client before the connection is fully
22
+ established are now queued and sent in bulk once the connection is
23
+ ready.
24
+
3
25
  ## Version 0.1.5
4
26
 
5
27
  July 3 2012
data/Gemfile CHANGED
@@ -3,6 +3,9 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem "rspec-rails"
6
+ gem "jasmine"
7
+ gem "headless"
8
+ gem "coffee-script"
6
9
  gem "thin"
7
10
  gem "eventmachine", ">= 1.0.0.beta.3"
8
11
  gem "faye-websocket"
@@ -10,6 +13,7 @@ gem "simplecov"
10
13
  gem "ruby_gntp"
11
14
  gem "guard"
12
15
  gem "guard-rspec"
16
+ gem "guard-coffeescript"
13
17
 
14
18
  platforms :jruby do
15
19
  gem 'activerecord-jdbcsqlite3-adapter', :require => 'jdbc-sqlite3', :require => 'arjdbc'
data/README.md CHANGED
@@ -4,199 +4,106 @@
4
4
 
5
5
  If you haven't done so yet, check out the [Project Page](http://danknox.github.com/websocket-rails/) to get a feel for the project direction. Feedback is very much appreciated. Post an issue on the issue tracker or [shoot us an email](mailto://support@threedotloft.com) to give us your thoughts.
6
6
 
7
- ## Overview
8
-
9
- Plug and play WebSocket support for ruby on rails with streaming HTTP fallback for improved cross browser compatibility. Includes event router for mapping javascript events to controller actions. There is no need for a separate WebSocket server process. Requests to `/websocket` will be passed through to the `ConnectionManager` class which is a simple Rack based WebSocket server developed using the `Faye::WebSocket` library.
10
-
11
- *Important Note*
12
-
13
- This is mostly a proof of concept as of right now. Please try it out and let me know what you like or dislike. We will be adding much more soon including a development road map.
14
-
15
- *Update*
16
-
17
- We are finally very close to the first production release. Any comments or suggestions would be appreciated. The test coverage is now solid and the `ConnectionManager` class has been completely rewritten. There were a few bugs in the connection management on the first release that have been eliminated. Please upgrade to the latest version if you have not yet done so.
18
-
19
- ## Installation
20
-
21
- Check out the [Example Application](https://github.com/DanKnox/websocket-rails-Example-Project) for additional information.
22
-
23
- 1. Add the gem to your Gemfile
24
- 3. Create a WebsocketRails controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/BaseController)
25
- 4. Create an `events.rb` initializer file to map events to your controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/EventMap)
26
- 5. Launch the web server and connect a WebSocket client to `ws://yourserver:port/websocket`
27
-
28
- *Important Note About Web Servers*
29
-
30
- Thin is the only web server currently supported. Use the `thin-socketrails` executable to override the Thin connection timeout setting. The full command to start the server in development is `thin-socketrails -p 3000 start`. Be sure to enable config.threadsafe! in your rails application and use the Rack::Fiberpool middleware to take advantage of Thin's asynchronous request processing.
31
-
32
- ````ruby
33
- gem 'websocket-rails'
34
- ````
7
+ ## Update July 17 2012
35
8
 
36
- ## Event Router
9
+ We just released a new version containing significant new functionality.
10
+ We will be updating the documentation and wiki guides to cover it all over the next day
11
+ or two but check out the
12
+ [CHANGELOG](https://github.com/DanKnox/websocket-rails/blob/master/CHANGELOG.md) to get an early preview.
37
13
 
38
- Map WebSocket events to controller actions by creating an `events.rb` file in your app/config/initializers directory
14
+ ## Overview
39
15
 
40
- There are two built in events that are fired automatically by the dispatcher. The built in events are `:client_connected` and `:client_disconnected`. They are triggered when a new WebSocket client connects or disconnects to the server. You can handle them however you like by subscribing to the appropriate event in your `events.rb` file.
16
+ Start treating client side events as first class citizens inside your
17
+ Rails application with a built in WebSocket server. Sure, WebSockets
18
+ aren't quite universal yet. That's why we also support streaming HTTP.
19
+ Oh, and if you don't mind running a separate process, you can support
20
+ just about any browser with Flash sockets.
41
21
 
42
- You can subscribe multiple controllers and actions to the same event to provide very clean event handling logic. The new message will be available in each controller using the `message` method discussed in the *Controllers* section below. The example event router below demonstrates subscribing to the `:new_message` event with one controller action to rebroadcast the message out to all connected clients and another controller action to log the message to a database.
22
+ ## Respond Quicker with Socket Events
43
23
 
44
- The Event Map now supports namespacing events. Events triggered on the
45
- client as `namespace.event_name` will now be dispatched to the action
46
- subscribed to the `event_name` event under the `namespace` namespace.
47
- See the [EventMap
48
- Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/EventMap) for more details.
24
+ Map events to controller actions using an Event Router.
49
25
 
50
26
  ````ruby
51
- # app/config/initializers
52
-
53
27
  WebsocketRails::EventMap.describe do
54
- # The :client_connected method is fired automatically when a new client connects
55
- subscribe :client_connected, to: ChatController, with_method: :client_connected
56
-
57
- # You can subscribe any number of controller actions to a single event
58
- subscribe :new_message, to: ChatController, with_method: :new_message
59
- subscribe :new_message, to: ChatLogController, with_method: :log_message
60
-
61
- subscribe :new_user, to: ChatController, with_method: :new_user
62
- subscribe :change_username, to: ChatController, with_method: :change_username
63
-
64
- namespace :product do
65
- subscribe :new, to: ProductController, with_method: :new_product
28
+ namespace :tasks do
29
+ subscribe :create, :to => TaskController, :with_method => :create
66
30
  end
67
-
68
- # The :client_disconnected method is fired automatically when a client disconnects
69
- subscribe :client_disconnected, to: ChatController, with_method: :delete_user
70
31
  end
71
32
  ````
72
33
 
73
- The `subscribe` method takes the event name as the first argument, then a hash where `:to` is the Controller class and `:with_method` is the action to execute.
74
-
75
- ## WebSocketRails JavaScript Client
76
-
77
- There is an accompanying JavaScript client [located here](https://github.com/DanKnox/websocket-rails/tree/master/lib/assets/javascripts/websocket_rails). The client detects support for WebSockets in the browser and falls back to HTTP streaming if it is unavailable. The client currently works in every major browser except for internet explorer. If you are using the current master branch of this repository, you can require the client in your application.js manifest directly: `//= require websocket_rails/main`. The client will be released to rubygems soon.
34
+ Trigger events using our JavaScript client.
78
35
 
79
36
  ````javascript
80
- // Setting up the client and connecting to the server:
81
- // Do not pass the prefix of the URL, 'ws://' will be
82
- // added automatically when the client uses WebSockets
83
- var dispatcher = new WebSocketRails("localhost:port/websocket");
84
-
85
- dispatcher.on_open = function() {
86
- // trigger a server event immediately after opening connection
87
- dispatcher.trigger('new_user',{user_name: 'guest'});
37
+ var task = {
38
+ name: 'Start taking advantage of WebSockets',
39
+ completed: false
88
40
  }
89
-
90
- //Triggering a new event on the server
91
- dispatcher.trigger('event_name',object_to_be_serialized_to_json);
92
-
93
- //Listening for new events from the server
94
- dispatcher.bind('event_name', function(data) {
95
- console.log(data.user_name);
96
- })
41
+ dispatcher = new WebSocketRails('localhost:3000/websocket');
42
+ dispatcher.trigger('tasks.create', task);
97
43
  ````
98
44
 
99
- Check out the [example application](https://github.com/DanKnox/websocket-rails-Example-Project) for a working implementation.
100
-
101
- ## Controllers
102
-
103
- There are a few differences between WebSocket controllers and standard Rails controllers. The biggest of which, is that each event will be handled by the same, continually running instance of your controller class. This means that if you set any instance variables in your methods, they will still be available when the next event is processed. On top of that, every single client that is connected will share these same instance variables. This can be an advantage if used properly, but it can also lead to bugs if not expected. We provide our own `DataStore` object accessible in a WebsocketRails controller to make it easier to store data isolated from each connected client. This is explained further below.
104
-
105
- Do not override the `initialize` method in your class to set up. Instead, define an `initialize_session` method and perform your set up there. The `initialize_session` method will be called the first time a controller is subscribed to an event in the event router. Instance variables defined in the `initialize_session` method will be available throughout the course of the server lifetime.
106
-
107
- ````
108
- class ChatController < WebsocketRails::BaseController
109
- def initialize_session
110
- # perform application setup here
111
- @message_count = 0
112
- end
113
- end
114
- ````
115
-
116
- The WebsocketRails::BaseController class provides methods for working with the WebSocket connection. Make sure you extend this class for controllers that you are using. The two most important methods are `send_message` and `broadcast_message`. The `send_message` method sends a message to the client that initiated this event, the `broadcast_message` method broadcasts messages to all connected clients. Both methods take two arguments, the event name to trigger on the client, and the message that accompanies it.
45
+ Handle events in your controller.
117
46
 
118
47
  ````ruby
119
- new_message = {:message => 'this is a message'}
120
- broadcast_message :event_name, new_message
121
- send_message :event_name, new_message
122
- ````
123
-
124
- Here is an example controller for handling the `:new_message` event for a basic chat application.
125
-
126
- ````ruby
127
- class ChatController < WebsocketRails::BaseController
128
- def new_message
129
- # Print the new message and client id to the console
130
- puts "Message from client #{client_id} received: #{message.inspect}"
131
-
132
- # Broadcast the new message to all connected clients
133
- broadcast_message :new_message, message
48
+ class TaskController < WebsocketRails::BaseController
49
+ def create
50
+ # The `message` method contains the data received
51
+ task = Task.new message
52
+ if task.save
53
+ send_message :create_success, task, :namespace => :tasks
54
+ else
55
+ send_message :create_fail, task, :namespace => :tasks
56
+ end
134
57
  end
135
58
  end
136
59
  ````
137
60
 
138
- We are using several of the methods provided by `WebsocketRails::BaseController` here, two of which are in the `puts` statement.
139
-
140
- The first method used is the `client_id` method. This method contains the ID of the current WebSocket client that initiated this event. Each connected client is randomly assigned an ID upon connecting to the server. You can keep track of who is who by storing the `client_id` associated with each user somewhere. You can also use the provided `DataStore` (explained later) to make keeping track of users easier.
61
+ Receive the response in the client.
141
62
 
142
- The next method used is the `message` method. This method will always return the message, if any, that was received along with the event initiated by the client. These messages are JSON decoded by the dispatcher automatically so you can serialize objects in your javascript client and send them along with events.
143
-
144
- Lastly, the `broadcast_message` method is called, triggering the `:new_message` event on every connected client and sending the `message` received from the client along with it.
145
-
146
- ## Data Store
147
-
148
- The `DataStore` object is a private Hash. When you access it in a controller, you will be accessing a Hash that is private to the client that initiated the event currently executing. You can use it exactly as you would a regular Hash, except you do not have to worry about it being overridden by the next client that triggers an event. This means that unlike instance variables in WebsocketRails controllers which are shared amongst all connected clients, the data store is an easy place to temporarily persist data for each user between events.
149
-
150
- You can access the `DataStore` object by using the `data_store` controller method.
151
-
152
- ````ruby
153
- class ChatController < WebsocketRails::BaseController
154
- def new_user
155
- # The instance variable would be overwritten when the next user joins
156
- @user = User.new(name: message[:user_name]) # No Good!
157
-
158
- # This will be private for each user
159
- data_store[:user] = User.new(name: message[:user_name]) # Good!
160
- broadcast_user_list
161
- end
162
- end
63
+ ````javascript
64
+ dispatcher.bind('tasks.create_successful', function(task) {
65
+ console.log('successfully created ' + task.name);
66
+ });
163
67
  ````
164
68
 
165
- If you wish to output an Array of the assigned values in the data store for every connected client, you can use the `each_<key>` method, replacing `<key>` with the hash key that you wish to collect.
166
-
167
- Given our ongoing chat server example, we could collect all of the current `User` objects like so:
168
- d
169
- ````ruby
170
- data_store[:user] = 'User3'
171
- data_store.each_user
172
- => ['User1','User2','User3']
173
- ````
69
+ ## Channel Support
174
70
 
175
- A simple method for broadcasting the current user list to all clients would look like this:
71
+ Keep your users up to date without waiting for them to refresh the page.
72
+ Subscribe them to a channel and update it from wherever you please.
176
73
 
177
- ````ruby
178
- def broadcast_user_list
179
- users = data_store.each_user
180
- broadcast_message :user_list, users
181
- end
182
- ````
74
+ Tune in on the client side.
183
75
 
184
- ## Message Format
185
-
186
- The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client. The message arrives at the client as a two element serialized array with the `event_name` string as the first element, and the message object you passed to the `message` parameter of the `send_message` method as the second element. The example JavaScript dispatchers decode the JSON for you as well as provide a few conveniences for event subscribing and dispatching.
76
+ ````javascript
77
+ channel = dispatcher.subscribe('posts');
78
+ channel.bind('new', function(post) {
79
+ console.log('a new post about '+post.title+' arrived!');
80
+ });
81
+ ````
187
82
 
188
- If you executed this code in your controller:
83
+ Broadcast to the channel from anywhere inside your Rails application. An
84
+ existing controller, a model, a background job, or a new WebsocketRails
85
+ controller.
189
86
 
190
87
  ````ruby
191
- new_message = {:message => 'this is a message'}
192
- send_message :new_message, new_message
88
+ latest_post = Post.latest
89
+ WebsocketRails[:posts].trigger('new', latest_post)
193
90
  ````
194
91
 
195
- The message that arrives on the client would look like:
196
-
197
- ````javascript
198
- ['new_message',{message: 'this is a message'}]
199
- ````
92
+ ## Installation and Usage Guides
93
+
94
+ Check out the [Example Application](https://github.com/DanKnox/websocket-rails-Example-Project) for an example implementation.
95
+
96
+ * [Installation
97
+ Guide](https://github.com/DanKnox/websocket-rails/wiki/Installation-and-Setup)
98
+ * [Event
99
+ Router](https://github.com/DanKnox/websocket-rails/wiki/The-Event-Router)
100
+ * [WebsocketRails Controllers](https://github.com/DanKnox/websocket-rails/wiki/WebsocketRails Controllers)
101
+ * [Using the JavaScript
102
+ Client](https://github.com/DanKnox/websocket-rails/wiki/Using-the-JavaScript-Client)
103
+ * [Using
104
+ Channels](https://github.com/DanKnox/websocket-rails/wiki/Working-with-Channels)
105
+ * [The
106
+ DataStore](https://github.com/DanKnox/websocket-rails/wiki/Using-the-DataStore)
200
107
 
201
108
  ## Development
202
109
 
data/Rakefile CHANGED
@@ -17,8 +17,6 @@ end
17
17
 
18
18
  Bundler::GemHelper.install_tasks
19
19
 
20
- task :default => :spec
21
-
22
20
  RDoc::Task.new(:rdoc) do |rdoc|
23
21
  rdoc.rdoc_dir = 'rdoc'
24
22
  rdoc.title = 'websocket-rails'
@@ -29,17 +27,46 @@ end
29
27
 
30
28
  require 'rspec/core/rake_task'
31
29
 
32
- desc 'Default: run specs.'
33
- task :default => :spec
30
+ desc 'Default: run RSpec and Jasmine specs.'
31
+ task :default => :spec_and_jasmine
34
32
 
35
33
  desc "Run specs"
36
34
  RSpec::Core::RakeTask.new do |t|
37
35
  t.pattern = "./spec/**/*_spec.rb"
38
36
  end
39
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
+
40
44
  desc "Generate code coverage"
41
45
  task :coverage do
42
46
  ENV['COVERAGE'] = 'true'
43
47
  Rake::Task["spec"].execute
44
48
  `open coverage/index.html`
45
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
data/bin/thin-socketrails CHANGED
@@ -18,6 +18,21 @@
18
18
  require 'rubygems'
19
19
  require 'thin'
20
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
+
21
36
  if EM::VERSION < "1.0.0"
22
37
  begin
23
38
  old_verbose, $VERBOSE = $VERBOSE, nil
@@ -27,4 +42,4 @@ if EM::VERSION < "1.0.0"
27
42
  end
28
43
  end
29
44
 
30
- Thin::Runner.new(ARGV).run!
45
+ Thin::Runner.new(ARGV).run!
@@ -9,18 +9,33 @@ For instance:
9
9
  ###
10
10
  class WebSocketRails.Channel
11
11
 
12
- constructor: (@name,@dispatcher) ->
13
- @dispatcher.trigger 'websocket_rails.subscribe', {channel: @name}
14
- @callbacks = {}
12
+ constructor: (@name,@_dispatcher,@is_private) ->
13
+ if @is_private
14
+ event_name = 'websocket_rails.subscribe_private'
15
+ else
16
+ event_name = 'websocket_rails.subscribe'
17
+
18
+ event = new WebSocketRails.Event( [event_name, {data: {channel: @name}},@_dispatcher.connection_id], @_success_launcher, @_failure_launcher)
19
+ @_dispatcher.trigger_event event
20
+ @_callbacks = {}
15
21
 
16
22
  bind: (event_name, callback) =>
17
- @callbacks[event_name] ?= []
18
- @callbacks[event_name].push callback
23
+ @_callbacks[event_name] ?= []
24
+ @_callbacks[event_name].push callback
19
25
 
20
26
  trigger: (event_name, message) =>
21
- @dispatcher.trigger_channel @name, event_name, message
27
+ event = new WebSocketRails.Event( [event_name, {channel: @name, data: message}, @_dispatcher.connection_id] )
28
+ @_dispatcher.trigger_event event
22
29
 
23
30
  dispatch: (event_name, message) =>
24
- return unless @callbacks[event_name]?
25
- for callback in @callbacks[event_name]
31
+ return unless @_callbacks[event_name]?
32
+ for callback in @_callbacks[event_name]
26
33
  callback message
34
+
35
+ # using this method because @on_success will not be defined when the constructor is executed
36
+ _success_launcher: (data) =>
37
+ @on_success(data) if @on_success?
38
+
39
+ # using this method because @on_failure will not be defined when the constructor is executed
40
+ _failure_launcher: (data) =>
41
+ @on_failure(data) if @on_failure?
@@ -0,0 +1,40 @@
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
+ @connection_id = data[2]
15
+ if attr.success?
16
+ @result = true
17
+ @success = attr.success
18
+
19
+ is_channel: =>
20
+ @channel?
21
+
22
+ is_result: =>
23
+ @result == true
24
+
25
+ is_ping: =>
26
+ @name == 'websocket_rails.ping'
27
+
28
+ serialize: =>
29
+ JSON.stringify [@name, @attributes()]
30
+
31
+ attributes: =>
32
+ id: @id,
33
+ channel: @channel,
34
+ data: @data
35
+
36
+ run_callbacks: (success,data) =>
37
+ if success == true
38
+ @success_callback?(data)
39
+ else
40
+ @failure_callback?(data)
@@ -21,8 +21,9 @@ class WebSocketRails.HttpConnection
21
21
  xmlhttp
22
22
 
23
23
  constructor: (@url, @dispatcher) ->
24
- @_conn = @createXMLHttpObject()
25
- @last_pos = 0
24
+ @_conn = @createXMLHttpObject()
25
+ @last_pos = 0
26
+ @message_queue = []
26
27
  @_conn.onreadystatechange = @parse_stream
27
28
  @_conn.open "GET", "/websocket", true
28
29
  @_conn.send()
@@ -31,16 +32,15 @@ class WebSocketRails.HttpConnection
31
32
  if @_conn.readyState == 3
32
33
  data = @_conn.responseText.substring @last_pos
33
34
  @last_pos = @_conn.responseText.length
35
+ data = data.replace "]][[", "],["
34
36
  decoded_data = JSON.parse data
35
37
  @dispatcher.new_message decoded_data
36
38
 
37
- trigger: (event_name, data, connection_id) =>
38
- payload = JSON.stringify [event_name, data]
39
- @post_data connection_id, payload
40
-
41
- trigger_channel: (channel_name, event_name, data, connection_id) =>
42
- payload = JSON.stringify [channel_name, event_name, data]
43
- @post_data connection_id, payload
39
+ trigger: (event) =>
40
+ if @dispatcher.state != 'connected'
41
+ @message_queue.push event
42
+ else
43
+ @post_data @dispatcher.connection_id, event.serialize()
44
44
 
45
45
  post_data: (connection_id, payload) ->
46
46
  $.ajax "/websocket",
@@ -49,4 +49,12 @@ class WebSocketRails.HttpConnection
49
49
  client_id: connection_id
50
50
  data: payload
51
51
  success: ->
52
- console.log "success"
52
+
53
+ flush_queue: (connection_id) =>
54
+ for event in @message_queue
55
+ # Events queued before connecting do not have the correct
56
+ # connection_id set yet. We need to update it before dispatching.
57
+ if connection_id?
58
+ event.connection_id = @dispatcher.connection_id
59
+ @trigger event
60
+ @message_queue = []
@@ -1,4 +1,5 @@
1
1
  //= require ./websocket_rails
2
+ //= require ./event
2
3
  //= require ./http_connection
3
4
  //= require ./websocket_connection
4
5
  //= require ./channel
@@ -4,22 +4,27 @@ WebSocket Interface for the WebSocketRails client.
4
4
  class WebSocketRails.WebSocketConnection
5
5
 
6
6
  constructor: (@url,@dispatcher) ->
7
- @_conn = new WebSocket("ws://#{@url}")
7
+ @url = "ws://#{@url}" unless @url.match(/^wss?:\/\//)
8
+ @message_queue = []
9
+ @_conn = new WebSocket(@url)
8
10
  @_conn.onmessage = @on_message
9
11
  @_conn.onclose = @on_close
10
12
 
11
- trigger: (event_name, data, connection_id) =>
12
- payload = JSON.stringify [event_name, data]
13
- @_conn.send payload
14
-
15
- trigger_channel: (channel_name, event_name, data, connection_id) =>
16
- payload = JSON.stringify [channel_name, event_name, data]
17
- @_conn.send payload
13
+ trigger: (event) =>
14
+ if @dispatcher.state != 'connected'
15
+ @message_queue.push event
16
+ else
17
+ @_conn.send event.serialize()
18
18
 
19
19
  on_message: (event) =>
20
20
  data = JSON.parse event.data
21
- console.log data
22
21
  @dispatcher.new_message data
23
22
 
24
23
  on_close: (event) =>
25
- @dispatcher.dispatch 'connection_close', {}
24
+ close_event = new WebSocketRails.Event(['connection_closed',{}])
25
+ @dispatcher.dispatch close_event
26
+
27
+ flush_queue: =>
28
+ for event in @message_queue
29
+ @_conn.send event.serialize()
30
+ @message_queue = []