intercom 3.5.10 → 3.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|