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