faye-huboard 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +351 -0
- data/README.md +35 -0
- data/lib/faye-browser-min.js +3 -0
- data/lib/faye-browser-min.js.map +1 -0
- data/lib/faye-browser.js +2659 -0
- data/lib/faye.rb +127 -0
- data/lib/faye/adapters/rack_adapter.rb +248 -0
- data/lib/faye/adapters/static_server.rb +56 -0
- data/lib/faye/engines/connection.rb +58 -0
- data/lib/faye/engines/memory.rb +121 -0
- data/lib/faye/engines/proxy.rb +126 -0
- data/lib/faye/error.rb +48 -0
- data/lib/faye/mixins/deferrable.rb +14 -0
- data/lib/faye/mixins/logging.rb +35 -0
- data/lib/faye/mixins/publisher.rb +18 -0
- data/lib/faye/mixins/timeouts.rb +26 -0
- data/lib/faye/protocol/channel.rb +123 -0
- data/lib/faye/protocol/client.rb +334 -0
- data/lib/faye/protocol/dispatcher.rb +146 -0
- data/lib/faye/protocol/extensible.rb +45 -0
- data/lib/faye/protocol/grammar.rb +57 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +291 -0
- data/lib/faye/protocol/socket.rb +24 -0
- data/lib/faye/protocol/subscription.rb +23 -0
- data/lib/faye/transport/http.rb +69 -0
- data/lib/faye/transport/local.rb +21 -0
- data/lib/faye/transport/transport.rb +155 -0
- data/lib/faye/transport/web_socket.rb +134 -0
- data/lib/faye/util/namespace.rb +19 -0
- metadata +400 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Faye
|
2
|
+
module Extensible
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
def add_extension(extension)
|
6
|
+
@extensions ||= []
|
7
|
+
@extensions << extension
|
8
|
+
extension.added(self) if extension.respond_to?(:added)
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_extension(extension)
|
12
|
+
return unless @extensions
|
13
|
+
@extensions.delete_if do |ext|
|
14
|
+
next false unless ext == extension
|
15
|
+
extension.removed(self) if extension.respond_to?(:removed)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def pipe_through_extensions(stage, message, env, &callback)
|
21
|
+
debug('Passing through ? extensions: ?', stage, message)
|
22
|
+
|
23
|
+
return callback.call(message) unless @extensions
|
24
|
+
extensions = @extensions.dup
|
25
|
+
|
26
|
+
pipe = lambda do |message|
|
27
|
+
next callback.call(message) unless message
|
28
|
+
|
29
|
+
extension = extensions.shift
|
30
|
+
next callback.call(message) unless extension
|
31
|
+
|
32
|
+
next pipe.call(message) unless extension.respond_to?(stage)
|
33
|
+
|
34
|
+
arity = extension.method(stage).arity
|
35
|
+
if arity >= 3
|
36
|
+
extension.__send__(stage, message, env, pipe)
|
37
|
+
else
|
38
|
+
extension.__send__(stage, message, pipe)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
pipe.call(message)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Faye
|
2
|
+
module Grammar
|
3
|
+
|
4
|
+
def self.rule(&block)
|
5
|
+
source = instance_eval(&block)
|
6
|
+
%r{^#{string(source)}$}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.choice(*list)
|
10
|
+
'(' + list.map(&method(:string)) * '|' + ')'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.repeat(*pattern)
|
14
|
+
'(' + string(pattern) + ')*'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.oneormore(*pattern)
|
18
|
+
'(' + string(pattern) + ')+'
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.string(item)
|
22
|
+
return item.map(&method(:string)) * '' if Array === item
|
23
|
+
String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
LOWALPHA = rule {[ '[a-z]' ]}
|
27
|
+
UPALPHA = rule {[ '[A-Z]' ]}
|
28
|
+
ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]}
|
29
|
+
DIGIT = rule {[ '[0-9]' ]}
|
30
|
+
ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]}
|
31
|
+
MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]}
|
32
|
+
STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]}
|
33
|
+
TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]}
|
34
|
+
INTEGER = rule {[ oneormore(DIGIT) ]}
|
35
|
+
|
36
|
+
CHANNEL_SEGMENT = rule {[ TOKEN ]}
|
37
|
+
CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]}
|
38
|
+
CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]}
|
39
|
+
|
40
|
+
WILD_CARD = rule {[ '\\*{1,2}' ]}
|
41
|
+
CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]}
|
42
|
+
|
43
|
+
VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]}
|
44
|
+
VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]}
|
45
|
+
|
46
|
+
CLIENT_ID = rule {[ oneormore(ALPHANUM) ]}
|
47
|
+
|
48
|
+
ID = rule {[ oneormore(ALPHANUM) ]}
|
49
|
+
|
50
|
+
ERROR_MESSAGE = rule {[ STRING ]}
|
51
|
+
ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]}
|
52
|
+
ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]}
|
53
|
+
ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]),
|
54
|
+
string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]}
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
module Faye
|
2
|
+
class Server
|
3
|
+
|
4
|
+
autoload :Socket, File.join(ROOT, 'faye', 'protocol', 'socket')
|
5
|
+
|
6
|
+
include Logging
|
7
|
+
include Extensible
|
8
|
+
|
9
|
+
META_METHODS = %w[handshake connect disconnect subscribe unsubscribe]
|
10
|
+
|
11
|
+
attr_reader :engine
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@options = options || {}
|
15
|
+
engine_opts = @options[:engine] || {}
|
16
|
+
engine_opts[:timeout] = @options[:timeout]
|
17
|
+
@engine = Faye::Engine.get(engine_opts)
|
18
|
+
|
19
|
+
info('Created new server: ?', @options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
@engine.close
|
24
|
+
end
|
25
|
+
|
26
|
+
def open_socket(client_id, socket, env)
|
27
|
+
return unless client_id and socket
|
28
|
+
@engine.open_socket(client_id, Socket.new(self, socket, env))
|
29
|
+
end
|
30
|
+
|
31
|
+
def close_socket(client_id, close = true)
|
32
|
+
@engine.flush_connection(client_id, close)
|
33
|
+
end
|
34
|
+
|
35
|
+
def process(messages, env, &callback)
|
36
|
+
local = env.nil?
|
37
|
+
messages = [messages].flatten
|
38
|
+
info('Processing messages: ? (local: ?)', messages, local)
|
39
|
+
|
40
|
+
return callback.call([]) if messages.size == 0
|
41
|
+
processed, responses = 0, []
|
42
|
+
|
43
|
+
gather_replies = lambda do |replies|
|
44
|
+
responses.concat(replies)
|
45
|
+
processed += 1
|
46
|
+
responses.compact!
|
47
|
+
info('Returning replies: ?', responses)
|
48
|
+
callback.call(responses) if processed == messages.size
|
49
|
+
end
|
50
|
+
|
51
|
+
handle_reply = lambda do |replies|
|
52
|
+
extended, expected = 0, replies.size
|
53
|
+
gather_replies.call(replies) if expected == 0
|
54
|
+
|
55
|
+
replies.each_with_index do |reply, i|
|
56
|
+
debug('Processing reply: ?', reply)
|
57
|
+
pipe_through_extensions(:outgoing, reply, env) do |message|
|
58
|
+
replies[i] = message
|
59
|
+
extended += 1
|
60
|
+
gather_replies.call(replies) if extended == expected
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
messages.each do |message|
|
66
|
+
pipe_through_extensions(:incoming, message, env) do |piped_message|
|
67
|
+
handle(piped_message, local, &handle_reply)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def make_response(message)
|
73
|
+
response = {}
|
74
|
+
|
75
|
+
response['id'] = message['id'] if message['id']
|
76
|
+
response['clientId'] = message['clientId'] if message['clientId']
|
77
|
+
response['channel'] = message['channel'] if message['channel']
|
78
|
+
response['error'] = message['error'] if message['error']
|
79
|
+
|
80
|
+
response['successful'] = !response['error']
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle(message, local = false, &callback)
|
85
|
+
return callback.call([]) if !message
|
86
|
+
info('Handling message: ? (local: ?)', message, local)
|
87
|
+
|
88
|
+
channel_name = message['channel']
|
89
|
+
error = message['error']
|
90
|
+
|
91
|
+
return handle_meta(message, local, &callback) if Channel.meta?(channel_name)
|
92
|
+
|
93
|
+
if Grammar::CHANNEL_NAME !~ channel_name
|
94
|
+
error = Faye::Error.channel_invalid(channel_name)
|
95
|
+
end
|
96
|
+
|
97
|
+
@engine.publish(message) unless error
|
98
|
+
|
99
|
+
response = make_response(message)
|
100
|
+
response['error'] = error if error
|
101
|
+
response['successful'] = !response['error']
|
102
|
+
callback.call([response])
|
103
|
+
end
|
104
|
+
|
105
|
+
def handle_meta(message, local, &callback)
|
106
|
+
method = Channel.parse(message['channel'])[1]
|
107
|
+
|
108
|
+
unless META_METHODS.include?(method)
|
109
|
+
response = make_response(message)
|
110
|
+
response['error'] = Faye::Error.channel_forbidden(message['channel'])
|
111
|
+
response['successful'] = false
|
112
|
+
return callback.call([response])
|
113
|
+
end
|
114
|
+
|
115
|
+
__send__(method, message, local) do |responses|
|
116
|
+
responses = [responses].flatten
|
117
|
+
responses.each { |r| advize(r, message['connectionType']) }
|
118
|
+
callback.call(responses)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def advize(response, connection_type)
|
123
|
+
return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel'])
|
124
|
+
|
125
|
+
if connection_type == 'eventsource'
|
126
|
+
interval = (@engine.timeout * 1000).floor
|
127
|
+
timeout = 0
|
128
|
+
else
|
129
|
+
interval = (@engine.interval * 1000).floor
|
130
|
+
timeout = (@engine.timeout * 1000).floor
|
131
|
+
end
|
132
|
+
|
133
|
+
advice = response['advice'] ||= {}
|
134
|
+
if response['error']
|
135
|
+
advice['reconnect'] ||= 'handshake'
|
136
|
+
else
|
137
|
+
advice['reconnect'] ||= 'retry'
|
138
|
+
advice['interval'] ||= interval
|
139
|
+
advice['timeout'] ||= timeout
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# MUST contain * version
|
144
|
+
# * supportedConnectionTypes
|
145
|
+
# MAY contain * minimumVersion
|
146
|
+
# * ext
|
147
|
+
# * id
|
148
|
+
def handshake(message, local = false, &callback)
|
149
|
+
response = make_response(message)
|
150
|
+
response['version'] = BAYEUX_VERSION
|
151
|
+
|
152
|
+
response['error'] = Error.parameter_missing('version') if message['version'].nil?
|
153
|
+
|
154
|
+
client_conns = message['supportedConnectionTypes']
|
155
|
+
|
156
|
+
response['supportedConnectionTypes'] = CONNECTION_TYPES
|
157
|
+
|
158
|
+
if client_conns
|
159
|
+
common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
|
160
|
+
response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
|
161
|
+
else
|
162
|
+
response['error'] = Error.parameter_missing('supportedConnectionTypes')
|
163
|
+
end
|
164
|
+
|
165
|
+
response['successful'] = response['error'].nil?
|
166
|
+
return callback.call(response) unless response['successful']
|
167
|
+
|
168
|
+
@engine.create_client do |client_id|
|
169
|
+
response['clientId'] = client_id
|
170
|
+
callback.call(response)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# MUST contain * clientId
|
175
|
+
# * connectionType
|
176
|
+
# MAY contain * ext
|
177
|
+
# * id
|
178
|
+
def connect(message, local = false, &callback)
|
179
|
+
response = make_response(message)
|
180
|
+
client_id = message['clientId']
|
181
|
+
connection_type = message['connectionType']
|
182
|
+
|
183
|
+
@engine.client_exists(client_id) do |exists|
|
184
|
+
response['error'] = Error.client_unknown(client_id) unless exists
|
185
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
186
|
+
|
187
|
+
unless CONNECTION_TYPES.include?(connection_type)
|
188
|
+
response['error'] = Error.conntype_mismatch(connection_type)
|
189
|
+
end
|
190
|
+
|
191
|
+
response['error'] = Error.parameter_missing('connectionType') if connection_type.nil?
|
192
|
+
|
193
|
+
response['successful'] = response['error'].nil?
|
194
|
+
|
195
|
+
if !response['successful']
|
196
|
+
response.delete('clientId')
|
197
|
+
next callback.call(response)
|
198
|
+
end
|
199
|
+
|
200
|
+
if message['connectionType'] == 'eventsource'
|
201
|
+
message['advice'] ||= {}
|
202
|
+
message['advice']['timeout'] = 0
|
203
|
+
end
|
204
|
+
|
205
|
+
@engine.connect(response['clientId'], message['advice']) do |events|
|
206
|
+
callback.call([response] + events)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# MUST contain * clientId
|
212
|
+
# MAY contain * ext
|
213
|
+
# * id
|
214
|
+
def disconnect(message, local = false, &callback)
|
215
|
+
response = make_response(message)
|
216
|
+
client_id = message['clientId']
|
217
|
+
|
218
|
+
@engine.client_exists(client_id) do |exists|
|
219
|
+
response['error'] = Error.client_unknown(client_id) unless exists
|
220
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
221
|
+
|
222
|
+
response['successful'] = response['error'].nil?
|
223
|
+
response.delete('clientId') unless response['successful']
|
224
|
+
|
225
|
+
@engine.destroy_client(client_id) if response['successful']
|
226
|
+
callback.call(response)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# MUST contain * clientId
|
231
|
+
# * subscription
|
232
|
+
# MAY contain * ext
|
233
|
+
# * id
|
234
|
+
def subscribe(message, local = false, &callback)
|
235
|
+
response = make_response(message)
|
236
|
+
client_id = message['clientId']
|
237
|
+
subscription = [message['subscription']].flatten
|
238
|
+
|
239
|
+
@engine.client_exists(client_id) do |exists|
|
240
|
+
response['error'] = Error.client_unknown(client_id) unless exists
|
241
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
242
|
+
response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
|
243
|
+
|
244
|
+
response['subscription'] = message['subscription'] || []
|
245
|
+
|
246
|
+
subscription.each do |channel|
|
247
|
+
next if response['error']
|
248
|
+
response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
|
249
|
+
response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
|
250
|
+
|
251
|
+
next if response['error']
|
252
|
+
@engine.subscribe(client_id, channel)
|
253
|
+
end
|
254
|
+
|
255
|
+
response['successful'] = response['error'].nil?
|
256
|
+
callback.call(response)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# MUST contain * clientId
|
261
|
+
# * subscription
|
262
|
+
# MAY contain * ext
|
263
|
+
# * id
|
264
|
+
def unsubscribe(message, local = false, &callback)
|
265
|
+
response = make_response(message)
|
266
|
+
client_id = message['clientId']
|
267
|
+
subscription = [message['subscription']].flatten
|
268
|
+
|
269
|
+
@engine.client_exists(client_id) do |exists|
|
270
|
+
response['error'] = Error.client_unknown(client_id) unless exists
|
271
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
272
|
+
response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
|
273
|
+
|
274
|
+
response['subscription'] = message['subscription'] || []
|
275
|
+
|
276
|
+
subscription.each do |channel|
|
277
|
+
next if response['error']
|
278
|
+
response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
|
279
|
+
response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
|
280
|
+
|
281
|
+
next if response['error']
|
282
|
+
@engine.unsubscribe(client_id, channel)
|
283
|
+
end
|
284
|
+
|
285
|
+
response['successful'] = response['error'].nil?
|
286
|
+
callback.call(response)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Faye
|
2
|
+
class Server
|
3
|
+
|
4
|
+
class Socket
|
5
|
+
def initialize(server, socket, env)
|
6
|
+
@server = server
|
7
|
+
@socket = socket
|
8
|
+
@env = env
|
9
|
+
end
|
10
|
+
|
11
|
+
def send(message)
|
12
|
+
@server.pipe_through_extensions(:outgoing, message, @env) do |piped_message|
|
13
|
+
@socket.send(Faye.to_json([piped_message])) if @socket
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@socket.close if @socket
|
19
|
+
@socket = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Faye
|
2
|
+
class Subscription
|
3
|
+
include Deferrable
|
4
|
+
|
5
|
+
def initialize(client, channels, callback)
|
6
|
+
@client = client
|
7
|
+
@channels = channels
|
8
|
+
@callback = callback
|
9
|
+
@cancelled = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def cancel
|
13
|
+
return if @cancelled
|
14
|
+
@client.unsubscribe(@channels, &@callback)
|
15
|
+
@cancelled = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def unsubscribe
|
19
|
+
cancel
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|