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.
@@ -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,5 @@
1
+ module Faye
2
+ class Publication
3
+ include Deferrable
4
+ end
5
+ 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