urbanairship 1.0.0
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/LICENSE +22 -0
- data/README.markdown +82 -0
- data/Rakefile +8 -0
- data/lib/urbanairship.rb +100 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/urbanairship_spec.rb +440 -0
- metadata +131 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 Groupon, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Urbanairship is a Ruby library for interacting with the [Urbanairship API](http://urbanairship.com).
|
|
2
|
+
|
|
3
|
+
Installation
|
|
4
|
+
============
|
|
5
|
+
gem install urbanairship
|
|
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' # => true
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Unregistering a device token
|
|
27
|
+
----------------------------
|
|
28
|
+
```ruby
|
|
29
|
+
Urbanairship.unregister_device 'DEVICE-TOKEN' # => true
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Sending a push notification
|
|
33
|
+
---------------------------
|
|
34
|
+
```ruby
|
|
35
|
+
notification = {
|
|
36
|
+
:schedule_for => 1.hour.from_now,
|
|
37
|
+
:device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
|
|
38
|
+
:aps => {:alert => 'You have a new message!', :badge => 1}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Urbanairship.push notification # => true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Batching push notification sends
|
|
45
|
+
--------------------------------
|
|
46
|
+
```ruby
|
|
47
|
+
notifications = [
|
|
48
|
+
{
|
|
49
|
+
:schedule_for => 1.hour.from_now,
|
|
50
|
+
:device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
|
|
51
|
+
:aps => {:alert => 'You have a new message!', :badge => 1}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
:schedule_for => 3.hours.from_now,
|
|
55
|
+
:device_tokens => ['DEVICE-TOKEN-THREE'],
|
|
56
|
+
:aps => {:alert => 'You have a new message!', :badge => 1}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
Urbanairship.batch_push notifications # => true
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Polling the feedback API
|
|
64
|
+
------------------------
|
|
65
|
+
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 Urbanairship will register that token in their feedback API. Urbanairship will prevent further attempted notification sends to that device, but it's a good practice to periodically poll Urbanairship's feedback API and mark those tokens as inactive in your own system as well.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# find all device tokens deactivated in the past 24 hours
|
|
69
|
+
Urbanairship.feedback 24.hours.ago # =>
|
|
70
|
+
# [
|
|
71
|
+
# {
|
|
72
|
+
# "marked_inactive_on"=>"2011-06-03 22:53:23",
|
|
73
|
+
# "alias"=>nil,
|
|
74
|
+
# "device_token"=>"DEVICE-TOKEN-ONE"
|
|
75
|
+
# },
|
|
76
|
+
# {
|
|
77
|
+
# "marked_inactive_on"=>"2011-06-03 22:53:23",
|
|
78
|
+
# "alias"=>nil,
|
|
79
|
+
# "device_token"=>"DEVICE-TOKEN-TWO"
|
|
80
|
+
# }
|
|
81
|
+
# ]
|
|
82
|
+
```
|
data/Rakefile
ADDED
data/lib/urbanairship.rb
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'net/https'
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'system_timer'
|
|
5
|
+
|
|
6
|
+
module Urbanairship
|
|
7
|
+
VALID_PUSH_PARAMS = %w(device_tokens aliases tags schedule_for exclude_tokens aps)
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :application_key, :application_secret, :master_secret, :logger, :request_timeout
|
|
11
|
+
|
|
12
|
+
def register_device(device_token)
|
|
13
|
+
response = do_request(:put, "/api/device_tokens/#{device_token}", :authenticate_with => :application_secret)
|
|
14
|
+
response && %w(200 201).include?(response.code)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def unregister_device(device_token)
|
|
18
|
+
response = do_request(:delete, "/api/device_tokens/#{device_token}", :authenticate_with => :application_secret)
|
|
19
|
+
response && response.code == "204"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def push(options = {})
|
|
23
|
+
response = do_request(:post, "/api/push/", :authenticate_with => :master_secret) do |request|
|
|
24
|
+
request.body = parse_push_options(options).to_json
|
|
25
|
+
request.add_field "Content-Type", "application/json"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
response && response.code == "200"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def batch_push(notifications = [])
|
|
32
|
+
response = do_request(:post, "/api/push/batch/", :authenticate_with => :master_secret) do |request|
|
|
33
|
+
request.body = notifications.map{|notification| parse_push_options(notification)}.to_json
|
|
34
|
+
request.add_field "Content-Type", "application/json"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
response && response.code == "200"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def feedback(time)
|
|
41
|
+
response = do_request(:get, "/api/device_tokens/feedback/?since=#{format_time(time)}", :authenticate_with => :master_secret)
|
|
42
|
+
response && response.code == "200" ? JSON.parse(response.body) : false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def do_request(http_method, path, options = {})
|
|
48
|
+
verify_configuration_values(:application_key, options[:authenticate_with])
|
|
49
|
+
|
|
50
|
+
klass = Net::HTTP.const_get(http_method.to_s.capitalize)
|
|
51
|
+
|
|
52
|
+
request = klass.new(path)
|
|
53
|
+
request.basic_auth @application_key, instance_variable_get("@#{options[:authenticate_with]}")
|
|
54
|
+
|
|
55
|
+
yield(request) if block_given?
|
|
56
|
+
|
|
57
|
+
SystemTimer.timeout_after(request_timeout) do
|
|
58
|
+
start_time = Time.now
|
|
59
|
+
response = http_client.request(request)
|
|
60
|
+
log_request_and_response(request, response, Time.now - start_time)
|
|
61
|
+
response
|
|
62
|
+
end
|
|
63
|
+
rescue Timeout::Error
|
|
64
|
+
logger.error "Urbanairship request timed out after #{request_timeout} seconds: [#{http_method} #{request.path} #{request.body}]"
|
|
65
|
+
return false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def verify_configuration_values(*symbols)
|
|
69
|
+
absent_values = symbols.select{|symbol| instance_variable_get("@#{symbol}").nil? }
|
|
70
|
+
raise("Must configure #{absent_values.join(", ")} before making this request.") unless absent_values.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def http_client
|
|
74
|
+
Net::HTTP.new("go.urbanairship.com", 443).tap{|http| http.use_ssl = true}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_push_options(hash = {})
|
|
78
|
+
hash[:schedule_for] = hash[:schedule_for].map{|time| format_time(time)} unless hash[:schedule_for].nil?
|
|
79
|
+
hash.delete_if{|key, value| !VALID_PUSH_PARAMS.include?(key.to_s)}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def log_request_and_response(request, response, time)
|
|
83
|
+
return if logger.nil?
|
|
84
|
+
|
|
85
|
+
time = (time * 1000).to_i
|
|
86
|
+
http_method = request.class.to_s.split('::')[-1]
|
|
87
|
+
logger.info "Urbanairship (#{time}ms): [#{http_method} #{request.path}, #{request.body}], [#{response.code}, #{response.body}]"
|
|
88
|
+
logger.flush if logger.respond_to?(:flush)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def format_time(time)
|
|
92
|
+
time = Time.parse(time) if time.is_a?(String)
|
|
93
|
+
time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def request_timeout
|
|
97
|
+
@request_timeout || 5.0
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
describe Urbanairship do
|
|
2
|
+
|
|
3
|
+
before(:all) do
|
|
4
|
+
FakeWeb.allow_net_connect = false
|
|
5
|
+
|
|
6
|
+
# register_device
|
|
7
|
+
FakeWeb.register_uri(:put, "https://my_app_key:my_app_secret@go.urbanairship.com/api/device_tokens/new_device_token", :status => ["201", "Created"])
|
|
8
|
+
FakeWeb.register_uri(:put, "https://my_app_key:my_app_secret@go.urbanairship.com/api/device_tokens/existing_device_token", :status => ["200", "OK"])
|
|
9
|
+
FakeWeb.register_uri(:put, /bad_key\:my_app_secret\@go\.urbanairship\.com/, :status => ["401", "Unauthorized"])
|
|
10
|
+
|
|
11
|
+
# unregister_device
|
|
12
|
+
FakeWeb.register_uri(:delete, /my_app_key\:my_app_secret\@go\.urbanairship.com\/api\/device_tokens\/.+/, :status => ["204", "No Content"])
|
|
13
|
+
FakeWeb.register_uri(:delete, /bad_key\:my_app_secret\@go\.urbanairship.com\/api\/device_tokens\/.+/, :status => ["401", "Unauthorized"])
|
|
14
|
+
|
|
15
|
+
# push
|
|
16
|
+
FakeWeb.register_uri(:post, "https://my_app_key:my_master_secret@go.urbanairship.com/api/push/", :status => ["200", "OK"])
|
|
17
|
+
FakeWeb.register_uri(:post, "https://my_app_key2:my_master_secret2@go.urbanairship.com/api/push/", :status => ["400", "Bad Request"])
|
|
18
|
+
FakeWeb.register_uri(:post, /bad_key\:my_master_secret\@go\.urbanairship\.com/, :status => ["401", "Unauthorized"])
|
|
19
|
+
|
|
20
|
+
# batch_push
|
|
21
|
+
FakeWeb.register_uri(:post, "https://my_app_key:my_master_secret@go.urbanairship.com/api/push/batch/", :status => ["200", "OK"])
|
|
22
|
+
FakeWeb.register_uri(:post, "https://my_app_key2:my_master_secret2@go.urbanairship.com/api/push/batch/", :status => ["400", "Bad Request"])
|
|
23
|
+
|
|
24
|
+
# feedback
|
|
25
|
+
FakeWeb.register_uri(:get, /my_app_key\:my_master_secret\@go\.urbanairship.com\/api\/device_tokens\/feedback/, :status => ["200", "OK"], :body => "[{\"device_token\":\"token\",\"marked_inactive_on\":\"2010-10-14T19:15:13Z\",\"alias\":\"my_alias\"}]")
|
|
26
|
+
FakeWeb.register_uri(:get, /my_app_key2\:my_master_secret2\@go\.urbanairship.com\/api\/device_tokens\/feedback/, :status => ["500", "Internal Server Error"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
after(:each) do
|
|
30
|
+
# reset configuration
|
|
31
|
+
Urbanairship.application_key = nil
|
|
32
|
+
Urbanairship.application_secret = nil
|
|
33
|
+
Urbanairship.master_secret = nil
|
|
34
|
+
Urbanairship.logger = nil
|
|
35
|
+
|
|
36
|
+
FakeWeb.instance_variable_set("@last_request", nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "configuration" do
|
|
40
|
+
|
|
41
|
+
it "enables you to configure the application key" do
|
|
42
|
+
Urbanairship.application_key.should be_nil
|
|
43
|
+
Urbanairship.application_key = "asdf1234"
|
|
44
|
+
Urbanairship.application_key.should == "asdf1234"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "enables you to configure the application secret" do
|
|
48
|
+
Urbanairship.application_secret.should be_nil
|
|
49
|
+
Urbanairship.application_secret = "asdf1234"
|
|
50
|
+
Urbanairship.application_secret.should == "asdf1234"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "enables you to configure the master secret" do
|
|
54
|
+
Urbanairship.master_secret.should be_nil
|
|
55
|
+
Urbanairship.master_secret = "asdf1234"
|
|
56
|
+
Urbanairship.master_secret.should == "asdf1234"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "registering a device" do
|
|
62
|
+
|
|
63
|
+
before(:each) do
|
|
64
|
+
Urbanairship.application_key = "my_app_key"
|
|
65
|
+
Urbanairship.application_secret = "my_app_secret"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "raises an error if call is made without an app key and secret configured" do
|
|
69
|
+
Urbanairship.application_key = nil
|
|
70
|
+
Urbanairship.application_secret = nil
|
|
71
|
+
|
|
72
|
+
lambda {
|
|
73
|
+
Urbanairship.register_device("asdf1234")
|
|
74
|
+
}.should raise_error(RuntimeError, "Must configure application_key, application_secret before making this request.")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "uses app key and secret to sign the request" do
|
|
78
|
+
Urbanairship.register_device("new_device_token")
|
|
79
|
+
FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_app_secret').chomp}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "takes and sends a device token" do
|
|
83
|
+
Urbanairship.register_device("new_device_token")
|
|
84
|
+
FakeWeb.last_request.path.should == "/api/device_tokens/new_device_token"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "returns true when the device is registered for the first time" do
|
|
88
|
+
Urbanairship.register_device("new_device_token").should == true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "returns true when the device is registered again" do
|
|
92
|
+
Urbanairship.register_device("existing_device_token").should == true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "returns false when the authorization is invalid" do
|
|
96
|
+
Urbanairship.application_key = "bad_key"
|
|
97
|
+
Urbanairship.register_device("new_device_token").should == false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# TODO:
|
|
101
|
+
# it "accepts additional parameters (EXPAND THIS)"
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe "unregistering a device" do
|
|
106
|
+
before(:each) do
|
|
107
|
+
Urbanairship.application_key = "my_app_key"
|
|
108
|
+
Urbanairship.application_secret = "my_app_secret"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "raises an error if call is made without an app key and secret configured" do
|
|
112
|
+
Urbanairship.application_key = nil
|
|
113
|
+
Urbanairship.application_secret = nil
|
|
114
|
+
|
|
115
|
+
lambda {
|
|
116
|
+
Urbanairship.unregister_device("asdf1234")
|
|
117
|
+
}.should raise_error(RuntimeError, "Must configure application_key, application_secret before making this request.")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "uses app key and secret to sign the request" do
|
|
121
|
+
Urbanairship.unregister_device("key_to_delete")
|
|
122
|
+
FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_app_secret').chomp}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "sends the key that needs to be deleted" do
|
|
126
|
+
Urbanairship.unregister_device("key_to_delete")
|
|
127
|
+
FakeWeb.last_request.path.should == "/api/device_tokens/key_to_delete"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "returns true when the device is successfully unregistered" do
|
|
131
|
+
Urbanairship.unregister_device("key_to_delete").should == true
|
|
132
|
+
FakeWeb.last_request.body.should be_nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "returns false when the authorization is invalid" do
|
|
136
|
+
Urbanairship.application_key = "bad_key"
|
|
137
|
+
Urbanairship.unregister_device("key_to_delete").should == false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe "sending multiple push notifications" do
|
|
143
|
+
|
|
144
|
+
before(:each) do
|
|
145
|
+
@valid_params = {:device_tokens => ['device_token_one', 'device_token_two'], :aps => {:alert => 'foo'}}
|
|
146
|
+
Urbanairship.application_key = "my_app_key"
|
|
147
|
+
Urbanairship.master_secret = "my_master_secret"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "raises an error if call is made without an app key and master secret configured" do
|
|
151
|
+
Urbanairship.application_key = nil
|
|
152
|
+
Urbanairship.master_secret = nil
|
|
153
|
+
|
|
154
|
+
lambda {
|
|
155
|
+
Urbanairship.push(@valid_params)
|
|
156
|
+
}.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "uses app key and secret to sign the request" do
|
|
160
|
+
Urbanairship.push(@valid_params)
|
|
161
|
+
FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "returns true when it successfully pushes a notification" do
|
|
165
|
+
Urbanairship.push(@valid_params).should == true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "returns false when the authorization is invalid" do
|
|
169
|
+
Urbanairship.application_key = "bad_key"
|
|
170
|
+
Urbanairship.push(@valid_params).should == false
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "sets the content-type header to application/json" do
|
|
174
|
+
Urbanairship.push(@valid_params)
|
|
175
|
+
FakeWeb.last_request['content-type'].should == 'application/json'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "adds device_tokens to the JSON payload" do
|
|
179
|
+
Urbanairship.push(@valid_params.merge(:device_tokens => ["one", "two"]))
|
|
180
|
+
request_json['device_tokens'].should == ["one", "two"]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "adds aliases to the JSON payload" do
|
|
184
|
+
Urbanairship.push(@valid_params.merge(:aliases => ["one", "two"]))
|
|
185
|
+
request_json['aliases'].should == ["one", "two"]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "adds tags to the JSON payload" do
|
|
189
|
+
Urbanairship.push(@valid_params.merge(:tags => ["one", "two"]))
|
|
190
|
+
request_json['tags'].should == ["one", "two"]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "adds schedule_for to the JSON payload" do
|
|
194
|
+
time = Time.parse("Oct 17th, 2010, 8:00 PM UTC")
|
|
195
|
+
Urbanairship.push(@valid_params.merge(:schedule_for => [time]))
|
|
196
|
+
request_json['schedule_for'].should == ['2010-10-17T20:00:00Z']
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "only attempts to format schedule_for if it is a time object" do
|
|
200
|
+
Urbanairship.push(@valid_params.merge(:schedule_for => ["2010-10-10 09:09:09 UTC"]))
|
|
201
|
+
request_json['schedule_for'].should == ['2010-10-10T09:09:09Z']
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "adds exclude_tokens to the JSON payload" do
|
|
205
|
+
Urbanairship.push(@valid_params.merge(:exclude_tokens => ["one", "two"]))
|
|
206
|
+
request_json['exclude_tokens'].should == ["one", "two"]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "adds aps parameters to the JSON payload" do
|
|
210
|
+
Urbanairship.push(@valid_params.merge(:aps => {:badge => 10, :alert => "Hi!", :sound => "cat.caf"}))
|
|
211
|
+
request_json['aps'].should == {'badge' => 10, 'alert' => 'Hi!', 'sound' => 'cat.caf'}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "excludes invalid parameters from the JSON payload" do
|
|
215
|
+
Urbanairship.push(@valid_params.merge(:foo => 'bar'))
|
|
216
|
+
request_json['foo'].should be_nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "returns false if urbanairship responds with a non-200 response" do
|
|
220
|
+
Urbanairship.application_key = "my_app_key2"
|
|
221
|
+
Urbanairship.master_secret = "my_master_secret2"
|
|
222
|
+
Urbanairship.push.should == false
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
describe "sending batch push notifications" do
|
|
228
|
+
|
|
229
|
+
before(:each) do
|
|
230
|
+
@valid_params = [
|
|
231
|
+
{:device_tokens => ['device_token_one', 'device_token_two'], :aps => {:alert => 'foo'}},
|
|
232
|
+
{:device_tokens => ['device_token_three', 'device_token_four'], :aps => {:alert => 'bar'}}
|
|
233
|
+
]
|
|
234
|
+
Urbanairship.application_key = "my_app_key"
|
|
235
|
+
Urbanairship.master_secret = "my_master_secret"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "raises an error if call is made without an app key and master secret configured" do
|
|
239
|
+
Urbanairship.application_key = nil
|
|
240
|
+
Urbanairship.master_secret = nil
|
|
241
|
+
|
|
242
|
+
lambda {
|
|
243
|
+
Urbanairship.batch_push(@valid_params)
|
|
244
|
+
}.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "uses app key and secret to sign the request" do
|
|
248
|
+
Urbanairship.batch_push(@valid_params)
|
|
249
|
+
FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it "returns true when it successfully pushes a notification" do
|
|
253
|
+
Urbanairship.batch_push(@valid_params).should == true
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it "returns false when the authorization is invalid" do
|
|
257
|
+
Urbanairship.application_key = "bad_key"
|
|
258
|
+
Urbanairship.batch_push(@valid_params).should == false
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "sets the content-type header to application/json" do
|
|
262
|
+
Urbanairship.batch_push(@valid_params)
|
|
263
|
+
FakeWeb.last_request['content-type'].should == 'application/json'
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "adds device_tokens to the JSON payload" do
|
|
267
|
+
@valid_params[0].merge!(:device_tokens => ["one", "two"])
|
|
268
|
+
Urbanairship.batch_push(@valid_params)
|
|
269
|
+
request_json[0]['device_tokens'].should == ["one", "two"]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "adds aliases to the JSON payload" do
|
|
273
|
+
@valid_params[0].merge!(:aliases => ["one", "two"])
|
|
274
|
+
Urbanairship.batch_push(@valid_params)
|
|
275
|
+
request_json[0]['aliases'].should == ["one", "two"]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "adds tags to the JSON payload" do
|
|
279
|
+
@valid_params[0].merge!(:tags => ["one", "two"])
|
|
280
|
+
Urbanairship.batch_push(@valid_params)
|
|
281
|
+
request_json[0]['tags'].should == ["one", "two"]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it "adds schedule_for to the JSON payload" do
|
|
285
|
+
time = Time.parse("Oct 17th, 2010, 8:00 PM UTC")
|
|
286
|
+
@valid_params[0].merge!(:schedule_for => [time])
|
|
287
|
+
Urbanairship.batch_push(@valid_params)
|
|
288
|
+
request_json[0]['schedule_for'].should == ['2010-10-17T20:00:00Z']
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it "accepts strings as schedule_for values" do
|
|
292
|
+
@valid_params[0].merge!(:schedule_for => ["2010-10-10 09:09:09 UTC"])
|
|
293
|
+
Urbanairship.batch_push(@valid_params)
|
|
294
|
+
request_json[0]['schedule_for'].should == ['2010-10-10T09:09:09Z']
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "adds exclude_tokens to the JSON payload" do
|
|
298
|
+
@valid_params[0].merge!(:exclude_tokens => ["one", "two"])
|
|
299
|
+
Urbanairship.batch_push(@valid_params)
|
|
300
|
+
request_json[0]['exclude_tokens'].should == ["one", "two"]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "adds aps parameters to the JSON payload" do
|
|
304
|
+
@valid_params[0].merge!(:aps => {:badge => 10, :alert => "Hi!", :sound => "cat.caf"})
|
|
305
|
+
Urbanairship.batch_push(@valid_params)
|
|
306
|
+
request_json[0]['aps'].should == {'badge' => 10, 'alert' => 'Hi!', 'sound' => 'cat.caf'}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "excludes invalid parameters from the JSON payload" do
|
|
310
|
+
@valid_params[0].merge!(:foo => 'bar')
|
|
311
|
+
Urbanairship.batch_push(@valid_params)
|
|
312
|
+
request_json[0]['foo'].should be_nil
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it "returns false if urbanairship responds with a non-200 response" do
|
|
316
|
+
Urbanairship.application_key = "my_app_key2"
|
|
317
|
+
Urbanairship.master_secret = "my_master_secret2"
|
|
318
|
+
Urbanairship.batch_push.should == false
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
describe "feedback service" do
|
|
324
|
+
|
|
325
|
+
before(:each) do
|
|
326
|
+
Urbanairship.application_key = "my_app_key"
|
|
327
|
+
Urbanairship.master_secret = "my_master_secret"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it "raises an error if call is made without an app key and master secret configured" do
|
|
331
|
+
Urbanairship.application_key = nil
|
|
332
|
+
Urbanairship.master_secret = nil
|
|
333
|
+
|
|
334
|
+
lambda {
|
|
335
|
+
Urbanairship.feedback(Time.now)
|
|
336
|
+
}.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
it "uses app key and secret to sign the request" do
|
|
340
|
+
Urbanairship.feedback(Time.now)
|
|
341
|
+
FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it "encodes the time argument in UTC, ISO 8601 format" do
|
|
345
|
+
time = Time.parse("October 10, 2010, 8:00pm")
|
|
346
|
+
formatted_time = time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
347
|
+
Urbanairship.feedback(time)
|
|
348
|
+
FakeWeb.last_request.path.should include(formatted_time)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "accepts a string as the time argument" do
|
|
352
|
+
Urbanairship.feedback("Oct 07, 2010 8:00AM UTC")
|
|
353
|
+
FakeWeb.last_request.path.should include("2010-10-07T08:00:00Z")
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it "returns an array of responses from the feedback API" do
|
|
357
|
+
response = Urbanairship.feedback(Time.now)
|
|
358
|
+
response.class.should == Array
|
|
359
|
+
response[0].keys.should include("device_token")
|
|
360
|
+
response[0].keys.should include("marked_inactive_on")
|
|
361
|
+
response[0].keys.should include("alias")
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
it "returns false and doesn't parse JSON when the call doesn't return 200" do
|
|
365
|
+
Urbanairship.application_key = "my_app_key2"
|
|
366
|
+
Urbanairship.master_secret = "my_master_secret2"
|
|
367
|
+
JSON.should_not_receive(:parse)
|
|
368
|
+
Urbanairship.feedback(Time.now).should == false
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
describe "logging" do
|
|
374
|
+
|
|
375
|
+
before(:each) do
|
|
376
|
+
@logger = mock("logger", :info => true)
|
|
377
|
+
Urbanairship.application_key = "my_app_key"
|
|
378
|
+
Urbanairship.application_secret = "my_app_secret"
|
|
379
|
+
Urbanairship.master_secret = "my_master_secret"
|
|
380
|
+
Urbanairship.logger = @logger
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it "logs request and response information when registering a device" do
|
|
384
|
+
@logger.should_receive(:info).with(/\/api\/device_tokens\/new_device_token/)
|
|
385
|
+
Urbanairship.register_device('new_device_token')
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
it "logs request and response information when sending push notifications" do
|
|
389
|
+
@logger.should_receive(:info).with(/\/api\/push/)
|
|
390
|
+
Urbanairship.push(:device_tokens => ["device_token"], :aps => {:alert => "foo"})
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it "logs request and response information when sending batch push notifications" do
|
|
394
|
+
@logger.should_receive(:info).with(/\/api\/push\/batch/)
|
|
395
|
+
Urbanairship.batch_push([:device_tokens => ["device_token"], :aps => {:alert => "foo"}])
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it "logs request and response information when sending feedback requests" do
|
|
399
|
+
@logger.should_receive(:info).with(/\/api\/device_tokens\/feedback/)
|
|
400
|
+
Urbanairship.feedback(Time.now)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
it "flushes the logger buffer if it's an ActiveSupport::BufferedLogger (Default Rails logger)" do
|
|
404
|
+
@logger.stub(:flush).and_return("message in the buffer\n")
|
|
405
|
+
@logger.should_receive(:flush)
|
|
406
|
+
Urbanairship.feedback(Time.now)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
describe "request timeout" do
|
|
412
|
+
before(:each) do
|
|
413
|
+
@logger = mock("logger", :info => true)
|
|
414
|
+
Urbanairship.application_key = "my_app_key"
|
|
415
|
+
Urbanairship.application_secret = "my_app_secret"
|
|
416
|
+
Urbanairship.master_secret = "my_master_secret"
|
|
417
|
+
Urbanairship.logger = @logger
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it "uses a default request_timeout value of five seconds" do
|
|
421
|
+
SystemTimer.should_receive(:timeout_after).with(5.0).and_raise(Timeout::Error)
|
|
422
|
+
@logger.should_receive(:error).with(/Urbanairship request timed out/)
|
|
423
|
+
|
|
424
|
+
Urbanairship.register_device('new_device_token')
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it "accepts a configured request_timeout value" do
|
|
428
|
+
SystemTimer.should_receive(:timeout_after).with(1.23).and_raise(Timeout::Error)
|
|
429
|
+
@logger.should_receive(:error).with(/Urbanairship request timed out/)
|
|
430
|
+
|
|
431
|
+
Urbanairship.request_timeout = 1.23
|
|
432
|
+
Urbanairship.register_device('new_device_token')
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def request_json
|
|
439
|
+
JSON.parse FakeWeb.last_request.body
|
|
440
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: urbanairship
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
- 0
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Groupon, Inc.
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-06-04 00:00:00 -05:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies:
|
|
21
|
+
- !ruby/object:Gem::Dependency
|
|
22
|
+
name: system_timer
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
hash: 3
|
|
30
|
+
segments:
|
|
31
|
+
- 0
|
|
32
|
+
version: "0"
|
|
33
|
+
type: :runtime
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: json
|
|
37
|
+
prerelease: false
|
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
hash: 3
|
|
44
|
+
segments:
|
|
45
|
+
- 0
|
|
46
|
+
version: "0"
|
|
47
|
+
type: :runtime
|
|
48
|
+
version_requirements: *id002
|
|
49
|
+
- !ruby/object:Gem::Dependency
|
|
50
|
+
name: rspec
|
|
51
|
+
prerelease: false
|
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
hash: 3
|
|
58
|
+
segments:
|
|
59
|
+
- 0
|
|
60
|
+
version: "0"
|
|
61
|
+
type: :development
|
|
62
|
+
version_requirements: *id003
|
|
63
|
+
- !ruby/object:Gem::Dependency
|
|
64
|
+
name: fakeweb
|
|
65
|
+
prerelease: false
|
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
67
|
+
none: false
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
hash: 3
|
|
72
|
+
segments:
|
|
73
|
+
- 0
|
|
74
|
+
version: "0"
|
|
75
|
+
type: :development
|
|
76
|
+
version_requirements: *id004
|
|
77
|
+
description: Urbanairship is a Ruby library for interacting with the Urbanairship (http://urbanairship.com) API.
|
|
78
|
+
email:
|
|
79
|
+
- rubygems@groupon.com
|
|
80
|
+
executables: []
|
|
81
|
+
|
|
82
|
+
extensions: []
|
|
83
|
+
|
|
84
|
+
extra_rdoc_files: []
|
|
85
|
+
|
|
86
|
+
files:
|
|
87
|
+
- README.markdown
|
|
88
|
+
- LICENSE
|
|
89
|
+
- Rakefile
|
|
90
|
+
- lib/urbanairship.rb
|
|
91
|
+
- spec/spec_helper.rb
|
|
92
|
+
- spec/urbanairship_spec.rb
|
|
93
|
+
has_rdoc: true
|
|
94
|
+
homepage: http://github.com/groupon/urbanairship
|
|
95
|
+
licenses: []
|
|
96
|
+
|
|
97
|
+
post_install_message:
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
|
|
100
|
+
require_paths:
|
|
101
|
+
- lib
|
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
|
+
none: false
|
|
104
|
+
requirements:
|
|
105
|
+
- - ~>
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
hash: 59
|
|
108
|
+
segments:
|
|
109
|
+
- 1
|
|
110
|
+
- 8
|
|
111
|
+
- 6
|
|
112
|
+
version: 1.8.6
|
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
|
+
none: false
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
hash: 3
|
|
119
|
+
segments:
|
|
120
|
+
- 0
|
|
121
|
+
version: "0"
|
|
122
|
+
requirements: []
|
|
123
|
+
|
|
124
|
+
rubyforge_project:
|
|
125
|
+
rubygems_version: 1.3.7
|
|
126
|
+
signing_key:
|
|
127
|
+
specification_version: 3
|
|
128
|
+
summary: A Ruby wrapper for the Urbanairship API
|
|
129
|
+
test_files:
|
|
130
|
+
- spec/spec_helper.rb
|
|
131
|
+
- spec/urbanairship_spec.rb
|