lara-sdk 1.0.6 → 1.0.9

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: 4f716cc4e241ce65c527b569fa22d48c570b974f29a8274c09c46af198e2f3e1
4
- data.tar.gz: 43bc6db5d3199a3959f329d9f6b425341655432a9fbfe308bc424fb18ed3002b
3
+ metadata.gz: bec4d6b11a9be0d2481e0c83488befb4230e87cba9ec5d598f8ee397307b0c8c
4
+ data.tar.gz: 9db2905ef0db583ef555c1792d326369776c2508d2d5bdc3a75ff9176320c284
5
5
  SHA512:
6
- metadata.gz: d99be00c98ce8ecb50ccf6e0f3efb0d5a726340136cb3630c18e8ac7b3f2dab0d33fd69742859a6a0a4cfc19b3c89e7f8128d21edef48287c63f13133e665ac8
7
- data.tar.gz: 4ccfd2e79297dff673b1fd06b00ab9a465cdab46c62e104f7a26d47e2ba08945d3d84b6fc22b64bde22078a612d24ab2dccef37b1e09f8b4d3fbbde335b62ed3
6
+ metadata.gz: 9b644e13aeb677fa3a95af224165f87d1b35fdaf7f356ad25d1fc803cd0e243641e99a25514e5cbfef15d110efb4bca232d69e92fff333348ce68794d4e9f6f3
7
+ data.tar.gz: 488de412d6562bbca3b8c6f9ba613da79150dccbdfdb58a1be4e1288ccaa8f3f060d6584c98b58ecb9e1802f12dae6b2ffa6e41b34ac52469c2a0aa3c9b1d271
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+ require "base64"
5
+
3
6
  module Lara
4
7
  # JWT authentication token for API access
5
8
  class AuthToken
@@ -8,10 +11,34 @@ module Lara
8
11
  def initialize(token, refresh_token)
9
12
  @token = token
10
13
  @refresh_token = refresh_token
14
+ @expires_at_ms = parse_expires_at_ms(token)
11
15
  end
12
16
 
13
17
  def to_s
14
18
  token
15
19
  end
20
+
21
+ def token_expired?
22
+ @expires_at_ms <= (Time.now.to_f * 1000).to_i + 5_000 # 5 seconds buffer
23
+ end
24
+
25
+ private
26
+
27
+ def parse_expires_at_ms(token)
28
+ return 0 if token.nil? || token.empty?
29
+
30
+ parts = token.split(".")
31
+ return 0 if parts.length != 3
32
+
33
+ b64 = parts[1].tr("-_", "+/")
34
+ b64 += "=" * (4 - (b64.length % 4)) if b64.length % 4 != 0
35
+
36
+ exp = JSON.parse(Base64.decode64(b64))["exp"]
37
+ return 0 unless exp.is_a?(Numeric)
38
+
39
+ (exp * 1000).to_i
40
+ rescue StandardError
41
+ 0
42
+ end
16
43
  end
17
44
  end
data/lib/lara/client.rb CHANGED
@@ -7,6 +7,7 @@ require "openssl"
7
7
  require "base64"
8
8
  require "digest"
9
9
  require "uri"
10
+ require "monitor"
10
11
 
11
12
  module Lara
12
13
  # This class is used to interact with Lara via the REST API.
@@ -31,7 +32,7 @@ module Lara
31
32
  @connection_timeout = connection_timeout
32
33
  @read_timeout = read_timeout
33
34
  @extra_headers = {}
34
- @auth_mutex = Mutex.new
35
+ @auth_mutex = Monitor.new
35
36
 
36
37
  @connection = build_connection
37
38
  end
@@ -93,23 +94,37 @@ module Lara
93
94
  make_request(method, path, body: body, files: files, headers: headers, params: params,
94
95
  raw_response: raw_response, &callback)
95
96
  rescue LaraApiError => e
96
- # Auto-refresh token on 401 with jwt expired
97
- raise unless e.status_code == 401 && e.message.include?("jwt expired")
97
+ raise unless e.status_code == 401
98
98
 
99
- refresh_token
100
- # Retry once with new token
99
+ @auth_mutex.synchronize { refresh_or_reauthenticate }
101
100
  make_request(method, path, body: body, files: files, headers: headers, params: params,
102
101
  raw_response: raw_response, &callback)
103
102
  end
104
103
 
105
104
  def ensure_valid_token
106
105
  @auth_mutex.synchronize do
107
- return if @auth_token
106
+ return if @auth_token && !@auth_token.token_expired?
108
107
 
109
- raise LaraError, "No authentication method available" unless @credentials
108
+ refresh_or_reauthenticate
109
+ end
110
+ end
111
+
112
+ def refresh_or_reauthenticate
113
+ if @auth_token&.refresh_token && !@auth_token.refresh_token.empty?
114
+ begin
115
+ do_refresh
116
+ return
117
+ rescue StandardError
118
+ raise unless @credentials
119
+ end
120
+ end
110
121
 
122
+ if @credentials
111
123
  @auth_token = authenticate
124
+ return
112
125
  end
126
+
127
+ raise LaraError, "No authentication method available for token renewal"
113
128
  end
114
129
 
115
130
  def authenticate
@@ -143,47 +158,37 @@ module Lara
143
158
  raise LaraApiError.from_response(response) unless response.success?
144
159
 
145
160
  data = JSON.parse(response.body)
146
- refresh_token = response.headers["x-lara-refresh-token"]
161
+ refresh_token_value = response.headers["x-lara-refresh-token"]
147
162
 
148
- raise LaraError, "Missing refresh token in authentication response" unless refresh_token
163
+ raise LaraError, "Missing refresh token in authentication response" unless refresh_token_value
149
164
 
150
- AuthToken.new(data["token"], refresh_token)
165
+ AuthToken.new(data["token"], refresh_token_value)
151
166
  end
152
167
 
153
- def refresh_token
154
- @auth_mutex.synchronize do
155
- return unless @auth_token
168
+ def do_refresh
169
+ raise LaraError, "No refresh token available" unless @auth_token&.refresh_token
156
170
 
157
- conn = Faraday.new(url: @base_url) do |c|
158
- c.adapter Faraday.default_adapter
159
- end
160
-
161
- response = conn.post("/v2/auth/refresh") do |req|
162
- req.headers = {
163
- "Date" => Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S GMT"),
164
- "X-Lara-SDK-Name" => "lara-ruby",
165
- "X-Lara-SDK-Version" => Lara::VERSION,
166
- "Authorization" => "Bearer #{@auth_token&.refresh_token}"
167
- }
168
- end
171
+ conn = Faraday.new(url: @base_url) do |c|
172
+ c.adapter Faraday.default_adapter
173
+ end
169
174
 
170
- if response.success?
171
- data = JSON.parse(response.body)
172
- refresh_token = response.headers["x-lara-refresh-token"]
173
-
174
- if refresh_token
175
- @auth_token = AuthToken.new(data["token"], refresh_token)
176
- else
177
- # Refresh failed, force re-authentication
178
- @auth_token = nil
179
- ensure_valid_token
180
- end
181
- else
182
- # Refresh failed, force re-authentication
183
- @auth_token = nil
184
- ensure_valid_token
185
- end
175
+ response = conn.post("/v2/auth/refresh") do |req|
176
+ req.headers = {
177
+ "Date" => Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S GMT"),
178
+ "X-Lara-SDK-Name" => "lara-ruby",
179
+ "X-Lara-SDK-Version" => Lara::VERSION,
180
+ "Authorization" => "Bearer #{@auth_token.refresh_token}"
181
+ }
186
182
  end
183
+
184
+ raise LaraApiError.from_response(response) unless response.success?
185
+
186
+ data = JSON.parse(response.body)
187
+ refresh_token_value = response.headers["x-lara-refresh-token"]
188
+
189
+ raise LaraError, "Missing refresh token in refresh response" unless refresh_token_value
190
+
191
+ @auth_token = AuthToken.new(data["token"], refresh_token_value)
187
192
  end
188
193
 
189
194
  def make_request(method, path, body: nil, files: nil, headers: nil, params: nil, raw_response: false, &callback)
data/lib/lara/errors.rb CHANGED
@@ -17,15 +17,16 @@ module Lara
17
17
  class LaraApiError < LaraError
18
18
  attr_reader :type
19
19
 
20
- # Builds an error from an HTTP response with JSON body:
21
- # { "error": { "type": "...", "message": "..." } }
20
+ # Builds an error from an HTTP response with JSON body.
21
+ # Supports both { "error": { "type": "...", "message": "..." } }
22
+ # and { "type": "...", "message": "..." } response formats.
22
23
  def self.from_response(response)
23
24
  data = begin
24
25
  JSON.parse(response.body)
25
26
  rescue StandardError
26
27
  {}
27
28
  end
28
- error = data["error"] || {}
29
+ error = data["error"] || data
29
30
  error_type = error["type"] || "UnknownError"
30
31
  error_message = error["message"] || "An unknown error occurred"
31
32
  new(response.status, error_type, error_message)
data/lib/lara/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lara
4
- VERSION = "1.0.6"
4
+ VERSION = "1.0.9"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lara-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Translated
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-11 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday