Package fitbit: Update RefreshToken in Datastore after refreshing OAuth token.
authorFlorian Forster <ff@octo.it>
Thu, 11 Jan 2018 07:39:16 +0000 (08:39 +0100)
committerFlorian Forster <ff@octo.it>
Thu, 11 Jan 2018 07:39:16 +0000 (08:39 +0100)
fitbit/fitbit.go

index 6f15027..bdfc3dd 100644 (file)
@@ -123,19 +123,40 @@ type Client struct {
 }
 
 func NewClient(ctx context.Context, fitbitUserID string, u *app.User) (*Client, error) {
-       tok, err := u.Token(ctx, "Fitbit")
+       if fitbitUserID == "" {
+               fitbitUserID = "-"
+       }
+
+       storedToken, err := u.Token(ctx, "Fitbit")
        if err != nil {
                return nil, err
        }
 
-       if fitbitUserID == "" {
-               fitbitUserID = "-"
+       // The oauth2 package will refresh the token when it is valid for less
+       // than 10 seconds. To avoid a race with later calls (which would
+       // refresh the token but the new RefreshToken wouldn't make it back
+       // into datastore), we refresh earlier than that. The Fitbit tokens are
+       // quite long-lived (six hours?); the additional load this puts on the
+       // backends is negligible.
+       if storedToken.Expiry.Round(0).Add(-5 * time.Minute).Before(time.Now()) {
+               storedToken.Expiry = time.Now()
+       }
+
+       refreshedToken, err := oauth2Config.TokenSource(ctx, storedToken).Token()
+       if err != nil {
+               return nil, err
+       }
+
+       if refreshedToken.RefreshToken != storedToken.RefreshToken {
+               if err := u.SetToken(ctx, "Fitbit", refreshedToken); err != nil {
+                       return nil, err
+               }
        }
 
        return &Client{
                fitbitUserID: fitbitUserID,
                appUser:      u,
-               client:       oauth2Config.Client(ctx, tok),
+               client:       oauth2Config.Client(ctx, refreshedToken),
        }, nil
 }