diff --git a/lib/privy/public_api/privy_client.rb b/lib/privy/public_api/privy_client.rb index fb02097..ad95f13 100644 --- a/lib/privy/public_api/privy_client.rb +++ b/lib/privy/public_api/privy_client.rb @@ -2,6 +2,10 @@ module Privy class PrivyClient + # 15 minutes. Used when a caller doesn't pass `request_expiry:` and the + # client's PrivyRequestExpiryOptions doesn't override `default_ms`. + DEFAULT_REQUEST_EXPIRY_MS = 15 * 60 * 1_000 + attr_reader :app_id, :app_secret # @api private @@ -10,6 +14,9 @@ class PrivyClient # Service accessors. attr_reader :wallets, :users, :policies, :key_quorums, :jwt_exchange + # @return [Privy::PrivyRequestExpiryOptions] + attr_reader :request_expiry_options + def initialize( app_id: ENV["PRIVY_APP_ID"], app_secret: ENV["PRIVY_APP_SECRET"], @@ -18,10 +25,12 @@ def initialize( max_retries: Privy::Client::DEFAULT_MAX_RETRIES, timeout: Privy::Client::DEFAULT_TIMEOUT_IN_SECONDS, initial_retry_delay: Privy::Client::DEFAULT_INITIAL_RETRY_DELAY, - max_retry_delay: Privy::Client::DEFAULT_MAX_RETRY_DELAY + max_retry_delay: Privy::Client::DEFAULT_MAX_RETRY_DELAY, + request_expiry: Privy::PrivyRequestExpiryOptions.build ) @app_id = app_id @app_secret = app_secret + @request_expiry_options = request_expiry @api = Privy::Client.new( app_id: app_id, @@ -40,5 +49,23 @@ def initialize( @key_quorums = Privy::Services::KeyQuorums.new(client: @api, privy_client: self) @jwt_exchange = Privy::JwtExchangeService.new(wallets_resource: @api.wallets) end + + # Resolves the absolute Unix-ms timestamp to send as `privy-request-expiry`. + # + # Precedence: + # 1. Explicit per-call value (caller wins, even over `disabled: true`). + # 2. `disabled: true` -> nil (no header sent). + # 3. `request_expiry_options.default_ms` (offset from now). + # 4. {DEFAULT_REQUEST_EXPIRY_MS} (15 minutes from now). + # + # @param override_absolute_ms [Integer, nil] Absolute Unix-ms timestamp from caller. + # @return [Integer, nil] + def compute_request_expiry(override_absolute_ms = nil) + return override_absolute_ms unless override_absolute_ms.nil? + return nil if @request_expiry_options.disabled + + Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) + + (@request_expiry_options.default_ms || DEFAULT_REQUEST_EXPIRY_MS) + end end end diff --git a/test/privy/public_api/privy_client_test.rb b/test/privy/public_api/privy_client_test.rb index 85c3e19..8ea67c5 100644 --- a/test/privy/public_api/privy_client_test.rb +++ b/test/privy/public_api/privy_client_test.rb @@ -8,4 +8,62 @@ def test_constructs_with_explicit_credentials assert_equal("app-123", client.app_id) assert_instance_of(Privy::Client, client.api) end + + def build_client(**opts) + Privy::PrivyClient.new( + app_id: "app-abc", + app_secret: "secret", + environment: :staging, + **opts + ) + end + + def now_ms + Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) + end + + def test_default_expiry_is_15_minutes_from_now + client = build_client + expiry = client.compute_request_expiry(nil) + assert_in_delta(now_ms + (15 * 60 * 1_000), expiry, 1_000) + end + + def test_default_ms_overrides_hardcoded_default + client = build_client( + request_expiry: Privy::PrivyRequestExpiryOptions.build(default_ms: 60_000) + ) + expiry = client.compute_request_expiry(nil) + assert_in_delta(now_ms + 60_000, expiry, 1_000) + end + + def test_disabled_returns_nil + client = build_client( + request_expiry: Privy::PrivyRequestExpiryOptions.build(disabled: true) + ) + assert_nil(client.compute_request_expiry(nil)) + end + + def test_explicit_override_wins_over_default_ms + client = build_client( + request_expiry: Privy::PrivyRequestExpiryOptions.build(default_ms: 60_000) + ) + assert_equal(1_750_000_000_000, client.compute_request_expiry(1_750_000_000_000)) + end + + def test_explicit_override_wins_over_disabled + client = build_client( + request_expiry: Privy::PrivyRequestExpiryOptions.build(disabled: true) + ) + assert_equal(1_750_000_000_000, client.compute_request_expiry(1_750_000_000_000)) + end + + def test_request_expiry_options_default_value_does_not_disable + client = build_client + refute_nil(client.compute_request_expiry(nil)) + end + + def test_compute_request_expiry_callable_without_arguments + client = build_client + refute_nil(client.compute_request_expiry) + end end