customerio 2.2.1 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,17 +1,7 @@
1
1
  ---
2
- !binary "U0hBMjU2":
3
- metadata.gz: !binary |-
4
- NGJmMGRlMjNmNzZmNzFiZDI5NjM4YzRhNTczOWY1MzUzY2Y1MzVhMmMwZDg3
5
- YTcyNTM4NDY5MjM4OTBiOTNlOA==
6
- data.tar.gz: !binary |-
7
- NDE5YWFjYmM4NjQxYWU2YThhZDNhYzM0ZDdmOTU2OWExNWZmODM4YjEwY2Ri
8
- ZTZlOWIzNzU5ZDUyZmY4ZDg5Yg==
2
+ SHA256:
3
+ metadata.gz: fbff37c21ea57a934d671356e89ef06114170d0b753c91048c255b399ce146de
4
+ data.tar.gz: 1d225801d24005870b63aa2f95850b47cef6ef822f7f3132ea9b7378cffaa852
9
5
  SHA512:
10
- metadata.gz: !binary |-
11
- MDkxMTRkNGZjYWQyYjIzZjEwODM2YzQxN2Y4YzVmZmZiOWE5YmU3NjUwYzdi
12
- Zjg5NzE2ZDg1YWI0YTg2MDY5NTkzM2QwOWNlNTAwN2U3MThmODMwODU5YTVi
13
- YzA1ZmM2OTFmYzVkNWIxMjhhMGViNjhhODQ1ZWJkODIxODlhODg=
14
- data.tar.gz: !binary |-
15
- YjNiNTRlZTNkMjg2NGEwNjNiZmEyMTQyZWZhNTMyYjgxZDI1MDkxOGM4MDI1
16
- NjRlY2U4OTBmMTM0ZTM1ZDU5M2FkNjJjNjE2ZjhmZTRiMzE0ZmE1MWJkMTQx
17
- NDE1MDNhMWYzYmFmZTBlMTJlZDU5OGQzOGQwYjM5ZWNkMGZhOTU=
6
+ metadata.gz: c41f22bc7fe6038432cd9e854c5bd18cd4b117f2320d02c3e142f5256cf0626de05e982db9676fdc3feed48cd26a6343e10717c196ab0e22b7d3798d4b5bb444
7
+ data.tar.gz: f70520dda7f7defab832cc4efc85771c10e3e042aff034512a261b630f73db2f63474b8103dbf43e4e98be29a0e03a20b0993f1a9e31871f84e2fad41d6d0f8c
@@ -0,0 +1,30 @@
1
+ name: ruby_ci
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ matrix:
9
+ ruby: ['2.5', '2.6', '2.7']
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - name: set up ruby
14
+ uses: actions/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ - name: install bundle
18
+ run: |
19
+ sudo apt-get -yqq install libpq-dev
20
+ gem install bundler
21
+ - name: Cache dependencies
22
+ uses: actions/cache@v2
23
+ with:
24
+ path: vendor/bundle
25
+ key: customerio-${{ matrix.ruby }}-${{ hashFiles('customerio.gemspec') }}
26
+ restore-keys: |
27
+ customerio-${{ matrix.ruby }}-
28
+ - name: Install dependencies
29
+ run: bundle install --path vendor/bundle
30
+ - run: bundle exec rspec
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,51 @@
1
+ ## Customerio 4.0.1 - July 13, 2021
2
+ ### Changed
3
+ - Update addressable gem dependency to v2.8.0
4
+
5
+ ## Customerio 4.0.0 - July 6, 2021
6
+ ### Removed
7
+ - The `anonymous_track` method.
8
+
9
+ ### Added
10
+ - The `track_anonymous` method replaces `anonymous_track`. This method requires an `anonymous_id` parameter and will no longer trigger campaigns. If you previously used anonymous events to trigger campaigns, you can still do so [directly through the API](https://customer.io/docs/api/#operation/trackAnonymous). We now refer to anonymous events that trigger campaigns as ["invite events"](https://customer.io/docs/anonymous-events/#anonymous-or-invite).
11
+
12
+ ## Customerio 3.1.0 - March 25, 2021
13
+ ### Added
14
+ - Support for EU region
15
+
16
+ ### Removed
17
+ ### Changed
18
+ - `Customerio::Client` and `CustomerIO::APIClient` have a new parameter `region` that can be set to either `Customerio::Regions::EU` or `Customerio::Regions::US` (defaults to `Customerio::Regions::US`)
19
+
20
+ ## Customerio 3.0.0 - Dec 2, 2020
21
+
22
+ ### Added
23
+ - Support for the Transactional API
24
+
25
+ ### Removed
26
+ - `add_to_segment` and `remove_from_segment` methods
27
+ - Support for non-JSON data
28
+
29
+ ### Changed
30
+ - IDs in the URLs are now escaped.
31
+ - Improved validations for data that's passed in.
32
+ - Earlier, if you passed in an event name without a customer ID to the `track` method, we would create an anonymous event. That is now removed. To create an anonymous event, use the `anonymous_track` method.
33
+
34
+
35
+ ## Customerio 3.0.0 - Dec 2, 2020
36
+
37
+ ### Added
38
+ - Support for the Transactional API
39
+
40
+ ### Removed
41
+ - `add_to_segment` and `remove_from_segment` methods
42
+ - Support for non-JSON data
43
+
44
+ ### Changed
45
+ - IDs in the URLs are now escaped.
46
+ - Improved validations for data that's passed in.
47
+ - Earlier, if you passed in an event name without a customer ID to the `track` method, we would create an anonymous event. That is now removed. To create an anonymous event, use the `anonymous_track` method.
48
+
1
49
  ## Customerio 2.2.1 - Mar 23, 2020
2
50
 
3
51
  - Add license to gemspec [#55](https://github.com/customerio/customerio-ruby/pull/55)
data/README.md CHANGED
@@ -42,20 +42,15 @@ You'll be able to integrate **fully** with [Customer.io](http://customer.io) wit
42
42
 
43
43
  ### Setup
44
44
 
45
- Create an instance of the client with your [customer.io](http://customer.io) credentials
46
- which can be found on the [customer.io integration screen](https://fly.customer.io/account/customerio_integration).
45
+ Create an instance of the client with your [Customer.io credentials](https://fly.customer.io/settings/api_credentials).
47
46
 
48
47
  If you're using Rails, create an initializer `config/initializers/customerio.rb`:
49
48
 
50
49
  ```ruby
51
- $customerio = Customerio::Client.new("YOUR SITE ID", "YOUR API SECRET KEY")
50
+ $customerio = Customerio::Client.new("YOUR SITE ID", "YOUR API SECRET KEY", region: Customerio::Regions::US)
52
51
  ```
53
52
 
54
- If you'd like to send complex data to associate to a user as json, pass a json option:
55
-
56
- ```ruby
57
- customerio = Customerio::Client.new("YOUR SITE ID", "YOUR API SECRET KEY", :json => true)
58
- ```
53
+ `region` is optional and takes one of two values—`US` or `EU`. If you do not specify your region, we assume that your account is based in the US (`US`). If your account is based in the EU and you do not provide the correct region (`EU`), we'll route requests to our EU data centers accordingly, however this may cause data to be logged in the US.
59
54
 
60
55
  ### Identify logged in customers
61
56
 
@@ -127,7 +122,7 @@ encourage your customers to perform an action.
127
122
  $customerio.track(5, "purchase", :type => "socks", :price => "13.99")
128
123
  ```
129
124
 
130
- **Note:** If you'd like to track events which occurred in the past, you can include a `timestamp` attribute
125
+ **Note:** If you want to track events which occurred in the past, you can include a `timestamp` attribute
131
126
  (in seconds since the epoch), and we'll use that as the date the event occurred.
132
127
 
133
128
  ```ruby
@@ -136,10 +131,18 @@ $customerio.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp
136
131
 
137
132
  ### Tracking anonymous events
138
133
 
139
- You can also send anonymous events, for situations where you don't yet have a customer record but still want to trigger a campaign:
134
+ You can also send anonymous events, for situations where you don't yet have a customer record yet. An anonymous event requires an `anonymous_id` representing the unknown person and an event `name`. When you identify a person, you can set their `anonymous_id` attribute. If [event merging](https://customer.io/docs/anonymous-events/#turn-on-merging) is turned on in your workspace, and the attribute matches the `anonymous_id` in one or more events that were logged within the last 30 days, we associate those events with the person.
135
+
136
+ Anonymous events cannot trigger campaigns by themselves. To trigger a campaign, the anonymous event must be associated with a person within 72 hours of the `track_anonymous` request.
140
137
 
141
138
  ```ruby
142
- $customerio.anonymous_track("help_enquiry", :recipient => 'user@example.com')
139
+ # Arguments
140
+ # anonymous_id (required) - the id representing the unknown person.
141
+ # name (required) - the name of the event you want to track.
142
+ # attributes (optional) - any related information you want to attach to the
143
+ # event.
144
+
145
+ $customerio.track_anonymous(anonymous_id, "product_view", :type => "socks" )
143
146
  ```
144
147
 
145
148
  Use the `recipient` attribute to specify the email address to send the messages to. [See our documentation on how to use anonymous events for more details](https://learn.customer.io/recipes/invite-emails.html).
@@ -183,20 +186,51 @@ Start tracking events and identifies again for a previously suppressed customer.
183
186
  $customerio.unsuppress(5)
184
187
  ```
185
188
 
186
- ### Add customers to a manual segment
189
+ ### Send Transactional Messages
187
190
 
188
- Add the list of customer ids to the specified manual segment. If you send customer ids that don't exist yet in an add_to_segment request, we will automatically create customer profiles for the new customer ids.
191
+ To use the Customer.io [Transactional API](https://customer.io/docs/transactional-api), create an instance of the API client using an [app key](https://customer.io/docs/managing-credentials#app-api-keys).
189
192
 
190
- ```ruby
191
- $customerio.add_to_segment(segment_id=1,customer_ids=['1','2','3'])
192
- ```
193
+ Create a new `SendEmailRequest` object containing:
194
+
195
+ * `transactional_message_id`: the ID of the transactional message you want to send, or the `body`, `from`, and `subject` of a new message.
196
+ * `to`: the email address of your recipients
197
+ * an `identifiers` object containing the `id` of your recipient. If the `id` does not exist, Customer.io creates it.
198
+ * a `message_data` object containing properties that you want reference in your message using liquid.
199
+ * You can also send attachments with your message. Use `attach` to encode attachments.
193
200
 
194
- ### Remove customers from a manual segment
201
+ Use `send_email` referencing your request to send a transactional message. [Learn more about transactional messages and `SendEmailRequest` properties](https://customer.io/docs/transactional-api).
195
202
 
196
- Remove the list of customer ids from the specified manual segment.
197
203
 
198
204
  ```ruby
199
- $customerio.remove_from_segment(segment_id=1,customer_ids=['1','2','3'])
205
+ require "customerio"
206
+
207
+ client = Customerio::APIClient.new("your API key", region: Customerio::Regions::US)
208
+
209
+ request = Customerio::SendEmailRequest.new(
210
+ to: "person@example.com",
211
+ transactional_message_id: "3",
212
+ message_data: {
213
+ name: "Person",
214
+ items: {
215
+ name: "shoes",
216
+ price: "59.99",
217
+ },
218
+ products: [],
219
+ },
220
+ identifiers: {
221
+ id: "2",
222
+ },
223
+ )
224
+
225
+ file = File.open('<file-path>', 'r')
226
+ request.attach("filename", file.read)
227
+
228
+ begin
229
+ response = client.send_email(request)
230
+ puts response
231
+ rescue Customerio::InvalidResponse => e
232
+ puts e.code, e.message
233
+ end
200
234
  ```
201
235
 
202
236
  ## Contributing
data/customerio.gemspec CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |gem|
17
17
  gem.version = Customerio::VERSION
18
18
 
19
19
  gem.add_dependency('multi_json', "~> 1.0")
20
+ gem.add_dependency('addressable', '~> 2.8.0')
20
21
 
21
22
  gem.add_development_dependency('rake', '~> 10.5')
22
23
  gem.add_development_dependency('rspec', '3.3.0')
23
- gem.add_development_dependency('webmock', '1.24.2')
24
- gem.add_development_dependency('addressable', '~> 2.3.6')
24
+ gem.add_development_dependency('webmock', '3.6.0')
25
25
  gem.add_development_dependency('json')
26
26
  end
data/lib/customerio.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require "customerio/version"
2
2
 
3
3
  module Customerio
4
+ require "customerio/regions"
5
+ require "customerio/base_client"
4
6
  require "customerio/client"
7
+ require "customerio/requests/send_email_request"
8
+ require "customerio/api"
5
9
  require "customerio/param_encoder"
6
10
  end
@@ -0,0 +1,35 @@
1
+ require 'net/http'
2
+ require 'multi_json'
3
+
4
+ module Customerio
5
+ class APIClient
6
+ def initialize(app_key, options = {})
7
+ options[:region] = Customerio::Regions::US if options[:region].nil?
8
+ raise "region must be an instance of Customerio::Regions::Region" unless options[:region].is_a?(Customerio::Regions::Region)
9
+
10
+ options[:url] = options[:region].api_url if options[:url].nil? || options[:url].empty?
11
+ @client = Customerio::BaseClient.new({ app_key: app_key }, options)
12
+ end
13
+
14
+ def send_email(req)
15
+ raise "request must be an instance of Customerio::SendEmailRequest" unless req.is_a?(Customerio::SendEmailRequest)
16
+ response = @client.request(:post, send_email_path, req.message)
17
+
18
+ case response
19
+ when Net::HTTPSuccess then
20
+ JSON.parse(response.body)
21
+ when Net::HTTPBadRequest then
22
+ json = JSON.parse(response.body)
23
+ raise Customerio::InvalidResponse.new(response.code, json['meta']['error'], response)
24
+ else
25
+ raise InvalidResponse.new(response.code, response.body)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def send_email_path
32
+ "/v1/send/email"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,87 @@
1
+ require 'net/http'
2
+ require 'multi_json'
3
+
4
+ module Customerio
5
+ DEFAULT_TIMEOUT = 10
6
+
7
+ class InvalidRequest < RuntimeError; end
8
+ class InvalidResponse < RuntimeError
9
+ attr_reader :code, :response
10
+
11
+ def initialize(code, body, response=nil)
12
+ @message = body
13
+ @code = code
14
+ @response = response
15
+
16
+ super(@message)
17
+ end
18
+ end
19
+
20
+ class BaseClient
21
+ def initialize(auth, options = {})
22
+ @auth = auth
23
+ @timeout = options[:timeout] || DEFAULT_TIMEOUT
24
+ @base_uri = options[:url]
25
+ end
26
+
27
+ def request(method, path, body = nil, headers = {})
28
+ execute(method, path, body, headers)
29
+ end
30
+
31
+ def request_and_verify_response(method, path, body = nil, headers = {})
32
+ verify_response(request(method, path, body, headers))
33
+ end
34
+
35
+ private
36
+
37
+ def execute(method, path, body = nil, headers = {})
38
+ uri = URI.join(@base_uri, path)
39
+
40
+ session = Net::HTTP.new(uri.host, uri.port)
41
+ session.use_ssl = (uri.scheme == 'https')
42
+ session.open_timeout = @timeout
43
+ session.read_timeout = @timeout
44
+
45
+ req = request_class(method).new(uri.path)
46
+
47
+ if @auth.has_key?(:site_id) && @auth.has_key?(:api_key)
48
+ req.initialize_http_header(headers)
49
+ req.basic_auth @auth[:site_id], @auth[:api_key]
50
+ else
51
+ headers['Authorization'] = "Bearer #{@auth[:app_key]}"
52
+ req.initialize_http_header(headers)
53
+ end
54
+
55
+ if !body.nil?
56
+ req.add_field('Content-Type', 'application/json')
57
+ req.body = MultiJson.dump(body)
58
+ end
59
+
60
+ session.start do |http|
61
+ http.request(req)
62
+ end
63
+ end
64
+
65
+ def request_class(method)
66
+ case method
67
+ when :post
68
+ Net::HTTP::Post
69
+ when :put
70
+ Net::HTTP::Put
71
+ when :delete
72
+ Net::HTTP::Delete
73
+ else
74
+ raise InvalidRequest.new("Invalid request method #{method.inspect}")
75
+ end
76
+ end
77
+
78
+ def verify_response(response)
79
+ case response
80
+ when Net::HTTPSuccess then
81
+ response
82
+ else
83
+ raise InvalidResponse.new(response.code, response.body, response)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,29 +1,22 @@
1
- require 'net/http'
2
- require 'multi_json'
1
+ require "addressable/uri"
3
2
 
4
3
  module Customerio
5
- DEFAULT_BASE_URI = 'https://track.customer.io'
6
- DEFAULT_TIMEOUT = 10
7
-
8
4
  class Client
5
+ PUSH_OPENED = 'opened'
6
+ PUSH_CONVERTED = 'converted'
7
+ PUSH_DELIVERED = 'delivered'
8
+
9
+ VALID_PUSH_EVENTS = [PUSH_OPENED, PUSH_CONVERTED, PUSH_DELIVERED]
10
+
9
11
  class MissingIdAttributeError < RuntimeError; end
10
12
  class ParamError < RuntimeError; end
11
- class InvalidRequest < RuntimeError; end
12
- class InvalidResponse < RuntimeError
13
- attr_reader :response
14
13
 
15
- def initialize(message, response)
16
- super(message)
17
- @response = response
18
- end
19
- end
14
+ def initialize(site_id, api_key, options = {})
15
+ options[:region] = Customerio::Regions::US if options[:region].nil?
16
+ raise "region must be an instance of Customerio::Regions::Region" unless options[:region].is_a?(Customerio::Regions::Region)
20
17
 
21
- def initialize(site_id, secret_key, options = {})
22
- @username = site_id
23
- @password = secret_key
24
- @json = options.has_key?(:json) ? options[:json] : true
25
- @base_uri = options[:base_uri] || DEFAULT_BASE_URI
26
- @timeout = options[:timeout] || DEFAULT_TIMEOUT
18
+ options[:url] = options[:region].track_url if options[:url].nil? || options[:url].empty?
19
+ @client = Customerio::BaseClient.new({ site_id: site_id, api_key: api_key }, options)
27
20
  end
28
21
 
29
22
  def identify(attributes)
@@ -31,41 +24,38 @@ module Customerio
31
24
  end
32
25
 
33
26
  def delete(customer_id)
34
- verify_response(request(:delete, customer_path(customer_id)))
27
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
28
+ @client.request_and_verify_response(:delete, customer_path(customer_id))
35
29
  end
36
30
 
37
31
  def suppress(customer_id)
38
- verify_response(request(:post, suppress_path(customer_id)))
32
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
33
+ @client.request_and_verify_response(:post, suppress_path(customer_id))
39
34
  end
40
35
 
41
36
  def unsuppress(customer_id)
42
- verify_response(request(:post, unsuppress_path(customer_id)))
37
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
38
+ @client.request_and_verify_response(:post, unsuppress_path(customer_id))
43
39
  end
44
40
 
45
- def track(*args)
46
- attributes = extract_attributes(args)
47
-
48
- if args.length == 1
49
- # Only passed in an event name, create an anonymous event
50
- event_name = args.first
51
- create_anonymous_event(event_name, attributes)
52
- else
53
- # Passed in a customer id and an event name.
54
- # Track the event for the given customer
55
- customer_id, event_name = args
41
+ def track(customer_id, event_name, attributes = {})
42
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
43
+ raise ParamError.new("event_name must be a non-empty string") if is_empty?(event_name)
56
44
 
57
- create_customer_event(customer_id, event_name, attributes)
58
- end
45
+ create_customer_event(customer_id, event_name, attributes)
59
46
  end
60
47
 
61
- def anonymous_track(event_name, attributes = {})
62
- create_anonymous_event(event_name, attributes)
48
+ def track_anonymous(anonymous_id, event_name, attributes = {})
49
+ raise ParamError.new("anonymous_id must be a non-empty string") if is_empty?(anonymous_id)
50
+ raise ParamError.new("event_name must be a non-empty string") if is_empty?(event_name)
51
+
52
+ create_anonymous_event(anonymous_id, event_name, attributes)
63
53
  end
64
54
 
65
55
  def add_device(customer_id, device_id, platform, data={})
66
- raise ParamError.new("customer_id must be a non-empty string") unless customer_id != "" and !customer_id.nil?
67
- raise ParamError.new("device_id must be a non-empty string") unless device_id != "" and !device_id.nil?
68
- raise ParamError.new("platform must be a non-empty string") unless platform != "" and !platform.nil?
56
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
57
+ raise ParamError.new("device_id must be a non-empty string") if is_empty?(device_id)
58
+ raise ParamError.new("platform must be a non-empty string") if is_empty?(platform)
69
59
 
70
60
  if data.nil?
71
61
  data = {}
@@ -73,155 +63,104 @@ module Customerio
73
63
 
74
64
  raise ParamError.new("data parameter must be a hash") unless data.is_a?(Hash)
75
65
 
76
- verify_response(request(:put, device_path(customer_id), {
66
+ @client.request_and_verify_response(:put, device_path(customer_id), {
77
67
  :device => data.update({
78
68
  :id => device_id,
79
69
  :platform => platform,
80
70
  })
81
- }))
71
+ })
82
72
  end
83
73
 
84
74
  def delete_device(customer_id, device_id)
85
- raise ParamError.new("customer_id must be a non-empty string") unless customer_id != "" and !customer_id.nil?
86
- raise ParamError.new("device_id must be a non-empty string") unless device_id != "" and !device_id.nil?
87
-
88
- verify_response(request(:delete, device_id_path(customer_id, device_id)))
89
- end
75
+ raise ParamError.new("customer_id must be a non-empty string") if is_empty?(customer_id)
76
+ raise ParamError.new("device_id must be a non-empty string") if is_empty?(device_id)
90
77
 
91
- def add_to_segment(segment_id, customer_ids)
92
- raise ParamError.new("segment_id must be an integer") unless segment_id.is_a? Integer
93
- raise ParamError.new("customer_ids must be a list of values") unless customer_ids.is_a? Array
94
-
95
- customer_ids = customer_ids.map{ |id| id.to_s }
96
-
97
- verify_response(request(:post, add_to_segment_path(segment_id), {
98
- :ids => customer_ids,
99
- }))
78
+ @client.request_and_verify_response(:delete, device_id_path(customer_id, device_id))
100
79
  end
101
80
 
102
- def remove_from_segment(segment_id, customer_ids)
103
- raise ParamError.new("segment_id must be an integer") unless segment_id.is_a? Integer
104
- raise ParamError.new("customer_ids must be a list of values") unless customer_ids.is_a? Array
81
+ def track_push_notification_event(event_name, attributes = {})
82
+ keys = [:delivery_id, :device_id, :timestamp]
83
+ attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }].
84
+ select { |k, v| keys.include?(k) }
85
+
86
+ raise ParamError.new('event_name must be one of opened, converted, or delivered') unless VALID_PUSH_EVENTS.include?(event_name)
87
+ raise ParamError.new('delivery_id must be a non-empty string') unless attributes[:delivery_id] != "" and !attributes[:delivery_id].nil?
88
+ raise ParamError.new('device_id must be a non-empty string') unless attributes[:device_id] != "" and !attributes[:device_id].nil?
89
+ raise ParamError.new('timestamp must be a valid timestamp') unless valid_timestamp?(attributes[:timestamp])
105
90
 
106
- customer_ids = customer_ids.map{ |id| id.to_s }
107
-
108
- verify_response(request(:post, remove_from_segment_path(segment_id), {
109
- :ids => customer_ids,
110
- }))
91
+ @client.request_and_verify_response(:post, track_push_notification_event_path, attributes.merge(event: event_name))
111
92
  end
112
93
 
113
94
  private
114
95
 
115
- def add_to_segment_path(segment_id)
116
- "/api/v1/segments/#{segment_id}/add_customers"
117
- end
118
-
119
- def remove_from_segment_path(segment_id)
120
- "/api/v1/segments/#{segment_id}/remove_customers"
96
+ def escape(val)
97
+ # CGI.escape is recommended for escaping, however, it doesn't correctly escape spaces.
98
+ Addressable::URI.encode_component(val.to_s, Addressable::URI::CharacterClasses::UNRESERVED)
121
99
  end
122
100
 
123
101
  def device_path(customer_id)
124
- "/api/v1/customers/#{customer_id}/devices"
102
+ "/api/v1/customers/#{escape(customer_id)}/devices"
125
103
  end
126
104
 
127
105
  def device_id_path(customer_id, device_id)
128
- "/api/v1/customers/#{customer_id}/devices/#{device_id}"
129
- end
130
-
131
- def create_or_update(attributes = {})
132
- attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }]
133
-
134
- raise MissingIdAttributeError.new("Must provide a customer id") unless attributes[:id]
135
-
136
- url = customer_path(attributes[:id])
137
-
138
- verify_response(request(:put, url, attributes))
139
- end
140
-
141
- def create_customer_event(customer_id, event_name, attributes = {})
142
- create_event("#{customer_path(customer_id)}/events", event_name, attributes)
143
- end
144
-
145
- def create_anonymous_event(event_name, attributes = {})
146
- create_event("/api/v1/events", event_name, attributes)
147
- end
148
-
149
- def create_event(url, event_name, attributes = {})
150
- body = { :name => event_name, :data => attributes }
151
- body[:timestamp] = attributes[:timestamp] if valid_timestamp?(attributes[:timestamp])
152
- verify_response(request(:post, url, body))
106
+ "/api/v1/customers/#{escape(customer_id)}/devices/#{escape(device_id)}"
153
107
  end
154
108
 
155
109
  def customer_path(id)
156
- "/api/v1/customers/#{id}"
110
+ "/api/v1/customers/#{escape(id)}"
157
111
  end
158
112
 
159
113
  def suppress_path(customer_id)
160
- "/api/v1/customers/#{customer_id}/suppress"
114
+ "/api/v1/customers/#{escape(customer_id)}/suppress"
161
115
  end
162
116
 
163
117
  def unsuppress_path(customer_id)
164
- "/api/v1/customers/#{customer_id}/unsuppress"
118
+ "/api/v1/customers/#{escape(customer_id)}/unsuppress"
165
119
  end
166
120
 
167
- def valid_timestamp?(timestamp)
168
- timestamp && timestamp.is_a?(Integer) && timestamp > 999999999 && timestamp < 100000000000
121
+ def track_push_notification_event_path
122
+ "/push/events"
169
123
  end
170
124
 
125
+ def create_or_update(attributes = {})
126
+ attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }]
127
+ raise MissingIdAttributeError.new("Must provide a customer id") if is_empty?(attributes[:id])
171
128
 
172
- def verify_response(response)
173
- if response.code.to_i >= 200 && response.code.to_i < 300
174
- response
175
- else
176
- raise InvalidResponse.new("Customer.io API returned an invalid response: #{response.code}", response)
177
- end
129
+ url = customer_path(attributes[:id])
130
+ @client.request_and_verify_response(:put, url, attributes)
178
131
  end
179
132
 
180
- def extract_attributes(args)
181
- hash = args.last.is_a?(Hash) ? args.pop : {}
182
- hash.inject({}){ |hash, (k,v)| hash[k.to_sym] = v; hash }
133
+ def create_customer_event(customer_id, event_name, attributes = {})
134
+ create_event(
135
+ url: "#{customer_path(customer_id)}/events",
136
+ event_name: event_name,
137
+ attributes: attributes
138
+ )
183
139
  end
184
140
 
185
- def request(method, path, body = nil, headers = {})
186
- uri = URI.join(@base_uri, path)
187
-
188
- session = Net::HTTP.new(uri.host, uri.port)
189
- session.use_ssl = (uri.scheme == 'https')
190
- session.open_timeout = @timeout
191
- session.read_timeout = @timeout
192
-
193
- req = request_class(method).new(uri.path)
194
- req.initialize_http_header(headers)
195
- req.basic_auth @username, @password
141
+ def create_anonymous_event(anonymous_id, event_name, attributes = {})
142
+ create_event(
143
+ url: "/api/v1/events",
144
+ event_name: event_name,
145
+ anonymous_id: anonymous_id,
146
+ attributes: attributes
147
+ )
148
+ end
196
149
 
197
- add_request_body(req, body) unless body.nil?
150
+ def create_event(url:, event_name:, anonymous_id: nil, attributes: {})
151
+ body = { :name => event_name, :data => attributes }
152
+ body[:timestamp] = attributes[:timestamp] if valid_timestamp?(attributes[:timestamp])
153
+ body[:anonymous_id] = anonymous_id unless anonymous_id.nil?
198
154
 
199
- session.start do |http|
200
- http.request(req)
201
- end
155
+ @client.request_and_verify_response(:post, url, body)
202
156
  end
203
157
 
204
- def request_class(method)
205
- case method
206
- when :post
207
- Net::HTTP::Post
208
- when :put
209
- Net::HTTP::Put
210
- when :delete
211
- Net::HTTP::Delete
212
- else
213
- raise InvalidRequest.new("Invalid request method #{method.inspect}")
214
- end
158
+ def valid_timestamp?(timestamp)
159
+ timestamp && timestamp.is_a?(Integer) && timestamp > 999999999 && timestamp < 100000000000
215
160
  end
216
161
 
217
- def add_request_body(req, body)
218
- if @json
219
- req.add_field('Content-Type', 'application/json')
220
- req.body = MultiJson.dump(body)
221
- else
222
- req.add_field('Content-Type', 'application/x-www-form-urlencoded')
223
- req.body = ParamEncoder.to_params(body)
224
- end
162
+ def is_empty?(val)
163
+ val.nil? || (val.is_a?(String) && val.strip == "")
225
164
  end
226
165
  end
227
166
  end