diff --git a/lib/latch.ex b/lib/latch.ex index 4e0a00d..ae50946 100644 --- a/lib/latch.ex +++ b/lib/latch.ex @@ -218,6 +218,8 @@ defmodule Latch do * `:service` - the service endpoint identifier when proxying through PDS, eg did:web:api.bsky.app#bsky_appview * `:params` - params for the method, eg `params: [actor: "did:plc:bvraa6gajy4tfr3eh2sisdkr"]` resulting in `app.bsky.actor.getProfile?actor=did:plc:bvraa6gajy4tfr3eh2sisdkr` + * `:http` - opts to pass through to the HTTP client + * `:receive_timeout` - milliseconds before timing out the request ## Examples @@ -245,6 +247,8 @@ defmodule Latch do ## Options * `service` - the service endpoint identifier when proxying through PDS, eg did:web:api.bsky.app#bsky_appview + * `:http` - opts to pass through to the HTTP client + * `:receive_timeout` - milliseconds before timing out the request ## Examples @@ -279,6 +283,8 @@ defmodule Latch do ## Options * `service` - the service endpoint identifier when proxying through PDS, eg did:web:api.bsky.app#bsky_appview + * `:http` - opts to pass through to the HTTP client + * `:receive_timeout` - milliseconds before timing out the request """ @spec upload_blob(name(), String.t(), binary(), String.t(), keyword()) :: {:ok, map()} diff --git a/lib/latch/http.ex b/lib/latch/http.ex index ac6641d..94d2ee7 100644 --- a/lib/latch/http.ex +++ b/lib/latch/http.ex @@ -73,11 +73,11 @@ defmodule Latch.HTTP do Performs an HTTP request with the given method, headers, and optional body, returning the raw status, body, and response headers. """ - @spec request(String.t(), String.t(), [{String.t(), String.t()}], body()) :: + @spec request(String.t(), String.t(), [{String.t(), String.t()}], body(), keyword()) :: {:ok, %{status: pos_integer(), body: binary(), headers: %{optional(binary()) => [binary()]}}} | {:error, Transport.t()} - def request(method, url, headers, body \\ nil) do + def request(method, url, headers, body \\ nil, http_opts \\ []) do options = [ method: method_atom(method), url: url, @@ -87,7 +87,10 @@ defmodule Latch.HTTP do redirect: false ] - case Req.request(put_body(options, body)) do + req = put_body(options, body) + http_opts = allowed_opts(http_opts) + + case Req.request(req, http_opts) do {:ok, %Req.Response{status: status, body: resp_body, headers: resp_headers}} -> {:ok, %{status: status, body: resp_body, headers: resp_headers}} @@ -122,4 +125,8 @@ defmodule Latch.HTTP do |> Keyword.put(:body, body) |> Keyword.update!(:headers, &[{"content-type", content_type} | &1]) end + + defp allowed_opts(opts) do + Keyword.take(opts, [:receive_timeout]) + end end diff --git a/lib/latch/xrpc.ex b/lib/latch/xrpc.ex index b092f65..218ca63 100644 --- a/lib/latch/xrpc.ex +++ b/lib/latch/xrpc.ex @@ -89,7 +89,9 @@ defmodule Latch.XRPC do headers end - case HTTP.request(http_method, url, headers, body) do + http_opts = Keyword.get(opts, :http, []) + + case HTTP.request(http_method, url, headers, body, http_opts) do {:ok, %{status: status, body: raw, headers: resp}} -> fresh_nonce = DPoP.nonce_header(resp) diff --git a/test/latch/xrpc_test.exs b/test/latch/xrpc_test.exs index 45262bb..3f8326a 100644 --- a/test/latch/xrpc_test.exs +++ b/test/latch/xrpc_test.exs @@ -22,7 +22,7 @@ defmodule Latch.XRPCTest do {Latch.NonceCache, config: config, name: config.name, sweep_disabled: true} ) - expect(HTTP, :request, fn http_method, url, headers, body -> + expect(HTTP, :request, fn http_method, url, headers, body, _opts -> assert http_method == "GET" assert url == "#{session.pds_endpoint}/xrpc/#{method}?#{URI.encode_query(params)}" assert {"atproto-proxy", service} in headers @@ -50,7 +50,7 @@ defmodule Latch.XRPCTest do {Latch.NonceCache, config: config, name: config.name, sweep_disabled: true} ) - expect(HTTP, :request, fn http_method, url, headers, body -> + expect(HTTP, :request, fn http_method, url, headers, body, _opts -> assert http_method == "GET" assert url == "#{session.pds_endpoint}/xrpc/#{method}" assert {"authorization", "DPoP access-token"} in headers @@ -63,6 +63,31 @@ defmodule Latch.XRPCTest do assert {:ok, %{"did" => "string"}} = XRPC.query(config, session, method, []) end + + test "passes http opts through" do + config = make_config() + session = make_session() + method = "com.atproto.server.getSession" + + start_link_supervised!( + {Latch.NonceCache, config: config, name: config.name, sweep_disabled: true} + ) + + expect(HTTP, :request, fn http_method, url, headers, body, opts -> + assert Keyword.fetch!(opts, :receive_timeout) == 30_000 + assert http_method == "GET" + assert url == "#{session.pds_endpoint}/xrpc/#{method}" + assert {"authorization", "DPoP access-token"} in headers + assert {"dpop", _} = List.keyfind(headers, "dpop", 0) + + assert body == nil + + {:ok, %{status: 200, body: ~s|{"did": "string"}|, headers: %{}}} + end) + + assert {:ok, %{"did" => "string"}} = + XRPC.query(config, session, method, http: [receive_timeout: 30_000]) + end end describe "procedure/5" do @@ -77,7 +102,7 @@ defmodule Latch.XRPCTest do {Latch.NonceCache, config: config, name: config.name, sweep_disabled: true} ) - expect(HTTP, :request, fn http_method, url, headers, body -> + expect(HTTP, :request, fn http_method, url, headers, body, _opts -> assert http_method == "POST" assert url == "#{session.pds_endpoint}/xrpc/#{method}" assert {"atproto-proxy", service} in headers @@ -106,7 +131,7 @@ defmodule Latch.XRPCTest do {Latch.NonceCache, config: config, name: config.name, sweep_disabled: true} ) - expect(HTTP, :request, fn http_method, url, headers, body -> + expect(HTTP, :request, fn http_method, url, headers, body, _opts -> assert http_method == "POST" assert url == "#{session.pds_endpoint}/xrpc/#{method}" assert {"atproto-proxy", service} in headers