faye-huboard 1.0.4
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.
- 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
|