customerio 2.2.1 → 4.0.1

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,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