websocket-rails-js 0.0.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.
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