pubnub 0.1.12 → 3.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pubnub might be problematic. Click here for more details.
- data/lib/pubnub.rb +298 -235
- data/lib/pubnub_crypto.rb +53 -142
- data/lib/pubnub_deferrable.rb +37 -0
- data/lib/pubnub_request.rb +287 -0
- metadata +90 -71
- data/README +0 -83
- data/examples/history_example.rb +0 -41
- data/examples/publish_example.rb +0 -69
- data/examples/subscribe_example.rb +0 -43
- data/examples/uuid_example.rb +0 -15
- data/tests/unit_test.rb +0 -88
data/lib/pubnub.rb
CHANGED
@@ -2,291 +2,354 @@
|
|
2
2
|
## http://www.pubnub.com/blog/ruby-push-api - Ruby Push API Blog
|
3
3
|
|
4
4
|
## PubNub Real Time Push APIs and Notifications Framework
|
5
|
-
## Copyright (c)
|
5
|
+
## Copyright (c) 2012 PubNub
|
6
6
|
## http://www.pubnub.com/
|
7
7
|
|
8
8
|
## -----------------------------------
|
9
|
-
## PubNub 3.
|
9
|
+
## PubNub 3.3 Real-time Push Cloud API
|
10
10
|
## -----------------------------------
|
11
11
|
|
12
|
-
## including required libraries
|
13
|
-
require 'openssl'
|
14
12
|
require 'base64'
|
15
13
|
require 'open-uri'
|
16
14
|
require 'uri'
|
17
|
-
|
18
|
-
require 'net/https'
|
19
|
-
require 'json'
|
20
|
-
require 'pp'
|
21
|
-
require 'rubygems'
|
22
|
-
require 'securerandom'
|
23
|
-
require 'digest'
|
15
|
+
|
24
16
|
require 'pubnub_crypto'
|
17
|
+
require 'pubnub_request'
|
18
|
+
require 'pubnub_deferrable'
|
19
|
+
|
20
|
+
require 'eventmachine'
|
21
|
+
require 'uuid'
|
22
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
23
|
+
require 'active_support/core_ext/string/inflections'
|
24
|
+
require 'active_support/core_ext/object/try'
|
25
|
+
require 'active_support/core_ext/object/blank'
|
26
|
+
|
25
27
|
|
26
28
|
class Pubnub
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@
|
29
|
+
|
30
|
+
class PresenceError < RuntimeError;
|
31
|
+
end
|
32
|
+
class PublishError < RuntimeError;
|
33
|
+
end
|
34
|
+
class SubscribeError < RuntimeError;
|
35
|
+
end
|
36
|
+
class InitError < RuntimeError;
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor :publish_key, :subscribe_key, :secret_key, :cipher_key, :ssl, :channel, :origin, :session_uuid
|
40
|
+
|
41
|
+
|
42
|
+
#ORIGINS = %w(newcloud-virginia.pubnub.com newcloud-california.pubnub.com newcloud-ireland.pubnub.com newcloud-tokyo.pubnub.com)
|
43
|
+
ORIGIN_HOST = 'pubsub.pubnub.com'
|
44
|
+
#ORIGIN_HOST = 'newcloud-california.pubnub.com'
|
45
|
+
#ORIGIN_HOST = 'test.pubnub.com'
|
46
|
+
|
47
|
+
def initialize(*args)
|
48
|
+
|
49
|
+
if args.size == 5 # passing in named parameters
|
50
|
+
|
51
|
+
@publish_key = args[0].to_s
|
52
|
+
@subscribe_key = args[1].to_s
|
53
|
+
@secret_key = args[2].to_s
|
54
|
+
@cipher_key = args[3].to_s
|
55
|
+
@ssl = args[4]
|
56
|
+
|
57
|
+
elsif args.size == 1 && args[0].class == Hash # passing in an options hash
|
58
|
+
|
59
|
+
options_hash = HashWithIndifferentAccess.new(args[0])
|
60
|
+
@publish_key = options_hash[:publish_key].blank? ? nil : options_hash[:publish_key].to_s
|
61
|
+
@subscribe_key = options_hash[:subscribe_key].blank? ? nil : options_hash[:subscribe_key].to_s
|
62
|
+
@secret_key = options_hash[:secret_key].blank? ? nil : options_hash[:secret_key].to_s
|
63
|
+
@cipher_key = options_hash[:cipher_key].blank? ? nil : options_hash[:cipher_key].to_s
|
64
|
+
@ssl = options_hash[:ssl].blank? ? false : true
|
65
|
+
|
50
66
|
else
|
51
|
-
|
67
|
+
raise(InitError, "Initialize with either a hash of options, or exactly 5 named parameters.")
|
52
68
|
end
|
69
|
+
|
70
|
+
@session_uuid = uuid
|
71
|
+
verify_init
|
72
|
+
end
|
73
|
+
|
74
|
+
def verify_init
|
75
|
+
# publish_key and cipher_key are both optional.
|
76
|
+
raise(InitError, "subscribe_key is a mandatory parameter.") if @subscribe_key.blank?
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def publish(options)
|
81
|
+
options = HashWithIndifferentAccess.new(options)
|
82
|
+
publish_request = PubnubRequest.new(:operation => :publish)
|
83
|
+
|
84
|
+
#TODO: refactor into initializer code on request instantiation
|
85
|
+
|
86
|
+
publish_request.ssl = @ssl
|
87
|
+
publish_request.set_channel(options)
|
88
|
+
publish_request.set_callback(options)
|
89
|
+
publish_request.set_cipher_key(options, self.cipher_key)
|
90
|
+
publish_request.set_message(options, self.cipher_key)
|
91
|
+
publish_request.set_publish_key(options, self.publish_key)
|
92
|
+
publish_request.set_subscribe_key(options, self.subscribe_key)
|
93
|
+
publish_request.set_secret_key(options, self.secret_key)
|
94
|
+
|
95
|
+
|
96
|
+
publish_request.format_url!
|
97
|
+
|
98
|
+
_request(publish_request)
|
53
99
|
end
|
54
100
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
101
|
+
def subscribe(options)
|
102
|
+
options = HashWithIndifferentAccess.new(options)
|
103
|
+
|
104
|
+
operation = options[:operation].nil? ? :subscribe : :presence
|
105
|
+
|
106
|
+
subscribe_request = PubnubRequest.new(:operation => operation, :session_uuid => @session_uuid)
|
107
|
+
|
108
|
+
#TODO: refactor into initializer code on request instantiation
|
109
|
+
|
110
|
+
subscribe_request.ssl = @ssl
|
111
|
+
subscribe_request.set_channel(options)
|
112
|
+
subscribe_request.set_callback(options)
|
113
|
+
subscribe_request.set_cipher_key(options, self.cipher_key) unless subscribe_request.operation == "presence"
|
114
|
+
|
115
|
+
subscribe_request.set_subscribe_key(options, self.subscribe_key)
|
116
|
+
|
117
|
+
format_url_options = options[:override_timetoken].present? ? options[:override_timetoken] : nil
|
118
|
+
subscribe_request.format_url!(format_url_options)
|
119
|
+
|
120
|
+
_request(subscribe_request)
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
def presence(options)
|
125
|
+
usage_error = "presence() requires :channel and :callback options."
|
126
|
+
if options.class != Hash
|
127
|
+
raise(ArgumentError, usage_error)
|
68
128
|
end
|
69
129
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
130
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
131
|
+
|
132
|
+
unless options[:channel] && options[:callback]
|
133
|
+
raise(ArgumentError, usage_error)
|
134
|
+
end
|
135
|
+
|
136
|
+
subscribe(options.merge(:operation => "presence"))
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def here_now(options = nil)
|
142
|
+
usage_error = "here_now() requires :channel and :callback options."
|
143
|
+
if options.class != Hash
|
144
|
+
raise(ArgumentError, usage_error)
|
85
145
|
end
|
86
146
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
digest = OpenSSL::Digest.new("sha256")
|
92
|
-
key = [ @secret_key ]
|
93
|
-
hmac = OpenSSL::HMAC.hexdigest(digest, key.pack("H*"), signature)
|
94
|
-
signature = hmac
|
147
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
148
|
+
|
149
|
+
unless options[:channel] && options[:callback]
|
150
|
+
raise(ArgumentError, usage_error)
|
95
151
|
end
|
96
152
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
153
|
+
here_now_request = PubnubRequest.new(:operation => :here_now)
|
154
|
+
|
155
|
+
here_now_request.ssl = @ssl
|
156
|
+
here_now_request.set_channel(options)
|
157
|
+
here_now_request.set_callback(options)
|
158
|
+
|
159
|
+
here_now_request.set_subscribe_key(options, self.subscribe_key)
|
160
|
+
|
161
|
+
here_now_request.format_url!
|
162
|
+
_request(here_now_request)
|
163
|
+
|
102
164
|
end
|
103
165
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
#* Listen for a message on a channel.
|
109
|
-
#*
|
110
|
-
#* @param array args with channel and message.
|
111
|
-
#* @return false on fail, array on success.
|
112
|
-
#*
|
113
|
-
def subscribe(args)
|
114
|
-
## Capture User Input
|
115
|
-
channel = args['channel']
|
116
|
-
callback = args['callback']
|
117
|
-
|
118
|
-
## Fail if missing channel
|
119
|
-
if !channel
|
120
|
-
puts "Missing Channel."
|
121
|
-
return false
|
166
|
+
def detailed_history(options = nil)
|
167
|
+
usage_error = "detailed_history() requires :channel, :callback, and :count options."
|
168
|
+
if options.class != Hash
|
169
|
+
raise(ArgumentError, usage_error)
|
122
170
|
end
|
123
171
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
172
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
173
|
+
|
174
|
+
unless options[:count] && options[:channel] && options[:callback]
|
175
|
+
raise(ArgumentError, usage_error)
|
128
176
|
end
|
129
177
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
178
|
+
|
179
|
+
detailed_history_request = PubnubRequest.new(:operation => :detailed_history)
|
180
|
+
|
181
|
+
#TODO: refactor into initializer code on request instantiation
|
182
|
+
|
183
|
+
# /detailed_history/SUBSCRIBE_KEY/CHANNEL/JSONP_CALLBACK/LIMIT
|
184
|
+
|
185
|
+
detailed_history_request.ssl = @ssl
|
186
|
+
detailed_history_request.set_channel(options)
|
187
|
+
detailed_history_request.set_callback(options)
|
188
|
+
detailed_history_request.set_cipher_key(options, self.cipher_key)
|
189
|
+
|
190
|
+
detailed_history_request.set_subscribe_key(options, self.subscribe_key)
|
191
|
+
|
192
|
+
detailed_history_request.history_count = options[:count]
|
193
|
+
detailed_history_request.history_start = options[:start]
|
194
|
+
detailed_history_request.history_end = options[:end]
|
195
|
+
detailed_history_request.history_reverse = options[:reverse]
|
196
|
+
|
197
|
+
detailed_history_request.format_url!
|
198
|
+
_request(detailed_history_request)
|
199
|
+
|
137
200
|
end
|
138
201
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
#*
|
144
|
-
#* @param array args with 'channel' and 'limit'.
|
145
|
-
#* @return mixed false on fail, array on success.
|
146
|
-
#*
|
147
|
-
def history(args)
|
148
|
-
## Capture User Input
|
149
|
-
limit = +args['limit'] ? +args['limit'] : 5
|
150
|
-
channel = args['channel']
|
151
|
-
callback = args['callback']
|
152
|
-
|
153
|
-
## Fail if bad input.
|
154
|
-
if (!channel)
|
155
|
-
puts 'Missing Channel.'
|
156
|
-
return false
|
202
|
+
def history(options = nil)
|
203
|
+
usage_error = "history() requires :channel, :callback, and :limit options."
|
204
|
+
if options.class != Hash
|
205
|
+
raise(ArgumentError, usage_error)
|
157
206
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
207
|
+
|
208
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
209
|
+
|
210
|
+
unless options[:limit] && options[:channel] && options[:callback]
|
211
|
+
raise(ArgumentError, usage_error)
|
161
212
|
end
|
162
213
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
214
|
+
|
215
|
+
history_request = PubnubRequest.new(:operation => :history)
|
216
|
+
|
217
|
+
#TODO: refactor into initializer code on request instantiation
|
218
|
+
|
219
|
+
# /history/SUBSCRIBE_KEY/CHANNEL/JSONP_CALLBACK/LIMIT
|
220
|
+
|
221
|
+
history_request.ssl = @ssl
|
222
|
+
history_request.set_channel(options)
|
223
|
+
history_request.set_callback(options)
|
224
|
+
history_request.set_cipher_key(options, self.cipher_key)
|
225
|
+
|
226
|
+
history_request.set_subscribe_key(options, self.subscribe_key)
|
227
|
+
history_request.history_limit = options[:limit]
|
228
|
+
|
229
|
+
history_request.format_url!
|
230
|
+
_request(history_request)
|
231
|
+
|
167
232
|
end
|
168
233
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
234
|
+
def time(options)
|
235
|
+
options = HashWithIndifferentAccess.new(options)
|
236
|
+
raise(PubNubRuntimeError, "You must supply a callback.") if options['callback'].blank?
|
237
|
+
|
238
|
+
time_request = PubnubRequest.new(:operation => :time)
|
239
|
+
time_request.set_callback(options)
|
240
|
+
|
241
|
+
time_request.format_url!
|
242
|
+
_request(time_request)
|
243
|
+
end
|
244
|
+
|
245
|
+
def my_callback(x, quiet = false)
|
246
|
+
if quiet !=false
|
247
|
+
puts("mycallback says: #{x.to_s}")
|
248
|
+
else
|
249
|
+
""
|
250
|
+
end
|
251
|
+
|
180
252
|
end
|
181
253
|
|
182
|
-
|
183
|
-
|
184
|
-
#*
|
185
|
-
#* Unique identifier generation
|
186
|
-
#*
|
187
|
-
#* @return Unique Identifier
|
188
|
-
#*
|
189
|
-
def UUID()
|
190
|
-
uuid=SecureRandom.base64(32).gsub("/","_").gsub(/=+$/,"")
|
254
|
+
def uuid
|
255
|
+
UUID.new.generate
|
191
256
|
end
|
192
257
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
## Capture the message and encrypt it
|
220
|
-
if @cipher_key.length > 0
|
221
|
-
pc = PubnubCrypto.new(@cipher_key)
|
222
|
-
messages.each do |message|
|
223
|
-
if message.is_a? Array
|
224
|
-
message=pc.decryptArray(message)
|
225
|
-
else
|
226
|
-
message=pc.decryptObject(message)
|
258
|
+
def _request(request)
|
259
|
+
|
260
|
+
if (defined?(Rails) && Rails.present? && Rails.env.present?) && Rails.env.test?
|
261
|
+
|
262
|
+
open(request.url, 'r', :read_timeout => 300) do |response|
|
263
|
+
request.package_response!(response.read)
|
264
|
+
request.callback.call(request.response)
|
265
|
+
request.response
|
266
|
+
end
|
267
|
+
|
268
|
+
else
|
269
|
+
|
270
|
+
request.format_url!
|
271
|
+
#puts("- Fetching #{request.url}")
|
272
|
+
|
273
|
+
begin
|
274
|
+
|
275
|
+
EM.run do
|
276
|
+
|
277
|
+
conn = PubnubDeferrable.connect request.host, request.port
|
278
|
+
conn.pubnub_request = request
|
279
|
+
req = conn.get(request.query)
|
280
|
+
|
281
|
+
timeout_timer = EM.add_periodic_timer(290) do
|
282
|
+
#puts("#{Time.now}: Reconnecting from timeout.")
|
283
|
+
reconnect_and_query(conn, request)
|
227
284
|
end
|
228
|
-
|
229
|
-
|
285
|
+
|
286
|
+
error_timer = EM.add_periodic_timer(5) do
|
287
|
+
#puts("#{Time.now}: Checking for errors.")
|
288
|
+
if conn.error?
|
289
|
+
|
290
|
+
error_message = "Intermittent Error: #{response.status}, extended info: #{response.internal_error}"
|
291
|
+
puts(error_message)
|
292
|
+
request.callback.call([0, error_message])
|
293
|
+
|
294
|
+
reconnect_and_query(conn, request)
|
295
|
+
end
|
230
296
|
end
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
297
|
+
|
298
|
+
req.errback do |response|
|
299
|
+
conn.close_connection
|
300
|
+
error_message = "Unknown Error: #{response.to_s}"
|
301
|
+
|
302
|
+
puts(error_message)
|
303
|
+
request.callback.call([0, error_message])
|
304
|
+
|
305
|
+
reconnect_and_query(conn, request)
|
236
306
|
end
|
237
|
-
end
|
238
|
-
end
|
239
307
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
308
|
+
req.callback do |response|
|
309
|
+
|
310
|
+
if response.status != 200
|
311
|
+
error_message = "Server Error, status: #{response.status}, extended info: #{response.internal_error}"
|
312
|
+
|
313
|
+
puts(error_message)
|
314
|
+
request.callback.call([0, error_message])
|
315
|
+
|
316
|
+
conn.reconnect request.host, request.port
|
317
|
+
end
|
318
|
+
|
319
|
+
request.package_response!(response.content)
|
320
|
+
request.callback.call(request.response)
|
321
|
+
|
322
|
+
EM.next_tick do
|
323
|
+
if %w(subscribe presence).include?(request.operation)
|
324
|
+
conn.close_connection
|
246
325
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
#* @return array from JSON response.
|
252
|
-
#*
|
253
|
-
def _request(args)
|
254
|
-
request = args['request']
|
255
|
-
callback = args['callback']
|
256
|
-
url = encode_URL(request)
|
257
|
-
url = @origin + url
|
258
|
-
open(url) do |f|
|
259
|
-
response = JSON.parse(f.read)
|
260
|
-
if request[0] == 'history'
|
261
|
-
if @cipher_key.length > 0
|
262
|
-
myarr=Array.new()
|
263
|
-
response.each do |message|
|
264
|
-
pc=PubnubCrypto.new(@cipher_key)
|
265
|
-
if message.is_a? Array
|
266
|
-
message=pc.decryptArray(message)
|
326
|
+
timeout_timer.cancel
|
327
|
+
error_timer.cancel
|
328
|
+
|
329
|
+
_request(request)
|
267
330
|
else
|
268
|
-
|
331
|
+
conn.close_connection
|
332
|
+
return request.response
|
333
|
+
|
269
334
|
end
|
270
|
-
|
335
|
+
|
271
336
|
end
|
272
|
-
callback.call(myarr)
|
273
|
-
else
|
274
|
-
callback.call(response)
|
275
337
|
end
|
276
|
-
elsif request[0] == 'publish'
|
277
|
-
callback.call(response)
|
278
|
-
else
|
279
|
-
callback.call(response)
|
280
338
|
end
|
339
|
+
|
340
|
+
rescue EventMachine::ConnectionError => e
|
341
|
+
error_message = "Network Error: #{e.message}"
|
342
|
+
puts(error_message)
|
343
|
+
return [0, error_message]
|
281
344
|
end
|
345
|
+
|
346
|
+
end
|
282
347
|
end
|
283
348
|
|
284
|
-
def
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
'%' + ch.unpack('H2')[0].to_s.upcase : URI.encode(ch)
|
289
|
-
}.join('') }.join('/')
|
290
|
-
return url
|
349
|
+
def reconnect_and_query(conn, request)
|
350
|
+
conn.reconnect request.host, request.port
|
351
|
+
conn.pubnub_request = request
|
352
|
+
conn.get(request.query)
|
291
353
|
end
|
354
|
+
|
292
355
|
end
|