help_scout-sdk 1.0.2 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f99c317fac88d6b07729a5a730c26ae64af24ec1ea94eb194c83f9a49c6bfa0
4
- data.tar.gz: 5e7ccef0d2293c93b9c9622de7caca137456f3ccdaa5521add45a08e0a19b916
3
+ metadata.gz: 9d4c58f9f96b57b2d1bbc0cd69ad741ffa6201244861167992e8212901d491d2
4
+ data.tar.gz: 055c66adf697283a7836ce6fdb91156cea27ea1cddf0fc6f8b2c05c07ed174a8
5
5
  SHA512:
6
- metadata.gz: 592babfac0e434ea33a7f6066d26ee9b83a700ee1bbfd7c699a5ed3386384016a191714e31a7e6bd0853dc5ed6a8a4cc2947655ecd8254834bebf163dd0c387d
7
- data.tar.gz: 82b191e5922303d0398b5164f1f50d5a7fa1b488e2647338ad4b7475f629cbe760f00fe970a8b2fa75614c5bb1ab91a46b968b86ad677c2d7c34cedca4f50570
6
+ metadata.gz: 2058da0b726acce0ef9993b40e827bb2be27f2947c78dfcb57216e51acb3fbcd4969b86a658e621e1bd63c1e6410f291a55cb2aaa03fd446374208e2376eeed0
7
+ data.tar.gz: 10b8bbca725910b62a0b3120247506d50b8c7e3f34ed50a39ae2ddeb340d9081803b66d2649e9a9b410de4d1af2eb346372f3b96bcb6e7543f38e6f7c790f163
data/README.md CHANGED
@@ -95,6 +95,19 @@ HelpScout::User.list
95
95
  user = HelpScout::User.get(id)
96
96
  ```
97
97
 
98
+ ### Caching Access Tokens
99
+
100
+ Since short-lived access tokens aren't likely to be embedded into environment variables, it can be difficult to share them across processes. To work around this, you can configure a `token_cache` (and optional `token_cache_key`) to be used to store and retrieve the token until expiry. In general any object that conforms to the `ActiveSupport::Cache::Store` API should work. For example, using an application's Rails cache:
101
+
102
+ ```ruby
103
+ HelpScout.configuration.token_cache = Rails.cache
104
+ HelpScout.configuration.token_cache_key
105
+ # => 'help_scout_token_cache'
106
+ HelpScout.configuration.token_cache_key = 'my-own-key'
107
+ ```
108
+
109
+ With caching configured, whenever the gem attempts to create an access token, it will first attempt to read a value from the cache using the configured cache key. If it's a hit, the cached values will be used to create a new `AccessToken`. If it's a miss, then the gem will make a request to the Help Scout API to retrieve a new token, writing the token's details to the cache before returning the new token.
110
+
98
111
  ## Development
99
112
 
100
113
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -52,7 +52,7 @@ module HelpScout
52
52
  response = new_connection.send(action, path, params.compact)
53
53
 
54
54
  if response.status == 401
55
- access_token.invalidate!
55
+ access_token&.invalidate!
56
56
  response = new_connection.send(action, path, params.compact)
57
57
  end
58
58
 
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
4
+ require 'help_scout/api/access_token/cache'
5
+ require 'help_scout/api/access_token/request'
6
+
3
7
  module HelpScout
4
8
  class API
5
9
  class AccessToken
6
10
  class << self
7
11
  def create
8
- connection = HelpScout::API::Client.new(authorize: false).connection
9
- response = connection.post('oauth2/token', token_request_params)
10
-
11
- case response.status
12
- when 200 then new HelpScout::Response.new(response).body
13
- when 429 then raise HelpScout::API::ThrottleLimitReached, response.body&.dig('error')
14
- else raise HelpScout::API::InternalError, "unexpected response (status #{response.status})"
15
- end
16
- end
12
+ cache = HelpScout::API::AccessToken::Cache.new
13
+ request = HelpScout::API::AccessToken::Request.new
17
14
 
18
- def refresh!
19
- HelpScout.api.access_token = create
15
+ cache.configured? ? cache.fetch_token { request.execute } : request.execute
20
16
  end
21
17
 
22
- private
18
+ def refresh!
19
+ return HelpScout.api.access_token unless HelpScout.access_token.nil? || HelpScout.access_token.stale?
23
20
 
24
- def token_request_params
25
- @_token_request_params ||= {
26
- grant_type: 'client_credentials',
27
- client_id: HelpScout.app_id,
28
- client_secret: HelpScout.app_secret
29
- }
21
+ HelpScout.api.access_token = create
30
22
  end
31
23
  end
32
24
 
@@ -35,10 +27,20 @@ module HelpScout
35
27
 
36
28
  def initialize(params)
37
29
  @value = params[:access_token]
38
- @expires_in = params[:expires_in]
39
- return unless @expires_in
40
30
 
41
- @expires_at = Time.now.utc + expires_in
31
+ if params[:expires_at]
32
+ @expires_at = DateTime.parse(params[:expires_at].to_s).to_time.utc
33
+ elsif params[:expires_in]
34
+ @expires_in = params[:expires_in].to_i
35
+ @expires_at = (Time.now.utc + @expires_in)
36
+ end
37
+ end
38
+
39
+ def as_json(*)
40
+ {
41
+ access_token: value,
42
+ expires_at: expires_at
43
+ }
42
44
  end
43
45
 
44
46
  def expired?
@@ -48,10 +50,14 @@ module HelpScout
48
50
  end
49
51
 
50
52
  def invalid?
51
- invalid
53
+ !!invalid # rubocop:disable Style/DoubleNegation
52
54
  end
53
55
 
54
56
  def invalidate!
57
+ cache = HelpScout::API::AccessToken::Cache.new
58
+
59
+ cache.delete if cache.configured?
60
+
55
61
  self.invalid = true
56
62
  end
57
63
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HelpScout
4
+ class API
5
+ class AccessToken
6
+ class Cache
7
+ attr_reader :backend, :key
8
+
9
+ def initialize(backend: nil, key: nil)
10
+ @backend = backend || HelpScout.configuration.token_cache
11
+ @key = key || HelpScout.configuration.token_cache_key
12
+ end
13
+
14
+ def configured?
15
+ backend.present? && key.present?
16
+ end
17
+
18
+ def delete
19
+ backend.delete(key)
20
+ end
21
+
22
+ def fetch_token(&token_request)
23
+ raise ArgumentError, 'A request fallback block is required' unless block_given?
24
+
25
+ if (cached_token_json = backend.read(key))
26
+ AccessToken.new(JSON.parse(cached_token_json, symbolize_names: true))
27
+ else
28
+ token_request.call.tap { |token| write(token) }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def write(token)
35
+ backend.write(key, token.to_json, expires_in: token.expires_in)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HelpScout
4
+ class API
5
+ class AccessToken
6
+ class Request
7
+ attr_reader :response
8
+
9
+ def execute
10
+ @response = request_token
11
+ end
12
+
13
+ private
14
+
15
+ def request_token
16
+ connection = API::Client.new(authorize: false).connection
17
+ http_response = connection.post('oauth2/token', token_request_params)
18
+
19
+ case http_response.status
20
+ when 200 then AccessToken.new(Response.new(http_response).body)
21
+ when 429 then raise API::ThrottleLimitReached, http_response.body&.dig('error')
22
+ else raise API::InternalError, "unexpected response (status #{http_response.status})"
23
+ end
24
+ end
25
+
26
+ def token_request_params
27
+ @_token_request_params ||= {
28
+ grant_type: 'client_credentials',
29
+ client_id: HelpScout.app_id,
30
+ client_secret: HelpScout.app_secret
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -10,14 +10,20 @@ module HelpScout
10
10
  end
11
11
 
12
12
  def connection
13
- @_connection ||= begin
14
- HelpScout::API::AccessToken.refresh! if authorize? && token_needs_refresh?
15
- build_connection
13
+ @_connection ||= build_connection.tap do |conn|
14
+ if authorize?
15
+ HelpScout::API::AccessToken.refresh!
16
+ conn.authorization(:Bearer, access_token) if access_token
17
+ end
16
18
  end
17
19
  end
18
20
 
19
21
  private
20
22
 
23
+ def access_token
24
+ HelpScout.access_token&.value
25
+ end
26
+
21
27
  def authorize?
22
28
  authorize
23
29
  end
@@ -25,15 +31,10 @@ module HelpScout
25
31
  def build_connection
26
32
  Faraday.new(url: BASE_URL) do |conn|
27
33
  conn.request :json
28
- conn.authorization(:Bearer, HelpScout.access_token.value) if authorize? && HelpScout.access_token&.value
29
34
  conn.response(:json, content_type: /\bjson$/)
30
35
  conn.adapter(Faraday.default_adapter)
31
36
  end
32
37
  end
33
-
34
- def token_needs_refresh?
35
- HelpScout.access_token.nil? || HelpScout.access_token.stale?
36
- end
37
38
  end
38
39
  end
39
40
  end
@@ -2,13 +2,22 @@
2
2
 
3
3
  module HelpScout
4
4
  class Configuration
5
- attr_accessor :app_id, :app_secret, :default_mailbox
5
+ attr_accessor :app_id, :app_secret, :default_mailbox, :token_cache
6
6
  attr_reader :access_token
7
+ attr_writer :token_cache_key
8
+
9
+ DEFAULT_CACHE_KEY = 'help_scout_token_cache'
7
10
 
8
11
  def access_token=(token_value)
9
12
  return unless token_value
10
13
 
11
14
  @access_token = HelpScout::API::AccessToken.new(access_token: token_value)
12
15
  end
16
+
17
+ def token_cache_key
18
+ return @token_cache_key if defined?(@token_cache_key)
19
+
20
+ @token_cache_key = DEFAULT_CACHE_KEY
21
+ end
13
22
  end
14
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HelpScout
4
- VERSION = '1.0.2'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: help_scout-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TaxJar
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-07 00:00:00.000000000 Z
11
+ date: 2019-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -234,6 +234,8 @@ files:
234
234
  - lib/help_scout-sdk.rb
235
235
  - lib/help_scout/api.rb
236
236
  - lib/help_scout/api/access_token.rb
237
+ - lib/help_scout/api/access_token/cache.rb
238
+ - lib/help_scout/api/access_token/request.rb
237
239
  - lib/help_scout/api/client.rb
238
240
  - lib/help_scout/attachment.rb
239
241
  - lib/help_scout/base.rb