privy_rs/
client.rs

1//! Privy client implementations.
2//!
3//! This module contains the `PrivyClient` with typed wallet support.
4
5use std::{num::NonZeroUsize, time::Duration};
6
7use reqwest::header::{CONTENT_TYPE, HeaderValue};
8
9use crate::{PrivyCreateError, generated::Client, get_auth_header, jwt_exchange::JwtExchange};
10
11const DEFAULT_BASE_URL: &str = "https://api.privy.io";
12const APP_ID_ENV_VAR: &str = "PRIVY_APP_ID";
13const APP_SECRET_ENV_VAR: &str = "PRIVY_APP_SECRET";
14const BASE_URL_ENV_VAR: &str = "PRIVY_BASE_URL";
15
16/// Privy client for interacting with the Privy API.
17///
18/// This provides access to global operations like user and wallet management.
19/// For wallet-specific operations, use `TypedWallet<T>` instances created via
20/// the `wallet()` method.
21///
22/// # Errors
23///
24/// The api calls that require a signature to run will return a `PrivySignedApiError`
25/// while the others will return a normal `PrivyApiError`.
26#[derive(Clone, Debug)]
27pub struct PrivyClient {
28    pub(crate) app_id: String,
29    #[allow(dead_code)]
30    pub(crate) app_secret: String,
31    pub(crate) base_url: String,
32    pub(crate) client: Client,
33
34    /// A store of all jwt operations for this client
35    pub jwt_exchange: JwtExchange,
36}
37
38/// Options for configuring a `PrivyClient`
39pub struct PrivyClientOptions {
40    /// The maximum number of cached JWT secret keys to store
41    pub cache_size: NonZeroUsize,
42    /// The base url to use when making requests
43    pub base_url: String,
44}
45
46impl Default for PrivyClientOptions {
47    fn default() -> Self {
48        Self {
49            cache_size: NonZeroUsize::new(1000).expect("non-zero"),
50            base_url: String::from(DEFAULT_BASE_URL),
51        }
52    }
53}
54
55impl PrivyClient {
56    /// Create a new `PrivyClient`
57    ///
58    /// # Usage
59    /// ```no_run
60    /// # use privy_rs::{PrivyCreateError, PrivateKey, AuthorizationContext};
61    /// # async fn foo() -> Result<(), PrivyCreateError> {
62    /// # let my_key = include_str!("../tests/test_private_key.pem").to_string();
63    /// let ctx = AuthorizationContext::new();
64    /// ctx.push(PrivateKey(my_key));
65    /// # Ok(())
66    /// # }
67    /// ```
68    ///
69    /// # Errors
70    /// This can fail for two reasons, either the `app_id` or `app_secret` are not
71    /// valid headers, or that the underlying http client could not be created.
72    pub fn new(app_id: String, app_secret: String) -> Result<Self, PrivyCreateError> {
73        Self::new_with_options(app_id, app_secret, PrivyClientOptions::default())
74    }
75
76    /// Create a new `PrivyClient` from environment variables
77    ///
78    /// # Errors
79    /// This can fail for three reasons, either the `app_id` or `app_secret` are not
80    /// valid headers, or that the underlying http client could not be created, or
81    /// that the environment variables are not set.
82    pub fn new_from_env() -> Result<Self, PrivyCreateError> {
83        let app_id = std::env::var(APP_ID_ENV_VAR).map_err(|_| PrivyCreateError::InvalidAppId)?;
84        let app_secret =
85            std::env::var(APP_SECRET_ENV_VAR).map_err(|_| PrivyCreateError::InvalidAppSecret)?;
86        Self::new_with_options(
87            app_id,
88            app_secret,
89            PrivyClientOptions {
90                base_url: std::env::var(BASE_URL_ENV_VAR)
91                    .unwrap_or_else(|_| DEFAULT_BASE_URL.to_string()),
92                ..PrivyClientOptions::default()
93            },
94        )
95    }
96
97    /// Create a new `PrivyClient` with a custom url
98    ///
99    /// # Errors
100    /// This can fail for two reasons, either the `app_id` or `app_secret` are not
101    /// valid headers, or that the underlying http client could not be created.
102    pub fn new_with_options(
103        app_id: String,
104        app_secret: String,
105        options: PrivyClientOptions,
106    ) -> Result<Self, PrivyCreateError> {
107        let client_version = concat!("rust:", env!("CARGO_PKG_VERSION"));
108
109        tracing::debug!("Privy client version: {}", client_version);
110
111        let mut headers = reqwest::header::HeaderMap::new();
112        headers.insert(
113            reqwest::header::AUTHORIZATION,
114            HeaderValue::from_str(&get_auth_header(&app_id, &app_secret))?,
115        );
116        headers.insert("privy-app-id", HeaderValue::from_str(&app_id)?);
117        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
118        headers.insert("privy-client", HeaderValue::from_static(client_version));
119
120        let client_with_custom_defaults = reqwest::ClientBuilder::new()
121            .connect_timeout(Duration::from_secs(15))
122            .timeout(Duration::from_secs(15))
123            .default_headers(headers)
124            .build()?;
125
126        Ok(Self {
127            app_id,
128            app_secret,
129            client: Client::new_with_client(&options.base_url, client_with_custom_defaults),
130            base_url: options.base_url,
131            jwt_exchange: JwtExchange::new(options.cache_size),
132        })
133    }
134
135    /// Returns a new [`Utils`] instance
136    #[must_use]
137    pub fn utils(&self) -> crate::utils::Utils {
138        crate::utils::Utils {
139            app_id: self.app_id.clone(),
140        }
141    }
142
143    /// Returns the app id for the client
144    pub fn app_id(&self) -> &str {
145        &self.app_id
146    }
147
148    /// Returns the base url for the client
149    pub fn base_url(&self) -> &str {
150        &self.base_url
151    }
152}