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 +5 -15
- data/.github/workflows/main.yml +30 -0
- data/CHANGELOG.markdown +48 -0
- data/README.md +53 -19
- data/customerio.gemspec +2 -2
- data/lib/customerio.rb +4 -0
- data/lib/customerio/api.rb +35 -0
- data/lib/customerio/base_client.rb +87 -0
- data/lib/customerio/client.rb +83 -144
- data/lib/customerio/regions.rb +11 -0
- data/lib/customerio/requests/send_email_request.rb +49 -0
- data/lib/customerio/version.rb +1 -1
- data/spec/api_client_spec.rb +172 -0
- data/spec/base_client_spec.rb +67 -0
- data/spec/client_spec.rb +340 -230
- data/spec/spec_helper.rb +2 -2
- metadata +40 -33
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,17 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
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:
|
11
|
-
|
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 [
|
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'
|
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
|
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
|
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
|
-
|
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
|
-
###
|
189
|
+
### Send Transactional Messages
|
187
190
|
|
188
|
-
|
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
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
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', '
|
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
|
data/lib/customerio/client.rb
CHANGED
@@ -1,29 +1,22 @@
|
|
1
|
-
require
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
22
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
46
|
-
|
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
|
-
|
58
|
-
end
|
45
|
+
create_customer_event(customer_id, event_name, attributes)
|
59
46
|
end
|
60
47
|
|
61
|
-
def
|
62
|
-
|
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")
|
67
|
-
raise ParamError.new("device_id must be a non-empty string")
|
68
|
-
raise ParamError.new("platform must be a non-empty string")
|
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
|
-
|
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")
|
86
|
-
raise ParamError.new("device_id must be a non-empty string")
|
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
|
-
|
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
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
116
|
-
|
117
|
-
|
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
|
168
|
-
|
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
|
-
|
173
|
-
|
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
|
181
|
-
|
182
|
-
|
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
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
200
|
-
http.request(req)
|
201
|
-
end
|
155
|
+
@client.request_and_verify_response(:post, url, body)
|
202
156
|
end
|
203
157
|
|
204
|
-
def
|
205
|
-
|
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
|
218
|
-
|
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
|