faye 0.3.4 → 0.5.0

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.

Potentially problematic release.


This version of faye might be problematic. Click here for more details.

Files changed (49) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +16 -33
  3. data/README.txt +9 -274
  4. data/Rakefile +4 -4
  5. data/lib/faye-browser-min.js +1 -0
  6. data/lib/faye.rb +26 -9
  7. data/lib/faye/{rack_adapter.rb → adapters/rack_adapter.rb} +38 -25
  8. data/lib/faye/error.rb +0 -7
  9. data/lib/faye/{logging.rb → mixins/logging.rb} +0 -0
  10. data/lib/faye/mixins/publisher.rb +29 -0
  11. data/lib/faye/{timeouts.rb → mixins/timeouts.rb} +1 -0
  12. data/lib/faye/{transport.rb → network/transport.rb} +32 -43
  13. data/lib/faye/{channel.rb → protocol/channel.rb} +23 -3
  14. data/lib/faye/{client.rb → protocol/client.rb} +124 -90
  15. data/lib/faye/{connection.rb → protocol/connection.rb} +41 -23
  16. data/lib/faye/protocol/extensible.rb +47 -0
  17. data/lib/faye/{grammar.rb → protocol/grammar.rb} +0 -0
  18. data/lib/faye/{server.rb → protocol/server.rb} +98 -54
  19. data/lib/faye/protocol/subscription.rb +23 -0
  20. data/lib/faye/{namespace.rb → util/namespace.rb} +0 -0
  21. data/lib/faye/util/web_socket.rb +119 -0
  22. data/lib/thin_extensions.rb +86 -0
  23. data/test/scenario.rb +68 -14
  24. data/test/test_clients.rb +215 -2
  25. data/test/test_server.rb +10 -10
  26. metadata +102 -71
  27. data/Jakefile +0 -13
  28. data/build/faye-client-min.js +0 -1
  29. data/build/faye.js +0 -1488
  30. data/examples/README.rdoc +0 -41
  31. data/examples/node/app.js +0 -26
  32. data/examples/node/client.js +0 -23
  33. data/examples/node/faye-client-min.js +0 -1
  34. data/examples/node/faye.js +0 -1488
  35. data/examples/rack/app.rb +0 -16
  36. data/examples/rack/client.rb +0 -25
  37. data/examples/rack/config.ru +0 -8
  38. data/examples/shared/public/favicon.ico +0 -0
  39. data/examples/shared/public/index.html +0 -49
  40. data/examples/shared/public/jquery.js +0 -19
  41. data/examples/shared/public/mootools.js +0 -4329
  42. data/examples/shared/public/prototype.js +0 -4874
  43. data/examples/shared/public/robots.txt +0 -0
  44. data/examples/shared/public/soapbox.js +0 -100
  45. data/examples/shared/public/style.css +0 -43
  46. data/jake.yml +0 -40
  47. data/lib/faye-client-min.js +0 -1
  48. data/test/scenario.js +0 -138
  49. data/test/test_clients.js +0 -195
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'rack'
3
3
  require 'thin'
4
+ require Faye::ROOT + '/thin_extensions'
4
5
 
5
6
  module Faye
6
7
  class RackAdapter
@@ -9,7 +10,7 @@ module Faye
9
10
  ASYNC_RESPONSE = [-1, {}, []].freeze
10
11
 
11
12
  DEFAULT_ENDPOINT = '/bayeux'
12
- SCRIPT_PATH = File.join(ROOT, 'faye-client-min.js')
13
+ SCRIPT_PATH = File.join(ROOT, 'faye-browser-min.js')
13
14
 
14
15
  TYPE_JSON = {'Content-Type' => 'application/json'}
15
16
  TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
@@ -20,21 +21,29 @@ module Faye
20
21
  @options = [app, options].grep(Hash).first || {}
21
22
 
22
23
  @endpoint = @options[:mount] || DEFAULT_ENDPOINT
23
- @endpoint_re = Regexp.new('^' + @endpoint + '(/[^/]+)*(\\.js)?$')
24
+ @endpoint_re = Regexp.new('^' + @endpoint + '(/[^/]*)*(\\.js)?$')
24
25
  @server = Server.new(@options)
25
26
  end
26
27
 
28
+ def add_extension(extension)
29
+ @server.add_extension(extension)
30
+ end
31
+
32
+ def remove_extension(extension)
33
+ @server.remove_extension(extension)
34
+ end
35
+
27
36
  def get_client
28
37
  @client ||= Client.new(@server)
29
38
  end
30
39
 
31
- def run(port)
40
+ def listen(port)
32
41
  handler = Rack::Handler.get('thin')
33
42
  handler.run(self, :Port => port)
34
43
  end
35
44
 
36
45
  def call(env)
37
- ensure_reactor_running!
46
+ Faye.ensure_reactor_running!
38
47
  request = Rack::Request.new(env)
39
48
 
40
49
  unless request.path_info =~ @endpoint_re
@@ -43,6 +52,10 @@ module Faye
43
52
  [404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
44
53
  end
45
54
 
55
+ if env['HTTP_UPGRADE'] == 'WebSocket'
56
+ return handle_upgrade(request)
57
+ end
58
+
46
59
  if request.path_info =~ /\.js$/
47
60
  return [200, TYPE_SCRIPT, File.new(SCRIPT_PATH)]
48
61
  end
@@ -51,14 +64,22 @@ module Faye
51
64
  json_msg = request.post? ? request.body.read : request.params['message']
52
65
  message = JSON.parse(json_msg)
53
66
  jsonp = request.params['jsonp'] || JSONP_CALLBACK
67
+ type = request.get? ? TYPE_SCRIPT : TYPE_JSON
68
+ callback = env['async.callback']
69
+ body = DeferredBody.new
54
70
 
55
71
  @server.flush_connection(message) if request.get?
56
72
 
57
- on_response(env, message) do |replies|
73
+ callback.call [200, type, body]
74
+
75
+ @server.process(message, false) do |replies|
58
76
  response = JSON.unparse(replies)
59
77
  response = "#{ jsonp }(#{ response });" if request.get?
60
- response
78
+ body.succeed(response)
61
79
  end
80
+
81
+ ASYNC_RESPONSE
82
+
62
83
  rescue
63
84
  [400, TYPE_TEXT, 'Bad request']
64
85
  end
@@ -66,27 +87,19 @@ module Faye
66
87
 
67
88
  private
68
89
 
69
- def on_response(env, message, &block)
70
- request = Rack::Request.new(env)
71
- type = request.get? ? TYPE_SCRIPT : TYPE_JSON
72
- callback = env['async.callback']
90
+ def handle_upgrade(request)
91
+ socket = Faye::WebSocket.new(request)
73
92
 
74
- if callback
75
- body = DeferredBody.new
76
- callback.call [200, type, body]
77
- @server.process(message, false) { |r| body.succeed block.call(r) }
78
- return ASYNC_RESPONSE
93
+ socket.onmessage = lambda do |message|
94
+ begin
95
+ message = JSON.parse(message.data)
96
+ @server.process(message, socket) do |replies|
97
+ socket.send(JSON.unparse(replies))
98
+ end
99
+ rescue
100
+ end
79
101
  end
80
-
81
- response = nil
82
- @server.process(message, false) { |r| response = block.call(r) }
83
- sleep(0.1) while response.nil?
84
- [200, type, [response]]
85
- end
86
-
87
- def ensure_reactor_running!
88
- Thread.new { EM.run } unless EM.reactor_running?
89
- while not EM.reactor_running?; end
102
+ ASYNC_RESPONSE
90
103
  end
91
104
 
92
105
  class DeferredBody
@@ -1,13 +1,6 @@
1
1
  module Faye
2
2
  class Error
3
3
 
4
- def self.parse(string)
5
- return nil unless Grammar::ERROR =~ string
6
- parts = string.split(':')
7
- args = parts[1].split(',')
8
- new(parts[0].to_i, args, parts[2])
9
- end
10
-
11
4
  def self.method_missing(type, *args)
12
5
  code = const_get(type.to_s.upcase)
13
6
  new(code[0], args, code[1]).to_s
@@ -0,0 +1,29 @@
1
+ module Faye
2
+ module Publisher
3
+
4
+ def count_subscribers(event_type)
5
+ return 0 unless @subscribers and @subscribers[event_type]
6
+ @subscribers[event_type].size
7
+ end
8
+
9
+ def add_subscriber(event_type, listener)
10
+ @subscribers ||= {}
11
+ list = @subscribers[event_type] ||= []
12
+ list << listener
13
+ end
14
+
15
+ def remove_subscriber(event_type, listener)
16
+ return unless @subscribers and @subscribers[event_type]
17
+ @subscribers[event_type].delete_if(&listener.method(:==))
18
+ end
19
+
20
+ def publish_event(event_type, *args)
21
+ return unless @subscribers and @subscribers[event_type]
22
+ @subscribers[event_type].each do |listener|
23
+ listener.call(*args)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+
@@ -1,6 +1,7 @@
1
1
  module Faye
2
2
  module Timeouts
3
3
  def add_timeout(name, delay, &block)
4
+ Faye.ensure_reactor_running!
4
5
  @timeouts ||= {}
5
6
  return if @timeouts.has_key?(name)
6
7
  @timeouts[name] = EventMachine.add_timer(delay) do
@@ -8,49 +8,33 @@ module Faye
8
8
  include Logging
9
9
 
10
10
  def initialize(client, endpoint)
11
- debug('Created new transport for ?', endpoint)
12
- @client = client
13
- @endpoint = endpoint
11
+ debug('Created new ? transport for ?', connection_type, endpoint)
12
+ @client = client
13
+ @endpoint = endpoint
14
14
  end
15
15
 
16
16
  def connection_type
17
17
  self.class.connection_type
18
18
  end
19
19
 
20
- def send(message, &block)
21
- if message.is_a?(Hash) and not message.has_key?('id')
22
- message['id'] = @client.namespace.generate
20
+ def send(messages, timeout)
21
+ messages = [messages].flatten
22
+ debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, messages)
23
+ request(messages, timeout)
24
+ end
25
+
26
+ def receive(responses)
27
+ debug('Client ? received from ?: ?', @client.client_id, @endpoint, responses)
28
+ responses.each { |response| @client.receive_message(response) }
29
+ end
30
+
31
+ def retry_block(message, timeout)
32
+ lambda do
33
+ EventMachine.add_timer(timeout) { request(message, 2 * timeout) }
23
34
  end
24
-
25
- debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, message)
26
-
27
- request(message) { |responses|
28
- debug('Client ? received from ?: ?', @client.client_id, @endpoint, responses)
29
-
30
- if block_given?
31
- messages, deliverable = [], true
32
- [responses].flatten.each do |response|
33
-
34
- if message.is_a?(Hash) and response['id'] == message['id']
35
- deliverable = false if block.call(response) == false
36
- end
37
-
38
- if response['advice']
39
- @client.handle_advice(response['advice'])
40
- end
41
-
42
- if response['data'] and response['channel']
43
- messages << response
44
- end
45
-
46
- end
47
-
48
- @client.deliver_messages(messages) if deliverable
49
- end
50
- }
51
35
  end
52
36
 
53
- @transports = {}
37
+ @transports = []
54
38
 
55
39
  class << self
56
40
  attr_accessor :connection_type
@@ -59,7 +43,7 @@ module Faye
59
43
  endpoint = client.endpoint
60
44
  connection_types ||= supported_connection_types
61
45
 
62
- candidate_class = @transports.find do |type, klass|
46
+ candidate_class = @transports.find do |(type, klass)|
63
47
  connection_types.include?(type) and
64
48
  klass.usable?(endpoint)
65
49
  end
@@ -72,12 +56,12 @@ module Faye
72
56
  end
73
57
 
74
58
  def register(type, klass)
75
- @transports[type] = klass
59
+ @transports << [type, klass]
76
60
  klass.connection_type = type
77
61
  end
78
62
 
79
63
  def supported_connection_types
80
- @transports.keys
64
+ @transports.map { |t| t.first }
81
65
  end
82
66
  end
83
67
  end
@@ -87,7 +71,9 @@ module Faye
87
71
  endpoint.is_a?(String)
88
72
  end
89
73
 
90
- def request(message, &block)
74
+ def request(message, timeout)
75
+ retry_block = retry_block(message, timeout)
76
+
91
77
  content = JSON.unparse(message)
92
78
  params = {
93
79
  :head => {
@@ -100,8 +86,13 @@ module Faye
100
86
  }
101
87
  request = EventMachine::HttpRequest.new(@endpoint).post(params)
102
88
  request.callback do
103
- block.call(JSON.parse(request.response))
89
+ begin
90
+ receive(JSON.parse(request.response))
91
+ rescue
92
+ retry_block.call
93
+ end
104
94
  end
95
+ request.errback { retry_block.call }
105
96
  end
106
97
  end
107
98
  Transport.register 'long-polling', HttpTransport
@@ -111,10 +102,8 @@ module Faye
111
102
  endpoint.is_a?(Server)
112
103
  end
113
104
 
114
- def request(message, &block)
115
- @endpoint.process(message, true) do |response|
116
- block.call(response)
117
- end
105
+ def request(message, timeout)
106
+ @endpoint.process(message, true, &method(:receive))
118
107
  end
119
108
  end
120
109
  Transport.register 'in-process', LocalTransport
@@ -1,7 +1,7 @@
1
1
  module Faye
2
2
  class Channel
3
3
 
4
- include Observable
4
+ include Publisher
5
5
  attr_reader :name
6
6
 
7
7
  def initialize(name)
@@ -9,8 +9,7 @@ module Faye
9
9
  end
10
10
 
11
11
  def <<(message)
12
- changed(true)
13
- notify_observers(:message, message)
12
+ publish_event(:message, message)
14
13
  end
15
14
 
16
15
  HANDSHAKE = '/meta/handshake'
@@ -120,6 +119,27 @@ module Faye
120
119
  list << @children[:**].value if @children[:**]
121
120
  list.flatten
122
121
  end
122
+
123
+ def subscribe(names, callback)
124
+ return unless callback
125
+ names.each do |name|
126
+ channel = self[name] ||= Channel.new(name)
127
+ channel.add_subscriber(:message, callback)
128
+ end
129
+ end
130
+
131
+ def unsubscribe(name, callback)
132
+ channel = self[name]
133
+ return false unless channel
134
+ channel.remove_subscriber(:message, callback)
135
+ channel.count_subscribers(:message).zero?
136
+ end
137
+
138
+ def distribute_message(message)
139
+ glob(message['channel']).each do |channel|
140
+ channel.publish_event(:message, message['data'])
141
+ end
142
+ end
123
143
  end
124
144
 
125
145
  end
@@ -4,6 +4,7 @@ module Faye
4
4
  include EventMachine::Deferrable
5
5
  include Timeouts
6
6
  include Logging
7
+ include Extensible
7
8
 
8
9
  UNCONNECTED = 1
9
10
  CONNECTING = 2
@@ -16,22 +17,27 @@ module Faye
16
17
 
17
18
  CONNECTION_TIMEOUT = 60.0
18
19
 
19
- attr_reader :endpoint, :client_id, :namespace
20
+ attr_reader :endpoint, :client_id
20
21
 
21
22
  def initialize(endpoint = nil, options = {})
22
23
  info('New client created for ?', endpoint)
23
24
 
24
25
  @endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
25
26
  @options = options
26
- @timeout = @options[:timeout] || CONNECTION_TIMEOUT
27
27
 
28
- @transport = Transport.get(self)
28
+ @transport = Transport.get(self, MANDATORY_CONNECTION_TYPES)
29
29
  @state = UNCONNECTED
30
- @namespace = Namespace.new
31
30
  @outbox = []
32
31
  @channels = Channel::Tree.new
33
32
 
34
- @advice = {'reconnect' => RETRY, 'interval' => Connection::INTERVAL}
33
+ @namespace = Namespace.new
34
+ @response_callbacks = {}
35
+
36
+ @advice = {
37
+ 'reconnect' => RETRY,
38
+ 'interval' => 1000.0 * (@options[:interval] || Connection::INTERVAL),
39
+ 'timeout' => 1000.0 * (@options[:timeout] || CONNECTION_TIMEOUT)
40
+ }
35
41
  end
36
42
 
37
43
  # Request
@@ -61,10 +67,10 @@ module Faye
61
67
 
62
68
  info('Initiating handshake with ?', @endpoint)
63
69
 
64
- @transport.send({
70
+ send({
65
71
  'channel' => Channel::HANDSHAKE,
66
72
  'version' => BAYEUX_VERSION,
67
- 'supportedConnectionTypes' => Transport.supported_connection_types
73
+ 'supportedConnectionTypes' => [@transport.connection_type]
68
74
 
69
75
  }) do |response|
70
76
 
@@ -74,6 +80,8 @@ module Faye
74
80
  @transport = Transport.get(self, response['supportedConnectionTypes'])
75
81
 
76
82
  info('Handshake successful: ?', @client_id)
83
+
84
+ subscribe(@channels.keys)
77
85
  block.call if block_given?
78
86
 
79
87
  else
@@ -97,38 +105,28 @@ module Faye
97
105
  return if @advice['reconnect'] == NONE or
98
106
  @state == DISCONNECTED
99
107
 
100
- if @advice['reconnect'] == HANDSHAKE or @state == UNCONNECTED
101
- begin_reconnect_timeout
102
- return handshake { connect(&block) }
103
- end
108
+ return handshake { connect(&block) } if @state == UNCONNECTED
104
109
 
105
- return callback(&block) if @state == CONNECTING
110
+ callback(&block)
106
111
  return unless @state == CONNECTED
107
112
 
108
113
  info('Calling deferred actions for ?', @client_id)
109
114
  set_deferred_status(:succeeded)
110
115
  set_deferred_status(:deferred)
111
- block.call if block_given?
112
116
 
113
- return unless @connection_id.nil?
114
- @connection_id = @namespace.generate
117
+ return unless @connect_request.nil?
118
+ @connect_request = true
119
+
115
120
  info('Initiating connection for ?', @client_id)
116
121
 
117
- @transport.send({
122
+ send({
118
123
  'channel' => Channel::CONNECT,
119
124
  'clientId' => @client_id,
120
- 'connectionType' => @transport.connection_type,
121
- 'id' => @connection_id
125
+ 'connectionType' => @transport.connection_type
122
126
 
123
- }, &verify_client_id { |response|
124
- @connection_id = nil
125
- remove_timeout(:reconnect)
126
-
127
- info('Closed connection for ?', @client_id)
128
- EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
129
- })
130
-
131
- begin_reconnect_timeout
127
+ }) do
128
+ cycle_connection
129
+ end
132
130
  end
133
131
 
134
132
  # Request Response
@@ -144,14 +142,13 @@ module Faye
144
142
 
145
143
  info('Disconnecting ?', @client_id)
146
144
 
147
- @transport.send({
145
+ send({
148
146
  'channel' => Channel::DISCONNECT,
149
147
  'clientId' => @client_id
150
148
  })
151
149
 
152
150
  info('Clearing channel listeners for ?', @client_id)
153
151
  @channels = Channel::Tree.new
154
- remove_timeout(:reconnect)
155
152
  end
156
153
 
157
154
  # Request Response
@@ -165,27 +162,32 @@ module Faye
165
162
  # * id
166
163
  # * timestamp
167
164
  def subscribe(channels, &block)
165
+ if Array === channels
166
+ return channels.each do |channel|
167
+ subscribe(channel, &block)
168
+ end
169
+ end
170
+
171
+ validate_channel(channels)
172
+
168
173
  connect {
169
- channels = [channels].flatten
170
- validate_channels(channels)
171
-
172
174
  info('Client ? attempting to subscribe to ?', @client_id, channels)
173
175
 
174
- @transport.send({
176
+ send({
175
177
  'channel' => Channel::SUBSCRIBE,
176
178
  'clientId' => @client_id,
177
179
  'subscription' => channels
178
180
 
179
- }, &verify_client_id { |response|
180
- if response['successful'] and block
181
-
182
- info('Subscription acknowledged for ? to ?', @client_id, channels)
181
+ }) do |response|
182
+ if response['successful']
183
183
 
184
184
  channels = [response['subscription']].flatten
185
- channels.each { |channel| @channels[channel] = block }
185
+ info('Subscription acknowledged for ? to ?', @client_id, channels)
186
+ @channels.subscribe(channels, block)
186
187
  end
187
- })
188
+ end
188
189
  }
190
+ Subscription.new(self, channels, block)
189
191
  end
190
192
 
191
193
  # Request Response
@@ -199,26 +201,32 @@ module Faye
199
201
  # * id
200
202
  # * timestamp
201
203
  def unsubscribe(channels, &block)
204
+ if Array === channels
205
+ return channels.each do |channel|
206
+ unsubscribe(channel, &block)
207
+ end
208
+ end
209
+
210
+ validate_channel(channels)
211
+
212
+ dead = @channels.unsubscribe(channels, block)
213
+ return unless dead
214
+
202
215
  connect {
203
- channels = [channels].flatten
204
- validate_channels(channels)
205
-
206
216
  info('Client ? attempting to unsubscribe from ?', @client_id, channels)
207
217
 
208
- @transport.send({
218
+ send({
209
219
  'channel' => Channel::UNSUBSCRIBE,
210
220
  'clientId' => @client_id,
211
221
  'subscription' => channels
212
222
 
213
- }, &verify_client_id { |response|
223
+ }) do |response|
214
224
  if response['successful']
215
225
 
216
- info('Unsubscription acknowledged for ? from ?', @client_id, channels)
217
-
218
226
  channels = [response['subscription']].flatten
219
- channels.each { |channel| @channels[channel] = nil }
227
+ info('Unsubscription acknowledged for ? from ?', @client_id, channels)
220
228
  end
221
- })
229
+ end
222
230
  }
223
231
  end
224
232
 
@@ -229,77 +237,103 @@ module Faye
229
237
  # * id * error
230
238
  # * ext * ext
231
239
  def publish(channel, data)
240
+ validate_channel(channel)
241
+
232
242
  connect {
233
- validate_channels([channel])
234
-
235
243
  info('Client ? queueing published message to ?: ?', @client_id, channel, data)
236
244
 
237
- enqueue({
245
+ send({
238
246
  'channel' => channel,
239
247
  'data' => data,
240
248
  'clientId' => @client_id
241
249
  })
242
-
243
- add_timeout(:publish, Connection::MAX_DELAY) { flush! }
244
250
  }
245
251
  end
246
252
 
247
- def handle_advice(advice)
248
- @advice.update(advice)
249
- @client_id = nil if @advice['reconnect'] == HANDSHAKE
250
- end
251
-
252
- def deliver_messages(messages)
253
- messages.each do |message|
254
- info('Client ? calling listeners for ? with ?', @client_id, message['channel'], message['data'])
255
-
256
- channels = @channels.glob(message['channel'])
257
- channels.each { |callback| callback.call(message['data']) }
253
+ def receive_message(message)
254
+ pipe_through_extensions(:incoming, message) do |message|
255
+ if message
256
+ handle_advice(message['advice']) if message['advice']
257
+
258
+ callback = @response_callbacks[message['id']]
259
+ if callback
260
+ @response_callbacks.delete(message['id'])
261
+ callback.call(message)
262
+ end
263
+
264
+ deliver_message(message)
265
+ end
258
266
  end
259
267
  end
260
268
 
261
269
  private
262
270
 
263
- def begin_reconnect_timeout
264
- add_timeout(:reconnect, @timeout) do
265
- @connection_id = nil
271
+ def handle_advice(advice)
272
+ @advice.update(advice)
273
+
274
+ if @advice['reconnect'] == HANDSHAKE and @state != DISCONNECTED
275
+ @state = UNCONNECTED
266
276
  @client_id = nil
267
- @state = UNCONNECTED
268
-
269
- info('Server took >?s to reply to connection for ?: attempting to reconnect',
270
- @timeout, @client_id)
271
-
272
- subscribe(@channels.keys)
277
+ cycle_connection
273
278
  end
274
279
  end
275
280
 
276
- def enqueue(message)
277
- @outbox << message
281
+ def deliver_message(message)
282
+ return unless message['channel'] and message['data']
283
+ info('Client ? calling listeners for ? with ?', @client_id, message['channel'], message['data'])
284
+ @channels.distribute_message(message)
278
285
  end
279
286
 
280
- def flush!
281
- @transport.send(@outbox)
282
- @outbox = []
287
+ def teardown_connection
288
+ return unless @connect_request
289
+ @connect_request = nil
290
+ info('Closed connection for ?', @client_id)
283
291
  end
284
292
 
285
- def validate_channels(channels)
286
- channels.each do |channel|
287
- raise "'#{ channel }' is not a valid channel name" unless Channel.valid?(channel)
288
- raise "Clients may not subscribe to channel '#{ channel }'" unless Channel.subscribable?(channel)
289
- end
293
+ def cycle_connection
294
+ teardown_connection
295
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
290
296
  end
291
297
 
292
- def verify_client_id(&block)
293
- lambda do |response|
294
- if response['clientId'] != @client_id
295
- false
296
- else
297
- block.call(response)
298
- true
298
+ def send(message, &callback)
299
+ message['id'] = @namespace.generate
300
+ @response_callbacks[message['id']] = callback if callback
301
+
302
+ pipe_through_extensions(:outgoing, message) do |message|
303
+ if message
304
+ if message['channel'] == Channel::HANDSHAKE
305
+ @transport.send(message, @advice['timeout'] / 1000.0)
306
+ else
307
+ @outbox << message
308
+
309
+ if message['channel'] == Channel::CONNECT
310
+ @connect_message = message
311
+ end
312
+
313
+ add_timeout(:publish, Connection::MAX_DELAY) { flush! }
314
+ end
299
315
  end
300
316
  end
301
317
  end
302
318
 
319
+ def flush!
320
+ remove_timeout(:publish)
321
+
322
+ if @outbox.size > 1 and @connect_message
323
+ @connect_message['advice'] = {'timeout' => 0}
324
+ end
325
+
326
+ @connect_message = nil
327
+
328
+ @transport.send(@outbox, @advice['timeout'] / 1000.0)
329
+ @outbox = []
330
+ end
331
+
332
+ def validate_channel(channel)
333
+ raise "'#{ channel }' is not a valid channel name" unless Channel.valid?(channel)
334
+ raise "Clients may not subscribe to channel '#{ channel }'" unless Channel.subscribable?(channel)
335
+ end
336
+
303
337
  end
304
338
  end
305
339