@@ -18,50 +18,97 @@ type Client struct {
1818 expiration time.Time
1919 token * Token
2020 m * sync.Mutex
21+ stopChan chan struct {}
2122}
2223
23- type refreshTokenTransport struct {
24+ type Transport struct {
2425 rt http.RoundTripper
2526 cli * Client
2627}
2728
28- func (t refreshTokenTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
29- var err error
29+ func (c * Client ) refreshTokenIfNeeded () error {
30+ c .m .Lock ()
31+ defer c .m .Unlock ()
3032
33+ if time .Now ().Add (time .Minute ).Before (c .expiration ) {
34+ return nil
35+ } else {
36+ // Refresh the token if its expiration is less than a minute away
37+ newToken , err := c .refreshToken (c .token .Refresh )
38+ if err != nil {
39+ return err
40+ }
41+ c .token = newToken
42+ c .expiration = time .Now ().Add (time .Duration (newToken .RefreshExpires - 60 ) * time .Second )
43+ return nil
44+ }
45+ }
46+
47+ func (c * Client ) StartTokenHandler () {
48+ c .stopChan = make (chan struct {})
49+
50+ // Initialize the first token and start the token handler
51+ token , err := c .newToken ()
52+ if err != nil {
53+ panic ("Failed to get initial token: " + err .Error ())
54+ }
55+ c .token = token
56+
57+ go func () {
58+ for {
59+ timeToWait := time .Until (c .expiration ) - time .Minute
60+ if timeToWait < 0 {
61+ // If the token is already expired, try to refresh immediately
62+ timeToWait = 0
63+ }
64+
65+ select {
66+ case <- c .stopChan :
67+ return
68+ case <- time .After (timeToWait ):
69+ if err := c .refreshTokenIfNeeded (); err != nil {
70+ // TODO(Martin): add retry logic
71+ panic ("Failed to refresh token: " + err .Error ())
72+ }
73+ }
74+ }
75+ }()
76+ }
77+
78+ func (c * Client ) StopTokenHandler () {
79+ close (c .stopChan )
80+ }
81+
82+ func (t Transport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
3183 req .URL .Scheme = "https"
3284 req .URL .Host = baseUrl
3385 req .URL .Path = strings .Join ([]string {apiPath , req .URL .Path }, "/" )
3486
3587 req .Header .Add ("Content-Type" , "application/json" )
3688 req .Header .Add ("Accept" , "application/json" )
3789
38- t .cli .m .Lock ()
39-
40- if t .cli .expiration .Before (time .Now ()) {
41- t .cli .token , err = t .cli .refreshToken (t .cli .token .Refresh )
42-
43- if err != nil {
44- return nil , err
45- }
46- t .cli .expiration = t .cli .expiration .Add (time .Duration (t .cli .token .RefreshExpires - 60 ) * time .Second )
90+ // Add the access token to the request if it exists
91+ if t .cli .token != nil {
92+ req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , t .cli .token .Access ))
4793 }
48- t .cli .m .Unlock ()
49- req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , t .cli .token .Access ))
5094
5195 return t .rt .RoundTrip (req )
5296}
5397
98+ // NewClient creates a new Nordigen client that handles token refreshes and adds
99+ // the necessary headers, host, and path to all requests.
54100func NewClient (secretId , secretKey string ) (* Client , error ) {
55- var err error
101+ c := & Client {c : & http.Client {Timeout : 60 * time .Second }, m : & sync.Mutex {},
102+ secretId : secretId ,
103+ secretKey : secretKey ,
104+ }
56105
57- c := & Client { c : & http. Client { Timeout : 60 * time . Second }, m : & sync. Mutex {}}
58- c .token , err = c . newToken ( secretId , secretKey )
106+ // Add transport to handle headers, host and path for all requests
107+ c .c . Transport = Transport { rt : http . DefaultTransport , cli : c }
59108
60- if err != nil {
61- return nil , err
62- }
63- c .c .Transport = refreshTokenTransport {rt : http .DefaultTransport , cli : c }
64- c .expiration = time .Now ().Add (time .Duration (c .token .AccessExpires - 60 ) * time .Second )
109+ // Start token handler
110+ c .StartTokenHandler ()
111+ defer c .StopTokenHandler ()
65112
66113 return c , nil
67114}
0 commit comments