zeppelin 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,8 @@
1
1
  require 'faraday'
2
- require 'yajl'
3
2
  require 'time'
4
3
 
5
4
  # A very tiny Urban Airship Push Notification API client.
6
- #
5
+ #
7
6
  # Provides thin wrappers around API calls to the most common API tasks. For more
8
7
  # information on how the requests and responses are formatted, visit the [Urban
9
8
  # Airship Push Notification API docs](http://urbanairship.com/docs/push.html).
@@ -12,140 +11,138 @@ class Zeppelin
12
11
  PUSH_URI = '/api/push/'
13
12
  BATCH_PUSH_URI = '/api/push/batch/'
14
13
  BROADCAST_URI = '/api/push/broadcast/'
15
- SUCCESSFUL_STATUS_CODES = (200..299)
16
14
  JSON_HEADERS = { 'Content-Type' => 'application/json' }
17
-
18
- # The connection to `https://go.urbanairship.com`.
19
- attr_reader :connection
20
-
21
- # Creates a new client.
22
- #
15
+
23
16
  # @param [String] application_key your Urban Airship Application Key
17
+ #
24
18
  # @param [String] application_master_secret your Urban Airship Application
25
19
  # Master Secret
26
20
  def initialize(application_key, application_master_secret, options = {})
27
- @connection = Faraday::Connection.new(BASE_URI, options) do |builder|
28
- builder.request :json
29
- builder.adapter :net_http
30
- end
31
-
32
- @connection.basic_auth(application_key, application_master_secret)
21
+ @application_key = application_key
22
+ @application_master_secret = application_master_secret
23
+ @options = options
24
+ end
25
+
26
+ # The connection to UrbanAirship
27
+ def connection
28
+ return @connection unless @connection.nil?
29
+ @connection = initialize_connection
33
30
  end
34
-
31
+
35
32
  # Registers a device token.
36
- #
33
+ #
37
34
  # @param [String] device_token
38
35
  # @param [Hash] payload the payload to send during registration
39
36
  # @return [Boolean] whether or not the registration was successful
40
37
  def register_device_token(device_token, payload = {})
41
38
  uri = device_token_uri(device_token)
42
-
39
+
43
40
  if payload.empty?
44
- response = @connection.put(uri)
41
+ response = connection.put(uri)
45
42
  else
46
- response = @connection.put(uri, payload, JSON_HEADERS)
43
+ response = connection.put(uri, payload, JSON_HEADERS)
47
44
  end
48
-
49
- successful?(response)
45
+
46
+ response.success?
50
47
  end
51
-
48
+
52
49
  # Retrieves information on a device token.
53
- #
50
+ #
54
51
  # @param [String] device_token
55
52
  # @return [Hash, nil]
56
53
  def device_token(device_token)
57
- response = @connection.get(device_token_uri(device_token))
58
- successful?(response) ? parse(response.body) : nil
54
+ response = connection.get(device_token_uri(device_token))
55
+ response.success? ? response.body : nil
59
56
  end
60
-
57
+
61
58
  # Deletes a device token.
62
- #
59
+ #
63
60
  # @param [String] device_token
64
61
  # @return [Boolean] whether or not the deletion was successful
65
62
  def delete_device_token(device_token)
66
- response = @connection.delete(device_token_uri(device_token))
67
- successful?(response)
63
+ response = connection.delete(device_token_uri(device_token))
64
+ response.success?
68
65
  end
69
-
66
+
70
67
  # Registers an APID.
71
- #
68
+ #
72
69
  # @param [String] apid
73
70
  # @param [Hash] payload the payload to send during registration
74
71
  # @return [Boolean] whether or not the registration was successful
75
72
  def register_apid(apid, payload = {})
76
73
  uri = apid_uri(apid)
77
-
74
+
78
75
  if payload.empty?
79
- response = @connection.put(uri)
76
+ response = connection.put(uri)
80
77
  else
81
- response = @connection.put(uri, payload, JSON_HEADERS)
78
+ response = connection.put(uri, payload, JSON_HEADERS)
82
79
  end
83
-
84
- successful?(response)
80
+
81
+ response.success?
85
82
  end
86
-
83
+
87
84
  # Retrieves information on an APID.
88
- #
85
+ #
89
86
  # @param [String] apid
90
87
  # @return [Hash, nil]
91
88
  def apid(apid)
92
- response = @connection.get(apid_uri(apid))
93
- successful?(response) ? parse(response.body) : nil
89
+ response = connection.get(apid_uri(apid))
90
+ response.success? ? response.body : nil
94
91
  end
95
-
92
+
96
93
  # Deletes an APID.
97
- #
94
+ #
98
95
  # @param [String] apid
99
96
  # @return [Boolean] whether or not the deletion was successful
100
97
  def delete_apid(apid)
101
- response = @connection.delete(apid_uri(apid))
102
- successful?(response)
98
+ response = connection.delete(apid_uri(apid))
99
+ response.success?
103
100
  end
104
-
101
+
105
102
  # Pushes a message.
106
- #
103
+ #
107
104
  # @param [Hash] payload the payload of the message
108
105
  # @return [Boolean] whether or not pushing the message was successful
109
106
  def push(payload)
110
- response = @connection.post(PUSH_URI, payload, JSON_HEADERS)
111
- successful?(response)
107
+ response = connection.post(PUSH_URI, payload, JSON_HEADERS)
108
+ response.success?
112
109
  end
113
-
110
+
114
111
  # Batch pushes multiple messages.
115
- #
112
+ #
116
113
  # @param [<Hash>] payload the payloads of each message
117
114
  # @return [Boolean] whether or not pushing the messages was successful
118
115
  def batch_push(*payload)
119
- response = @connection.post(BATCH_PUSH_URI, payload, JSON_HEADERS)
120
- successful?(response)
116
+ response = connection.post(BATCH_PUSH_URI, payload, JSON_HEADERS)
117
+ response.success?
121
118
  end
122
-
119
+
123
120
  # Broadcasts a message.
124
- #
121
+ #
125
122
  # @param [Hash] payload the payload of the message
126
123
  # @return [Boolean] whether or not broadcasting the message was successful
127
124
  def broadcast(payload)
128
- response = @connection.post(BROADCAST_URI, payload, JSON_HEADERS)
129
- successful?(response)
125
+ response = connection.post(BROADCAST_URI, payload, JSON_HEADERS)
126
+ response.success?
130
127
  end
131
-
128
+
132
129
  # Retrieves feedback on device tokens.
133
- #
130
+ #
134
131
  # This is useful for removing inactive device tokens for the database.
135
- #
132
+ #
136
133
  # @param [Time] since the time to retrieve inactive tokens from
137
134
  # @return [Hash, nil]
138
135
  def feedback(since)
139
- response = @connection.get(feedback_uri(since))
140
- successful?(response) ? parse(response.body) : nil
136
+ response = connection.get(feedback_uri(since))
137
+ response.success? ? response.body : nil
141
138
  end
142
139
 
143
140
  # Retrieve all tags on the service
144
141
  #
145
142
  # @return [Hash, nil]
146
143
  def tags
147
- response = @connection.get(tag_uri(nil))
148
- successful?(response) ? parse(response.body) : nil
144
+ response = connection.get(tag_uri(nil))
145
+ response.success? ? response.body : nil
149
146
  end
150
147
 
151
148
  # Modifies device tokens associated with a tag.
@@ -156,7 +153,7 @@ class Zeppelin
156
153
  #
157
154
  # @see http://urbanairship.com/docs/tags.html#modifying-device-tokens-on-a-tag
158
155
  def modify_device_tokens_on_tag(tag_name, payload = {})
159
- @connection.post(tag_uri(tag_name), payload, JSON_HEADERS)
156
+ connection.post(tag_uri(tag_name), payload, JSON_HEADERS)
160
157
  end
161
158
 
162
159
  # Creates a tag that is not associated with any device
@@ -165,8 +162,8 @@ class Zeppelin
165
162
  #
166
163
  # @return [Boolean] whether or not the request was successful
167
164
  def add_tag(name)
168
- response = @connection.put(tag_uri(name))
169
- successful?(response)
165
+ response = connection.put(tag_uri(name))
166
+ response.success?
170
167
  end
171
168
 
172
169
  # Removes a tag from the service
@@ -176,16 +173,16 @@ class Zeppelin
176
173
  # @return [Boolean] true when the request was successful. Note that this
177
174
  # method will return false if the tag has already been removed.
178
175
  def remove_tag(name)
179
- response = @connection.delete(tag_uri(name))
180
- successful?(response)
176
+ response = connection.delete(tag_uri(name))
177
+ response.success?
181
178
  end
182
179
 
183
180
  # @param [String] device_token
184
181
  #
185
182
  # @return [Hash, nil]
186
183
  def device_tags(device_token)
187
- response = @connection.get(device_tag_uri(device_token, nil))
188
- successful?(response) ? parse(response.body) : nil
184
+ response = connection.get(device_tag_uri(device_token, nil))
185
+ response.success? ? response.body : nil
189
186
  end
190
187
 
191
188
  # @param [String] device_token
@@ -195,8 +192,8 @@ class Zeppelin
195
192
  # @return [Boolean] whether or not a tag was successfully associated with
196
193
  # a device
197
194
  def add_tag_to_device(device_token, tag_name)
198
- response = @connection.put(device_tag_uri(device_token, tag_name))
199
- successful?(response)
195
+ response = connection.put(device_tag_uri(device_token, tag_name))
196
+ response.success?
200
197
  end
201
198
 
202
199
  # @param [String] device_token
@@ -206,20 +203,34 @@ class Zeppelin
206
203
  # @return [Boolean] whether or not a tag was successfully dissociated from
207
204
  # a device
208
205
  def remove_tag_from_device(device_token, tag_name)
209
- response = @connection.delete(device_tag_uri(device_token, tag_name))
210
- successful?(response)
206
+ response = connection.delete(device_tag_uri(device_token, tag_name))
207
+ response.success?
211
208
  end
212
-
209
+
213
210
  private
214
-
211
+
212
+ def initialize_connection
213
+ conn = Faraday::Connection.new(BASE_URI, @options) do |builder|
214
+ builder.request :json
215
+
216
+ builder.use Zeppelin::JsonParserMiddleware
217
+
218
+ builder.adapter :net_http
219
+ end
220
+
221
+ conn.basic_auth(@application_key, @application_master_secret)
222
+
223
+ conn
224
+ end
225
+
215
226
  def device_token_uri(device_token)
216
227
  "/api/device_tokens/#{device_token}"
217
228
  end
218
-
229
+
219
230
  def apid_uri(apid)
220
231
  "/api/apids/#{apid}"
221
232
  end
222
-
233
+
223
234
  def feedback_uri(since)
224
235
  "/api/device_tokens/feedback/?since=#{since.utc.iso8601}"
225
236
  end
@@ -231,14 +242,7 @@ class Zeppelin
231
242
  def device_tag_uri(device_token, tag_name)
232
243
  device_token_uri(device_token) + "/tags/#{tag_name}"
233
244
  end
234
-
235
- def successful?(response)
236
- SUCCESSFUL_STATUS_CODES.include?(response.status)
237
- end
238
-
239
- def parse(json)
240
- Yajl::Parser.parse(json)
241
- end
242
245
  end
243
246
 
247
+ require 'zeppelin/json_parser_middleware'
244
248
  require 'zeppelin/version'
@@ -0,0 +1,35 @@
1
+ require 'yajl'
2
+
3
+ class Zeppelin
4
+ # Middleware for Faraday that parses JSON response bodies. Based on code in
5
+ # the FaradayMiddleware project.
6
+ #
7
+ # @private
8
+ class JsonParserMiddleware < Faraday::Middleware
9
+ CONTENT_TYPE = 'Content-Type'
10
+
11
+ def initialize(app=nil)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ @app.call(env).on_complete do
17
+ parse_response(env) if process_content_type?(env) && parse_response?(env)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def parse_response(env)
24
+ env[:body] = Yajl::Parser.parse(env[:body])
25
+ end
26
+
27
+ def process_content_type?(env)
28
+ env[:response_headers][CONTENT_TYPE].to_s =~ /\bjson$/
29
+ end
30
+
31
+ def parse_response?(env)
32
+ env[:body].respond_to? :to_str
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  class Zeppelin
2
- VERSION = '0.4.0'
3
- end
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+
3
+ class JsonParserMiddlewareTest < Zeppelin::TestCase
4
+ def setup
5
+ @json_body = "{\"foo\":\"bar\"}"
6
+ @expected_parsed_body = { 'foo' => 'bar' }
7
+ end
8
+
9
+ test 'parses a standard JSON content type' do
10
+ assert_equal @expected_parsed_body, process(@json_body, 'application/json').body
11
+ end
12
+
13
+ test 'parses vendor JSON content type' do
14
+ assert_equal @expected_parsed_body, process(@json_body, 'application/vnd.urbanairship+json').body
15
+ end
16
+
17
+ test 'does not change nil body' do
18
+ assert process(nil).body.nil?
19
+ end
20
+
21
+ test 'does not parse non-JSON content types' do
22
+ assert_equal '<hello>world</hello>', process('<hello>world</hello>', 'text/xml').body
23
+ end
24
+
25
+ def process(body, content_type=nil, options={})
26
+ env = { :body => body, :response_headers => Faraday::Utils::Headers.new }
27
+ env[:response_headers]['content-type'] = content_type if content_type
28
+
29
+ middleware = Zeppelin::JsonParserMiddleware.new(
30
+ lambda { |env| Faraday::Response.new(env) }
31
+ )
32
+
33
+ middleware.call(env)
34
+ end
35
+ end
@@ -12,6 +12,7 @@ class ZeppelinTest < Zeppelin::TestCase
12
12
  assert_equal 'go.urbanairship.com', @client.connection.host
13
13
  assert_includes @client.connection.builder.handlers, Faraday::Adapter::NetHttp
14
14
  assert_includes @client.connection.builder.handlers, Faraday::Request::JSON
15
+ assert_includes @client.connection.builder.handlers, Zeppelin::JsonParserMiddleware
15
16
  assert_equal 'Basic YXBwIGtleTphcHAgbWFzdGVyIHNlY3JldA==', @client.connection.headers['Authorization']
16
17
  end
17
18
 
@@ -70,7 +71,7 @@ class ZeppelinTest < Zeppelin::TestCase
70
71
  response_body = { 'foo' => 'bar' }
71
72
  stub_requests @client.connection do |stub|
72
73
  stub.get("/api/device_tokens/#{@device_token}") do
73
- [200, {}, Yajl::Encoder.encode(response_body)]
74
+ [200, { 'Content-Type' => 'application/json' }, Yajl::Encoder.encode(response_body)]
74
75
  end
75
76
  end
76
77
 
@@ -160,7 +161,7 @@ class ZeppelinTest < Zeppelin::TestCase
160
161
  response_body = { 'foo' => 'bar' }
161
162
  stub_requests @client.connection do |stub|
162
163
  stub.get("/api/apids/#{@apid}") do
163
- [200, {}, Yajl::Encoder.encode(response_body)]
164
+ [200, { 'Content-Type' => 'application/json' }, Yajl::Encoder.encode(response_body)]
164
165
  end
165
166
  end
166
167
 
@@ -289,7 +290,7 @@ class ZeppelinTest < Zeppelin::TestCase
289
290
 
290
291
  stub_requests @client.connection do |stub|
291
292
  stub.get('/api/device_tokens/feedback/?since=1970-01-01T00%3A00%3A00Z') do
292
- [200, {}, Yajl::Encoder.encode(response_body)]
293
+ [200, { 'Content-Type' => 'application/json' }, Yajl::Encoder.encode(response_body)]
293
294
  end
294
295
  end
295
296
 
@@ -315,7 +316,7 @@ class ZeppelinTest < Zeppelin::TestCase
315
316
 
316
317
  stub_requests @client.connection do |stub|
317
318
  stub.get('/api/tags/') do
318
- [200, {}, Yajl::Encoder.encode(response_body)]
319
+ [200, { 'Content-Type' => 'application/json' }, Yajl::Encoder.encode(response_body)]
319
320
  end
320
321
  end
321
322
 
@@ -382,7 +383,7 @@ class ZeppelinTest < Zeppelin::TestCase
382
383
 
383
384
  stub_requests @client.connection do |stub|
384
385
  stub.get("/api/device_tokens/#{device_token}/tags/") do
385
- [200, {}, Yajl::Encoder.encode(response_body)]
386
+ [200, { 'Content-Type' => 'application/json' }, Yajl::Encoder.encode(response_body)]
386
387
  end
387
388
  end
388
389
 
@@ -11,19 +11,20 @@ Gem::Specification.new do |s|
11
11
  s.homepage = 'https://github.com/CapnKernul/zeppelin'
12
12
  s.summary = %q{Urban Airship library for Ruby}
13
13
  s.description = %q{Ruby client for the Urban Airship Push Notification API}
14
-
14
+
15
15
  s.rubyforge_project = 'zeppelin'
16
-
16
+
17
17
  s.add_dependency 'faraday'
18
18
  s.add_dependency 'yajl-ruby'
19
-
19
+
20
20
  s.add_development_dependency 'minitest', '~> 2.0'
21
21
  s.add_development_dependency 'journo'
22
22
  s.add_development_dependency 'mocha'
23
23
  s.add_development_dependency 'test_declarative'
24
-
24
+ s.add_development_dependency 'rake'
25
+
25
26
  s.files = `git ls-files`.split("\n")
26
27
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
28
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
29
  s.require_paths = ['lib']
29
- end
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeppelin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-08-19 00:00:00.000000000Z
12
+ date: 2012-02-23 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
16
- requirement: &2165051200 !ruby/object:Gem::Requirement
16
+ requirement: &70115497098880 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2165051200
24
+ version_requirements: *70115497098880
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yajl-ruby
27
- requirement: &2165050780 !ruby/object:Gem::Requirement
27
+ requirement: &70115497097860 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2165050780
35
+ version_requirements: *70115497097860
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: minitest
38
- requirement: &2165050280 !ruby/object:Gem::Requirement
38
+ requirement: &70115497095520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '2.0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2165050280
46
+ version_requirements: *70115497095520
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: journo
49
- requirement: &2165049860 !ruby/object:Gem::Requirement
49
+ requirement: &70115497094220 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2165049860
57
+ version_requirements: *70115497094220
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: mocha
60
- requirement: &2165049400 !ruby/object:Gem::Requirement
60
+ requirement: &70115497092480 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2165049400
68
+ version_requirements: *70115497092480
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: test_declarative
71
- requirement: &2165048980 !ruby/object:Gem::Requirement
71
+ requirement: &70115497090680 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,18 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2165048980
79
+ version_requirements: *70115497090680
80
+ - !ruby/object:Gem::Dependency
81
+ name: rake
82
+ requirement: &70115497089600 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70115497089600
80
91
  description: Ruby client for the Urban Airship Push Notification API
81
92
  email:
82
93
  - alex@kernul.com
@@ -93,7 +104,9 @@ files:
93
104
  - README.md
94
105
  - Rakefile
95
106
  - lib/zeppelin.rb
107
+ - lib/zeppelin/json_parser_middleware.rb
96
108
  - lib/zeppelin/version.rb
109
+ - test/json_parser_middleware_test.rb
97
110
  - test/test_helper.rb
98
111
  - test/zeppelin_test.rb
99
112
  - zeppelin.gemspec
@@ -109,18 +122,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
122
  - - ! '>='
110
123
  - !ruby/object:Gem::Version
111
124
  version: '0'
125
+ segments:
126
+ - 0
127
+ hash: 437314845617063875
112
128
  required_rubygems_version: !ruby/object:Gem::Requirement
113
129
  none: false
114
130
  requirements:
115
131
  - - ! '>='
116
132
  - !ruby/object:Gem::Version
117
133
  version: '0'
134
+ segments:
135
+ - 0
136
+ hash: 437314845617063875
118
137
  requirements: []
119
138
  rubyforge_project: zeppelin
120
- rubygems_version: 1.8.6
139
+ rubygems_version: 1.8.10
121
140
  signing_key:
122
141
  specification_version: 3
123
142
  summary: Urban Airship library for Ruby
124
143
  test_files:
144
+ - test/json_parser_middleware_test.rb
125
145
  - test/test_helper.rb
126
146
  - test/zeppelin_test.rb