intercom 3.5.10 → 3.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +35 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  4. data/Gemfile +1 -4
  5. data/README.md +152 -83
  6. data/RELEASING.md +9 -0
  7. data/changes.txt +116 -0
  8. data/intercom.gemspec +1 -2
  9. data/lib/intercom.rb +4 -0
  10. data/lib/intercom/api_operations/{delete.rb → archive.rb} +4 -2
  11. data/lib/intercom/api_operations/find_all.rb +1 -1
  12. data/lib/intercom/api_operations/request_hard_delete.rb +12 -0
  13. data/lib/intercom/api_operations/search.rb +17 -0
  14. data/lib/intercom/client.rb +42 -5
  15. data/lib/intercom/customer.rb +10 -0
  16. data/lib/intercom/errors.rb +41 -4
  17. data/lib/intercom/request.rb +144 -81
  18. data/lib/intercom/search_collection_proxy.rb +82 -0
  19. data/lib/intercom/service/base_service.rb +6 -0
  20. data/lib/intercom/service/company.rb +14 -2
  21. data/lib/intercom/service/contact.rb +4 -2
  22. data/lib/intercom/service/conversation.rb +12 -0
  23. data/lib/intercom/service/customer.rb +14 -0
  24. data/lib/intercom/service/event.rb +12 -0
  25. data/lib/intercom/service/subscription.rb +2 -2
  26. data/lib/intercom/service/tag.rb +1 -1
  27. data/lib/intercom/service/team.rb +17 -0
  28. data/lib/intercom/service/user.rb +4 -2
  29. data/lib/intercom/service/visitor.rb +2 -2
  30. data/lib/intercom/team.rb +7 -0
  31. data/lib/intercom/traits/api_resource.rb +4 -9
  32. data/lib/intercom/version.rb +1 -1
  33. data/spec/spec_helper.rb +124 -2
  34. data/spec/unit/intercom/client_collection_proxy_spec.rb +5 -5
  35. data/spec/unit/intercom/client_spec.rb +69 -1
  36. data/spec/unit/intercom/company_spec.rb +20 -16
  37. data/spec/unit/intercom/contact_spec.rb +6 -0
  38. data/spec/unit/intercom/conversation_spec.rb +15 -0
  39. data/spec/unit/intercom/event_spec.rb +19 -0
  40. data/spec/unit/intercom/request_spec.rb +150 -9
  41. data/spec/unit/intercom/search_collection_proxy_spec.rb +56 -0
  42. data/spec/unit/intercom/team_spec.rb +21 -0
  43. data/spec/unit/intercom/traits/api_resource_spec.rb +34 -7
  44. data/spec/unit/intercom/user_spec.rb +15 -3
  45. metadata +33 -22
  46. data/.travis.yml +0 -6
  47. data/lib/intercom/extended_api_operations/users.rb +0 -16
data/RELEASING.md ADDED
@@ -0,0 +1,9 @@
1
+ # Releasing Intercom
2
+
3
+ We use https://github.com/svenfuchs/gem-release to tag, bump, and release new versions
4
+
5
+ Please add a line to changes.txt before you release a new version.
6
+
7
+ ```
8
+ gem bump --tag --release
9
+ ```
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 Delete
6
- def delete(object)
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
- ClientCollectionProxy.new(collection_name, finder_details: finder_details, client: @client)
19
+ collection_proxy_class.new(collection_name, finder_details: finder_details, client: @client)
20
20
  end
21
21
 
22
22
  private
@@ -0,0 +1,12 @@
1
+ require 'intercom/utils'
2
+
3
+ module Intercom
4
+ module ApiOperations
5
+ module RequestHardDelete
6
+ def request_hard_delete(object)
7
+ @client.post("/user_delete_requests", {intercom_user_id: object.id})
8
+ object
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
@@ -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
- @base_url = 'https://api.intercom.io'
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
- result = request.execute(@base_url, username: @username_part, secret: @password_part)
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
@@ -0,0 +1,10 @@
1
+ require 'intercom/traits/api_resource'
2
+
3
+ module Intercom
4
+ class Customer
5
+ include Traits::ApiResource
6
+
7
+ def identity_vars ; [:id, :email, :user_id] ; end
8
+ def flat_store_attributes ; [:custom_attributes] ; end
9
+ end
10
+ end
@@ -23,16 +23,28 @@ module Intercom
23
23
  end
24
24
  end
25
25
 
26
- # Raised when the credentials you provide don't match a valid account on Intercom.
27
- # Check that you have set <b>Intercom.app_id=</b> and <b>Intercom.app_api_key=</b> correctly.
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 ; end
85
+ class Intercom::AttributeNotSetError < IntercomError; end
59
86
 
60
87
  # Raised when unexpected nil returned from server
61
- class Intercom::HttpError < IntercomError ; end
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
@@ -3,73 +3,82 @@ require 'net/https'
3
3
 
4
4
  module Intercom
5
5
  class Request
6
- attr_accessor :path, :net_http_method, :rate_limit_details
7
-
8
- def initialize(path, net_http_method)
9
- self.path = path
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
- def set_basic_auth(method, username, secret)
18
- method.basic_auth(CGI.unescape(username), CGI.unescape(secret))
19
- end
11
+ def post(path, form_data)
12
+ new(path, method_with_body(Net::HTTP::Post, path, form_data))
13
+ end
20
14
 
21
- def self.get(path, params)
22
- new(path, Net::HTTP::Get.new(append_query_string_to_url(path, params), default_headers))
23
- end
15
+ def delete(path, params)
16
+ new(path, method_with_body(Net::HTTP::Delete, path, params))
17
+ end
24
18
 
25
- def self.post(path, form_data)
26
- new(path, method_with_body(Net::HTTP::Post, path, form_data))
27
- end
19
+ def put(path, form_data)
20
+ new(path, method_with_body(Net::HTTP::Put, path, form_data))
21
+ end
28
22
 
29
- def self.delete(path, params)
30
- new(path, method_with_body(Net::HTTP::Delete, path, params))
31
- end
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
- def self.put(path, form_data)
34
- new(path, method_with_body(Net::HTTP::Put, path, form_data))
35
- end
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
- def self.method_with_body(http_method, path, params)
38
- request = http_method.send(:new, path, default_headers)
39
- request.body = params.to_json
40
- request["Content-Type"] = "application/json"
41
- request
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 self.default_headers
45
- {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"}
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
- def client(uri)
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
- def decode_body(response)
83
- decode(response['content-encoding'], response.body)
84
- end
91
+ attr_accessor :path,
92
+ :net_http_method,
93
+ :rate_limit_details
85
94
 
86
- def parse_body(decoded_body, response)
87
- parsed_body = nil
88
- return parsed_body if decoded_body.nil? || decoded_body.strip.empty?
89
- begin
90
- parsed_body = JSON.parse(decoded_body)
91
- rescue JSON::ParserError => _
92
- raise_errors_on_failure(response)
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
- raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list'
95
- parsed_body
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 set_rate_limit_details(response)
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 decode(content_encoding, body)
107
- return body if (!body) || body.empty? || content_encoding != 'gzip'
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 raise_errors_on_failure(res)
112
- if res.code.to_i.eql?(404)
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 res.code.to_i.eql?(401)
159
+ elsif code == 401
115
160
  raise Intercom::AuthenticationError.new('Unauthorized')
116
- elsif res.code.to_i.eql?(403)
161
+ elsif code == 403
117
162
  raise Intercom::AuthenticationError.new('Forbidden')
118
- elsif res.code.to_i.eql?(500)
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 res.code.to_i.eql?(502)
167
+ elsif code == 502
121
168
  raise Intercom::BadGatewayError.new('Bad Gateway Error')
122
- elsif res.code.to_i.eql?(503)
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 "not_found"
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