ortc 0.1.1

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,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: []