urbanairship-ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 634851e82146e4d7774e3d7d353650b5eb8170d3
4
+ data.tar.gz: e1ccd545450475e9250172ddb12c8aa43b74cc56
5
+ SHA512:
6
+ metadata.gz: d47927b532355e361f8ec17d16ee1f6565253952978fbefebe407ff5ffa82fd8070e56ec37f7771556774851bc1ce955366eff48a0670c5828da7c1eec5a3d3e
7
+ data.tar.gz: 349b8e0cc64ae48f5da928a51bdfc467fdc337f1e95a22996ab7158234284ae5d51f779c0a75eff5e33a7390980db382787d14def68343538fedbbaef0e87a8e
data/README.markdown ADDED
@@ -0,0 +1,397 @@
1
+ Urbanairship is a Ruby library for interacting with the [Urban Airship API](http://urbanairship.com).
2
+
3
+ Installation
4
+ ============
5
+ gem install urbanairship-ruby
6
+
7
+ Configuration
8
+ =============
9
+ ```ruby
10
+ Urbanairship.application_key = 'application-key'
11
+ Urbanairship.application_secret = 'application-secret'
12
+ Urbanairship.master_secret = 'master-secret'
13
+ Urbanairship.logger = Rails.logger
14
+ Urbanairship.request_timeout = 5 # default
15
+ ```
16
+
17
+ Usage
18
+ =====
19
+
20
+ Registering a device token
21
+ --------------------------
22
+ ```ruby
23
+ Urbanairship.register_device('DEVICE-TOKEN')
24
+ ```
25
+ You can also pass an alias, and a set of tags to device registration.
26
+ ```ruby
27
+ Urbanairship.register_device('DEVICE-TOKEN',
28
+ :alias => 'user-123',
29
+ :tags => ['san-francisco-users']
30
+ )
31
+ ```
32
+
33
+ Unregistering a device token
34
+ ----------------------------
35
+ ```ruby
36
+ Urbanairship.unregister_device('DEVICE-TOKEN')
37
+ ```
38
+
39
+ Retrieving Device Info
40
+ ----------------------------
41
+ ```ruby
42
+ Urbanairship.device_info('DEVICE-TOKEN')
43
+ ```
44
+
45
+ Sending a push notification
46
+ ---------------------------
47
+ ```ruby
48
+ notification = {
49
+ :audience => {
50
+ :ios_channel => "9c36e8c7-5a73-47c0-9716-99fd3d4197d5"
51
+ },
52
+ :notification => {
53
+ :alert => "Hello!"
54
+ },
55
+ :device_types => "all"
56
+ }
57
+
58
+ Urbanairship.push(notification) # =>
59
+ # {
60
+ # :ok => true,
61
+ # :operation_id => "df6a6b50-9843-0304-d5a5-743f246a4946",
62
+ # :push_ids => [
63
+ # "9d78a53b-b16a-c58f-b78d-181d5e242078",
64
+ # ]
65
+ # }
66
+ ```
67
+
68
+ Batching push notification sends
69
+ --------------------------------
70
+ ```ruby
71
+ notifications = [
72
+ {
73
+ :audience => {
74
+ :ios_channel => "9c36e8c7-5a73-47c0-9716-99fd3d4197d5"
75
+ },
76
+ :notification => {
77
+ :alert => "Hello!"
78
+ },
79
+ :device_types => "all"
80
+ },
81
+ {
82
+ :audience => {
83
+ :android_channel => "b8f9b663-0a3b-cf45-587a-be880946e880"
84
+ },
85
+ :notification => {
86
+ :alert => "Hello!"
87
+ },
88
+ :device_types => "all"
89
+ }
90
+ ]
91
+
92
+ Urbanairship.batch_push(notifications)
93
+ ```
94
+
95
+ Polling the feedback API
96
+ ------------------------
97
+ The first time you attempt to send a push notification to a device that has uninstalled your app (or has opted-out of notifications), both Apple and Urban Airship will register that token in their feedback API. Urban Airship will prevent further attempted notification sends to that device, but it's a good practice to periodically poll Urban Airship's feedback API and mark those tokens as inactive in your own system as well.
98
+
99
+ ```ruby
100
+ # find all device tokens deactivated in the past 24 hours
101
+ Urbanairship.feedback(24.hours.ago) # =>
102
+ # [
103
+ # {
104
+ # "marked_inactive_on"=>"2011-06-03 22:53:23",
105
+ # "alias"=>nil,
106
+ # "device_token"=>"DEVICE-TOKEN-ONE"
107
+ # },
108
+ # {
109
+ # "marked_inactive_on"=>"2011-06-03 22:53:23",
110
+ # "alias"=>nil,
111
+ # "device_token"=>"DEVICE-TOKEN-TWO"
112
+ # }
113
+ # ]
114
+ ```
115
+
116
+ Schedule notifications
117
+ --------------------------------
118
+
119
+ ### Listing your schedules ###
120
+
121
+ ```ruby
122
+ Urbanairship.schedules
123
+ Urbanairship.schedule('03ab5ba1-6f8d-415d-baae-5a81cc24fae2')
124
+ ```
125
+
126
+ ### Creating your schedules ###
127
+
128
+ ```ruby
129
+ schedule = {
130
+ :name => "Booyah Sports",
131
+ :schedule => {
132
+ :scheduled_time => "2015-08-01T18:45:00Z"
133
+ },
134
+ :push => {
135
+ :audience => {
136
+ :tag => "spoaaaarts"
137
+ },
138
+ :notification => {
139
+ :alert => "Booyah!"
140
+ },
141
+ :device_types => "all"
142
+ }
143
+ }
144
+
145
+ Urbanairship.create_schedule(schedule) # =>
146
+ # {
147
+ # "ok" => true,
148
+ # "operation_id" => "cd9d6390-2a12-11e5-a2b8-90e2ba2901f0",
149
+ # "schedule_urls" => [
150
+ # "https://go.urbanairship.com/api/schedules/03ab5ba1-6f8d-415d-baae-5a81cc24fae2"
151
+ # ],
152
+ # "schedule_ids" => [
153
+ # "03ab5ba1-6f8d-415d-baae-5a81cc24fae2"
154
+ # ],
155
+ # "schedules" => [
156
+ # {
157
+ # "url" => "https://go.urbanairship.com/api/schedules/03ab5ba1-6f8d-415d-baae-5a81cc24fae2",
158
+ # "schedule" => {
159
+ # "scheduled_time" => "2015-08-01T18:45:00"
160
+ # },
161
+ # "name" => "Booyah Sports",
162
+ # "push" => {
163
+ # "audience" => {
164
+ # "tag" => "spoaaaarts"
165
+ # },
166
+ # "device_types" => "all",
167
+ # "notification" => {
168
+ # "alert" => "Booyah!"
169
+ # }
170
+ # },
171
+ # "push_ids" => [
172
+ # "a74db2b6-65f0-465f-82d6-4fa25448b642"
173
+ # ]
174
+ # }
175
+ # ]
176
+ # }
177
+ ```
178
+
179
+ ### Modifying your schedules ###
180
+
181
+ You can modify an unsent scheduled push if you know its ID
182
+
183
+ ```ruby
184
+ schedule = {
185
+ :name => "Booyah Sports",
186
+ :schedule => {
187
+ :scheduled_time => "2015-08-01T18:45:00Z"
188
+ },
189
+ :push => {
190
+ :audience => {
191
+ :tag => "spoaaaarts"
192
+ },
193
+ :notification => {
194
+ :alert => "Booyah!"
195
+ },
196
+ :device_types => "all"
197
+ }
198
+ }
199
+
200
+ Urbanairship.update_schedule('03ab5ba1-6f8d-415d-baae-5a81cc24fae2', schedule)
201
+ ```
202
+
203
+
204
+ ### Deleting your schedules ###
205
+
206
+ If you know the alias or id of a scheduled push notification then you can delete it from Urban Airship's queue and it will not be delivered.
207
+
208
+ ```ruby
209
+ Urbanairship.delete_schedule("123456789")
210
+ Urbanairship.delete_schedule(123456789)
211
+ Urbanairship.delete_schedule(:alias => "deadbeef")
212
+ ```
213
+
214
+ Segments
215
+ ---------------------------
216
+
217
+ ### Creating a segment ###
218
+ ``` ruby
219
+ Urbanairship.create_segment({
220
+ :display_name => 'segment1',
221
+ :criteria => {:and => [{:tag => 'one'}, {:tag => 'two'}]}
222
+ }) # => {}
223
+ ```
224
+
225
+ ### Listing your segments ###
226
+
227
+ ```ruby
228
+ Urbanairship.segments # =>
229
+ # {
230
+ # "segments" => [
231
+ # {
232
+ # "id" => "abcd-efgh-ijkl",
233
+ # "display_name" => "segment1",
234
+ # "creation_date" => 1360950614201,
235
+ # "modification_date" => 1360950614201
236
+ # }
237
+ # ]
238
+ # }
239
+
240
+ Urbanairship.segment("abcd-efgh-ijkl") # =>
241
+ # {
242
+ # "id" => "abcd-efgh-ijkl",
243
+ # "display_name" => "segment1",
244
+ # "creation_date" => 1360950614201,
245
+ # "modification_date" => 1360950614201
246
+ # }
247
+ ```
248
+
249
+ ### Modifying a segment ###
250
+ Note that you must provide both the display name and criteria when updating a segment, even if you are only changing one or the other.
251
+ ``` ruby
252
+ Urbanairship.update_segment('abcd-efgh-ijkl', {
253
+ :display_name => 'segment1',
254
+ :criteria => {:and => [{:tag => 'asdf'}]}
255
+ }) # => {}
256
+ ```
257
+
258
+ ### Deleting a segment ###
259
+ ```ruby
260
+ Urbanairship.delete_segment("abcd-efgh-ijkl") # => {}
261
+ ```
262
+
263
+ Getting your device tokens
264
+ -------------------------------------
265
+ ```ruby
266
+ Urbanairship.device_tokens # =>
267
+ # {
268
+ # "device_tokens" => {"device_token"=>"<token>", "active"=>true, "alias"=>"<alias>", "tags"=>[]},
269
+ # "device_tokens_count" => 3,
270
+ # "active_device_tokens_count" => 1
271
+ # }
272
+ ```
273
+
274
+ Getting a count of your device tokens
275
+ -------------------------------------
276
+ ```ruby
277
+ Urbanairship.device_tokens_count # =>
278
+ # {
279
+ # "device_tokens_count" => 3,
280
+ # "active_device_tokens_count" => 1
281
+ # }
282
+ ```
283
+
284
+ Tags
285
+ ----
286
+
287
+ Urban Airship allows you to create tags and associate them with devices. Then you can easily send a notification to every device matching a certain tag with a single call to the push API.
288
+
289
+ ### Creating a tag ###
290
+
291
+ Tags must be registered before you can use them.
292
+
293
+ ```ruby
294
+ Urbanairship.add_tag('TAG')
295
+ ```
296
+
297
+ ### Listing your tags ###
298
+
299
+ ```ruby
300
+ Urbanairship.tags
301
+ ```
302
+
303
+ ### Removing a tag ##
304
+
305
+ This will remove a tag from your set of registered tags, as well as removing that tag from any devices that are currently using it.
306
+
307
+ ```ruby
308
+ Urbanairship.remove_tag('TAG')
309
+ ```
310
+
311
+ ### View tags associated with device ###
312
+
313
+ ```ruby
314
+ Urbanairship.tags_for_device('DEVICE-TOKEN')
315
+ ```
316
+
317
+ ### Tag a device ###
318
+
319
+ ```ruby
320
+ Urbanairship.tag_device(:device_token => 'DEVICE-TOKEN', :tag => 'TAG')
321
+ ```
322
+
323
+ You can also tag a device during device registration.
324
+
325
+ ```ruby
326
+ Urbanairship.register_device('DEVICE-TOKEN', :tags => ['san-francisco-users'])
327
+ ```
328
+
329
+ ### Untag a device ###
330
+
331
+ ```ruby
332
+ Urbanairship.untag_device(:device_token => 'DEVICE-TOKEN', :tag => 'TAG')
333
+ ```
334
+
335
+ ### Sending a notification to all devices with a given tag ###
336
+
337
+ ```ruby
338
+ notification = {
339
+ :tags => ['san-francisco-users'],
340
+ :aps => {:alert => 'Good morning San Francisco!', :badge => 1}
341
+ }
342
+
343
+ Urbanairship.push(notification)
344
+ ```
345
+
346
+ Using Urbanairship with Android
347
+ -------------------------------
348
+
349
+ The Urban Airship API extends a subset of their push API to Android devices. You can read more about what is currently supported [here](https://docs.urbanairship.com/display/DOCS/Server%3A+Android+Push+API), but as of this writing, only registration, aliases, tags, broadcast, individual push, and batch push are supported.
350
+
351
+ To use this library with Android devices, you can set the `provider` configuration option to `:android`:
352
+
353
+ ```ruby
354
+ Urbanairship.provider = :android
355
+ ```
356
+
357
+ Alternatively, you can pass the `:provider => :android` option to device registration calls if your app uses Urbanairship to send notifications to both Android and iOS devices.
358
+
359
+ ```ruby
360
+ Urbanairship.register_device("DEVICE-TOKEN", :provider => :android)
361
+ ```
362
+
363
+ Note: all other supported actions use the same API endpoints as iOS, so it is not necessary to specify the provider as `:android` when calling them.
364
+
365
+ -----------------------------
366
+
367
+ Note: all public library methods will return either an array or a hash, depending on the response from the Urban Airship API. In addition, you can inspect these objects to find out if they were successful or not, and what the http response code from Urban Airship was.
368
+
369
+ ```ruby
370
+ response = Urbanairship.push(payload)
371
+ response.success? # => true
372
+ response.code # => '200'
373
+ response.inspect # => "{\"scheduled_notifications\"=>[\"https://go.urbanairship.com/api/push/scheduled/123456\"]}"
374
+ ```
375
+
376
+ If the call to Urban Airship times out, you'll get a response object with a '503' code.
377
+
378
+ ```ruby
379
+ response = Urbanairship.feedback(1.year.ago)
380
+ response.success? # => false
381
+ response.code # => '503'
382
+ response.inspect # => "{\"error\"=>\"Request timeout\"}"
383
+ ```
384
+
385
+ Instantiating an Urbanairship::Client
386
+ -------------------------------------
387
+
388
+ Anything you can do directly with the Urbanairship module, you can also do with a Client.
389
+
390
+ ```ruby
391
+ client = Urbanairship::Client.new
392
+ client.application_key = 'application-key'
393
+ client.application_secret = 'application-secret'
394
+ client.register_device('DEVICE-TOKEN')
395
+ ```
396
+
397
+ This can be used to use clients for different Urbanairship applications in a thread-safe manner.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ desc 'Run all the tests'
2
+ task :default => :spec
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = ['-c', '-f progress', '-r ./spec/spec_helper.rb']
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
@@ -0,0 +1,33 @@
1
+ module Urbanairship
2
+ module Response
3
+ module InstanceMethods
4
+ attr_accessor :ua_response, :ua_options
5
+
6
+ def code
7
+ (ua_options[:code] || ua_response.code).to_s
8
+ end
9
+
10
+ def success?
11
+ !!(code =~ /^2/)
12
+ end
13
+ end
14
+
15
+ def self.wrap(response, options = {})
16
+ if options[:body]
17
+ output = options[:body]
18
+ else
19
+ begin
20
+ output = JSON.parse(response.body || '{}')
21
+ rescue JSON::ParserError => e
22
+ output = {}
23
+ end
24
+ end
25
+
26
+ output.extend(Urbanairship::Response::InstanceMethods)
27
+ output.ua_response = response
28
+ output.ua_options = options
29
+
30
+ return output
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,200 @@
1
+ require 'json'
2
+ require 'net/https'
3
+ require 'time'
4
+
5
+ require File.join(File.dirname(__FILE__), 'urbanairship/response')
6
+
7
+ module Urbanairship
8
+ begin
9
+ require 'system_timer'
10
+ Timer = SystemTimer
11
+ rescue LoadError
12
+ require 'timeout'
13
+ Timer = Timeout
14
+ end
15
+
16
+ module ClassMethods
17
+ attr_accessor :application_key, :application_secret, :master_secret, :logger, :request_timeout, :provider
18
+ VERSION = 3
19
+
20
+ def register_device(device_token, options = {})
21
+ body = parse_register_options(options).to_json
22
+
23
+ path = device_tokens_end_point(options[:provider] || @provider)
24
+ do_request(:put, "/api/#{path}/#{device_token}", :body => body, :authenticate_with => :application_secret)
25
+ end
26
+
27
+ def unregister_device(device_token, options = {})
28
+ path = device_tokens_end_point(options[:provider] || @provider)
29
+ do_request(:delete, "/api/#{path}/#{device_token}", :authenticate_with => :application_secret)
30
+ end
31
+
32
+ def device_info(device_token, options = {})
33
+ path = device_tokens_end_point(options[:provider] || @provider)
34
+ do_request(:get, "/api/#{path}/#{device_token}", :authenticate_with => :application_secret)
35
+ end
36
+
37
+ # Schedules API
38
+ def create_schedule(schedule)
39
+ do_request(:post, "/api/schedules/", :body => schedule.to_json, :authenticate_with => :master_secret)
40
+ end
41
+
42
+ def schedules
43
+ do_request(:get, "/api/schedules/", :authenticate_with => :master_secret)
44
+ end
45
+
46
+ def schedule(id)
47
+ do_request(:get, "/api/schedules/#{id}", :authenticate_with => :master_secret)
48
+ end
49
+
50
+ def update_schedule(id, schedule)
51
+ do_request(:put, "/api/schedules/#{id}", :body => schedule.to_json, :authenticate_with => :master_secret)
52
+ end
53
+
54
+ def delete_schedule(id)
55
+ do_request(:delete, "/api/schedules/#{id}", :authenticate_with => :master_secret)
56
+ end
57
+
58
+ # Push API
59
+ def push(options = {})
60
+ body = parse_push_options(options.dup).to_json
61
+ do_request(:post, "/api/push/", :body => body, :authenticate_with => :master_secret)
62
+ end
63
+
64
+ # Tags API
65
+ def tags
66
+ do_request(:get, "/api/tags/", :authenticate_with => :master_secret)
67
+ end
68
+
69
+ def add_tag(tag)
70
+ do_request(:put, "/api/tags/#{tag}", :authenticate_with => :master_secret, :content_type => 'text/plain')
71
+ end
72
+
73
+ def remove_tag(tag)
74
+ do_request(:delete, "/api/tags/#{tag}", :authenticate_with => :master_secret)
75
+ end
76
+
77
+ def tag_device(params)
78
+ provider_field = device_tokens_end_point(params[:provider] || @provider).to_sym
79
+ do_request(:post, "/api/tags/#{params[:tag]}", :body => {provider_field => {:add => [params[:device_token]]}}.to_json, :authenticate_with => :master_secret)
80
+ end
81
+
82
+ def untag_device(params)
83
+ provider_field = device_tokens_end_point(params[:provider] || @provider).to_sym
84
+ do_request(:post, "/api/tags/#{params[:tag]}", :body => {provider_field => {:remove => [params[:device_token]]}}.to_json, :authenticate_with => :master_secret)
85
+ end
86
+
87
+ # Device Tokens API
88
+ def tags_for_device(device_token)
89
+ do_request(:get, "/api/device_tokens/#{device_token}/tags/", :authenticate_with => :master_secret)
90
+ end
91
+
92
+ def device_tokens
93
+ do_request(:get, "/api/device_tokens/", :authenticate_with => :master_secret)
94
+ end
95
+
96
+ def device_tokens_count
97
+ do_request(:get, "/api/device_tokens/count/", :authenticate_with => :master_secret)
98
+ end
99
+
100
+ def feedback(time)
101
+ do_request(:get, "/api/device_tokens/feedback/?since=#{format_time(time)}", :authenticate_with => :master_secret)
102
+ end
103
+
104
+ # Segments API
105
+ def segments
106
+ do_request(:get, "/api/segments", :authenticate_with => :master_secret)
107
+ end
108
+
109
+ def create_segment(segment)
110
+ do_request(:post, "/api/segments", :body => segment.to_json, :authenticate_with => :master_secret)
111
+ end
112
+
113
+ def segment(id)
114
+ do_request(:get, "/api/segments/#{id}", :authenticate_with => :master_secret)
115
+ end
116
+
117
+ def update_segment(id, segment)
118
+ do_request(:put, "/api/segments/#{id}", :body => segment.to_json, :authenticate_with => :master_secret)
119
+ end
120
+
121
+ def delete_segment(id)
122
+ do_request(:delete, "/api/segments/#{id}", :authenticate_with => :master_secret)
123
+ end
124
+
125
+ private
126
+
127
+ def do_request(http_method, path, options = {})
128
+ verify_configuration_values(:application_key, options[:authenticate_with])
129
+
130
+ klass = Net::HTTP.const_get(http_method.to_s.capitalize)
131
+
132
+ request = klass.new(path)
133
+ request.basic_auth @application_key, instance_variable_get("@#{options[:authenticate_with]}")
134
+ request.add_field "Content-Type", options[:content_type] || "application/json"
135
+ request.body = options[:body] if options[:body]
136
+ request["Accept"] = "application/vnd.urbanairship+json; version=#{VERSION};"
137
+
138
+ Timer.timeout(request_timeout) do
139
+ start_time = Time.now
140
+ response = http_client.request(request)
141
+ log_request_and_response(request, response, Time.now - start_time)
142
+ Urbanairship::Response.wrap(response)
143
+ end
144
+ rescue Timeout::Error
145
+ unless logger.nil?
146
+ logger.error "Urbanairship request timed out after #{request_timeout} seconds: [#{http_method} #{request.path} #{request.body}]"
147
+ end
148
+ Urbanairship::Response.wrap(nil, :body => {'error' => 'Request timeout'}, :code => '503')
149
+ end
150
+
151
+ def verify_configuration_values(*symbols)
152
+ absent_values = symbols.select{|symbol| instance_variable_get("@#{symbol}").nil? }
153
+ raise("Must configure #{absent_values.join(", ")} before making this request.") unless absent_values.empty?
154
+ end
155
+
156
+ def http_client
157
+ Net::HTTP.new("go.urbanairship.com", 443).tap{|http| http.use_ssl = true}
158
+ end
159
+
160
+ def parse_register_options(hash = {})
161
+ hash[:alias] = hash[:alias].to_s unless hash[:alias].nil?
162
+ hash
163
+ end
164
+
165
+ def parse_push_options(hash = {})
166
+ hash[:aliases] = hash[:aliases].map{|a| a.to_s} unless hash[:aliases].nil?
167
+ hash
168
+ end
169
+
170
+ def log_request_and_response(request, response, time)
171
+ return if logger.nil?
172
+
173
+ time = (time * 1000).to_i
174
+ http_method = request.class.to_s.split('::')[-1]
175
+ logger.info "Urbanairship (#{time}ms): [#{http_method} #{request.path}, #{request.body}], [#{response.code}, #{response.body}]"
176
+ logger.flush if logger.respond_to?(:flush)
177
+ end
178
+
179
+ def format_time(time)
180
+ time = Time.parse(time) if time.is_a?(String)
181
+ time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
182
+ end
183
+
184
+ def request_timeout
185
+ @request_timeout || 5.0
186
+ end
187
+
188
+ def device_tokens_end_point(provider)
189
+ provider == :android || provider == 'android' ? 'apids' : 'device_tokens'
190
+ end
191
+ end
192
+
193
+ class << self
194
+ include ClassMethods
195
+ end
196
+
197
+ class Client
198
+ include ClassMethods
199
+ end
200
+ end