pubnub-picklive 3.3.0.7
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.
- data/lib/pubnub.rb +413 -0
- data/lib/pubnub_crypto.rb +53 -0
- data/lib/pubnub_request.rb +310 -0
- metadata +144 -0
data/lib/pubnub.rb
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
## www.pubnub.com - PubNub realtime push service in the cloud.
|
2
|
+
## http://www.pubnub.com/blog/ruby-push-api - Ruby Push API Blog
|
3
|
+
|
4
|
+
## PubNub Real Time Push APIs and Notifications Framework
|
5
|
+
## Copyright (c) 2012 PubNub
|
6
|
+
## http://www.pubnub.com/
|
7
|
+
|
8
|
+
## -----------------------------------
|
9
|
+
## PubNub 3.3 Real-time Push Cloud API
|
10
|
+
## -----------------------------------
|
11
|
+
|
12
|
+
require 'base64'
|
13
|
+
require 'open-uri'
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
require 'pubnub_crypto'
|
17
|
+
require 'pubnub_request'
|
18
|
+
|
19
|
+
require 'eventmachine'
|
20
|
+
require 'em-http-request'
|
21
|
+
require 'yajl'
|
22
|
+
require 'json'
|
23
|
+
require 'uuid'
|
24
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
25
|
+
require 'active_support/core_ext/string/inflections'
|
26
|
+
require 'active_support/core_ext/object/try'
|
27
|
+
require 'active_support/core_ext/object/blank'
|
28
|
+
|
29
|
+
|
30
|
+
class Pubnub
|
31
|
+
|
32
|
+
SUCCESS_RESPONSE = 200
|
33
|
+
MSG_TOO_LARGE_RESPONSE = 400
|
34
|
+
|
35
|
+
TIMEOUT_BAD_RESPONSE_CODE = 1
|
36
|
+
TIMEOUT_BAD_JSON_RESPONSE = 0.5
|
37
|
+
TIMEOUT_GENERAL_ERROR = 1
|
38
|
+
TIMEOUT_SUBSCRIBE = 310
|
39
|
+
TIMEOUT_NON_SUBSCRIBE = 5
|
40
|
+
|
41
|
+
class PresenceError < RuntimeError;
|
42
|
+
end
|
43
|
+
class PublishError < RuntimeError;
|
44
|
+
end
|
45
|
+
class SubscribeError < RuntimeError;
|
46
|
+
end
|
47
|
+
class InitError < RuntimeError;
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_accessor :publish_key, :subscribe_key, :secret_key, :cipher_key, :ssl, :channel, :origin, :session_uuid
|
51
|
+
|
52
|
+
ORIGIN_HOST = 'pubsub.pubnub.com'
|
53
|
+
#ORIGIN_HOST = 'test.pubnub.com'
|
54
|
+
|
55
|
+
def initialize(*args)
|
56
|
+
|
57
|
+
if args.size == 5 # passing in named parameters
|
58
|
+
|
59
|
+
@publish_key = args[0].to_s
|
60
|
+
@subscribe_key = args[1].to_s
|
61
|
+
@secret_key = args[2].to_s
|
62
|
+
@cipher_key = args[3].to_s
|
63
|
+
@ssl = args[4]
|
64
|
+
|
65
|
+
elsif args.size == 1 && args[0].class == Hash # passing in an options hash
|
66
|
+
|
67
|
+
options_hash = HashWithIndifferentAccess.new(args[0])
|
68
|
+
@publish_key = options_hash[:publish_key].blank? ? nil : options_hash[:publish_key].to_s
|
69
|
+
@subscribe_key = options_hash[:subscribe_key].blank? ? nil : options_hash[:subscribe_key].to_s
|
70
|
+
@secret_key = options_hash[:secret_key].blank? ? nil : options_hash[:secret_key].to_s
|
71
|
+
@cipher_key = options_hash[:cipher_key].blank? ? nil : options_hash[:cipher_key].to_s
|
72
|
+
@ssl = options_hash[:ssl].blank? ? false : true
|
73
|
+
@logger = options_hash[:logger]
|
74
|
+
|
75
|
+
else
|
76
|
+
raise(InitError, "Initialize with either a hash of options, or exactly 5 named parameters.")
|
77
|
+
end
|
78
|
+
|
79
|
+
@session_uuid = uuid
|
80
|
+
verify_init
|
81
|
+
end
|
82
|
+
|
83
|
+
def verify_init
|
84
|
+
# publish_key and cipher_key are both optional.
|
85
|
+
raise(InitError, "subscribe_key is a mandatory parameter.") if @subscribe_key.blank?
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def publish(options)
|
90
|
+
options = HashWithIndifferentAccess.new(options)
|
91
|
+
publish_request = PubnubRequest.new(:operation => :publish)
|
92
|
+
|
93
|
+
#TODO: refactor into initializer code on request instantiation
|
94
|
+
|
95
|
+
publish_request.ssl = @ssl
|
96
|
+
publish_request.set_origin(options)
|
97
|
+
publish_request.set_channel(options)
|
98
|
+
publish_request.set_callback(options)
|
99
|
+
publish_request.set_cipher_key(options, self.cipher_key)
|
100
|
+
publish_request.set_message(options, self.cipher_key)
|
101
|
+
publish_request.set_publish_key(options, self.publish_key)
|
102
|
+
publish_request.set_subscribe_key(options, self.subscribe_key)
|
103
|
+
publish_request.set_secret_key(options, self.secret_key)
|
104
|
+
|
105
|
+
|
106
|
+
publish_request.format_url!
|
107
|
+
|
108
|
+
check_for_em publish_request
|
109
|
+
end
|
110
|
+
|
111
|
+
def subscribe(options)
|
112
|
+
options = HashWithIndifferentAccess.new(options)
|
113
|
+
|
114
|
+
operation = options[:operation].nil? ? :subscribe : :presence
|
115
|
+
|
116
|
+
subscribe_request = PubnubRequest.new(:operation => operation, :session_uuid => @session_uuid)
|
117
|
+
|
118
|
+
#TODO: refactor into initializer code on request instantiation
|
119
|
+
|
120
|
+
subscribe_request.ssl = @ssl
|
121
|
+
subscribe_request.set_origin(options)
|
122
|
+
subscribe_request.set_channel(options)
|
123
|
+
subscribe_request.set_callback(options)
|
124
|
+
subscribe_request.set_cipher_key(options, self.cipher_key) unless subscribe_request.operation == "presence"
|
125
|
+
|
126
|
+
subscribe_request.set_subscribe_key(options, self.subscribe_key)
|
127
|
+
|
128
|
+
format_url_options = options[:override_timetoken].present? ? options[:override_timetoken] : nil
|
129
|
+
subscribe_request.format_url!(format_url_options)
|
130
|
+
|
131
|
+
check_for_em subscribe_request
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
def presence(options)
|
136
|
+
usage_error = "presence() requires :channel and :callback options."
|
137
|
+
if options.class != Hash
|
138
|
+
raise(ArgumentError, usage_error)
|
139
|
+
end
|
140
|
+
|
141
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
142
|
+
|
143
|
+
unless options[:channel] && options[:callback]
|
144
|
+
raise(ArgumentError, usage_error)
|
145
|
+
end
|
146
|
+
|
147
|
+
subscribe(options.merge(:operation => "presence"))
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
def here_now(options = nil)
|
153
|
+
usage_error = "here_now() requires :channel and :callback options."
|
154
|
+
if options.class != Hash
|
155
|
+
raise(ArgumentError, usage_error)
|
156
|
+
end
|
157
|
+
|
158
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
159
|
+
|
160
|
+
unless options[:channel] && options[:callback]
|
161
|
+
raise(ArgumentError, usage_error)
|
162
|
+
end
|
163
|
+
|
164
|
+
here_now_request = PubnubRequest.new(:operation => :here_now)
|
165
|
+
|
166
|
+
here_now_request.ssl = @ssl
|
167
|
+
here_now_request.set_channel(options)
|
168
|
+
here_now_request.set_callback(options)
|
169
|
+
|
170
|
+
here_now_request.set_subscribe_key(options, self.subscribe_key)
|
171
|
+
|
172
|
+
here_now_request.format_url!
|
173
|
+
check_for_em here_now_request
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
def detailed_history(options = nil)
|
178
|
+
usage_error = "detailed_history() requires :channel, :callback, and :count options."
|
179
|
+
if options.class != Hash
|
180
|
+
raise(ArgumentError, usage_error)
|
181
|
+
end
|
182
|
+
|
183
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
184
|
+
|
185
|
+
unless options[:count] && options[:channel] && options[:callback]
|
186
|
+
raise(ArgumentError, usage_error)
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
detailed_history_request = PubnubRequest.new(:operation => :detailed_history)
|
191
|
+
|
192
|
+
#TODO: refactor into initializer code on request instantiation
|
193
|
+
|
194
|
+
# /detailed_history/SUBSCRIBE_KEY/CHANNEL/JSONP_CALLBACK/LIMIT
|
195
|
+
|
196
|
+
detailed_history_request.ssl = @ssl
|
197
|
+
detailed_history_request.set_channel(options)
|
198
|
+
detailed_history_request.set_callback(options)
|
199
|
+
detailed_history_request.set_cipher_key(options, self.cipher_key)
|
200
|
+
|
201
|
+
detailed_history_request.set_subscribe_key(options, self.subscribe_key)
|
202
|
+
|
203
|
+
detailed_history_request.history_count = options[:count]
|
204
|
+
detailed_history_request.history_start = options[:start]
|
205
|
+
detailed_history_request.history_end = options[:end]
|
206
|
+
detailed_history_request.history_reverse = options[:reverse]
|
207
|
+
|
208
|
+
detailed_history_request.format_url!
|
209
|
+
check_for_em detailed_history_request
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
def history(options = nil)
|
214
|
+
usage_error = "history() requires :channel, :callback, and :limit options."
|
215
|
+
if options.class != Hash
|
216
|
+
raise(ArgumentError, usage_error)
|
217
|
+
end
|
218
|
+
|
219
|
+
options = HashWithIndifferentAccess.new(options) unless (options == nil)
|
220
|
+
|
221
|
+
unless options[:limit] && options[:channel] && options[:callback]
|
222
|
+
raise(ArgumentError, usage_error)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
history_request = PubnubRequest.new(:operation => :history)
|
227
|
+
|
228
|
+
#TODO: refactor into initializer code on request instantiation
|
229
|
+
|
230
|
+
# /history/SUBSCRIBE_KEY/CHANNEL/JSONP_CALLBACK/LIMIT
|
231
|
+
|
232
|
+
history_request.ssl = @ssl
|
233
|
+
history_request.set_channel(options)
|
234
|
+
history_request.set_callback(options)
|
235
|
+
history_request.set_cipher_key(options, self.cipher_key)
|
236
|
+
|
237
|
+
history_request.set_subscribe_key(options, self.subscribe_key)
|
238
|
+
history_request.history_limit = options[:limit]
|
239
|
+
|
240
|
+
history_request.format_url!
|
241
|
+
check_for_em history_request
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
def time(options)
|
246
|
+
options = HashWithIndifferentAccess.new(options)
|
247
|
+
raise(PubNubRuntimeError, "You must supply a callback.") if options['callback'].blank?
|
248
|
+
|
249
|
+
time_request = PubnubRequest.new(:operation => :time)
|
250
|
+
time_request.set_callback(options)
|
251
|
+
|
252
|
+
time_request.format_url!
|
253
|
+
check_for_em time_request
|
254
|
+
end
|
255
|
+
|
256
|
+
def my_callback(x, quiet = false)
|
257
|
+
if quiet !=false
|
258
|
+
puts("mycallback says: #{x.to_s}")
|
259
|
+
else
|
260
|
+
""
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
def uuid
|
266
|
+
UUID.new.generate
|
267
|
+
end
|
268
|
+
|
269
|
+
def check_for_em request
|
270
|
+
if EM.reactor_running?
|
271
|
+
_request(request, true)
|
272
|
+
else
|
273
|
+
EM.run do
|
274
|
+
_request(request)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
def _request(request, is_reactor_running = false)
|
282
|
+
request.format_url!
|
283
|
+
EM.schedule {
|
284
|
+
begin
|
285
|
+
|
286
|
+
operation_timeout = %w(subscribe presence).include?(request.operation) ? TIMEOUT_SUBSCRIBE : TIMEOUT_NON_SUBSCRIBE
|
287
|
+
conn = EM::HttpRequest.new(request.url, :inactivity_timeout => operation_timeout) #client times out in 310s unless the server returns or timeout first
|
288
|
+
req = conn.get()
|
289
|
+
|
290
|
+
req.errback{
|
291
|
+
logAndRetryGeneralError(is_reactor_running, req, request)
|
292
|
+
}
|
293
|
+
|
294
|
+
req.callback {
|
295
|
+
|
296
|
+
if %w(subscribe presence).include?(request.operation)
|
297
|
+
if (checkForBadJSON(req) == true && request.operation == "subscribe")
|
298
|
+
logAndRetryBadJSON(is_reactor_running, req, request)
|
299
|
+
else
|
300
|
+
processGoodResponse(is_reactor_running, req, request)
|
301
|
+
end
|
302
|
+
else
|
303
|
+
if req.response_header.http_status.to_i != SUCCESS_RESPONSE
|
304
|
+
|
305
|
+
begin
|
306
|
+
server_response = Yajl.load(req.response)
|
307
|
+
request.callback.call(server_response)
|
308
|
+
rescue => e
|
309
|
+
request.callback.call([0, "Bad server response: #{req.response_header.http_status.to_i}"])
|
310
|
+
ensure
|
311
|
+
EM.stop unless is_reactor_running
|
312
|
+
end
|
313
|
+
|
314
|
+
else
|
315
|
+
processGoodResponse(is_reactor_running, req, request)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
}
|
320
|
+
|
321
|
+
rescue EventMachine::ConnectionError, RuntimeError => e # RuntimeError for catching "EventMachine not initialized"
|
322
|
+
error_message = "Network Error: #{e.message}"
|
323
|
+
puts(error_message)
|
324
|
+
[0, error_message]
|
325
|
+
end
|
326
|
+
}
|
327
|
+
end
|
328
|
+
|
329
|
+
def checkForBadJSON(req)
|
330
|
+
jsonError = false
|
331
|
+
begin
|
332
|
+
JSON.parse(req.response)
|
333
|
+
rescue => e
|
334
|
+
jsonError = true
|
335
|
+
end
|
336
|
+
jsonError
|
337
|
+
end
|
338
|
+
|
339
|
+
def processGoodResponse(is_reactor_running, req, request)
|
340
|
+
|
341
|
+
if (req.response_header.http_status.to_i != SUCCESS_RESPONSE)
|
342
|
+
|
343
|
+
unless (req.response_header.http_status.to_i == MSG_TOO_LARGE_RESPONSE)
|
344
|
+
logAndRetryBadResponseCode(is_reactor_running, req, request)
|
345
|
+
end
|
346
|
+
|
347
|
+
else
|
348
|
+
|
349
|
+
request.package_response!(req.response)
|
350
|
+
cycle = request.callback.call(request.response)
|
351
|
+
|
352
|
+
if %w(subscribe presence).include?(request.operation) && (cycle != false || request.first_request?)
|
353
|
+
_request(request, is_reactor_running)
|
354
|
+
else
|
355
|
+
EM.stop unless is_reactor_running
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def logAndRetryGeneralError(is_reactor_running, req, request)
|
361
|
+
errMsg = "#{Time.now}: Network connectivity issue while attempting to reach #{request.url}"
|
362
|
+
logError(errMsg, request.url)
|
363
|
+
retryRequest(is_reactor_running, req, request, TIMEOUT_GENERAL_ERROR)
|
364
|
+
end
|
365
|
+
|
366
|
+
def logAndRetryBadJSON(is_reactor_running, req, request)
|
367
|
+
errMsg = "#{Time.now}: Retrying from bad JSON: #{req.response.to_s}"
|
368
|
+
logError(errMsg, request.url)
|
369
|
+
retryRequest(is_reactor_running, req, request, TIMEOUT_BAD_JSON_RESPONSE)
|
370
|
+
end
|
371
|
+
|
372
|
+
def logAndRetryBadResponseCode(is_reactor_running, req, request)
|
373
|
+
errMsg = "#{Time.now}: Retrying from bad server response code: (#{req.response_header.http_status.to_i}) #{req.response.to_s}"
|
374
|
+
logError(errMsg, request.url)
|
375
|
+
retryRequest(is_reactor_running, req, request, TIMEOUT_BAD_RESPONSE_CODE)
|
376
|
+
end
|
377
|
+
|
378
|
+
def logError(errMsg, url)
|
379
|
+
logger.debug("url: #{url}")
|
380
|
+
logger.debug("#{errMsg}")
|
381
|
+
logger.debug("")
|
382
|
+
end
|
383
|
+
|
384
|
+
def retryRequest(is_reactor_running, req, request, delay)
|
385
|
+
|
386
|
+
if %w(subscribe presence).include?(request.operation)
|
387
|
+
EM::Timer.new(delay) do
|
388
|
+
_request(request, is_reactor_running)
|
389
|
+
end
|
390
|
+
else
|
391
|
+
error_msg = [0, "Request to #{request.url} failed."]
|
392
|
+
|
393
|
+
request.set_error(true)
|
394
|
+
request.callback.call(error_msg)
|
395
|
+
|
396
|
+
logger.debug(error_msg)
|
397
|
+
|
398
|
+
EM.stop unless is_reactor_running
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
def logger
|
404
|
+
@logger ||= init_default_logger
|
405
|
+
end
|
406
|
+
|
407
|
+
def init_default_logger
|
408
|
+
logger = Logger.new("#{Dir.tmpdir}/pubnubError.log", 10, 10000000)
|
409
|
+
logger.level = Logger::DEBUG
|
410
|
+
logger
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class PubnubCrypto
|
2
|
+
require 'yajl'
|
3
|
+
|
4
|
+
def initialize(cipher_key)
|
5
|
+
@alg = "AES-256-CBC"
|
6
|
+
sha256_key = Digest::SHA256.hexdigest(cipher_key)
|
7
|
+
@key = sha256_key.slice(0,32)
|
8
|
+
|
9
|
+
#puts("\nraw sha cipher_key is: #{cipher_key}")
|
10
|
+
#puts("raw sha cipher_key is: #{sha256_key}")
|
11
|
+
#puts("padded cipher_key is: #{@key}\n")
|
12
|
+
|
13
|
+
@iv = '0123456789012345'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def encrypt(message)
|
18
|
+
|
19
|
+
aes = OpenSSL::Cipher::Cipher.new(@alg)
|
20
|
+
aes.encrypt
|
21
|
+
aes.key = @key
|
22
|
+
aes.iv = @iv
|
23
|
+
|
24
|
+
json_message = Yajl.dump(message)
|
25
|
+
cipher = aes.update(json_message)
|
26
|
+
cipher << aes.final
|
27
|
+
|
28
|
+
Base64.strict_encode64(cipher)
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def decrypt(cipher_text)
|
34
|
+
decode_cipher = OpenSSL::Cipher::Cipher.new(@alg)
|
35
|
+
decode_cipher.decrypt
|
36
|
+
decode_cipher.key = @key
|
37
|
+
decode_cipher.iv = @iv
|
38
|
+
|
39
|
+
plain_text = ""
|
40
|
+
|
41
|
+
begin
|
42
|
+
undecoded_text = Base64.decode64(cipher_text)
|
43
|
+
plain_text = decode_cipher.update(undecoded_text)
|
44
|
+
plain_text << decode_cipher.final
|
45
|
+
rescue => e
|
46
|
+
|
47
|
+
return "DECRYPTION_ERROR"
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
return Yajl.load(plain_text)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
class PubnubRequest
|
2
|
+
attr_accessor :cipher_key, :host, :query, :response, :timetoken, :url, :operation, :callback, :publish_key, :subscribe_key, :secret_key, :channel, :jsonp, :message, :ssl, :port
|
3
|
+
attr_accessor :history_limit, :history_count, :history_start, :history_end, :history_reverse, :session_uuid, :last_timetoken, :origin, :error
|
4
|
+
|
5
|
+
class RequestError < RuntimeError;
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
args = HashWithIndifferentAccess.new(args)
|
10
|
+
|
11
|
+
@operation = args[:operation].to_s
|
12
|
+
@callback = args[:callback]
|
13
|
+
@cipher_key = args[:cipher_key]
|
14
|
+
@session_uuid = args[:session_uuid]
|
15
|
+
@publish_key = args[:publish_key]
|
16
|
+
@subscribe_key = args[:subscribe_key]
|
17
|
+
@channel = args[:channel]
|
18
|
+
@jsonp = args[:jsonp].present? ? "1" : "0"
|
19
|
+
@message = args[:message]
|
20
|
+
@secret_key = args[:secret_key] || "0"
|
21
|
+
@timetoken = args[:timetoken] || "0"
|
22
|
+
@ssl = args[:ssl]
|
23
|
+
|
24
|
+
@port = args[:port]
|
25
|
+
@url = args[:url]
|
26
|
+
@host = args[:host]
|
27
|
+
@query = args[:query]
|
28
|
+
end
|
29
|
+
|
30
|
+
def op_exception
|
31
|
+
if @operation.present?
|
32
|
+
("Pubnub::" + @operation.to_s.capitalize + "Error").constantize
|
33
|
+
else
|
34
|
+
PubnubRequest::RequestError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(another)
|
39
|
+
self.operation == another.operation && self.callback == another.callback &&
|
40
|
+
self.channel == another.channel && self.message == another.message
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_error(options)
|
44
|
+
options = HashWithIndifferentAccess.new(options)
|
45
|
+
|
46
|
+
if options[:error].present?
|
47
|
+
self.error = true
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_origin(options)
|
53
|
+
options = HashWithIndifferentAccess.new(options)
|
54
|
+
|
55
|
+
if options[:origin].present?
|
56
|
+
self.origin = options[:origin].to_s
|
57
|
+
self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_channel(options)
|
62
|
+
options = HashWithIndifferentAccess.new(options)
|
63
|
+
|
64
|
+
if options[:channel].blank?
|
65
|
+
raise(op_exception, "channel is a required parameter.")
|
66
|
+
else
|
67
|
+
self.channel = options[:channel].to_s
|
68
|
+
self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_callback(options)
|
73
|
+
options = HashWithIndifferentAccess.new(options)
|
74
|
+
|
75
|
+
if options[:callback].blank?
|
76
|
+
raise(op_exception, "callback is a required parameter.")
|
77
|
+
elsif !options[:callback].try(:respond_to?, "call")
|
78
|
+
raise(op_exception, "callback is invalid.")
|
79
|
+
else
|
80
|
+
self.callback = options[:callback]
|
81
|
+
self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def set_cipher_key(options, self_cipher_key)
|
87
|
+
options = HashWithIndifferentAccess.new(options)
|
88
|
+
|
89
|
+
if self_cipher_key.present? && options['cipher_key'].present?
|
90
|
+
raise(op_exception, "existing cipher_key #{self_cipher_key} cannot be overridden at publish-time.")
|
91
|
+
|
92
|
+
elsif (self_cipher_key.present? && options[:cipher_key].blank?) || (self_cipher_key.blank? && options[:cipher_key].present?)
|
93
|
+
|
94
|
+
this_cipher_key = self_cipher_key || options[:cipher_key]
|
95
|
+
raise(Pubnub::PublishError, "secret key must be a string.") if this_cipher_key.class != String
|
96
|
+
self.cipher_key = this_cipher_key
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_secret_key(options, self_secret_key)
|
101
|
+
options = HashWithIndifferentAccess.new(options)
|
102
|
+
|
103
|
+
if self_secret_key.present? && options['secret_key'].present?
|
104
|
+
raise(Pubnub::PublishError, "existing secret_key #{self_secret_key} cannot be overridden at publish-time.")
|
105
|
+
|
106
|
+
elsif (self_secret_key.present? && options[:secret_key].blank?) || (self_secret_key.blank? && options[:secret_key].present?)
|
107
|
+
|
108
|
+
my_secret_key = self_secret_key || options[:secret_key]
|
109
|
+
raise(Pubnub::PublishError, "secret key must be a string.") if my_secret_key.class != String
|
110
|
+
|
111
|
+
signature = "{ @publish_key, @subscribe_key, @secret_key, channel, message}"
|
112
|
+
digest = OpenSSL::Digest.new("sha256")
|
113
|
+
key = [my_secret_key]
|
114
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, key.pack("H*"), signature)
|
115
|
+
self.secret_key = hmac
|
116
|
+
else
|
117
|
+
self.secret_key = "0"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def set_message(options, self_cipher_key)
|
122
|
+
options = HashWithIndifferentAccess.new(options)
|
123
|
+
|
124
|
+
if options[:message].blank? && options[:message] != ""
|
125
|
+
raise(op_exception, "message is a required parameter.")
|
126
|
+
else
|
127
|
+
my_cipher_key = options[:cipher_key] || self_cipher_key
|
128
|
+
|
129
|
+
if my_cipher_key.present?
|
130
|
+
self.message = Yajl.dump(aes_encrypt(my_cipher_key, options, self))
|
131
|
+
else
|
132
|
+
self.message = Yajl.dump(options[:message])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_publish_key(options, self_publish_key)
|
138
|
+
options = HashWithIndifferentAccess.new(options)
|
139
|
+
|
140
|
+
if options[:publish_key].blank? && self_publish_key.blank?
|
141
|
+
raise(Pubnub::PublishError, "publish_key is a required parameter.")
|
142
|
+
elsif self_publish_key.present? && options['publish_key'].present?
|
143
|
+
raise(Pubnub::PublishError, "existing publish_key #{self_publish_key} cannot be overridden at publish-time.")
|
144
|
+
else
|
145
|
+
self.publish_key = (self_publish_key || options[:publish_key]).to_s
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_subscribe_key(options, self_subscribe_key)
|
150
|
+
options = HashWithIndifferentAccess.new(options)
|
151
|
+
|
152
|
+
if options[:subscribe_key].blank? && self_subscribe_key.blank?
|
153
|
+
raise(op_exception, "subscribe_key is a required parameter.")
|
154
|
+
elsif self_subscribe_key.present? && options['subscribe_key'].present?
|
155
|
+
raise(op_exception, "existing subscribe_key #{self_subscribe_key} cannot be overridden at subscribe-time.")
|
156
|
+
else
|
157
|
+
self.subscribe_key = (self_subscribe_key || options[:subscribe_key]).to_s
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def package_response!(response_data)
|
162
|
+
self.response = response_data.respond_to?(:content) ? Yajl.load(response_data.content) : Yajl.load(response_data)
|
163
|
+
self.last_timetoken = self.timetoken
|
164
|
+
self.timetoken = self.response[1] unless self.operation == "time"
|
165
|
+
|
166
|
+
if self.cipher_key.present? && %w(subscribe history detailed_history).include?(self.operation)
|
167
|
+
|
168
|
+
myarr = Array.new
|
169
|
+
pc = PubnubCrypto.new(@cipher_key)
|
170
|
+
|
171
|
+
case @operation
|
172
|
+
when "publish"
|
173
|
+
iterate = self.response.first
|
174
|
+
when "subscribe"
|
175
|
+
iterate = self.response.first
|
176
|
+
when "history"
|
177
|
+
iterate = self.response
|
178
|
+
when "detailed_history"
|
179
|
+
iterate = self.response.first
|
180
|
+
|
181
|
+
else
|
182
|
+
raise(RequestError, "Don't know how to iterate on this operation.")
|
183
|
+
end
|
184
|
+
|
185
|
+
iterate.each do |message|
|
186
|
+
message = pc.decrypt(message)
|
187
|
+
myarr.push(message)
|
188
|
+
end
|
189
|
+
|
190
|
+
if %w(publish subscribe).include?(@operation)
|
191
|
+
self.response[0] = myarr
|
192
|
+
elsif @operation == "detailed_history"
|
193
|
+
json_response_data = Yajl.load(response_data)
|
194
|
+
self.response = [myarr, json_response_data[1], json_response_data[2]]
|
195
|
+
else
|
196
|
+
self.response = myarr
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def format_url!(override_timetoken = nil)
|
203
|
+
|
204
|
+
raise(Pubnub::PublishError, "Missing .operation in PubnubRequest object") if self.operation.blank?
|
205
|
+
|
206
|
+
if @ssl.present?
|
207
|
+
origin = 'https://' + (self.origin.present? ? self.origin : Pubnub::ORIGIN_HOST)
|
208
|
+
@port = 443
|
209
|
+
else
|
210
|
+
origin = 'http://' + (self.origin.present? ? self.origin : Pubnub::ORIGIN_HOST)
|
211
|
+
@port = 80
|
212
|
+
end
|
213
|
+
|
214
|
+
if override_timetoken.present?
|
215
|
+
self.timetoken = override_timetoken.to_s
|
216
|
+
end
|
217
|
+
|
218
|
+
case self.operation.to_s
|
219
|
+
when "publish"
|
220
|
+
url_array = [self.operation.to_s, self.publish_key.to_s, self.subscribe_key.to_s,
|
221
|
+
self.secret_key.to_s, self.channel.to_s, "0", self.message]
|
222
|
+
|
223
|
+
when "subscribe"
|
224
|
+
url_array = [self.operation.to_s, self.subscribe_key.to_s, self.channel.to_s, "0", @timetoken]
|
225
|
+
|
226
|
+
when "presence"
|
227
|
+
url_array = ["subscribe", self.subscribe_key.to_s, ((self.channel.to_s) + "-pnpres"), "0", @timetoken]
|
228
|
+
|
229
|
+
when "time"
|
230
|
+
url_array = [self.operation.to_s, "0"]
|
231
|
+
|
232
|
+
when "history"
|
233
|
+
url_array = [self.operation.to_s, self.subscribe_key.to_s, self.channel.to_s, "0", self.history_limit.to_s]
|
234
|
+
|
235
|
+
when "detailed_history"
|
236
|
+
url_array = ["v2", "history", "sub-key", self.subscribe_key.to_s, "channel", self.channel.to_s]
|
237
|
+
|
238
|
+
when "here_now"
|
239
|
+
url_array = ["v2", "presence", "sub-key", self.subscribe_key.to_s, "channel", self.channel.to_s]
|
240
|
+
|
241
|
+
else
|
242
|
+
|
243
|
+
raise(PubnubRequest::RequestError, "I can't create that URL for you due to unknown operation type.")
|
244
|
+
end
|
245
|
+
|
246
|
+
self.url = origin + encode_URL(url_array)
|
247
|
+
|
248
|
+
uri = URI.parse(self.url)
|
249
|
+
|
250
|
+
self.host = uri.host
|
251
|
+
url_params = ""
|
252
|
+
|
253
|
+
if %w(subscribe presence).include?(@operation)
|
254
|
+
uri.query = uri.query.blank? ? "uuid=#{@session_uuid}" : (uri.query + "uuid=#{@session_uuid}")
|
255
|
+
|
256
|
+
elsif @operation == "detailed_history"
|
257
|
+
url_sep = "?"
|
258
|
+
|
259
|
+
if @history_count || @history_start || @history_end || @history_reverse
|
260
|
+
|
261
|
+
if @history_count
|
262
|
+
url_params += url_sep + "count=" + @history_count.to_s
|
263
|
+
url_sep = "&"
|
264
|
+
end
|
265
|
+
|
266
|
+
if @history_start
|
267
|
+
url_params += url_sep + "start=" + @history_start.to_s
|
268
|
+
url_sep = "&"
|
269
|
+
end
|
270
|
+
|
271
|
+
if @history_end
|
272
|
+
url_params += url_sep + "end=" + @history_end.to_s
|
273
|
+
url_sep = "&"
|
274
|
+
end
|
275
|
+
|
276
|
+
if @history_reverse
|
277
|
+
url_params += url_sep + "reverse=true"
|
278
|
+
url_sep = "&"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
self.query = uri.path + (uri.query.present? ? ("?" + uri.query) : "") + url_params
|
284
|
+
self.url += url_params
|
285
|
+
self
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
def aes_encrypt(cipher_key, options, publish_request)
|
290
|
+
options = HashWithIndifferentAccess.new(options)
|
291
|
+
|
292
|
+
pc = PubnubCrypto.new(cipher_key)
|
293
|
+
publish_request.message = pc.encrypt(options[:message])
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
def encode_URL(request)
|
298
|
+
## Construct Request URL
|
299
|
+
url = '/' + request.map { |bit| bit.split('').map { |ch|
|
300
|
+
' ~`!@#$%^&*()+=[]\\{}|;\':",./<>?'.index(ch) ?
|
301
|
+
'%' + ch.unpack('H2')[0].to_s.upcase : URI.encode(ch)
|
302
|
+
}.join('') }.join('/')
|
303
|
+
return url
|
304
|
+
end
|
305
|
+
|
306
|
+
def first_request?
|
307
|
+
@last_timetoken == "0"
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pubnub-picklive
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 3.3.0.7
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- PubNub
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :runtime
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
name: activesupport
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
type: :runtime
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
name: eventmachine
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
type: :runtime
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
name: em-http-request
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
type: :runtime
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.3.5
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.3.5
|
77
|
+
name: uuid
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
type: :runtime
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
name: yajl-ruby
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
type: :runtime
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
prerelease: false
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
name: json
|
110
|
+
description: Ruby anywhere in the world in 250ms with PubNub!
|
111
|
+
email: support@pubnub.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files: []
|
115
|
+
files:
|
116
|
+
- lib/pubnub.rb
|
117
|
+
- lib/pubnub_crypto.rb
|
118
|
+
- lib/pubnub_request.rb
|
119
|
+
homepage: http://github.com/pubnub/pubnub-api
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.8.24
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: PubNub Official Ruby gem
|
144
|
+
test_files: []
|