1+ use std:: {
2+ sync:: Arc ,
3+ time:: { Duration , Instant } ,
4+ } ;
5+
16use async_trait:: async_trait;
7+ use reqwest:: header:: { HeaderValue , AUTHORIZATION } ;
28use reqwest:: Client as ReqestClient ;
39use reqwest_utils:: parse_custom_rpc_headers;
410use serde:: de:: DeserializeOwned ;
11+ use tokio:: sync:: RwLock ;
12+ use url:: Url ;
513
614use hyperlane_core:: { ChainCommunicationError , ChainResult } ;
7- use url:: Url ;
815
9- use crate :: provider:: HttpClient ;
16+ use crate :: provider:: { HttpClient , HttpClientBuilder } ;
1017use crate :: HyperlaneAleoError ;
11- use std:: time:: Duration ;
1218
1319// Default timeouts
1420pub const DEFAULT_CONNECT_TIMEOUT : Duration = Duration :: from_secs ( 10 ) ;
@@ -34,52 +40,156 @@ impl BaseHttpClient {
3440 let suffix = match network {
3541 0 => "mainnet" ,
3642 1 => "testnet" ,
43+ 2 => "canary" ,
3744 id => return Err ( HyperlaneAleoError :: UnknownNetwork ( id) . into ( ) ) ,
3845 } ;
3946 Ok ( Self {
4047 client,
4148 base_url : url. to_string ( ) . trim_end_matches ( "/" ) . to_string ( ) + "/" + suffix,
4249 } )
4350 }
51+ }
52+
53+ #[ async_trait]
54+ impl HttpClient for BaseHttpClient {
55+ /// Makes a GET request to the API
56+ async fn request < T : DeserializeOwned + Send > (
57+ & self ,
58+ path : & str ,
59+ query : impl Into < Option < serde_json:: Value > > + Send ,
60+ ) -> ChainResult < T > {
61+ let url = format ! ( "{}/{}" , self . base_url, path) ;
62+ let query: serde_json:: Value = query. into ( ) . unwrap_or_default ( ) ;
63+ let response = self
64+ . client
65+ . get ( & url)
66+ . query ( & query)
67+ . send ( )
68+ . await
69+ . map_err ( HyperlaneAleoError :: from) ?;
70+ let response = response
71+ . error_for_status ( )
72+ . map_err ( HyperlaneAleoError :: from) ?;
73+ let json = response. json ( ) . await . map_err ( HyperlaneAleoError :: from) ?;
74+ Ok ( json)
75+ }
4476
45- pub fn with_timeouts (
46- base_url : impl Into < String > ,
47- connect_timeout : Duration ,
48- request_timeout : Duration ,
49- ) -> Result < Self , HyperlaneAleoError > {
77+ /// Makes a POST request to the API
78+ async fn request_post < T : DeserializeOwned + Send > (
79+ & self ,
80+ path : & str ,
81+ body : & serde_json:: Value ,
82+ ) -> ChainResult < T > {
83+ let url = format ! ( "{}/{}" , self . base_url, path) ;
84+ let response = self
85+ . client
86+ . post ( & url)
87+ . json ( body)
88+ . send ( )
89+ . await
90+ . map_err ( HyperlaneAleoError :: from) ?;
91+ let response = response
92+ . error_for_status ( )
93+ . map_err ( HyperlaneAleoError :: from) ?;
94+ Ok ( response. json ( ) . await . map_err ( HyperlaneAleoError :: from) ?)
95+ }
96+ }
97+
98+ impl HttpClientBuilder for BaseHttpClient {
99+ type Client = BaseHttpClient ;
100+
101+ fn build ( url : Url , network : u16 ) -> ChainResult < Self :: Client > {
102+ BaseHttpClient :: new ( url, network)
103+ }
104+ }
105+
106+ /// Base Http client that performs REST-ful queries
107+ #[ derive( Clone , Debug ) ]
108+ pub struct JWTBaseHttpClient {
109+ client : ReqestClient ,
110+ base_url : String ,
111+ suffix : String ,
112+ auth_url : String ,
113+ auth_token : Arc < RwLock < Option < ( HeaderValue , Instant ) > > > ,
114+ }
115+
116+ impl JWTBaseHttpClient {
117+ /// Creates a new Http client
118+ pub fn new ( base_url : Url , network : u16 ) -> ChainResult < Self > {
119+ let ( headers, url) =
120+ parse_custom_rpc_headers ( & base_url) . map_err ( ChainCommunicationError :: from_other) ?;
121+ let auth_url = headers
122+ . get ( "x-auth-url" )
123+ . and_then ( |v| v. to_str ( ) . ok ( ) )
124+ . unwrap_or_default ( )
125+ . to_string ( ) ;
50126 let client = ReqestClient :: builder ( )
51- . connect_timeout ( connect_timeout)
52- . timeout ( request_timeout)
127+ . connect_timeout ( DEFAULT_CONNECT_TIMEOUT )
128+ . timeout ( DEFAULT_REQUEST_TIMEOUT )
129+ . default_headers ( headers)
53130 . build ( )
54131 . map_err ( HyperlaneAleoError :: from) ?;
132+ let suffix = match network {
133+ 0 => "mainnet" ,
134+ 1 => "testnet" ,
135+ 2 => "canary" ,
136+ id => return Err ( HyperlaneAleoError :: UnknownNetwork ( id) . into ( ) ) ,
137+ } ;
55138 Ok ( Self {
56139 client,
57- base_url : base_url. into ( ) ,
140+ base_url : url. to_string ( ) . trim_end_matches ( "/" ) . to_string ( ) ,
141+ auth_token : Default :: default ( ) ,
142+ suffix : suffix. to_string ( ) ,
143+ auth_url,
58144 } )
59145 }
60146
61- pub fn client ( & self ) -> & ReqestClient {
62- & self . client
63- }
147+ /// Gets the authentication token if it is still valid
148+ pub async fn get_auth_token ( & self ) -> ChainResult < HeaderValue > {
149+ {
150+ let auth_token = self . auth_token . read ( ) . await ;
151+ if let Some ( ( token, expires_at) ) = & * auth_token {
152+ if Instant :: now ( ) < * expires_at {
153+ return Ok ( token. clone ( ) ) ;
154+ }
155+ }
156+ }
64157
65- pub fn base_url ( & self ) -> & str {
66- & self . base_url
158+ let response = self
159+ . client
160+ . post ( & self . auth_url )
161+ . send ( )
162+ . await
163+ . map_err ( HyperlaneAleoError :: from) ?;
164+ let result = response
165+ . headers ( )
166+ . get ( AUTHORIZATION )
167+ . ok_or ( HyperlaneAleoError :: MissingAuthHeader ) ?
168+ . clone ( ) ;
169+ let expires = Instant :: now ( )
170+ . checked_add ( Duration :: from_secs ( 60 * 15 ) )
171+ . unwrap_or ( Instant :: now ( ) ) ; // Tokens last 15 minutes
172+ let mut auth_token = self . auth_token . write ( ) . await ;
173+ * auth_token = Some ( ( result. clone ( ) , expires) ) ;
174+ Ok ( result. clone ( ) )
67175 }
68176}
69177
70178#[ async_trait]
71- impl HttpClient for BaseHttpClient {
179+ impl HttpClient for JWTBaseHttpClient {
72180 /// Makes a GET request to the API
73181 async fn request < T : DeserializeOwned + Send > (
74182 & self ,
75183 path : & str ,
76184 query : impl Into < Option < serde_json:: Value > > + Send ,
77185 ) -> ChainResult < T > {
78- let url = format ! ( "{}/{}" , self . base_url, path) ;
186+ let url = format ! ( "{}/{}/{} " , self . base_url, self . suffix , path) ;
79187 let query: serde_json:: Value = query. into ( ) . unwrap_or_default ( ) ;
188+ let auth = self . get_auth_token ( ) . await ?;
80189 let response = self
81190 . client
82191 . get ( & url)
192+ . header ( AUTHORIZATION , auth)
83193 . query ( & query)
84194 . send ( )
85195 . await
@@ -97,10 +207,12 @@ impl HttpClient for BaseHttpClient {
97207 path : & str ,
98208 body : & serde_json:: Value ,
99209 ) -> ChainResult < T > {
100- let url = format ! ( "{}/{}" , self . base_url, path) ;
210+ let url = format ! ( "{}/{}/{}" , self . base_url, self . suffix, path) ;
211+ let auth = self . get_auth_token ( ) . await ?;
101212 let response = self
102213 . client
103214 . post ( & url)
215+ . header ( AUTHORIZATION , auth)
104216 . json ( body)
105217 . send ( )
106218 . await
@@ -111,3 +223,11 @@ impl HttpClient for BaseHttpClient {
111223 Ok ( response. json ( ) . await . map_err ( HyperlaneAleoError :: from) ?)
112224 }
113225}
226+
227+ impl HttpClientBuilder for JWTBaseHttpClient {
228+ type Client = JWTBaseHttpClient ;
229+
230+ fn build ( url : Url , network : u16 ) -> ChainResult < Self :: Client > {
231+ JWTBaseHttpClient :: new ( url, network)
232+ }
233+ }
0 commit comments