websocket-rails-js 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +11 -0
  4. data/Guardfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +13 -0
  7. data/Rakefile +36 -0
  8. data/app/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  9. data/app/assets/javascripts/websocket_rails/channel.js.coffee +66 -0
  10. data/app/assets/javascripts/websocket_rails/connection.js.coffee +62 -0
  11. data/app/assets/javascripts/websocket_rails/event.js.coffee +43 -0
  12. data/app/assets/javascripts/websocket_rails/http_connection.js.coffee +56 -0
  13. data/app/assets/javascripts/websocket_rails/main.js +6 -0
  14. data/app/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
  15. data/app/assets/javascripts/websocket_rails/websocket_rails.js.coffee +148 -0
  16. data/lib/websocket-rails-js.rb +1 -0
  17. data/lib/websocket_rails/js/engine.rb +8 -0
  18. data/lib/websocket_rails/js/version.rb +5 -0
  19. data/spec/javascripts/helpers/.gitkeep +0 -0
  20. data/spec/javascripts/helpers/helpers.coffee +7 -0
  21. data/spec/javascripts/support/jasmine.yml +139 -0
  22. data/spec/javascripts/support/jasmine_helper.rb +15 -0
  23. data/spec/javascripts/support/jquery.min.js +4 -0
  24. data/spec/javascripts/support/sinon-1.7.1.js +4343 -0
  25. data/spec/javascripts/websocket_rails/channel_spec.js.coffee +99 -0
  26. data/spec/javascripts/websocket_rails/connection_spec.js.coffee +136 -0
  27. data/spec/javascripts/websocket_rails/event_spec.js.coffee +70 -0
  28. data/spec/javascripts/websocket_rails/helpers.js.coffee +7 -0
  29. data/spec/javascripts/websocket_rails/websocket_rails_spec.js.coffee +233 -0
  30. data/src/websocket_rails/abstract_connection.js +71 -0
  31. data/src/websocket_rails/channel.js +133 -0
  32. data/src/websocket_rails/connection.js +91 -0
  33. data/src/websocket_rails/event.js +66 -0
  34. data/src/websocket_rails/http_connection.js +97 -0
  35. data/src/websocket_rails/websocket_connection.js +59 -0
  36. data/src/websocket_rails/websocket_rails.js +218 -0
  37. data/websocket-rails-js.gemspec +25 -0
  38. data/websocket_rails.0.0.1.min.js +1 -0
  39. metadata +135 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 08228ce10d8e4fb42c6d438673760224b7f01bac
4
+ data.tar.gz: b9e8c58acee13e1fde180bec6bc5b582a9786d28
5
+ SHA512:
6
+ metadata.gz: c6d67c1fc3391737d8e2763919d8a84c2edac191fb5564ff295f184c2c88080922a491c44798f406da35e202bb38f52728f52d3752a39b311d8179d5108ff9db
7
+ data.tar.gz: f1e815c159b453a6fefec193f2213e129fe7e8e2676e9d9184cd2e1ff0e9ba665e85f72ec3a1249c91f757ba416105b8b3a66b45889a8d4da15f72dcbdebf90f
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/javascripts/generated/*.js
19
+ spec/javascripts/helpers/*.js
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in websocket-rails-js.gemspec
4
+ gemspec
5
+
6
+ gem 'guard'
7
+ gem 'guard-coffeescript'
8
+ gem 'terminal-notifier-guard'
9
+
10
+ gem 'jasmine'
11
+ gem 'uglifier'
@@ -0,0 +1,6 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'coffeescript', :input => 'assets/javascripts', :output => 'src/'
5
+ guard 'coffeescript', :input => 'spec/javascripts/websocket_rails', :output => 'spec/javascripts/generated'
6
+ guard 'coffeescript', :input => 'spec/javascripts/helpers', :output => 'spec/javascripts/helpers'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dan Knox
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,13 @@
1
+ # Websocket Rails JavaScript Client
2
+
3
+ Refer to the [Websocket-Rails
4
+ Wiki](https://github.com/websocket-rails/websocket-rails/wiki) for usage
5
+ instructions.
6
+
7
+ ## Contributing
8
+
9
+ 1. Fork it ( http://github.com/<my-github-username>/websocket-rails-js/fork )
10
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
11
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
12
+ 4. Push to the branch (`git push origin my-new-feature`)
13
+ 5. Create new Pull Request
@@ -0,0 +1,36 @@
1
+ require "bundler/gem_tasks"
2
+ require 'websocket_rails/js/version'
3
+ require "uglifier"
4
+ require 'jasmine'
5
+
6
+ load 'jasmine/tasks/jasmine.rake'
7
+
8
+
9
+ task :build_js_release do
10
+ PATH = 'src/websocket_rails/'
11
+
12
+ SOURCE_FILES = %w(
13
+ websocket_rails.js
14
+ event.js
15
+ abstract_connection.js
16
+ websocket_connection.js
17
+ channel.js
18
+ )
19
+
20
+ tempfile = Tempfile.new('websocket_rails.min.js')
21
+
22
+ SOURCE_FILES.each do |fname|
23
+ tempfile.write(File.read(PATH + fname))
24
+ end
25
+ tempfile.rewind
26
+
27
+ uglifier = Uglifier.new(:mangle => false)
28
+
29
+ uglified = uglifier.compile(tempfile.read)
30
+
31
+ tempfile.unlink
32
+
33
+ File.open("websocket_rails.#{WebsocketRails::Js::VERSION}.min.js", "w+") do |f|
34
+ f.write(uglified)
35
+ end
36
+ end
@@ -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,66 @@
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
+ class WebSocketRails.Channel
11
+
12
+ constructor: (@name, @_dispatcher, @is_private, @on_success, @on_failure) ->
13
+ if @is_private
14
+ event_name = 'websocket_rails.subscribe_private'
15
+ else
16
+ event_name = 'websocket_rails.subscribe'
17
+
18
+ @connection_id = @_dispatcher._conn?.connection_id
19
+ event = new WebSocketRails.Event([event_name, {channel: @name}, {connection_id: @connection_id}], @_success_launcher, @_failure_launcher)
20
+ @_dispatcher.trigger_event event
21
+ @_callbacks = {}
22
+ @_token = undefined
23
+ @_queue = []
24
+
25
+ is_public: ->
26
+ !@is_private
27
+
28
+ destroy: ->
29
+ if @connection_id == @_dispatcher._conn?.connection_id
30
+ event_name = 'websocket_rails.unsubscribe'
31
+ event = new WebSocketRails.Event([event_name, {channel: @name}, {connection_id: @connection_id, token: @_token}])
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, message, {connection_id: @connection_id, channel: @name, token: @_token}])
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
+ @_token = message['token']
52
+ for event in @_queue
53
+ @_dispatcher.trigger_event event
54
+ @_queue = []
55
+ else
56
+ return unless @_callbacks[event_name]?
57
+ for callback in @_callbacks[event_name]
58
+ callback message
59
+
60
+ # using this method because @on_success will not be defined when the constructor is executed
61
+ _success_launcher: (data) =>
62
+ @on_success(data) if @on_success?
63
+
64
+ # using this method because @on_failure will not be defined when the constructor is executed
65
+ _failure_launcher: (data) =>
66
+ @on_failure(data) if @on_failure?
@@ -0,0 +1,62 @@
1
+ ###
2
+ WebSocket Interface for the WebSocketRails client.
3
+ ###
4
+ class WebSocketRails.Connection
5
+
6
+ constructor: (@url, @dispatcher) ->
7
+ @message_queue = []
8
+ @state = 'connecting'
9
+ @connection_id
10
+
11
+
12
+ unless @url.match(/^wss?:\/\//) || @url.match(/^ws?:\/\//)
13
+ if window.location.protocol == 'https:'
14
+ @url = "wss://#{@url}"
15
+ else
16
+ @url = "ws://#{@url}"
17
+
18
+ @_conn = new WebSocket(@url)
19
+
20
+ @_conn.onmessage = (event) =>
21
+ event_data = JSON.parse event.data
22
+ @on_message(event_data)
23
+
24
+ @_conn.onclose = (event) =>
25
+ @on_close(event)
26
+
27
+ @_conn.onerror = (event) =>
28
+ @on_error(event)
29
+
30
+ on_message: (event) ->
31
+ @dispatcher.new_message event
32
+
33
+ on_close: (event) ->
34
+ @dispatcher.state = 'disconnected'
35
+ # Pass event.data here if this was triggered by the WebSocket directly
36
+ data = if event?.data then event.data else event
37
+ @dispatcher.dispatch new WebSocketRails.Event(['connection_closed', data])
38
+
39
+ on_error: (event) ->
40
+ @dispatcher.state = 'disconnected'
41
+ # Pass event.data here since this was triggered by the WebSocket directly
42
+ @dispatcher.dispatch new WebSocketRails.Event(['connection_error', event.data])
43
+
44
+ trigger: (event) ->
45
+ if @dispatcher.state != 'connected'
46
+ @message_queue.push event
47
+ else
48
+ @send_event event
49
+
50
+ close: ->
51
+ @_conn.close()
52
+
53
+ setConnectionId: (connection_id) ->
54
+ @connection_id = connection_id
55
+
56
+ send_event: (event) ->
57
+ @_conn.send event.serialize()
58
+
59
+ flush_queue: ->
60
+ for event in @message_queue
61
+ @trigger event
62
+ @message_queue = []
@@ -0,0 +1,43 @@
1
+ ###
2
+ The Event object stores all the relevant event information.
3
+ ###
4
+
5
+ class WebSocketRails.Event
6
+
7
+ constructor: (message, @success_callback, @failure_callback) ->
8
+ @name = message[0]
9
+ @data = message[1]
10
+ options = message[2]
11
+
12
+ if options?
13
+ @id = if options['id']? then options['id'] else (((1+Math.random())*0x10000)|0)
14
+ @channel = options.channel
15
+ @token = options.token
16
+ @connection_id = options.connection_id
17
+ if options.success?
18
+ @result = true
19
+ @success = options.success
20
+
21
+ is_channel: ->
22
+ @channel?
23
+
24
+ is_result: ->
25
+ typeof @result != 'undefined'
26
+
27
+ is_ping: ->
28
+ @name == 'websocket_rails.ping'
29
+
30
+ serialize: ->
31
+ JSON.stringify [@name, @data, @meta_data()]
32
+
33
+ meta_data: ->
34
+ id: @id,
35
+ connection_id: @connection_id,
36
+ channel: @channel,
37
+ token: @token
38
+
39
+ run_callbacks: (@success, @result) ->
40
+ if @success == true
41
+ @success_callback?(@result)
42
+ else
43
+ @failure_callback?(@result)
@@ -0,0 +1,56 @@
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 XMLHttpRequest(),
9
+ -> new ActiveXObject("Msxml2.XMLHTTP"),
10
+ -> new ActiveXObject("Msxml3.XMLHTTP"),
11
+ -> new ActiveXObject("Microsoft.XMLHTTP")
12
+ ]
13
+
14
+ constructor: (url, @dispatcher) ->
15
+ super
16
+ @_url = "http://#{url}"
17
+ @_conn = @_createXMLHttpObject()
18
+ @last_pos = 0
19
+ @_conn.onreadystatechange = => @_parse_stream()
20
+ @_conn.addEventListener("load", @on_close, false)
21
+ @_conn.open "GET", @_url, true
22
+ @_conn.send()
23
+
24
+ close: ->
25
+ @_conn.abort()
26
+
27
+ send_event: (event) ->
28
+ super
29
+ @_post_data event.serialize()
30
+
31
+ _post_data: (payload) ->
32
+ $.ajax @_url,
33
+ type: 'POST'
34
+ data:
35
+ client_id: @connection_id
36
+ data: payload
37
+ success: ->
38
+
39
+ _createXMLHttpObject: ->
40
+ xmlhttp = false
41
+ factories = @_httpFactories()
42
+ for factory in factories
43
+ try
44
+ xmlhttp = factory()
45
+ catch e
46
+ continue
47
+ break
48
+ xmlhttp
49
+
50
+ _parse_stream: ->
51
+ if @_conn.readyState == 3
52
+ data = @_conn.responseText.substring @last_pos
53
+ @last_pos = @_conn.responseText.length
54
+ data = data.replace( /\]\]\[\[/g, "],[" )
55
+ event_data = JSON.parse data
56
+ @on_message(event_data)
@@ -0,0 +1,6 @@
1
+ //= require ./websocket_rails
2
+ //= require ./event
3
+ //= require ./abstract_connection
4
+ //= require ./http_connection
5
+ //= require ./websocket_connection
6
+ //= require ./channel
@@ -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,148 @@
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, this
35
+ else
36
+ @_conn = new WebSocketRails.WebSocketConnection @url, this
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
+ event = new WebSocketRails.Event(data)
69
+ if event.is_result()
70
+ @queue[event.id]?.run_callbacks(event.success, event.data)
71
+ @queue[event.id] = null
72
+ else if event.is_channel()
73
+ @dispatch_channel event
74
+ else
75
+ @dispatch event
76
+
77
+ if @state == 'connecting' and event.name == 'client_connected'
78
+ @connection_established event
79
+
80
+ connection_established: (event) =>
81
+ @state = 'connected'
82
+ @_conn.setConnectionId(event.connection_id)
83
+ @_conn.flush_queue()
84
+ if @on_open?
85
+ @on_open(event.data)
86
+
87
+ bind: (event_name, callback) =>
88
+ @callbacks[event_name] ?= []
89
+ @callbacks[event_name].push callback
90
+
91
+ trigger: (event_name, data, success_callback, failure_callback) =>
92
+ event = new WebSocketRails.Event([event_name, data, {connection_id: @connection_id}], success_callback, failure_callback)
93
+ @queue[event.id] = event
94
+ @_conn.trigger event
95
+
96
+ trigger_event: (event) =>
97
+ @queue[event.id] ?= event # Prevent replacing an event that has callbacks stored
98
+ @_conn.trigger event
99
+ event
100
+
101
+ dispatch: (event) =>
102
+ return unless @callbacks[event.name]?
103
+ for callback in @callbacks[event.name]
104
+ callback event.data
105
+
106
+ subscribe: (channel_name, success_callback, failure_callback) =>
107
+ unless @channels[channel_name]?
108
+ channel = new WebSocketRails.Channel channel_name, @, false, success_callback, failure_callback
109
+ @channels[channel_name] = channel
110
+ channel
111
+ else
112
+ @channels[channel_name]
113
+
114
+ subscribe_private: (channel_name, success_callback, failure_callback) =>
115
+ unless @channels[channel_name]?
116
+ channel = new WebSocketRails.Channel channel_name, @, true, success_callback, failure_callback
117
+ @channels[channel_name] = channel
118
+ channel
119
+ else
120
+ @channels[channel_name]
121
+
122
+ unsubscribe: (channel_name) =>
123
+ return unless @channels[channel_name]?
124
+ @channels[channel_name].destroy()
125
+ delete @channels[channel_name]
126
+
127
+ dispatch_channel: (event) =>
128
+ return unless @channels[event.channel]?
129
+ @channels[event.channel].dispatch event.name, event.data
130
+
131
+ supports_websockets: =>
132
+ (typeof(WebSocket) == "function" or typeof(WebSocket) == "object")
133
+
134
+ connection_stale: =>
135
+ @state != 'connected'
136
+
137
+ reconnect_channels: ->
138
+ for name, channel of @channels
139
+ callbacks = channel._callbacks
140
+ channel.destroy()
141
+ delete @channels[name]
142
+
143
+ channel = if channel.is_private
144
+ @subscribe_private name
145
+ else
146
+ @subscribe name
147
+ channel._callbacks = callbacks
148
+ channel