exponent-server-sdk 0.0.4 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e290026a253948ce4a49f205bb465c4fdff55ca9
4
- data.tar.gz: 685948552e549b3e73a661a6926566995a9f8242
3
+ metadata.gz: 61012c4117f2e3c9357a2b99e92ad363fb956844
4
+ data.tar.gz: 7c565d390d7e8d2b737c6c6a684a14c1f988ff97
5
5
  SHA512:
6
- metadata.gz: abdab3e84de434c00a58110af875108e3f04cfe0df00e32d52c1d71b96673843de012c422849f108fa515730e253ad75d088d89c9895e9f8909dec6acdf7b409
7
- data.tar.gz: b8140c4d97ab32205fbd9b77a51b632d92264d05d5f1e642d0fecc644f85413c12c36fe4b11f394e3a85581c9de2ed314516cedbf489b8cdcd4d2b36f088fd46
6
+ metadata.gz: 0a21006404a04267901a8c607dd9dfd7c154e57f93c07da8d0299c4c26a3292a13393d6bbe60a0471f66e53efa21529f7b1919a9b55b1d463203f88d2cfe594b
7
+ data.tar.gz: 30730a58b90f42f5cb20382abeb4affd59275d6ec78be59e511e06249ffb5324b5554d39a5e21c850ba2bc8be5cf747c2bb7e9d271eb32b247ebcae6e7d60ebc
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  *.log
19
19
  .ruby-version
20
+ /.idea
data/README.md CHANGED
@@ -3,8 +3,6 @@
3
3
 
4
4
  Use to send push notifications to Exponent Experiences from a Ruby server.
5
5
 
6
- If you have problems with the code in this repository, please file issues & bug reports at https://github.com/expo/expo. Thanks!
7
-
8
6
  ## Installation
9
7
 
10
8
  Add this line to your application's Gemfile:
@@ -32,7 +30,7 @@ $ gem install exponent-server-sdk
32
30
  The push client is the preferred way. This hits the latest version of the api.
33
31
 
34
32
  ```ruby
35
- exponent = Exponent::Push::Client.new
33
+ client = Exponent::Push::Client.new
36
34
 
37
35
  messages = [{
38
36
  to: "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
@@ -44,7 +42,12 @@ messages = [{
44
42
  body: "You've got mail"
45
43
  }]
46
44
 
47
- exponent.publish messages
45
+ client.publish messages
48
46
  ```
49
47
 
50
- The complete format of the messages can be found [here.](https://docs.expo.io/versions/v16.0.0/guides/push-notifications.html#http2-api)
48
+ The complete format of the messages can be found [here.](https://docs.expo.io/versions/latest/index.html#http2-api)
49
+
50
+ ## Contributing
51
+
52
+ If you have problems with the code in this repository, please file issues & bug reports. We encourage you
53
+ to submit a solution or a failing test to reproduce your issue. Thanks!
@@ -8,104 +8,144 @@ module Exponent
8
8
  end
9
9
 
10
10
  module Push
11
- Error = Class.new(StandardError)
12
11
 
13
12
  class Client
14
-
15
- def initialize(new_http_client = nil)
16
- @http_client = new_http_client || Typhoeus
13
+ def initialize(http_client = Typhoeus,
14
+ response_handler = ResponseHandler.new)
15
+ @http_client = http_client
16
+ @response_handler = response_handler
17
17
  end
18
18
 
19
19
  def publish(messages)
20
- handle_response(push_notifications(messages))
20
+ response_handler.handle(push_notifications(messages))
21
21
  end
22
22
 
23
23
  private
24
24
 
25
- attr_reader :http_client
25
+ attr_reader :http_client, :response_handler
26
+
27
+ def push_notifications(messages)
28
+ http_client.post(
29
+ push_url,
30
+ body: messages.to_json,
31
+ headers: headers
32
+ )
33
+ end
34
+
35
+ def push_url
36
+ 'https://exp.host/--/api/v2/push/send'
37
+ end
38
+
39
+ def headers
40
+ {
41
+ 'Content-Type' => 'application/json',
42
+ 'Accept' => 'application/json'
43
+ }
44
+ end
45
+ end
46
+
47
+ class ResponseHandler
48
+ def initialize(error_builder = ErrorBuilder.new)
49
+ @error_builder = error_builder
50
+ end
26
51
 
27
- def handle_response(response)
52
+ def handle(response)
28
53
  case response.code.to_s
29
54
  when /(^4|^5)/
30
- raise Error, build_error_from_failure(parse_json(response))
55
+ raise build_error_from_failure(parse_json(response))
31
56
  else
32
57
  handle_success(parse_json(response))
33
58
  end
34
59
  end
35
60
 
61
+ private
62
+
63
+ attr_reader :error_builder
64
+
36
65
  def parse_json(response)
37
66
  JSON.parse(response.body)
38
67
  end
39
68
 
40
69
  def build_error_from_failure(response)
41
- build_error_with_handling(response) do
42
- extract_error_from_response(response)
43
- end
70
+ error_builder.build_from_erroneous(response)
44
71
  end
45
72
 
46
- def extract_error_from_response(response)
47
- error = response.fetch('errors').first { unknown_error_format(response) }
48
- "#{error.fetch('code')} -> #{error.fetch('message')}"
73
+ def handle_success(response)
74
+ extract_data(response).tap do |data|
75
+ validate_status(data.fetch('status'), response)
76
+ end
49
77
  end
50
78
 
51
- def build_error_with_handling(response)
52
- yield(response)
53
- rescue KeyError
54
- unknown_error_format(response)
79
+ def extract_data(response)
80
+ response.fetch('data').first
55
81
  end
56
82
 
57
- def push_notifications(messages)
58
- http_client.post(
59
- push_url,
60
- body: messages.to_json,
61
- headers: headers
62
- )
83
+ def validate_status(status, response)
84
+ raise build_error_from_success(response) unless status == 'ok'
63
85
  end
64
86
 
65
- def push_url
66
- 'https://exp.host/--/api/v2/push/send'
87
+ def build_error_from_success(response)
88
+ error_builder.build_from_successful(response)
67
89
  end
90
+ end
68
91
 
69
- def headers
70
- {
71
- 'Content-Type' => 'application/json',
72
- 'Accept' => 'application/json'
73
- }
92
+ class ErrorBuilder
93
+ %i[erroneous successful].each do |selector|
94
+ define_method(:"build_from_#{selector}") do |response|
95
+ with_error_handling(response) do
96
+ send "from_#{selector}_response", response
97
+ end
98
+ end
74
99
  end
75
100
 
76
- def handle_success(response)
77
- data = extract_data(response)
78
- if data.fetch('status') == 'ok'
79
- data
80
- else
81
- raise Error, build_error_from_success(response)
82
- end
101
+ private
102
+
103
+ def with_error_handling(response)
104
+ yield(response)
105
+ rescue KeyError
106
+ unknown_error_format(response)
83
107
  end
84
108
 
85
- def build_error_from_success(response)
86
- build_error_with_handling(response) do
87
- extract_error_from_success(response)
88
- end
109
+ def from_erroneous_response(response)
110
+ error = response.fetch('errors').first
111
+ error_name = error.fetch('code')
112
+ message = error.fetch('message')
113
+
114
+ get_error_class(error_name).new(message)
89
115
  end
90
116
 
91
- def extract_error_from_success(response)
92
- data = extract_data(response)
117
+ def from_successful_response(response)
118
+ data = response.fetch('data').first
93
119
  message = data.fetch('message')
94
120
 
95
- if data['details']
96
- "#{data.fetch('details').fetch('error')} -> #{message}"
97
- else
98
- "#{data.fetch('status')} -> #{message}"
99
- end
121
+ get_error_class(data.fetch('details').fetch('error')).new(message)
100
122
  end
101
123
 
102
- def extract_data(response)
103
- response.fetch('data').first
124
+ def validate_error_name(condition)
125
+ condition ? yield : Exponent::Push::UnknownError
126
+ end
127
+
128
+ def get_error_class(error_name)
129
+ validate_error_name(Exponent::Push.error_names.include?(error_name)) do
130
+ Exponent::Push.const_get("#{error_name}Error")
131
+ end
104
132
  end
105
133
 
106
134
  def unknown_error_format(response)
107
- "Unknown error format: #{response}"
135
+ Exponent::Push::UnknownError.new("Unknown error format: #{response}")
108
136
  end
109
137
  end
138
+
139
+ Error = Class.new(StandardError)
140
+
141
+ def self.error_names
142
+ %w[DeviceNotRegistered MessageTooBig
143
+ MessageRateExceeded InvalidCredentials
144
+ Unknown]
145
+ end
146
+
147
+ error_names.each do |error_name|
148
+ const_set "#{error_name}Error", Class.new(Error)
149
+ end
110
150
  end
111
151
  end
@@ -1,3 +1,3 @@
1
1
  module Exponent
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'.freeze
3
3
  end
@@ -19,14 +19,14 @@ class ExponentServerSdkTest < Minitest::Test
19
19
  @mock.verify
20
20
  end
21
21
 
22
- def test_publish_with_error
22
+ def test_publish_with_unknown_error
23
23
  @response_mock.expect(:code, 400)
24
24
  @response_mock.expect(:body, error_body.to_json)
25
- message = 'INTERNAL_SERVER_ERROR -> An unknown error occurred.'
25
+ message = 'An unknown error occurred.'
26
26
 
27
27
  @mock.expect(:post, @response_mock, client_args)
28
28
 
29
- exception = assert_raises Exponent::Push::Error do
29
+ exception = assert_raises Exponent::Push::UnknownError do
30
30
  @exponent.publish(messages)
31
31
  end
32
32
 
@@ -35,14 +35,14 @@ class ExponentServerSdkTest < Minitest::Test
35
35
  @mock.verify
36
36
  end
37
37
 
38
- def test_publish_with_success_and_errors
38
+ def test_publish_with_device_not_registered_error
39
39
  @response_mock.expect(:code, 200)
40
- @response_mock.expect(:body, success_with_error_body.to_json)
41
- message = 'DeviceNotRegistered -> "ExponentPushToken[42]" is not a registered push notification recipient'
40
+ @response_mock.expect(:body, not_registered_device_error_body.to_json)
41
+ message = '"ExponentPushToken[42]" is not a registered push notification recipient'
42
42
 
43
43
  @mock.expect(:post, @response_mock, client_args)
44
44
 
45
- exception = assert_raises Exponent::Push::Error do
45
+ exception = assert_raises Exponent::Push::DeviceNotRegisteredError do
46
46
  @exponent.publish(messages)
47
47
  end
48
48
 
@@ -51,14 +51,14 @@ class ExponentServerSdkTest < Minitest::Test
51
51
  @mock.verify
52
52
  end
53
53
 
54
- def test_publish_with_success_and_apn_error
54
+ def test_publish_with_message_too_big_error
55
55
  @response_mock.expect(:code, 200)
56
- @response_mock.expect(:body, success_with_apn_error_body.to_json)
57
- message = 'error -> Could not find APNs credentials for you (your_app). Check whether you are trying to send a notification to a detached app.'
56
+ @response_mock.expect(:body, message_too_big_error_body.to_json)
57
+ message = 'Message too big'
58
58
 
59
59
  @mock.expect(:post, @response_mock, client_args)
60
60
 
61
- exception = assert_raises Exponent::Push::Error do
61
+ exception = assert_raises Exponent::Push::MessageTooBigError do
62
62
  @exponent.publish(messages)
63
63
  end
64
64
 
@@ -67,6 +67,53 @@ class ExponentServerSdkTest < Minitest::Test
67
67
  @mock.verify
68
68
  end
69
69
 
70
+ def test_publish_with_message_rate_exceeded_error
71
+ @response_mock.expect(:code, 200)
72
+ @response_mock.expect(:body, message_rate_exceeded_error_body.to_json)
73
+ message = 'Message rate exceeded'
74
+
75
+ @mock.expect(:post, @response_mock, client_args)
76
+
77
+ exception = assert_raises Exponent::Push::MessageRateExceededError do
78
+ @exponent.publish(messages)
79
+ end
80
+
81
+ assert_equal(message, exception.message)
82
+
83
+ @mock.verify
84
+ end
85
+
86
+ def test_publish_with_invalid_credentials_error
87
+ @response_mock.expect(:code, 200)
88
+ @response_mock.expect(:body, invalid_credentials_error_body.to_json)
89
+ message = 'Invalid credentials'
90
+
91
+ @mock.expect(:post, @response_mock, client_args)
92
+
93
+ exception = assert_raises Exponent::Push::InvalidCredentialsError do
94
+ @exponent.publish(messages)
95
+ end
96
+
97
+ assert_equal(message, exception.message)
98
+
99
+ @mock.verify
100
+ end
101
+
102
+ def test_publish_with_apn_error
103
+ @response_mock.expect(:code, 200)
104
+ @response_mock.expect(:body, apn_error_body.to_json)
105
+
106
+ @mock.expect(:post, @response_mock, client_args)
107
+
108
+ exception = assert_raises Exponent::Push::UnknownError do
109
+ @exponent.publish(messages)
110
+ end
111
+
112
+ assert_match(/Unknown error format/, exception.message)
113
+
114
+ @mock.verify
115
+ end
116
+
70
117
  private
71
118
 
72
119
  def success_body
@@ -82,17 +129,26 @@ class ExponentServerSdkTest < Minitest::Test
82
129
  }
83
130
  end
84
131
 
85
- def success_with_error_body
86
- {
87
- 'data' => [{
88
- 'status' => 'error',
89
- 'message' => '"ExponentPushToken[42]" is not a registered push notification recipient',
90
- 'details' => { 'error' => 'DeviceNotRegistered' }
91
- }]
92
- }
132
+ def message_too_big_error_body
133
+ build_error_body('MessageTooBig', 'Message too big')
93
134
  end
94
135
 
95
- def success_with_apn_error_body
136
+ def not_registered_device_error_body
137
+ build_error_body(
138
+ 'DeviceNotRegistered',
139
+ '"ExponentPushToken[42]" is not a registered push notification recipient'
140
+ )
141
+ end
142
+
143
+ def message_rate_exceeded_error_body
144
+ build_error_body('MessageRateExceeded', 'Message rate exceeded')
145
+ end
146
+
147
+ def invalid_credentials_error_body
148
+ build_error_body('InvalidCredentials', 'Invalid credentials')
149
+ end
150
+
151
+ def apn_error_body
96
152
  {
97
153
  'data' => [{
98
154
  'status' => 'error',
@@ -126,4 +182,14 @@ class ExponentServerSdkTest < Minitest::Test
126
182
  body: "You've got mail"
127
183
  }]
128
184
  end
185
+
186
+ def build_error_body(error_code, message)
187
+ {
188
+ 'data' => [{
189
+ 'status' => 'error',
190
+ 'message' => message,
191
+ 'details' => { 'error' => error_code }
192
+ }]
193
+ }
194
+ end
129
195
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exponent-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Ruder
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-02-26 00:00:00.000000000 Z
12
+ date: 2018-03-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: typhoeus