booqable 2.0.0 → 2.2.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: ee065493e3d48d59edfb13552c3dd8dbce2d800ecfb9d3c8230f6f3c52345be0
4
- data.tar.gz: a9fd9934fd93f8504ca1a58e2cab82e09ab1f4f9f2da9fb4a30033e3f84bfe16
3
+ metadata.gz: ea7652c0e4f9625ad965a20a0d9ec5aaf6265cbaf7b464c958b393d1d11da6e5
4
+ data.tar.gz: 95f3928c775d8c74bf1a89f86de03b46bd05df79a0d6e4371049936b69306081
5
5
  SHA512:
6
- metadata.gz: 4babbce96eead5118e72cf700490aa703fb221730a7bd601f23d229cad34f63c91b59f6a7d955e733b0ce111359eb1c8f4b5e54e3834647952c44baf6e222422
7
- data.tar.gz: fe5bd8c6bc0150c6a61f45e72de5edfc35ae33335d7bc2fa514a31650a20e44ccbd4dfa8fe3df5a9d7bf22915148c4e0bc7a259c9620e316220b66f36990db7d
6
+ metadata.gz: 99cef1b7b21ae72fa8df8516ad6791b2e2568cca68463270d8e620b71a36b333f321cd5850a637c8fd22d384321887c1ac27afd26929b571f989620d025501f2
7
+ data.tar.gz: 021b52cd5054f2033c83a6234dce59f64fb9a30cf8884ee7ded7d35bb48e865b9467c33c84329ca006a407ab36716ff3bcee95f116a668821ccb9c7411a94a45
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## [2.2.0] - 2026-11-03
2
+
3
+ - OAuth middleware: refresh the access token a short buffer
4
+ (`REFRESH_BUFFER_SECONDS`, 60s) before it expires, instead of only once it
5
+ has already expired. This fixes `Token is invalid` failures for requests
6
+ landing right at the token's expiry boundary, where network latency or clock
7
+ skew pushes an otherwise-valid token past expiry server-side.
8
+ - Fix: a token refresh failing without an HTTP response (e.g. the stored token
9
+ has no refresh token) now raises the original `OAuth2::Error` instead of
10
+ crashing with `NoMethodError` while trying to map a response that isn't
11
+ there.
12
+ - Require `multi_json` >= 1.21.2, the first release compatible with the
13
+ `json` 3.x gem. Older versions raise `ArgumentError` on every parse under
14
+ json 3.
15
+
16
+ ## [2.1.0] - 2026-07-16
17
+
18
+ - Add the `app_issues` resource (CRUD for `App::Issue`).
19
+ - Fix: a to-one relationship serialized with `"data": null` (e.g. a charge
20
+ line's `item`, or an included `barcode` on a product without one) now reads
21
+ as nil, matching the present-with-null attribute semantics — instead of the
22
+ parser dropping the key and the read raising `Booqable::MissingAttribute`.
23
+
1
24
  ## [2.0.0] - 2026-07-14
2
25
 
3
26
  - **Breaking:** reading an attribute that is absent from an API payload now
@@ -184,6 +184,11 @@ module Booqable
184
184
  # Handle multiple relationships (to-many)
185
185
  elsif relationship_data.is_a?(Array)
186
186
  hash[key] = relationship_data
187
+ # An explicitly null to-one relationship ("data": null) is present-with-null, not
188
+ # absent: keep the key so strict reads answer nil instead of raising
189
+ # Booqable::MissingAttribute (see Booqable::StrictAttributes).
190
+ elsif relationship_data.nil?
191
+ hash[key] = nil
187
192
  end
188
193
  end
189
194
 
@@ -15,6 +15,12 @@ module Booqable
15
15
  # read_token: -> { stored_token },
16
16
  # write_token: ->(token) { store_token(token) }
17
17
  class OAuth < Base
18
+ # Refresh the token this many seconds before it actually expires. A
19
+ # request sent in the last moment of a token's life can still be
20
+ # rejected once network latency or clock skew pushes it past the
21
+ # expiry boundary server-side.
22
+ REFRESH_BUFFER_SECONDS = 60
23
+
18
24
  # Initialize the OAuth authentication middleware
19
25
  #
20
26
  # @param app [#call] The next middleware in the Faraday stack
@@ -48,9 +54,9 @@ module Booqable
48
54
 
49
55
  # Process the HTTP request and add OAuth authentication
50
56
  #
51
- # Retrieves the stored access token, refreshes it if expired, and adds
52
- # it to the Authorization header. Then passes the request to the next
53
- # middleware in the stack.
57
+ # Retrieves the stored access token, refreshes it if it is expired or
58
+ # about to expire, and adds it to the Authorization header. Then
59
+ # passes the request to the next middleware in the stack.
54
60
  #
55
61
  # @param env [Faraday::Env] The request environment
56
62
  # @return [Faraday::Response] The response from the next middleware
@@ -58,7 +64,7 @@ module Booqable
58
64
  around_refresh_token do
59
65
  @token = @client.get_access_token_from_hash(@read_token.call)
60
66
 
61
- if @token.expired? || @token.expires_at.nil?
67
+ if expires_soon?(@token)
62
68
  @token = refresh_token!
63
69
  end
64
70
  end
@@ -70,6 +76,14 @@ module Booqable
70
76
 
71
77
  private
72
78
 
79
+ # Whether the token expires within the buffer, or has no usable
80
+ # expiry. expires_at may be an Integer epoch (OAuth2) or a Time
81
+ # (e.g. an ActiveRecord datetime handed back by read_token), so
82
+ # coerce it.
83
+ def expires_soon?(token)
84
+ token.expires_at.nil? || Time.now.to_i >= token.expires_at.to_i - REFRESH_BUFFER_SECONDS
85
+ end
86
+
73
87
  # Yield to the configured around-callback, if any
74
88
  #
75
89
  # When a host application provides one (e.g. an advisory lock), the
@@ -84,10 +98,16 @@ module Booqable
84
98
  #
85
99
  # Uses the refresh token to obtain a new access token and stores it
86
100
  # using the configured write_token proc. Converts OAuth2 errors to
87
- # Booqable errors for consistent error handling.
101
+ # Booqable errors for consistent error handling. The OAuth2 error
102
+ # propagates as-is when there is no HTTP response to map (e.g. the
103
+ # stored token has no refresh token, so the error carries a plain
104
+ # Hash) or when the response maps to no Booqable error class (e.g. a
105
+ # 200 from the token endpoint without an access token — only 4xx/5xx
106
+ # map). A refresh failure must never return: the caller would carry
107
+ # on with a nil token.
88
108
  #
89
109
  # @return [OAuth2::AccessToken] The new access token
90
- # @raise [Booqable::Error] For OAuth-related errors
110
+ # @raise [Booqable::Error, OAuth2::Error] When the refresh fails
91
111
  def refresh_token!
92
112
  new_token = @token.refresh!
93
113
 
@@ -95,9 +115,11 @@ module Booqable
95
115
 
96
116
  new_token
97
117
  rescue OAuth2::Error => e
98
- response = e.response.response.env
118
+ http_response = e.response.response if e.response.respond_to?(:response)
119
+ raise unless http_response&.env
99
120
 
100
- Booqable::Error.from_response(response)
121
+ Booqable::Error.from_response(http_response.env)
122
+ raise
101
123
  end
102
124
  end
103
125
  end
@@ -2,6 +2,7 @@
2
2
  { "app_carriers": "carriers" },
3
3
  { "app_subscriptions": "subscriptions" },
4
4
  { "app_payment_options": "payment_options" },
5
+ { "app_issues": "issues" },
5
6
  "authentication_methods",
6
7
  "barcodes",
7
8
  "bundles",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Booqable
4
- VERSION = "2.0.0"
4
+ VERSION = "2.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: booqable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hrvoje Šimić
@@ -58,6 +58,9 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '1.15'
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.21.2
61
64
  type: :runtime
62
65
  prerelease: false
63
66
  version_requirements: !ruby/object:Gem::Requirement
@@ -65,6 +68,9 @@ dependencies:
65
68
  - - "~>"
66
69
  - !ruby/object:Gem::Version
67
70
  version: '1.15'
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 1.21.2
68
74
  - !ruby/object:Gem::Dependency
69
75
  name: addressable
70
76
  requirement: !ruby/object:Gem::Requirement
@@ -187,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
193
  - !ruby/object:Gem::Version
188
194
  version: '0'
189
195
  requirements: []
190
- rubygems_version: 3.6.9
196
+ rubygems_version: 4.0.10
191
197
  specification_version: 4
192
198
  summary: Official Booqable API client for Ruby.
193
199
  test_files: []