intercom 3.5.10 → 3.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +35 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
- data/Gemfile +1 -4
- data/README.md +152 -83
- data/RELEASING.md +9 -0
- data/changes.txt +116 -0
- data/intercom.gemspec +1 -2
- data/lib/intercom.rb +4 -0
- data/lib/intercom/api_operations/{delete.rb → archive.rb} +4 -2
- data/lib/intercom/api_operations/find_all.rb +1 -1
- data/lib/intercom/api_operations/request_hard_delete.rb +12 -0
- data/lib/intercom/api_operations/search.rb +17 -0
- data/lib/intercom/client.rb +42 -5
- data/lib/intercom/customer.rb +10 -0
- data/lib/intercom/errors.rb +41 -4
- data/lib/intercom/request.rb +144 -81
- data/lib/intercom/search_collection_proxy.rb +82 -0
- data/lib/intercom/service/base_service.rb +6 -0
- data/lib/intercom/service/company.rb +14 -2
- data/lib/intercom/service/contact.rb +4 -2
- data/lib/intercom/service/conversation.rb +12 -0
- data/lib/intercom/service/customer.rb +14 -0
- data/lib/intercom/service/event.rb +12 -0
- data/lib/intercom/service/subscription.rb +2 -2
- data/lib/intercom/service/tag.rb +1 -1
- data/lib/intercom/service/team.rb +17 -0
- data/lib/intercom/service/user.rb +4 -2
- data/lib/intercom/service/visitor.rb +2 -2
- data/lib/intercom/team.rb +7 -0
- data/lib/intercom/traits/api_resource.rb +4 -9
- data/lib/intercom/version.rb +1 -1
- data/spec/spec_helper.rb +124 -2
- data/spec/unit/intercom/client_collection_proxy_spec.rb +5 -5
- data/spec/unit/intercom/client_spec.rb +69 -1
- data/spec/unit/intercom/company_spec.rb +20 -16
- data/spec/unit/intercom/contact_spec.rb +6 -0
- data/spec/unit/intercom/conversation_spec.rb +15 -0
- data/spec/unit/intercom/event_spec.rb +19 -0
- data/spec/unit/intercom/request_spec.rb +150 -9
- data/spec/unit/intercom/search_collection_proxy_spec.rb +56 -0
- data/spec/unit/intercom/team_spec.rb +21 -0
- data/spec/unit/intercom/traits/api_resource_spec.rb +34 -7
- data/spec/unit/intercom/user_spec.rb +15 -3
- metadata +33 -22
- data/.travis.yml +0 -6
- data/lib/intercom/extended_api_operations/users.rb +0 -16
data/RELEASING.md
ADDED
data/changes.txt
CHANGED
@@ -1,3 +1,119 @@
|
|
1
|
+
3.9.4
|
2
|
+
Add handling for Gateway Timeouts
|
3
|
+
|
4
|
+
3.9.3
|
5
|
+
Fix regression added in 3.9.2
|
6
|
+
|
7
|
+
3.9.2
|
8
|
+
Added error handling for malformed responses
|
9
|
+
|
10
|
+
3.9.1
|
11
|
+
Version skipped in error
|
12
|
+
|
13
|
+
3.9.0
|
14
|
+
Added Teams endpoint functionality
|
15
|
+
|
16
|
+
3.8.1
|
17
|
+
Added error handling for company_not_found
|
18
|
+
|
19
|
+
3.8.0
|
20
|
+
Add support for Customer Search (currently in Unstable API Version)
|
21
|
+
https://developers.intercom.com/intercom-api-reference/v0/reference#customers
|
22
|
+
|
23
|
+
3.7.7
|
24
|
+
Remove deprecated features from Gemspec
|
25
|
+
|
26
|
+
3.7.6
|
27
|
+
Added error handling for invalid_document error state
|
28
|
+
|
29
|
+
3.7.5
|
30
|
+
Added error handling for scroll_exists error state
|
31
|
+
|
32
|
+
3.7.4
|
33
|
+
Added support for API versioning via
|
34
|
+
Intercom::Client.new(token: "token", api_version "1.1")
|
35
|
+
|
36
|
+
3.7.3
|
37
|
+
Added error handling for when an admin cannot be found.
|
38
|
+
|
39
|
+
3.7.2
|
40
|
+
Added error handling for when an app's custom attribute limits have been reached.
|
41
|
+
|
42
|
+
3.7.1
|
43
|
+
Extra version bump after faulty previous bump
|
44
|
+
|
45
|
+
3.7.0
|
46
|
+
Providing the ability to hard delete users as described here:
|
47
|
+
https://developers.intercom.com/intercom-api-reference/reference#archive-a-user
|
48
|
+
|
49
|
+
This chaged the previous delete action to an archive action and added a new hard delete option
|
50
|
+
You can still use the delete method but it will archive a user, we added an alias for delete.
|
51
|
+
#442 archiving alias
|
52
|
+
#410 add ability to hard delete users
|
53
|
+
|
54
|
+
Alos enabling reply to last from the SDK
|
55
|
+
#443 Residently conversations last reply
|
56
|
+
|
57
|
+
3.6.2
|
58
|
+
#384 Add ability to snooze conversation
|
59
|
+
You can now snooze conversations in your app via:
|
60
|
+
intercom.conversations.snooze(...)
|
61
|
+
|
62
|
+
3.6.1
|
63
|
+
#430 Allow all conversations to be listed
|
64
|
+
You can now iterate over all conversations for your app via:
|
65
|
+
intercom.conversations.all.each { |convo| ... }
|
66
|
+
|
67
|
+
3.6.0
|
68
|
+
BREAKING CHANGE companies
|
69
|
+
We updated companies to be able to list users via company_id as well as id (#428 )
|
70
|
+
Note that this is a breaking change as we had to remove the old way of listing users via company.
|
71
|
+
|
72
|
+
Previously it was:
|
73
|
+
intercom.companies.users(company.id)
|
74
|
+
|
75
|
+
Now you get a list of users in a company by Intercom Company ID
|
76
|
+
intercom.companies.users_by_intercom_company_id(company.id)
|
77
|
+
|
78
|
+
Now you get a list of users in a company by external company_id
|
79
|
+
intercom.companies.users_by_company_id(company.company_id)
|
80
|
+
|
81
|
+
Rate limit handling
|
82
|
+
We also improved the way we handle rate limits in PR #409 which was related to issue #405
|
83
|
+
|
84
|
+
3.5.23
|
85
|
+
- New type of error (ResourceNotUniqueError). Thrown when trying to create a resource that already exists in Intercom
|
86
|
+
|
87
|
+
3.5.22
|
88
|
+
- Return object type
|
89
|
+
|
90
|
+
3.5.21
|
91
|
+
- Fix for PR-353 which addressed "NoMethodError in intercom/request"
|
92
|
+
- There were issues on older versions of Ruby (<2.3)
|
93
|
+
- This PR does not use lonely operator and instead simple checks for nil parsed_body
|
94
|
+
|
95
|
+
3.5.17
|
96
|
+
- Fix BlockedUserError typo
|
97
|
+
|
98
|
+
3.5.16
|
99
|
+
- Standardize comparison of attribute as string when input is Hash or JSON
|
100
|
+
|
101
|
+
3.5.15
|
102
|
+
- UnauthorizedError on invalid token
|
103
|
+
- BlockerUserError on restoring blocked user
|
104
|
+
|
105
|
+
3.5.14
|
106
|
+
- Rate Limit Exception (@jaimeiniesta)
|
107
|
+
|
108
|
+
3.5.12
|
109
|
+
- Use base_url in initialize parameter
|
110
|
+
|
111
|
+
3.5.11
|
112
|
+
- Add scroll api for companies
|
113
|
+
|
114
|
+
3.5.10
|
115
|
+
- Add Support for find_all events pagination (@jkeyes)
|
116
|
+
|
1
117
|
3.5.9
|
2
118
|
- Fix event create method
|
3
119
|
|
data/intercom.gemspec
CHANGED
@@ -12,7 +12,6 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = %q{Ruby bindings for the Intercom API}
|
13
13
|
spec.description = %Q{Intercom (https://www.intercom.io) is a customer relationship management and messaging tool for web app owners. This library wraps the api provided by Intercom. See http://docs.intercom.io/api for more details. }
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.rubyforge_project = "intercom"
|
16
15
|
|
17
16
|
spec.files = `git ls-files`.split("\n")
|
18
17
|
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -20,12 +19,12 @@ Gem::Specification.new do |spec|
|
|
20
19
|
spec.require_paths = ["lib"]
|
21
20
|
|
22
21
|
spec.add_development_dependency 'minitest', '~> 5.4'
|
22
|
+
spec.add_development_dependency "m", "~> 1.5.0"
|
23
23
|
spec.add_development_dependency 'rake', '~> 10.3'
|
24
24
|
spec.add_development_dependency 'mocha', '~> 1.0'
|
25
25
|
spec.add_development_dependency "fakeweb", ["~> 1.3"]
|
26
26
|
spec.add_development_dependency "pry"
|
27
27
|
|
28
|
-
spec.add_dependency 'json', '>= 1.8'
|
29
28
|
spec.required_ruby_version = '>= 2.1.0'
|
30
29
|
spec.add_development_dependency 'gem-release'
|
31
30
|
end
|
data/lib/intercom.rb
CHANGED
@@ -4,6 +4,7 @@ require 'intercom/service/company'
|
|
4
4
|
require 'intercom/service/contact'
|
5
5
|
require 'intercom/service/conversation'
|
6
6
|
require 'intercom/service/count'
|
7
|
+
require 'intercom/service/customer'
|
7
8
|
require 'intercom/service/event'
|
8
9
|
require 'intercom/service/message'
|
9
10
|
require 'intercom/service/note'
|
@@ -11,12 +12,14 @@ require 'intercom/service/job'
|
|
11
12
|
require 'intercom/service/subscription'
|
12
13
|
require 'intercom/service/segment'
|
13
14
|
require 'intercom/service/tag'
|
15
|
+
require 'intercom/service/team'
|
14
16
|
require 'intercom/service/user'
|
15
17
|
require 'intercom/service/visitor'
|
16
18
|
require 'intercom/options'
|
17
19
|
require 'intercom/client'
|
18
20
|
require "intercom/contact"
|
19
21
|
require "intercom/count"
|
22
|
+
require "intercom/customer"
|
20
23
|
require "intercom/user"
|
21
24
|
require "intercom/company"
|
22
25
|
require "intercom/note"
|
@@ -29,6 +32,7 @@ require "intercom/message"
|
|
29
32
|
require "intercom/admin"
|
30
33
|
require "intercom/request"
|
31
34
|
require "intercom/subscription"
|
35
|
+
require "intercom/team"
|
32
36
|
require "intercom/errors"
|
33
37
|
require "intercom/visitor"
|
34
38
|
require "json"
|
@@ -2,12 +2,14 @@ require 'intercom/utils'
|
|
2
2
|
|
3
3
|
module Intercom
|
4
4
|
module ApiOperations
|
5
|
-
module
|
6
|
-
def
|
5
|
+
module Archive
|
6
|
+
def archive(object)
|
7
7
|
collection_name = Utils.resource_class_to_collection_name(collection_class)
|
8
8
|
@client.delete("/#{collection_name}/#{object.id}", {})
|
9
9
|
object
|
10
10
|
end
|
11
|
+
|
12
|
+
alias_method 'delete', 'archive'
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -16,7 +16,7 @@ module Intercom
|
|
16
16
|
finder_details[:url] = "/#{collection_name}"
|
17
17
|
finder_details[:params] = params
|
18
18
|
end
|
19
|
-
|
19
|
+
collection_proxy_class.new(collection_name, finder_details: finder_details, client: @client)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'intercom/search_collection_proxy'
|
2
|
+
require 'intercom/utils'
|
3
|
+
|
4
|
+
module Intercom
|
5
|
+
module ApiOperations
|
6
|
+
module Search
|
7
|
+
def search(params)
|
8
|
+
collection_name = Utils.resource_class_to_collection_name(collection_class)
|
9
|
+
search_details = {
|
10
|
+
url: "/#{collection_name}/search",
|
11
|
+
params: params
|
12
|
+
}
|
13
|
+
SearchCollectionProxy.new(collection_name, search_details: search_details, client: @client)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/intercom/client.rb
CHANGED
@@ -2,7 +2,7 @@ module Intercom
|
|
2
2
|
class MisconfiguredClientError < StandardError; end
|
3
3
|
class Client
|
4
4
|
include Options
|
5
|
-
attr_reader :base_url, :rate_limit_details, :username_part, :password_part
|
5
|
+
attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts, :api_version
|
6
6
|
|
7
7
|
class << self
|
8
8
|
def set_base_url(base_url)
|
@@ -12,9 +12,20 @@ module Intercom
|
|
12
12
|
Proc.new { |obj| set_base_url(old_url).call(o) }
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
def set_timeouts(open_timeout: nil, read_timeout: nil)
|
17
|
+
return Proc.new do |o|
|
18
|
+
old_timeouts = o.timeouts
|
19
|
+
timeouts = {}
|
20
|
+
timeouts[:open_timeout] = open_timeout if open_timeout
|
21
|
+
timeouts[:read_timeout] = read_timeout if read_timeout
|
22
|
+
o.send(:timeouts=, timeouts)
|
23
|
+
Proc.new { |obj| set_timeouts(old_timeouts).call(o) }
|
24
|
+
end
|
25
|
+
end
|
15
26
|
end
|
16
27
|
|
17
|
-
def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil)
|
28
|
+
def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false, api_version: nil)
|
18
29
|
if token
|
19
30
|
@username_part = token
|
20
31
|
@password_part = ""
|
@@ -24,8 +35,16 @@ module Intercom
|
|
24
35
|
end
|
25
36
|
validate_credentials!
|
26
37
|
|
27
|
-
@
|
38
|
+
@api_version = api_version
|
39
|
+
validate_api_version!
|
40
|
+
|
41
|
+
@base_url = base_url
|
28
42
|
@rate_limit_details = {}
|
43
|
+
@handle_rate_limit = handle_rate_limit
|
44
|
+
@timeouts = {
|
45
|
+
open_timeout: 30,
|
46
|
+
read_timeout: 90
|
47
|
+
}
|
29
48
|
end
|
30
49
|
|
31
50
|
def admins
|
@@ -48,6 +67,10 @@ module Intercom
|
|
48
67
|
Intercom::Service::Counts.new(self)
|
49
68
|
end
|
50
69
|
|
70
|
+
def customers
|
71
|
+
Intercom::Service::Customer.new(self)
|
72
|
+
end
|
73
|
+
|
51
74
|
def events
|
52
75
|
Intercom::Service::Event.new(self)
|
53
76
|
end
|
@@ -72,6 +95,10 @@ module Intercom
|
|
72
95
|
Intercom::Service::Tag.new(self)
|
73
96
|
end
|
74
97
|
|
98
|
+
def teams
|
99
|
+
Intercom::Service::Team.new(self)
|
100
|
+
end
|
101
|
+
|
75
102
|
def users
|
76
103
|
Intercom::Service::User.new(self)
|
77
104
|
end
|
@@ -107,14 +134,24 @@ module Intercom
|
|
107
134
|
fail error if @username_part.nil?
|
108
135
|
end
|
109
136
|
|
137
|
+
def validate_api_version!
|
138
|
+
error = MisconfiguredClientError.new("api_version must be either nil or a valid API version")
|
139
|
+
fail error if (@api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0'))
|
140
|
+
end
|
141
|
+
|
110
142
|
def execute_request(request)
|
111
|
-
|
143
|
+
request.handle_rate_limit = handle_rate_limit
|
144
|
+
request.execute(@base_url, username: @username_part, secret: @password_part, api_version: @api_version, **timeouts)
|
145
|
+
ensure
|
112
146
|
@rate_limit_details = request.rate_limit_details
|
113
|
-
result
|
114
147
|
end
|
115
148
|
|
116
149
|
def base_url=(new_url)
|
117
150
|
@base_url = new_url
|
118
151
|
end
|
152
|
+
|
153
|
+
def timeouts=(timeouts)
|
154
|
+
@timeouts = @timeouts.merge(timeouts)
|
155
|
+
end
|
119
156
|
end
|
120
157
|
end
|
data/lib/intercom/errors.rb
CHANGED
@@ -23,16 +23,28 @@ module Intercom
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
# Raised when the
|
27
|
-
# Check that you have set
|
26
|
+
# Raised when the token you provided is incorrect or not authorized to access certain type of data.
|
27
|
+
# Check that you have set Intercom.token correctly.
|
28
28
|
class AuthenticationError < IntercomError; end
|
29
29
|
|
30
|
+
# Raised when the token provided is linked to a deleted application.
|
31
|
+
class AppSuspendedError < AuthenticationError; end
|
32
|
+
|
33
|
+
# Raised when the token provided has been revoked.
|
34
|
+
class TokenRevokedError < AuthenticationError; end
|
35
|
+
|
36
|
+
# Raised when the token provided can't be decoded, and is most likely invalid.
|
37
|
+
class TokenUnauthorizedError < AuthenticationError; end
|
38
|
+
|
30
39
|
# Raised when something goes wrong on within the Intercom API service.
|
31
40
|
class ServerError < IntercomError; end
|
32
41
|
|
33
42
|
# Raised when we have bad gateway errors.
|
34
43
|
class BadGatewayError < IntercomError; end
|
35
44
|
|
45
|
+
# Raised when we have gateway timeout errors.
|
46
|
+
class GatewayTimeoutError < IntercomError; end
|
47
|
+
|
36
48
|
# Raised when we experience a socket read timeout
|
37
49
|
class ServiceUnavailableError < IntercomError; end
|
38
50
|
|
@@ -42,23 +54,47 @@ module Intercom
|
|
42
54
|
# Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom.
|
43
55
|
class ResourceNotFound < IntercomError; end
|
44
56
|
|
57
|
+
# Raised when requesting an admin that doesn't exist in your Intercom workspace.
|
58
|
+
class AdminNotFound < IntercomError; end
|
59
|
+
|
60
|
+
# Raised when trying to create a resource that already exists in Intercom.
|
61
|
+
class ResourceNotUniqueError < IntercomError; end
|
62
|
+
|
45
63
|
# Raised when the request has bad syntax
|
46
64
|
class BadRequestError < IntercomError; end
|
47
65
|
|
48
66
|
# Raised when you have exceeded the API rate limit
|
49
67
|
class RateLimitExceeded < IntercomError; end
|
50
68
|
|
69
|
+
# Raised when some attribute of the response cannot be handled
|
70
|
+
class UnexpectedResponseError < IntercomError; end
|
71
|
+
|
51
72
|
# Raised when the request throws an error not accounted for
|
52
73
|
class UnexpectedError < IntercomError; end
|
53
74
|
|
75
|
+
# Raised when the CDA limit for the app has been reached
|
76
|
+
class CDALimitReachedError < IntercomError; end
|
77
|
+
|
54
78
|
# Raised when multiple users match the query (typically duplicate email addresses)
|
55
79
|
class MultipleMatchingUsersError < IntercomError; end
|
56
80
|
|
81
|
+
# Raised when restoring a blocked user
|
82
|
+
class BlockedUserError < IntercomError; end
|
83
|
+
|
57
84
|
# Raised when you try to call a non-setter method that does not exist on an object
|
58
|
-
class Intercom::AttributeNotSetError < IntercomError
|
85
|
+
class Intercom::AttributeNotSetError < IntercomError; end
|
59
86
|
|
60
87
|
# Raised when unexpected nil returned from server
|
61
|
-
class Intercom::HttpError < IntercomError
|
88
|
+
class Intercom::HttpError < IntercomError; end
|
89
|
+
|
90
|
+
# Raised when an invalid api version is used
|
91
|
+
class ApiVersionInvalid < IntercomError; end
|
92
|
+
|
93
|
+
# Raised when an creating a scroll if one already exists
|
94
|
+
class ScrollAlreadyExistsError < IntercomError; end
|
95
|
+
|
96
|
+
# Raised when a CDA is invalid
|
97
|
+
class InvalidDocumentError < IntercomError; end
|
62
98
|
|
63
99
|
#
|
64
100
|
# Non-public errors (internal to the gem)
|
@@ -71,4 +107,5 @@ module Intercom
|
|
71
107
|
class Intercom::NoMethodMissingHandler < IntercomInternalError; end
|
72
108
|
|
73
109
|
class Intercom::DeserializationError < IntercomInternalError; end
|
110
|
+
|
74
111
|
end
|
data/lib/intercom/request.rb
CHANGED
@@ -3,73 +3,82 @@ require 'net/https'
|
|
3
3
|
|
4
4
|
module Intercom
|
5
5
|
class Request
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
self.net_http_method = net_http_method
|
11
|
-
end
|
12
|
-
|
13
|
-
def set_common_headers(method, base_uri)
|
14
|
-
method.add_field('AcceptEncoding', 'gzip, deflate')
|
15
|
-
end
|
6
|
+
class << self
|
7
|
+
def get(path, params)
|
8
|
+
new(path, Net::HTTP::Get.new(append_query_string_to_url(path, params), default_headers))
|
9
|
+
end
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
def post(path, form_data)
|
12
|
+
new(path, method_with_body(Net::HTTP::Post, path, form_data))
|
13
|
+
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
def delete(path, params)
|
16
|
+
new(path, method_with_body(Net::HTTP::Delete, path, params))
|
17
|
+
end
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
def put(path, form_data)
|
20
|
+
new(path, method_with_body(Net::HTTP::Put, path, form_data))
|
21
|
+
end
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
private def method_with_body(http_method, path, params)
|
24
|
+
request = http_method.send(:new, path, default_headers)
|
25
|
+
request.body = params.to_json
|
26
|
+
request["Content-Type"] = "application/json"
|
27
|
+
request
|
28
|
+
end
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
private def default_headers
|
31
|
+
{'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"}
|
32
|
+
end
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
private def append_query_string_to_url(url, params)
|
35
|
+
return url if params.empty?
|
36
|
+
query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
|
37
|
+
url + "?#{query_string}"
|
38
|
+
end
|
42
39
|
end
|
43
40
|
|
44
|
-
def
|
45
|
-
|
41
|
+
def initialize(path, net_http_method)
|
42
|
+
self.path = path
|
43
|
+
self.net_http_method = net_http_method
|
44
|
+
self.handle_rate_limit = false
|
46
45
|
end
|
47
46
|
|
48
|
-
|
49
|
-
net = Net::HTTP.new(uri.host, uri.port)
|
50
|
-
if uri.is_a?(URI::HTTPS)
|
51
|
-
net.use_ssl = true
|
52
|
-
net.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
53
|
-
net.ca_file = File.join(File.dirname(__FILE__), '../data/cacert.pem')
|
54
|
-
end
|
55
|
-
net.read_timeout = 90
|
56
|
-
net.open_timeout = 30
|
57
|
-
net
|
58
|
-
end
|
47
|
+
attr_accessor :handle_rate_limit
|
59
48
|
|
60
|
-
def execute(target_base_url=nil, username:, secret: nil)
|
49
|
+
def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30, api_version: nil)
|
50
|
+
retries = 3
|
61
51
|
base_uri = URI.parse(target_base_url)
|
62
52
|
set_common_headers(net_http_method, base_uri)
|
63
53
|
set_basic_auth(net_http_method, username, secret)
|
54
|
+
set_api_version(net_http_method, api_version) if api_version
|
64
55
|
begin
|
65
|
-
client(base_uri).start do |http|
|
56
|
+
client(base_uri, read_timeout: read_timeout, open_timeout: open_timeout).start do |http|
|
66
57
|
begin
|
67
58
|
response = http.request(net_http_method)
|
59
|
+
|
68
60
|
set_rate_limit_details(response)
|
69
|
-
decoded_body = decode_body(response)
|
70
|
-
parsed_body = parse_body(decoded_body, response)
|
71
61
|
raise_errors_on_failure(response)
|
62
|
+
|
63
|
+
parsed_body = extract_response_body(response)
|
64
|
+
|
65
|
+
return nil if parsed_body.nil?
|
66
|
+
|
67
|
+
raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list'
|
68
|
+
|
72
69
|
parsed_body
|
70
|
+
rescue Intercom::RateLimitExceeded => e
|
71
|
+
if @handle_rate_limit
|
72
|
+
seconds_to_retry = (@rate_limit_details[:reset_at] - Time.now.utc).ceil
|
73
|
+
if (retries -= 1) < 0
|
74
|
+
raise Intercom::RateLimitExceeded.new('Rate limit retries exceeded. Please examine current API Usage.')
|
75
|
+
else
|
76
|
+
sleep seconds_to_retry unless seconds_to_retry < 0
|
77
|
+
retry
|
78
|
+
end
|
79
|
+
else
|
80
|
+
raise e
|
81
|
+
end
|
73
82
|
rescue Timeout::Error
|
74
83
|
raise Intercom::ServiceUnavailableError.new('Service Unavailable [request timed out]')
|
75
84
|
end
|
@@ -79,23 +88,50 @@ module Intercom
|
|
79
88
|
end
|
80
89
|
end
|
81
90
|
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
attr_accessor :path,
|
92
|
+
:net_http_method,
|
93
|
+
:rate_limit_details
|
85
94
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
private :path,
|
96
|
+
:net_http_method
|
97
|
+
|
98
|
+
private def client(uri, read_timeout:, open_timeout:)
|
99
|
+
net = Net::HTTP.new(uri.host, uri.port)
|
100
|
+
if uri.is_a?(URI::HTTPS)
|
101
|
+
net.use_ssl = true
|
102
|
+
net.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
103
|
+
net.ca_file = File.join(File.dirname(__FILE__), '../data/cacert.pem')
|
93
104
|
end
|
94
|
-
|
95
|
-
|
105
|
+
net.read_timeout = read_timeout
|
106
|
+
net.open_timeout = open_timeout
|
107
|
+
net
|
108
|
+
end
|
109
|
+
|
110
|
+
private def extract_response_body(response)
|
111
|
+
decoded_body = decode(response['content-encoding'], response.body)
|
112
|
+
|
113
|
+
json_parse_response(decoded_body, response.code)
|
114
|
+
end
|
115
|
+
|
116
|
+
private def decode(content_encoding, body)
|
117
|
+
return body if (!body) || body.empty? || content_encoding != 'gzip'
|
118
|
+
Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8")
|
96
119
|
end
|
97
120
|
|
98
|
-
def
|
121
|
+
private def json_parse_response(str, code)
|
122
|
+
return nil if str.to_s.empty?
|
123
|
+
|
124
|
+
JSON.parse(str)
|
125
|
+
rescue JSON::ParserError
|
126
|
+
msg = <<~MSG.gsub(/[[:space:]]+/, " ").strip # #squish from ActiveSuppor
|
127
|
+
Expected a JSON response body. Instead got '#{str}'
|
128
|
+
with status code '#{code}'.
|
129
|
+
MSG
|
130
|
+
|
131
|
+
raise UnexpectedResponseError, msg
|
132
|
+
end
|
133
|
+
|
134
|
+
private def set_rate_limit_details(response)
|
99
135
|
rate_limit_details = {}
|
100
136
|
rate_limit_details[:limit] = response['X-RateLimit-Limit'].to_i if response['X-RateLimit-Limit']
|
101
137
|
rate_limit_details[:remaining] = response['X-RateLimit-Remaining'].to_i if response['X-RateLimit-Remaining']
|
@@ -103,28 +139,41 @@ module Intercom
|
|
103
139
|
@rate_limit_details = rate_limit_details
|
104
140
|
end
|
105
141
|
|
106
|
-
def
|
107
|
-
|
108
|
-
Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8")
|
142
|
+
private def set_common_headers(method, base_uri)
|
143
|
+
method.add_field('AcceptEncoding', 'gzip, deflate')
|
109
144
|
end
|
110
145
|
|
111
|
-
def
|
112
|
-
|
146
|
+
private def set_basic_auth(method, username, secret)
|
147
|
+
method.basic_auth(CGI.unescape(username), CGI.unescape(secret))
|
148
|
+
end
|
149
|
+
|
150
|
+
private def set_api_version(method, api_version)
|
151
|
+
method.add_field('Intercom-Version', api_version)
|
152
|
+
end
|
153
|
+
|
154
|
+
private def raise_errors_on_failure(res)
|
155
|
+
code = res.code.to_i
|
156
|
+
|
157
|
+
if code == 404
|
113
158
|
raise Intercom::ResourceNotFound.new('Resource Not Found')
|
114
|
-
elsif
|
159
|
+
elsif code == 401
|
115
160
|
raise Intercom::AuthenticationError.new('Unauthorized')
|
116
|
-
elsif
|
161
|
+
elsif code == 403
|
117
162
|
raise Intercom::AuthenticationError.new('Forbidden')
|
118
|
-
elsif
|
163
|
+
elsif code == 429
|
164
|
+
raise Intercom::RateLimitExceeded.new('Rate Limit Exceeded')
|
165
|
+
elsif code == 500
|
119
166
|
raise Intercom::ServerError.new('Server Error')
|
120
|
-
elsif
|
167
|
+
elsif code == 502
|
121
168
|
raise Intercom::BadGatewayError.new('Bad Gateway Error')
|
122
|
-
elsif
|
169
|
+
elsif code == 503
|
123
170
|
raise Intercom::ServiceUnavailableError.new('Service Unavailable')
|
171
|
+
elsif code == 504
|
172
|
+
raise Intercom::GatewayTimeoutError.new('Gateway Timeout')
|
124
173
|
end
|
125
174
|
end
|
126
175
|
|
127
|
-
def raise_application_errors_on_failure(error_list_details, http_code)
|
176
|
+
private def raise_application_errors_on_failure(error_list_details, http_code)
|
128
177
|
# Currently, we don't support multiple errors
|
129
178
|
error_details = error_list_details['errors'].first
|
130
179
|
error_code = error_details['type'] || error_details['code']
|
@@ -137,18 +186,38 @@ module Intercom
|
|
137
186
|
:request_id => error_list_details['request_id']
|
138
187
|
}
|
139
188
|
case error_code
|
140
|
-
when 'unauthorized', 'forbidden'
|
189
|
+
when 'unauthorized', 'forbidden', 'token_not_found'
|
141
190
|
raise Intercom::AuthenticationError.new(error_details['message'], error_context)
|
191
|
+
when 'token_suspended'
|
192
|
+
raise Intercom::AppSuspendedError.new(error_details['message'], error_context)
|
193
|
+
when 'token_revoked'
|
194
|
+
raise Intercom::TokenRevokedError.new(error_details['message'], error_context)
|
195
|
+
when 'token_unauthorized'
|
196
|
+
raise Intercom::TokenUnauthorizedError.new(error_details['message'], error_context)
|
142
197
|
when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found'
|
143
198
|
raise Intercom::BadRequestError.new(error_details['message'], error_context)
|
144
|
-
when "
|
199
|
+
when "not_restorable"
|
200
|
+
raise Intercom::BlockedUserError.new(error_details['message'], error_context)
|
201
|
+
when "not_found", "company_not_found"
|
145
202
|
raise Intercom::ResourceNotFound.new(error_details['message'], error_context)
|
203
|
+
when "admin_not_found"
|
204
|
+
raise Intercom::AdminNotFound.new(error_details['message'], error_context)
|
146
205
|
when "rate_limit_exceeded"
|
147
206
|
raise Intercom::RateLimitExceeded.new(error_details['message'], error_context)
|
207
|
+
when "custom_data_limit_reached"
|
208
|
+
raise Intercom::CDALimitReachedError.new(error_details['message'], error_context)
|
209
|
+
when "invalid_document"
|
210
|
+
raise Intercom::InvalidDocumentError.new(error_details['message'], error_context)
|
148
211
|
when 'service_unavailable'
|
149
212
|
raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context)
|
150
213
|
when 'conflict', 'unique_user_constraint'
|
151
214
|
raise Intercom::MultipleMatchingUsersError.new(error_details['message'], error_context)
|
215
|
+
when 'resource_conflict'
|
216
|
+
raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context)
|
217
|
+
when 'intercom_version_invalid'
|
218
|
+
raise Intercom::ApiVersionInvalid.new(error_details['message'], error_context)
|
219
|
+
when 'scroll_exists'
|
220
|
+
raise Intercom::ScrollAlreadyExistsError.new(error_details['message'], error_context)
|
152
221
|
when nil, ''
|
153
222
|
raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context)
|
154
223
|
else
|
@@ -156,18 +225,12 @@ module Intercom
|
|
156
225
|
end
|
157
226
|
end
|
158
227
|
|
159
|
-
def message_for_unexpected_error_with_type(error_details, parsed_http_code)
|
228
|
+
private def message_for_unexpected_error_with_type(error_details, parsed_http_code)
|
160
229
|
"The error of type '#{error_details['type']}' is not recognized. It occurred with the message: #{error_details['message']} and http_code: '#{parsed_http_code}'. Please contact Intercom with these details."
|
161
230
|
end
|
162
231
|
|
163
|
-
def message_for_unexpected_error_without_type(error_details, parsed_http_code)
|
232
|
+
private def message_for_unexpected_error_without_type(error_details, parsed_http_code)
|
164
233
|
"An unexpected error occured. It occurred with the message: #{error_details['message']} and http_code: '#{parsed_http_code}'. Please contact Intercom with these details."
|
165
234
|
end
|
166
|
-
|
167
|
-
def self.append_query_string_to_url(url, params)
|
168
|
-
return url if params.empty?
|
169
|
-
query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
|
170
|
-
url + "?#{query_string}"
|
171
|
-
end
|
172
235
|
end
|
173
236
|
end
|