ortc 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0a624c528b6abbb1404407a0e0e36dc4dc58de25
4
+ data.tar.gz: bddc9022702a803acba26bbf73faf8d76edb0623
5
+ SHA512:
6
+ metadata.gz: 97888ea0f27025feba5595abffcd77b6fd81258118b0daee366922f19900c231595919a5939ad46c85e24c84d48a2b6981da930dbccc8322d43998178320d4cc
7
+ data.tar.gz: a7277e0bfd379b68ca4bffd25f6034450da4837c806d5ec2943db8d442b2c752d74df851e7974c147cc442092b8cd3973f240c88b04d3fd2de8e362993620780
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 07.04.2014
2
+
3
+ * Initial gem relase
4
+
@@ -0,0 +1,7 @@
1
+ # ortc
2
+
3
+ The ORTC (Open Real-Time Connectivity) was developed to add a layer of abstraction to real-time full-duplex web communications platforms by making real-time web applications independent of those platforms.
4
+
5
+ ORTC provides a standard software API (Application Programming Interface) for sending and receiving data in real-time over the web.
6
+
7
+ Visit our webpage (http://framework.realtime.co/messaging/) for more details.
@@ -0,0 +1,45 @@
1
+ require "ortc"
2
+
3
+ ortc_client = ORTC::OrtcClient.new
4
+ ortc_client.cluster_url = 'http://ortc-developers.realtime.co/server/2.1'
5
+
6
+ ortc_client.on_connected do |sender|
7
+ p [:Connected]
8
+ ortc_client.subscribe("blue", true) { |sender, channel, message|
9
+ puts "Message received on (#{channel}): #{message}"
10
+ ortc_client.unsubscribe(channel)
11
+ }
12
+ end
13
+
14
+ ortc_client.on_disconnected do |sender|
15
+ p [:Disconnected]
16
+ abort()
17
+ end
18
+
19
+ ortc_client.on_exception do |sender, exception|
20
+ p [:Exception, exception]
21
+ end
22
+
23
+ ortc_client.on_subscribed do |sender, channel|
24
+ p [:Subscribed, channel]
25
+ ortc_client.send(channel, 'This is a message')
26
+ end
27
+
28
+ ortc_client.on_unsubscribed do |sender, channel|
29
+ p [:Unsubscribed, channel]
30
+ ortc_client.disconnect
31
+ end
32
+
33
+ ortc_client.on_reconnecting do |sender|
34
+ p [:Reconnecting]
35
+ end
36
+
37
+ ortc_client.on_reconnected do |sender|
38
+ p [:Reconnected]
39
+ end
40
+
41
+ ortc_client.connect 'Your_application_key', 'Your_token'
42
+
43
+ loop do
44
+ sleep 1
45
+ end
@@ -0,0 +1,126 @@
1
+ require "ortc"
2
+
3
+ ortc_client = ORTC::OrtcClient.new
4
+
5
+ ortc_url = 'https://ortc-developers.realtime.co/server/ssl/2.1'
6
+ ortc_app_key = 'your_application_key'
7
+ ortc_auth_token = 'your_authentication_token' #needed only when using authentication
8
+ ortc_private_key = 'your_private_key' #needed only for 'saving authentication and enable/disable presence
9
+
10
+ ortc_client.cluster_url = ortc_url
11
+
12
+ ortc_client.on_connected do |sender|
13
+ p [:Connected]
14
+ end
15
+
16
+ ortc_client.on_disconnected do |sender|
17
+ p [:Disconnected]
18
+ end
19
+
20
+ ortc_client.on_exception do |sender, exception|
21
+ p [:Exception, exception]
22
+ end
23
+
24
+ ortc_client.on_subscribed do |sender, channel|
25
+ p [:Subscribed, channel]
26
+ end
27
+
28
+ ortc_client.on_unsubscribed do |sender, channel|
29
+ p [:Unsubscribed, channel]
30
+ end
31
+
32
+ ortc_client.on_reconnecting do |sender|
33
+ p [:Reconnecting]
34
+ end
35
+
36
+ ortc_client.on_reconnected do |sender|
37
+ p [:Reconnected]
38
+ end
39
+
40
+ command = ""
41
+ t = Thread.new {
42
+ puts "q - quit, 1 - connect, 2 - disconnect, 3 - subscribe, 4 - unsubscribe, 5 - send, 6 - save authentication, 7 - Enable presence, 8 - Disable presence, 9 - Presence"
43
+ begin
44
+ command = gets.chomp
45
+ if(command == "1")
46
+ ortc_client.connect ortc_app_key, ortc_auth_token
47
+ end
48
+ if(command == "2")
49
+ ortc_client.disconnect
50
+ end
51
+ if(command == "3")
52
+ puts "Subscribing... Channel name:"
53
+ channel_name = gets.chomp
54
+ ortc_client.subscribe(channel_name, true) { |sender, channel, message| puts "Received: #{message}" }
55
+ end
56
+ if(command == "4")
57
+ puts "Unsubscribing... Channel name:"
58
+ channel_name = gets.chomp
59
+ ortc_client.unsubscribe channel_name
60
+ end
61
+ if(command == "5")
62
+ puts "Sending message... Channel name:"
63
+ channel_name = gets.chomp
64
+ puts "Message to send:"
65
+ message = gets.chomp
66
+ ortc_client.send(channel_name, message)
67
+ end
68
+ if command == "6"
69
+ puts "Saving authentication..."
70
+ permissions = Hash.new
71
+ begin
72
+ puts "Channel name:"
73
+ channel_name = gets.chomp
74
+ puts "Permission: (r)ead, (w)rite, (p)resence"
75
+ permission = gets.chomp
76
+ permissions[channel_name] = permission
77
+ puts "Do you want to add permissions for another channel? (y/n)"
78
+ response = gets.chomp
79
+ end until response != 'y'
80
+ puts "Saving authentication..."
81
+ puts ortc_client.save_authentication(ortc_url, true, ortc_auth_token, false, ortc_app_key, 1800, ortc_private_key, permissions) ? 'Success' : 'Failed'
82
+ end
83
+ if command == "7"
84
+ puts "Enabling presence..."
85
+ puts "Channel name:"
86
+ channel_name = gets.chomp
87
+ ORTC.enable_presence(ortc_url, true, ortc_app_key, ortc_private_key, channel_name, true) { |error, result|
88
+ if error.to_s.empty?
89
+ puts "result: #{result}"
90
+ else
91
+ puts "error: #{error}"
92
+ end
93
+ }
94
+
95
+ end
96
+
97
+ if command == "8"
98
+ puts "Disabling presence..."
99
+ puts "Channel name:"
100
+ channel_name = gets.chomp
101
+ ORTC.disable_presence(ortc_url, true, ortc_app_key, ortc_private_key, channel_name) { |error, result|
102
+ if error.to_s.empty?
103
+ puts "result: #{result}"
104
+ else
105
+ puts "error: #{error}"
106
+ end
107
+ }
108
+
109
+ end
110
+ if command == "9"
111
+ puts "Presence..."
112
+ puts "Channel name:"
113
+ channel_name = gets.chomp
114
+ ORTC.presence(ortc_url, true, ortc_app_key, ortc_auth_token, channel_name) { |error, result|
115
+ if error.to_s.empty?
116
+ puts "result: #{result}"
117
+ else
118
+ puts "error: #{error}"
119
+ end
120
+ }
121
+
122
+ end
123
+ end until command == "q"
124
+ }
125
+ t.run
126
+ t.join
@@ -0,0 +1,865 @@
1
+ require 'rubygems'
2
+ require 'faye/websocket'
3
+ require 'eventmachine'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'json'
7
+ require 'ortc/ortc_extensibility'
8
+
9
+ module ORTC
10
+
11
+ MAX_CHANNEL_NAME_SIZE = 100
12
+ MAX_MESSAGE_SIZE = 800
13
+ MAX_HEARTBEAT_INTERVAL = 30
14
+ RECONNECT_INTERVAL = 5
15
+ MAX_CONNECTION_METADATA_SIZE = 255
16
+
17
+
18
+ #Enables presence for the specified channel with first 100 unique metadata if true.
19
+ #
20
+ #*Note:* This method will send your Private Key over the Internet. Make sure to use secure connection.
21
+ #- url - Server containing the presence service
22
+ #- is_cluster - Indicates whether the url is in a cluster.
23
+ #- application_key - Application key with access to presence service
24
+ #- private_key - The private key provided when the ORTC service is purchased.
25
+ #- channel - Channel to activate presence.
26
+ #- metadata - Defines if to collect first 100 unique metadata.
27
+ #- &block - Callback with error and result parameters.
28
+ #
29
+ #Usage:
30
+ # ORTC.enable_presence(ortc_url, true, ortc_app_key, ortc_private_key, channel, true) { |error, result|
31
+ # if error.to_s.empty?
32
+ # puts "result: #{result}"
33
+ # else
34
+ # puts "error: #{error}"
35
+ # end
36
+ # }
37
+ def self.enable_presence(url, is_cluster, application_key, private_key, channel, metadata, &block)
38
+ if url.to_s.empty?
39
+ block.call('URL is null or empty', nil)
40
+ elsif application_key.to_s.empty?
41
+ block.call('Application Key is null or empty', nil)
42
+ elsif private_key.to_s.empty?
43
+ block.call('Private key is null or empty', nil)
44
+ elsif channel.to_s.empty?
45
+ block.call('Channel is null or empty', nil)
46
+ elsif not channel =~ /^[\w\-:\/.]+$/
47
+ block.call('Channel has invalid characters', nil)
48
+ else
49
+ begin
50
+ r_thread = Thread.new {
51
+ server = ''
52
+ if is_cluster
53
+ server = _get_cluster(url)
54
+ begin
55
+ block.call('Can not connect with the server', nil)
56
+ r_thread.exit
57
+ end if server == ''
58
+ else
59
+ server = url.clone
60
+ end
61
+ server = server << (server.match(/\/$/) ? 'presence' : '/presence')
62
+ server = server << "/enable/#{application_key}/#{channel}"
63
+ body = "privatekey=#{private_key}&metadata=" << (metadata ? '1' : '0')
64
+ uri = URI.parse(server)
65
+ begin
66
+ if http = Net::HTTP.new(uri.host, uri.port)
67
+ if server.match /^https/
68
+ http.use_ssl = true
69
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
70
+ end
71
+ req = Net::HTTP::Post.new(uri.request_uri)
72
+ req.body = body
73
+ res = http.request(req)
74
+ if res.code == '200'
75
+ block.call(nil, res.body)
76
+ else
77
+ Block.call(res.body, nil)
78
+ end
79
+ end
80
+ rescue => e
81
+ block.call(e, nil)
82
+ r_thread.exit
83
+ end
84
+ }
85
+ r_thread.run
86
+ end
87
+ end
88
+ end
89
+
90
+ #Disables presence for the specified channel.
91
+ #
92
+ #*Note:* This method will send your Private Key over the Internet. Make sure to use secure connection.
93
+ #- url - Server containing the presence service
94
+ #- is_cluster - Indicates whether the url is in a cluster.
95
+ #- application_key - Application key with access to presence service
96
+ #- private_key - The private key provided when the ORTC service is purchased.
97
+ #- channel - Channel to disable presence.
98
+ #- &block - Callback with error and result parameters.
99
+ #
100
+ #Usage:
101
+ # ORTC.disable_presence(ortc_url, true, ortc_app_key, ortc_private_key, channel) { |error, result|
102
+ # if error.to_s.empty?
103
+ # puts "result: #{result}"
104
+ # else
105
+ # puts "error: #{error}"
106
+ # end
107
+ # }
108
+ def self.disable_presence(url, is_cluster, application_key, private_key, channel, &block)
109
+ if url.to_s.empty?
110
+ block.call('URL is null or empty', nil)
111
+ elsif application_key.to_s.empty?
112
+ block.call('Application Key is null or empty', nil)
113
+ elsif private_key.to_s.empty?
114
+ block.call('Private key is null or empty', nil)
115
+ elsif channel.to_s.empty?
116
+ block.call('Channel is null or empty', nil)
117
+ elsif not channel =~ /^[\w\-:\/.]+$/
118
+ block.call('Channel has invalid characters', nil)
119
+ else
120
+ begin
121
+ r_thread = Thread.new {
122
+ server = ''
123
+ if is_cluster
124
+ server = _get_cluster(url)
125
+ begin
126
+ block.call('Can not connect with the server', nil)
127
+ r_thread.exit
128
+ end if server == ''
129
+ else
130
+ server = url.clone
131
+ end
132
+ server = server << (server.match(/\/$/) ? 'presence' : '/presence')
133
+ server = server << "/disable/#{application_key}/#{channel}"
134
+ body = "privatekey=#{private_key}"
135
+ uri = URI.parse(server)
136
+ begin
137
+ if http = Net::HTTP.new(uri.host, uri.port)
138
+ if server.match /^https/
139
+ http.use_ssl = true
140
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
141
+ end
142
+ req = Net::HTTP::Post.new(uri.request_uri)
143
+ req.body = body
144
+ res = http.request(req)
145
+ if res.code == '200'
146
+ block.call(nil, res.body)
147
+ else
148
+ block.call(res.body, nil)
149
+ end
150
+ end
151
+ rescue => e
152
+ block.call(e, nil)
153
+ r_thread.exit
154
+ end
155
+ }
156
+ r_thread.run
157
+ end
158
+ end
159
+ end
160
+
161
+ #Gets a Hash table indicating the subscriptions in the specified channel and if active the first 100 unique metadata.
162
+ #- url - Server containing the presence service
163
+ #- is_cluster - Indicates whether the url is in a cluster.
164
+ #- application_key - Application key with access to presence service
165
+ #- authentication_token - Authentication token with access to presence service
166
+ #- channel - Channel to presence data active.
167
+ #- &block - Callback with error and result parameters.
168
+ #
169
+ #Usage:
170
+ # ORTC.presence(ortc_url, true, ortc_app_key, ortc_auth_token, channel) { |error, result|
171
+ # if error.to_s.empty?
172
+ # puts "result: #{result}"
173
+ # else
174
+ # puts "error: #{error}"
175
+ # end
176
+ # }
177
+ def self.presence(url, is_cluster, application_key, authentication_token, channel, &block)
178
+ if url.to_s.empty?
179
+ block.call('URL is null or empty', nil)
180
+ elsif application_key.to_s.empty?
181
+ block.call('Application Key is null or empty', nil)
182
+ elsif authentication_token.to_s.empty?
183
+ block.call('Authentication Token is null or empty', nil)
184
+ elsif channel.to_s.empty?
185
+ block.call('Channel is null or empty', nil)
186
+ elsif not channel =~ /^[\w\-:\/.]+$/
187
+ block.call('Channel has invalid characters', nil)
188
+ else
189
+ begin
190
+ r_thread = Thread.new {
191
+ server = ''
192
+ if is_cluster
193
+ server = _get_cluster(url)
194
+ begin
195
+ block.call('Can not connect with the server', nil)
196
+ r_thread.exit
197
+ end if server == ''
198
+ else
199
+ server = url.clone
200
+ end
201
+ server = server << (server.match(/\/$/) ? 'presence' : '/presence')
202
+ server = server << "/#{application_key}/#{authentication_token}/#{channel}"
203
+ #body = "privatekey=#{private_key}"
204
+ uri = URI.parse(server)
205
+ begin
206
+ if http = Net::HTTP.new(uri.host, uri.port)
207
+ if server.match /^https/
208
+ http.use_ssl = true
209
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
210
+ end
211
+ req = Net::HTTP::Get.new(uri.request_uri)
212
+ res = http.request(req)
213
+ if res.code == '200'
214
+ ret = Hash.new
215
+ ret = JSON.parse(res.body) if not res.body == 'null'
216
+ block.call(nil, ret)
217
+ else
218
+ block.call(res.body, nil)
219
+ end
220
+ end
221
+ rescue => e
222
+ block.call(e, nil)
223
+ r_thread.exit
224
+ end
225
+ }
226
+ r_thread.run
227
+ end
228
+ end
229
+ end
230
+
231
+
232
+ def self._get_cluster(url)
233
+ begin
234
+ uri = URI.parse(url)
235
+ if http = Net::HTTP.new(uri.host, uri.port)
236
+ if url.include? 'https'
237
+ http.use_ssl = true
238
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
239
+ end
240
+ uri = URI.parse(url<< "?appkey=#{@app_key}")
241
+ http.start do |http|
242
+ request = Net::HTTP::Get.new(uri.request_uri)
243
+ response = http.request(request)
244
+ response.body.scan(/"(.*?)"/).join
245
+ end
246
+ end
247
+ rescue Timeout::ExitException
248
+ return ''
249
+ rescue Timeout::Error
250
+ return ''
251
+ rescue => e
252
+ return ''
253
+ end
254
+ end
255
+
256
+
257
+ #A class representing an ORTC Client
258
+ class OrtcClient
259
+ #The client announcement subchannel
260
+ attr_accessor :announcement_subchannel
261
+ #The client connection metadata
262
+ attr_accessor :connection_metadata
263
+ #The cluster server URL
264
+ attr_accessor :cluster_url
265
+ #The server URL
266
+ attr_accessor :url
267
+ #The client identifier
268
+ attr_accessor :id
269
+ #The client session identifier
270
+ attr_reader :session_id
271
+ #Indicates whether the client is connected
272
+ attr_reader:is_connected
273
+
274
+ #Creates a new instance of OrtcClient
275
+ def initialize
276
+ @app_key = nil
277
+ @auth_token = nil
278
+ @ortcserver = nil
279
+ @url = nil
280
+ @is_connected = false
281
+ @is_connecting = false
282
+ @is_disconnecting = false
283
+ @is_reconnecting = false
284
+ @str_set = [('a'..'z'),('0'..'9')].map{|i| i.to_a}.flatten
285
+ @session_id = (0...16).map{@str_set[rand(@str_set.length)]}.join
286
+ @on_connected_callback = nil
287
+ @on_disconnected_callback =nil
288
+ @on_reconnected_callback = nil
289
+ @on_reconnecting_callback = nil
290
+ @on_subscribed_callback = nil
291
+ @on_unsubscribed_callback = nil
292
+ @on_exception_callback = nil
293
+ @permissions_table = Hash.new
294
+ @client_thread = nil
295
+ @heartbeat_thread = nil
296
+ @reconnect_thread = nil
297
+ @channels = Hash.new
298
+ @messages_buffer = Hash.new
299
+ @socket = nil
300
+ @got_heartbeat = false
301
+ EM.error_handler{ |e|
302
+ raise "Error during event loop: #{e.message}"
303
+ EM.stop_event_loop
304
+ }
305
+ Thread.abort_on_exception=true
306
+ end
307
+
308
+ #Saves the channel and its permissions for the supplied application key and authentication token.
309
+ #
310
+ #*Note:* This method will send your Private Key over the Internet. Make sure to use secure connection.
311
+ #- url - The ORTC server URL.
312
+ #- is_cluster - Indicates whether the ORTC server is in a cluster.
313
+ #- authentication_token - The authentication token generated by an application server (for instance: a unique session ID).
314
+ #- is_private - Indicates whether the authentication token is private.
315
+ #- application_key - The application key provided when the ORTC service is purchased.
316
+ #- time_to_live - The authentication token time to live (TTL), in other words, the allowed activity time (in seconds).
317
+ #- private_key - The private key provided when the ORTC service is purchased.
318
+ #- channels_permissions - The hash table containing channels and their permissions ('r' - read, 'w' - write, 'p' - presence).
319
+ #Returns boolean- Indicates whether the authentication was successful.
320
+ #
321
+ #Usage:
322
+ # permissions = Hash.new
323
+ # permissions['blue'] = 'r'
324
+ # permissions['yellow'] = 'wp'
325
+ # ortc_client.save_authentication('https://ortc-developers.realtime.co/server/ssl/2.1', true, 'Your_authentication_token', false, 'Your_app_key', 1800, 'Your_private_key', permissions)
326
+ def save_authentication(url, is_cluster, authentication_token, is_private, application_key, time_to_live, private_key, channels_permissions)
327
+ unless channels_permissions.class == Hash
328
+ @on_exception_callback.call(self, 'Wrong parameter: channels_permissions') if(@on_exception_callback)
329
+ return false
330
+ end
331
+ str = "AT=#{authentication_token}&AK=#{application_key}&PK=#{private_key}&TTL=#{time_to_live}&TP=#{channels_permissions.length}&PVT=#{is_private ? "1" : "0"}"
332
+ channels_permissions.each {|channel, permission|
333
+ return false unless is_channel_valid channel, false
334
+ str << "&#{channel}=#{permission}"
335
+ }
336
+ if is_cluster
337
+ auth_server = ORTC._get_cluster(url)
338
+ begin
339
+ @on_exception_callback.call(self, 'Can not connect with the server') if(@on_exception_callback)
340
+ return false
341
+ end if auth_server == ''
342
+ end
343
+ auth_server = auth_server << (auth_server.match(/\/$/) ? 'authenticate' : '/authenticate')
344
+ uri = URI.parse(auth_server)
345
+ begin
346
+ if http = Net::HTTP.new(uri.host, uri.port)
347
+ if auth_server.match /^https/
348
+ http.use_ssl = true
349
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
350
+ end
351
+ req = Net::HTTP::Post.new(uri.request_uri)
352
+ req.body = str
353
+ res = http.request(req)
354
+ end
355
+ rescue => e
356
+ @on_exception_callback.call(self, e) if(@on_exception_callback)
357
+ return false
358
+ end
359
+ return res.code=='201' ? true : false
360
+ end
361
+
362
+ #Enables presence for the specified channel with first 100 unique metadata if true.
363
+ #
364
+ #*Note:* This method will send your Private Key over the Internet. Make sure to use secure connection.
365
+ #- private_key - The private key provided when the ORTC service is purchased.
366
+ #- channel - Channel to activate presence.
367
+ #- metadata - Defines if to collect first 100 unique metadata.
368
+ #- &block - Callback with error and result parameters.
369
+ #
370
+ #Usage:
371
+ # ortc_client.enable_presence(ortc_private_key, channel, true) { |error, result|
372
+ # if error.to_s.empty?
373
+ # puts "result: #{result}"
374
+ # else
375
+ # puts "error: #{error}"
376
+ # end
377
+ # }
378
+ def enable_presence(private_key, channel, metadata, &block)
379
+ if not @is_connected
380
+ @on_exception_callback.call(self, 'Not connected') if(@on_exception_callback)
381
+ return false
382
+ elsif @cluster_url.to_s == '' && @url.to_s == ''
383
+ @on_exception_callback.call(self, 'URL is empty') if(@on_exception_callback)
384
+ return false
385
+ else
386
+ if not @url.to_s.empty?
387
+ ORTC.enable_presence(@url, false, @app_key, private_key, channel, metadata, &block)
388
+ else
389
+ ORTC.enable_presence(@cluster_url, true, @app_key, private_key, channel, metadata, &block)
390
+ end
391
+ end
392
+ end
393
+
394
+
395
+ #Disables presence for the specified channel.
396
+ #
397
+ #*Note:* This method will send your Private Key over the Internet. Make sure to use secure connection.
398
+ #- private_key - The private key provided when the ORTC service is purchased.
399
+ #- channel - Channel to disable presence.
400
+ #- &block - Callback with error and result parameters.
401
+ #
402
+ #Usage:
403
+ # ortc_client.disable_presence(ortc_private_key, channel) { |error, result|
404
+ # if error.to_s.empty?
405
+ # puts "result: #{result}"
406
+ # else
407
+ # puts "error: #{error}"
408
+ # end
409
+ # }
410
+ def disable_presence(private_key, channel, &block)
411
+ if not @is_connected
412
+ @on_exception_callback.call(self, 'Not connected') if(@on_exception_callback)
413
+ return false
414
+ elsif @cluster_url.to_s == '' && @url.to_s == ''
415
+ @on_exception_callback.call(self, 'URL is empty') if(@on_exception_callback)
416
+ return false
417
+ else
418
+ if not @url.to_s.empty?
419
+ ORTC.disable_presence(@url, false, @app_key, private_key, channel, &block)
420
+ else
421
+ ORTC.disable_presence(@cluster_url, true, @app_key, private_key, channel, &block)
422
+ end
423
+ end
424
+ end
425
+
426
+
427
+ #Gets a Hash table indicating the subscriptions in the specified channel and if active the first 100 unique metadata.
428
+ #- channel - Channel to presence data active.
429
+ #- &block - Callback with error and result parameters.
430
+ #
431
+ #Usage:
432
+ # ortc_client.presence(channel) { |error, result|
433
+ # if error.to_s.empty?
434
+ # puts "result: #{result}"
435
+ # else
436
+ # puts "error: #{error}"
437
+ # end
438
+ # }
439
+ def presence(channel, &block)
440
+ if not @is_connected
441
+ @on_exception_callback.call(self, 'Not connected') if(@on_exception_callback)
442
+ return false
443
+ elsif @cluster_url.to_s == '' && @url.to_s == ''
444
+ @on_exception_callback.call(self, 'URL is empty') if(@on_exception_callback)
445
+ return false
446
+ else
447
+ if not @url.to_s.empty?
448
+ ORTC.presence(@url, false, @app_key, @auth_token, channel, &block)
449
+ else
450
+ ORTC.presence(@cluster_url, true, @app_key, @auth_token, channel, &block)
451
+ end
452
+ end
453
+ end
454
+
455
+ #Indicates whether the client is subscribed to the supplied channel.
456
+ #- channel - The channel name.
457
+ #Returns boolean - Indicates whether the client is subscribed to the supplied channel.
458
+ #
459
+ #Usage:
460
+ # puts ortc_client.is_subscribed('blue')
461
+ def is_subscribed(channel)
462
+ return false unless is_channel_valid channel
463
+ ch = @channels[channel]
464
+ return false if ch == nil
465
+ return ch.is_subscribed
466
+ end
467
+
468
+ #Sets the callback which occurs when the client connects.
469
+ #- block - code to be interpreted when the client connects.
470
+ #Usage:
471
+ # ortc_client.on_connected do |sender|
472
+ # p [:Connected]
473
+ # end
474
+ def on_connected(&block) @on_connected_callback = block end
475
+
476
+ #Sets the callback which occurs when the client disconnects.
477
+ #- block - code to be interpreted when the client disconnects.
478
+ #Usage:
479
+ # ortc_client.on_disconnected do |sender|
480
+ # p [:Disconnected]
481
+ # end
482
+ def on_disconnected(&block) @on_disconnected_callback = block end
483
+
484
+ #Sets the callback which occurs when the client reconnects.
485
+ #- block - code to be interpreted when the client reconnects.
486
+ #Usage:
487
+ # ortc_client.on_reconnected do |sender|
488
+ # p [:Reconnected]
489
+ # end
490
+ def on_reconnected(&block) @on_reconnected_callback = block end
491
+
492
+ #Sets the callback which occurs when the client attempts to reconnect.
493
+ #- block - code to be interpreted when the client attempts to reconnect.
494
+ #Usage:
495
+ # ortc_client.on_reonnecting do |sender|
496
+ # p [:Reconnecting]
497
+ # end
498
+ def on_reconnecting(&block) @on_reconnecting_callback = block end
499
+
500
+ #Sets the callback which occurs when the client subscribes to a channel.
501
+ #- block - code to be interpreted when the client subscribes to a channel.
502
+ #Usage:
503
+ # ortc_client.on_subscribed do |sender, channel|
504
+ # p [:Subscribed, channel]
505
+ # end
506
+ def on_subscribed(&block) @on_subscribed_callback = block end
507
+
508
+ #Sets the callback which occurs when the client unsubscribes from a channel.
509
+ #- block - code to be interpreted when the client unsubscribes from a channel.
510
+ #Usage:
511
+ # ortc_client.on_unsubscribed do |sender, channel|
512
+ # p [:Unsubscribed, channel]
513
+ # end
514
+ def on_unsubscribed(&block) @on_unsubscribed_callback = block end
515
+
516
+ #Sets the callback which occurs when there is an exception.
517
+ #- block - code to be interpreted when there is an exception.
518
+ #Usage:
519
+ # ortc_client.on_exception do |sender, exception|
520
+ # p [:Exception, exception]
521
+ # end
522
+ def on_exception(&block) @on_exception_callback = block end
523
+
524
+ #Connects the client using the supplied application key and authentication token.
525
+ #- application_key - Your ORTC application key.
526
+ #- authentication_token - Your ORTC authentication token, this parameter is optional.
527
+ #Usage:
528
+ # ortc_client.connect 'aBc123'
529
+ # ortc_client.connect 'aBc123', 'au7h3n71ca710nT0k3n'
530
+ def connect(application_key, authentication_token='PM.Anonymous')
531
+ begin
532
+ @on_exception_callback.call(self, "Metadata exceeds the limit of #{MAX_CONNECTION_METADATA_SIZE} bytes") if(@on_exception_callback)
533
+ return
534
+ end if @connection_metadata.bytes.to_a.size > MAX_CONNECTION_METADATA_SIZE if @connection_metadata != nil
535
+ begin
536
+ @on_exception_callback.call(self, 'Wrong Applicaition Key') if(@on_exception_callback)
537
+ return
538
+ end if (!application_key.is_a?(String) || application_key.size < 1)
539
+ $1 if application_key =~ /^[\w\-:\/.]+$/ or begin @on_exception_callback.call(self, "Application key: \"#{application_key}\" has invalid characters") if (@on_exception_callback)
540
+ return end
541
+ $1 if authentication_token =~ /^[\w\-:\/.]+$/ or begin @on_exception_callback.call(self, "Authentication token: \"#{authentication_token}\" has invalid characters") if (@on_exception_callback)
542
+ return end
543
+ begin
544
+ @on_exception_callback.call(self, 'Already connected') if(@on_exception_callback)
545
+ return
546
+ end if @is_connected
547
+ begin
548
+ @on_exception_callback.call(self, 'Already trying to connect') if(@on_exception_callback)
549
+ return
550
+ end if @is_connecting
551
+ begin
552
+ @on_exception_callback.call(self, 'URL is empty') if(@on_exception_callback)
553
+ return
554
+ end if @cluster_url.to_s == '' && @url.to_s == ''
555
+
556
+ @is_connecting = true
557
+ @app_key = application_key
558
+ @auth_token = authentication_token
559
+ @client_thread = nil
560
+ @client_thread = Thread.new {
561
+ if @url.to_s == ''
562
+ $1 if @cluster_url=~ /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ or begin @on_exception_callback.call(self, "Invalid cluster URL") if (@on_exception_callback)
563
+ @client_thread.kill if @client_thread end
564
+ @ortcserver = ORTC._get_cluster(@cluster_url)
565
+ else
566
+ $1 if @url=~ /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ or begin @on_exception_callback.call(self, "Invalid URL") if (@on_exception_callback)
567
+ @client_thread.kill if @client_thread end
568
+ @ortcserver = @url.clone
569
+ end
570
+ begin
571
+ @on_exception_callback.call(self, 'There is no server available') if @on_exception_callback
572
+ @is_connecting = false
573
+ @client_thread.kill if @client_thread
574
+ end if @ortcserver.to_s == ''
575
+ @ortcserver["http"]="ws" if @ortcserver["http"]
576
+ conn_str = "#{@ortcserver}/broadcast/#{rand(1000)}/#{(0...8).map{@str_set[rand(@str_set.length)]}.join}/websocket"
577
+
578
+ EM.run {
579
+ @socket = Faye::WebSocket::Client.new(conn_str)
580
+
581
+ @socket.onopen = lambda do |event|
582
+
583
+ end
584
+
585
+ @socket.onmessage = lambda do |event|
586
+ if event.data.eql? "o"
587
+ EM.next_tick {
588
+ @socket.send "validate;#{@app_key};#{@auth_token};#{@announcement_subchannel};#{@session_id};#{@connection_metadata}".to_json
589
+ }
590
+ elsif event.data != "h"
591
+ parse_message event.data
592
+ else
593
+ @got_heartbeat = true
594
+ end
595
+ end
596
+
597
+ @socket.onclose = lambda do |event|
598
+ if(@is_disconnecting || @is_connecting)
599
+ @on_disconnected_callback.call(self) if(@on_disconnected_callback && @is_disconnecting)
600
+ @is_disconnecting = false
601
+ @is_connecting = false
602
+ unless @is_reconnecting
603
+ @reconnect_thread.kill if @reconnect_thread
604
+ @channels.clear
605
+ end
606
+ @heartbeat_thread.kill if @heartbeat_thread
607
+ end
608
+ @is_connected = false
609
+ EM.stop_event_loop
610
+ end
611
+ }
612
+ }
613
+ @client_thread.run
614
+ end
615
+
616
+ #Disconnects the client.
617
+ #
618
+ #Usage:
619
+ # ortc_client.disconnect
620
+ def disconnect
621
+ begin @on_exception_callback.call(self, 'Not connected') if(@on_exception_callback)
622
+ return
623
+ end unless @is_connected || @is_reconnecting
624
+ if @is_reconnecting
625
+ @reconnect_thread.kill if @reconnect_thread
626
+ @heartbeat_thread.kill if @heartbeat_thread
627
+ end
628
+ @is_connecting = false
629
+ @is_reconnecting = false
630
+ @is_disconnecting = true
631
+ @channels.clear
632
+ #@socket.close(nil, nil ,nil)
633
+ @socket.close
634
+ @client_thread.kill if @client_thread
635
+ end
636
+
637
+ #Subscribes to the supplied channel to receive messages sent to it.
638
+ #- channel - The channel name.
639
+ #- subscribeOnReconnected - Indicates whether the client should subscribe to the channel when reconnected (if it was previously subscribed when connected).
640
+ #- block - the callback called when a message arrives at the channel.
641
+ #Usage:
642
+ # ortc_client.subscribe('blue', true) { |sender, channel, message|
643
+ # puts "Message received on (#{channel}): #{message}"
644
+ # }
645
+ def subscribe(channel, subscribe_on_reconnect, &block)
646
+ return unless is_channel_valid channel
647
+ ch = @channels[channel]
648
+ if ch != nil
649
+ begin
650
+ @on_exception_callback.call(self, "Already subscribing to the channel #{channel}") if(@on_exception_callback)
651
+ return
652
+ end if ch.is_subscribing
653
+ begin
654
+ @on_exception_callback.call(self, "Already subscribed to the channel #{channel}") if(@on_exception_callback)
655
+ return
656
+ end if ch.is_subscribed
657
+ end
658
+ unless @permissions_table.empty?
659
+ hash = check_permissions 'subscribe', channel
660
+ return if hash == nil
661
+ end
662
+
663
+ @channels[channel] = Channel.new(channel, block, true, subscribe_on_reconnect) unless ch
664
+ EM.next_tick {
665
+ @socket.send "subscribe;#{@app_key};#{@auth_token};#{channel};#{hash}".to_json
666
+ }
667
+ end
668
+
669
+ #Unsubscribes from the supplied channel to stop receiving messages sent to it.
670
+ #- channel - The channel name.
671
+ #Usage:
672
+ # ortc_client.unsubscribe('blue')
673
+ def unsubscribe(channel)
674
+ return unless is_channel_valid channel
675
+ ch = @channels[channel]
676
+ if ch != nil
677
+ begin @on_exception_callback.call(self, "Not subscribed to the channel #{channel}") if(@on_exception_callback)
678
+ return end unless ch.is_subscribed
679
+ ch.subscribe_on_reconnected = false
680
+ EM.next_tick {
681
+ @socket.send "unsubscribe;#{@app_key};#{channel}".to_json
682
+ }
683
+ else
684
+ @on_exception_callback.call(self, "Not subscribed to the channel #{channel}") if(@on_exception_callback)
685
+ end
686
+ end
687
+
688
+ #Sends the supplied message to the supplied channel.
689
+ #- channel - The channel name.
690
+ #- message - The message to send.
691
+ #Usage:
692
+ # ortc_client.send('blue', 'This is a message')
693
+ def send(channel, message)
694
+ return false unless is_channel_valid channel
695
+ begin
696
+ @on_exception_callback.call(self, 'Message is null or empty') if(@on_exception_callback)
697
+ return
698
+ end if message.to_s.empty?
699
+ unless @permissions_table.empty?
700
+ hash = check_permissions 'send', channel
701
+ return false if hash == nil
702
+ end
703
+ message_id = (0...8).map{@str_set[rand(@str_set.length)]}.join
704
+ parts = message.bytes.to_a.each_slice(MAX_MESSAGE_SIZE).to_a
705
+ EM.next_tick {
706
+ parts.each_with_index { |part, index|
707
+ @socket.send "send;#{@app_key};#{@auth_token};#{channel};#{hash};#{message_id}_#{index+1}-#{parts.length}_#{part.pack('c*')}".to_json
708
+ }
709
+ }
710
+ return true
711
+ end
712
+
713
+ private
714
+
715
+ def reconnect
716
+ @reconnect_thread = Thread.new{
717
+ until @is_connected do
718
+ sleep RECONNECT_INTERVAL
719
+ connect @app_key, @auth_token unless @is_connected
720
+ end
721
+ }
722
+ @reconnect_thread.run
723
+ end
724
+
725
+ def check_connection_loop
726
+ @heartbeat_thread = Thread.new{
727
+ loop do
728
+ counter = 0
729
+ while counter < MAX_HEARTBEAT_INTERVAL
730
+ sleep 1
731
+ if @got_heartbeat
732
+ counter = 0
733
+ @got_heartbeat = false
734
+ end
735
+ counter += 1
736
+ end
737
+ if @is_connected
738
+ @is_connected = false
739
+ @is_disconnecting = false
740
+ @is_reconnecting = true
741
+ EM.stop_event_loop if EM.reactor_running?
742
+ @on_reconnecting_callback.call(self) if(@on_reconnecting_callback)
743
+ reconnect
744
+ @heartbeat_thread.kill if @heartbeat_thread
745
+ end
746
+ end
747
+ }
748
+ @heartbeat_thread.run
749
+ end
750
+
751
+ def check_permissions(reason, channel)
752
+ hash = @permissions_table[channel]
753
+ if hash == nil && !(channel=~ /^[\w\-\/]+:./).nil?
754
+ subchannel = channel[/^[\w\-\/]+:/]+'*'
755
+ puts "cp #{subchannel} z #{@permissions_table}"
756
+ hash = @permissions_table[subchannel]
757
+ end
758
+ if hash==nil
759
+ @on_exception_callback.call(self, "No permission found to #{reason} to the channel #{channel}") if (@on_exception_callback)
760
+ return
761
+ end
762
+ return hash
763
+ end
764
+
765
+ def is_channel_valid(channel, check_connection = true)
766
+ if check_connection
767
+ begin
768
+ @on_exception_callback.call(self, 'Not connected') if(@on_exception_callback)
769
+ return
770
+ end unless @is_connected
771
+ end
772
+ $1 if channel =~ /^[\w\-:\/.]+$/ or begin @on_exception_callback.call(self, "Channel name: #{channel} has invalid characters") if (@on_exception_callback)
773
+ return end
774
+ begin
775
+ @on_exception_callback.call(self, "Channel name size exceeds the limit of #{MAX_CHANNEL_NAME_SIZE} characters") if(@on_exception_callback)
776
+ return
777
+ end if channel.length > MAX_CHANNEL_NAME_SIZE
778
+ return true
779
+ end
780
+
781
+
782
+
783
+ def parse_message(message)
784
+ message = message.gsub("\\\"", "\"")
785
+ unless (message =~/^a?\["\{"ch":"(.*)","m":"([\s\S]*?)"\}"\]$/).nil?
786
+ channel = $1
787
+ raw_message = $2
788
+ unless (raw_message =~/^(.[^_]*)_(.[^-]*)-(.[^_]*)_([\s\S]*?)$/).nil?
789
+ if($2.to_i==1 && $3.to_i==1)
790
+ m = $4.gsub("\\\\n","\n").gsub("\\\"", "\"").gsub("\\\\\\\\", "\\").gsub(/\\"/, '"')
791
+ @channels[channel].on_received_message(self, m) if @channels[channel]
792
+ else
793
+ if @messages_buffer[$1].nil?
794
+ @messages_buffer[$1] = MultiMessage.new($3.to_i)
795
+ end
796
+ @messages_buffer[$1].set_part($2.to_i-1, $4)
797
+ if @messages_buffer[$1].is_ready
798
+ all = @messages_buffer[$1].get_all.gsub("\\\\n","\n").gsub("\\\"", "\"").gsub("\\\\\\\\", "\\").gsub(/\\"/, '"')
799
+ @messages_buffer[$1] = nil
800
+ @channels[channel].on_received_message(self, all) if @channels[channel]
801
+ end
802
+ end
803
+ return
804
+ end
805
+ @channels[channel].on_received_message(self, raw_message.gsub("\\\\n","\n").gsub("\\\"", "\"").gsub("\\\\\\\\", "\\").gsub(/\\"/, '"')) if @channels[channel]
806
+ end
807
+ unless (message =~ /^a\["\{"op":"([^"]+)",(.*)\}"\]$/).nil? #operation
808
+ operation = $1
809
+ params = $2
810
+ unless (params =~ /^\"up\":{1}(.*),\"set\":(.*)$/).nil? #validated
811
+ if $1 == 'null'
812
+ @permissions_table = Hash.new
813
+ else
814
+ @permissions_table = JSON.parse($1) if $1 != 'null'
815
+ end
816
+ #session_expiration_time = $2
817
+ check_connection_loop #starts a thread to watch for connection breaks
818
+ @is_connected = true
819
+ @is_connecting = false
820
+ if @is_reconnecting
821
+ @channels.each { |channel_name,channel|
822
+ if channel == nil
823
+ @channels.delete(channel_name)
824
+ next
825
+ end
826
+ if channel.subscribe_on_reconnected
827
+ channel.is_subscribing = true
828
+ channel.is_subscribed = false
829
+ unless @permissions_table.empty?
830
+ hash = check_permissions 'subscribe', channel.name
831
+ next if hash == nil
832
+ end
833
+ EM.next_tick {
834
+ @socket.send "subscribe;#{@app_key};#{@auth_token};#{channel_name};#{hash}".to_json
835
+ }
836
+ else
837
+ @channels.delete(channel_name)
838
+ end
839
+ }
840
+ @on_reconnected_callback.call(self) if(@on_reconnected_callback)
841
+ else
842
+ @on_connected_callback.call(self) if(@on_connected_callback)
843
+ end
844
+ @is_reconnecting = false
845
+ end
846
+ unless (params =~ /^\"ex\":(\{.*\})$/).nil? #error
847
+ error = JSON.parse($1)
848
+ @on_exception_callback.call(self, error['ex']) if(@on_exception_callback)
849
+ end
850
+ if operation=="ortc-subscribed"
851
+ channel = (JSON.parse "{#{params}}")["ch"]
852
+ @channels[channel].is_subscribing = false
853
+ @channels[channel].is_subscribed = true
854
+ @on_subscribed_callback.call(self, channel) if(@on_subscribed_callback)
855
+ end
856
+ if operation=="ortc-unsubscribed"
857
+ channel = (JSON.parse "{#{params}}")["ch"]
858
+ @channels[channel] = nil
859
+ @on_unsubscribed_callback.call(self, channel) if(@on_unsubscribed_callback)
860
+ end
861
+ end
862
+ end
863
+ end
864
+
865
+ end
@@ -0,0 +1,40 @@
1
+ module ORTC
2
+
3
+ class Channel
4
+ attr_accessor :is_subscribing, :is_subscribed, :subscribe_on_reconnected, :name
5
+ def initialize(name, on_message, is_subscribing, subscribe_on_reconnected = true)
6
+ @name = name
7
+ @is_subscribing = is_subscribing || false
8
+ @is_subscribed = false
9
+ @subscribe_on_reconnected = subscribe_on_reconnected
10
+ @on_message = on_message
11
+ end
12
+
13
+ def on_received_message(sender, message)
14
+ @on_message.call(sender, @name, message) if @on_message
15
+ end
16
+ end
17
+
18
+ class MultiMessage
19
+ attr_reader :total_parts, :ready_parts
20
+ def initialize(total_parts)
21
+ @total_parts = total_parts
22
+ @ready_parts = 0
23
+ @parts = Array.new total_parts
24
+ end
25
+
26
+ def set_part(part_id, part)
27
+ @ready_parts+=1 if @parts[part_id]==nil
28
+ @parts[part_id] = part
29
+ end
30
+
31
+ def is_ready
32
+ return true if @ready_parts == @total_parts
33
+ end
34
+
35
+ def get_all
36
+ return @parts.join("")
37
+ end
38
+ end
39
+
40
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ortc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Realtime.co
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.12.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.12.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: faye-websocket
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.4.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.0
55
+ description: ORTC aka Realtime Cloud Messaging provides a standard software API for
56
+ sending and receiving data in real-time over the web.
57
+ email: ortc@realtime.co
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CHANGELOG.md
63
+ - README.md
64
+ - example/ortc_example.rb
65
+ - example/ortc_example2.rb
66
+ - lib/ortc.rb
67
+ - lib/ortc/ortc_extensibility.rb
68
+ homepage: http://framework.realtime.co/messaging
69
+ licenses: []
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.2
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: ORTC (Open Real-Time Connectivity) module for pub/sub communications.
91
+ test_files: []