diff options
Diffstat (limited to 'internal/api/google_tasks_oauth.go')
| -rw-r--r-- | internal/api/google_tasks_oauth.go | 27 |
1 files changed, 23 insertions, 4 deletions
diff --git a/internal/api/google_tasks_oauth.go b/internal/api/google_tasks_oauth.go index 1f5f8c5..6edba21 100644 --- a/internal/api/google_tasks_oauth.go +++ b/internal/api/google_tasks_oauth.go @@ -3,6 +3,7 @@ package api import ( "context" "fmt" + "time" "golang.org/x/oauth2" "golang.org/x/oauth2/google" @@ -45,12 +46,14 @@ func GoogleTasksOAuthConfig(clientID, clientSecret, redirectURL string) *oauth2. // call -- cheap enough to just hit the DB each time rather than add a // second layer of in-process caching. type dbTokenSource struct { - ctx context.Context store TokenStore source string config *oauth2.Config } +// tokenRefreshTimeout bounds a single refresh round-trip to Google. +const tokenRefreshTimeout = 30 * time.Second + func (d *dbTokenSource) Token() (*oauth2.Token, error) { current, err := d.store.GetOAuthToken(d.source) if err != nil { @@ -60,7 +63,19 @@ func (d *dbTokenSource) Token() (*oauth2.Token, error) { return nil, fmt.Errorf("google tasks not connected -- visit /settings and connect it") } - refreshed, err := d.config.TokenSource(d.ctx, current).Token() + // Deliberately NOT a context captured at construction time. main.go builds + // the client with a short-lived init context and cancels it the moment + // construction returns; reusing that here meant the first refresh after the + // startup access token aged out — roughly an hour in — failed with + // "context canceled", and so did every one after it. Google Tasks was + // broken from that point until a restart, which bought another hour. + // + // A token refresh is not logically part of whichever request happened to + // trigger it, so it gets its own background context with its own bound. + ctx, cancel := context.WithTimeout(context.Background(), tokenRefreshTimeout) + defer cancel() + + refreshed, err := d.config.TokenSource(ctx, current).Token() if err != nil { return nil, fmt.Errorf("refreshing token: %w", err) } @@ -87,7 +102,11 @@ func (d *dbTokenSource) Token() (*oauth2.Token, error) { // succeeds; API calls fail with the dbTokenSource error above until a // token exists (see HandleGoogleTasksOAuthCallback). func NewGoogleTasksOAuthClient(ctx context.Context, oauthConfig *oauth2.Config, tokenStore TokenStore, tasklistID, timezone string) (*GoogleTasksClient, error) { - src := &dbTokenSource{ctx: ctx, store: tokenStore, source: GoogleTasksOAuthSource, config: oauthConfig} - httpClient := oauth2.NewClient(ctx, src) + src := &dbTokenSource{store: tokenStore, source: GoogleTasksOAuthSource, config: oauthConfig} + // context.Background(), not ctx: oauth2.NewClient binds the context it is + // given to the returned client's transport for its whole lifetime, and ctx + // here is the caller's short-lived init context. The per-call context still + // applies — it comes from the tasks service methods themselves. + httpClient := oauth2.NewClient(context.Background(), src) return NewGoogleTasksClient(ctx, option.WithHTTPClient(httpClient), tasklistID, timezone) } |
