@@ -17,6 +17,7 @@ import (
1717 "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
1818 "github.com/router-for-me/CLIProxyAPI/v7/internal/util"
1919 log "github.com/sirupsen/logrus"
20+ "golang.org/x/sync/singleflight"
2021)
2122
2223// OAuth configuration constants for OpenAI Codex
@@ -34,6 +35,8 @@ type CodexAuth struct {
3435 httpClient * http.Client
3536}
3637
38+ var codexRefreshGroup singleflight.Group
39+
3740// NewCodexAuth creates a new CodexAuth service instance.
3841// It initializes an HTTP client with proxy settings from the provided configuration.
3942func NewCodexAuth (cfg * config.Config ) * CodexAuth {
@@ -187,33 +190,52 @@ func (o *CodexAuth) RefreshTokens(ctx context.Context, refreshToken string) (*Co
187190 if refreshToken == "" {
188191 return nil , fmt .Errorf ("refresh token is required" )
189192 }
193+ if ctx == nil {
194+ ctx = context .Background ()
195+ }
190196
197+ result , err , _ := codexRefreshGroup .Do (refreshToken , func () (interface {}, error ) {
198+ return o .refreshTokensSingleFlight (context .WithoutCancel (ctx ), refreshToken )
199+ })
200+ if err != nil {
201+ return nil , err
202+ }
203+ tokenData , ok := result .(* CodexTokenData )
204+ if ! ok || tokenData == nil {
205+ return nil , fmt .Errorf ("token refresh failed: invalid single-flight result" )
206+ }
207+ return tokenData , nil
208+ }
209+
210+ func (o * CodexAuth ) refreshTokensSingleFlight (ctx context.Context , refreshToken string ) (* CodexTokenData , error ) {
191211 data := url.Values {
192212 "client_id" : {ClientID },
193213 "grant_type" : {"refresh_token" },
194214 "refresh_token" : {refreshToken },
195215 "scope" : {"openid profile email" },
196216 }
197217
198- req , err := http .NewRequestWithContext (ctx , "POST" , TokenURL , strings .NewReader (data .Encode ()))
199- if err != nil {
200- return nil , fmt .Errorf ("failed to create refresh request: %w" , err )
218+ req , errReq := http .NewRequestWithContext (ctx , "POST" , TokenURL , strings .NewReader (data .Encode ()))
219+ if errReq != nil {
220+ return nil , fmt .Errorf ("failed to create refresh request: %w" , errReq )
201221 }
202222
203223 req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
204224 req .Header .Set ("Accept" , "application/json" )
205225
206- resp , err := o .httpClient .Do (req )
207- if err != nil {
208- return nil , fmt .Errorf ("token refresh request failed: %w" , err )
226+ resp , errDo := o .httpClient .Do (req )
227+ if errDo != nil {
228+ return nil , fmt .Errorf ("token refresh request failed: %w" , errDo )
209229 }
210230 defer func () {
211- _ = resp .Body .Close ()
231+ if errClose := resp .Body .Close (); errClose != nil {
232+ log .Errorf ("token refresh response body close error: %v" , errClose )
233+ }
212234 }()
213235
214- body , err := io .ReadAll (resp .Body )
215- if err != nil {
216- return nil , fmt .Errorf ("failed to read refresh response: %w" , err )
236+ body , errRead := io .ReadAll (resp .Body )
237+ if errRead != nil {
238+ return nil , fmt .Errorf ("failed to read refresh response: %w" , errRead )
217239 }
218240
219241 if resp .StatusCode != http .StatusOK {
@@ -228,14 +250,14 @@ func (o *CodexAuth) RefreshTokens(ctx context.Context, refreshToken string) (*Co
228250 ExpiresIn int `json:"expires_in"`
229251 }
230252
231- if err = json .Unmarshal (body , & tokenResp ); err != nil {
232- return nil , fmt .Errorf ("failed to parse refresh response: %w" , err )
253+ if errUnmarshal : = json .Unmarshal (body , & tokenResp ); errUnmarshal != nil {
254+ return nil , fmt .Errorf ("failed to parse refresh response: %w" , errUnmarshal )
233255 }
234256
235257 // Extract account ID from ID token
236- claims , err := ParseJWTToken (tokenResp .IDToken )
237- if err != nil {
238- log .Warnf ("Failed to parse refreshed ID token: %v" , err )
258+ claims , errParseJWT := ParseJWTToken (tokenResp .IDToken )
259+ if errParseJWT != nil {
260+ log .Warnf ("Failed to parse refreshed ID token: %v" , errParseJWT )
239261 }
240262
241263 accountID := ""
0 commit comments