Longbridge.HTTPClient (longbridge v0.1.0)

Copy Markdown View Source

HTTP client for the Longbridge OpenAPI REST API.

Used for:

  • Refreshing the legacy API Key access_token via /v1/token/refresh
  • Arbitrary HTTP calls (e.g. /v1/trade/execution/today)

HTTP requests are signed with HMAC-SHA256 using the configured app_secret. OAuth 2.0 access tokens are passed directly via the Authorization: Bearer header (no signing required).

This module uses Finch under the hood.

Examples

config = Longbridge.Config.new(
  token: "current-access-token",
  app_key: "your-app-key",
  app_secret: "your-app-secret"
)

{:ok, %{token: new_token, expired_at: ts}} =
  Longbridge.HTTPClient.refresh_access_token(config)

Summary

Types

Optional OAuth token refresher.

Functions

Builds a URL query string from a keyword list.

Obtains a one-time-password (OTP) token for socket authentication.

Refreshes the legacy API Key access_token.

Performs a signed HTTP request to the Longbridge REST API.

Performs a signed HTTP request and unwraps the Longbridge response envelope.

Computes the Longbridge HMAC-SHA256 signature header.

Types

token_refresher()

@type token_refresher() :: (Longbridge.Config.t() ->
                        {:ok, Longbridge.Config.t()} | {:error, term()})

Optional OAuth token refresher.

Called by request/5 to obtain a fresh token before retrying a request that returned an auth error. The refresher receives the current config and returns either a new config with a fresh token (which request/5 will use to retry the request once) or an error tuple (which request/5 returns unchanged).

Used together with the :refresh_on_401 option.

Functions

build_query(kw)

@spec build_query(keyword()) :: String.t()

Builds a URL query string from a keyword list.

Nil values are filtered out. Values are URI-encoded.

Example

iex> Longbridge.HTTPClient.build_query(market: "US", symbol: nil)
"market=US"

get_socket_token(config, opts \\ [])

@spec get_socket_token(
  Longbridge.Config.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}

Obtains a one-time-password (OTP) token for socket authentication.

The Longbridge socket protocol requires an OTP token obtained via GET /v1/socket/token. The legacy access token (config.token) cannot be used directly for socket auth — it must be exchanged for an OTP first.

Returns {:ok, otp_token} or {:error, reason}.

refresh_access_token(config, opts \\ [])

@spec refresh_access_token(
  Longbridge.Config.t(),
  keyword()
) ::
  {:ok, %{token: String.t(), expired_at: non_neg_integer()}} | {:error, term()}

Refreshes the legacy API Key access_token.

Calls GET /v1/token/refresh?expired_at=<unix> with the current app_key / app_secret / access_token and returns the new token and expiry.

Options

  • :expired_at — When the new token should expire (Unix timestamp, seconds). Defaults to 90 days from now.
  • :http_url — Override the HTTP base URL (default from config).
  • :finch — Override the Finch instance (default Longbridge.Finch).

Returns {:ok, %{token: String.t(), expired_at: non_neg_integer()}} or {:error, reason}.

request(method, path, body, config, opts \\ [])

@spec request(atom(), String.t(), String.t(), Longbridge.Config.t(), keyword()) ::
  {:ok, term()} | {:error, term()}

Performs a signed HTTP request to the Longbridge REST API.

Signs the request with HMAC-SHA256 using config.app_secret and the Longbridge signing scheme. Returns the parsed JSON body on success, or {:error, reason}.

body is a binary (the raw HTTP body, e.g. JSON-encoded string). Pass "" for GET requests.

Options

  • :http_url — Override the HTTP base URL.
  • :params — URL query string to append to the path (e.g. "a=1&b=2").
  • :finch — Override the Finch instance (default Longbridge.Finch).
  • :token_refresher — A (Config.t() -> {:ok, Config.t()} | {:error, term()}) function called once if the request returns a token-expired error. On success, the request is retried once with the new token.

  • :on_token_refresh — A (Config.t() -> any()) function called after a successful token refresh, with the new config. Useful for updating a parent GenServer's state.

Token refresh retry

When a :token_refresher is provided and the server responds with a token-expired error, the refresher is called and the request is retried exactly once with the new token. If the refresher fails or the retry also fails, the original error is returned.

request_json(method, path, body, config, opts \\ [])

@spec request_json(atom(), String.t(), String.t(), Longbridge.Config.t(), keyword()) ::
  {:ok, map() | list()} | {:error, term()}

Performs a signed HTTP request and unwraps the Longbridge response envelope.

Longbridge REST API responses all share the form: {"code": 0, "data": {...}}

This helper calls request/5 and extracts the "data" field on success (code 0), or returns an {:error, {:api_error, code, message}} tuple.

sign(method, uri, headers, params, body, secret, signed_headers)

@spec sign(atom(), String.t(), map(), String.t(), String.t(), String.t(), String.t()) ::
  String.t()

Computes the Longbridge HMAC-SHA256 signature header.

Public for testing. The signing scheme matches the official Python SDK:

canonical = METHOD|URI|PARAMS|authorization:TOKEN
                      \nx-api-key:KEY
                      \nx-timestamp:TS
                      \n|authorization;x-api-key;x-timestamp|
if body != "": canonical += sha1(body).hex
sign_str = "HMAC-SHA256|" + sha1(canonical).hex
signature = hmac_sha256(secret, sign_str).hex
header = "HMAC-SHA256 SignedHeaders=..., Signature={sig}"