roopakv / google-photos

Library to make hitting the Google Photos api easy
MIT License
56 stars 22 forks source link

How to automatically handle expired access tokens ? #19

Open janvda opened 4 years ago

janvda commented 4 years ago

I am just wondering how to automatically handle expired access tokens.

I gradually understand how I can use google.auth.OAuth2() so that it will automatically request a new access token (using the refresh token) when it becomes expired but it is not clear how I should integrate this with this library ?

Should I call again new Photos(...) but this time with the new access token ?

kr Jan.

roopakv commented 4 years ago

interesting and good case. If I add a setNewToken to the photos object, you could call it after refresh? would that work?

janvda commented 4 years ago

Sorry for the delay I had another issue that blocked me.

Your proposal would be great.

I would call the method rather setNewAccessToken to make clear that it is the access token and not the refresh token that must be passed as argument.

skybldev commented 3 years ago

I had the same problem. Google tokens automatically expire in ~1hr, so one must reload it in advance, to avoid any HTTP - Unauthorized errors. Here is how I did it:

/*
    This doesn't need to be a global var, but it makes things much easier if
    you plan to use it in other parts of the code.
*/
var photos;

main();

async function main() {
    /*
        authorizeGAPI() follows ideal procedure with reading a credentials file,
        initializing google.auth.OAuth2 with those credentials, and calling
        .setCredentials() on it using a local token file. It also returns a JSON
        object representing the token (exact contents of the token file)
    */
    let [OA2Client, token] = await authorizeGAPI();

    // We only need the access token for this
    photos = new Photos(token.access_token);

    /*
        This is the important part; it will automatically refresh the token and
        Photos object every 45 minutes (2,700,000ms).
    */
    setInterval(() => refreshGAPIToken(OA2Client), 2700000);
}

async function refreshGAPIToken(OA2Client) {
    console.log("Refreshing GAPI token...");

    // You probably already know about this procedure as you said.
    let token = await OA2Client.refreshAccessToken().catch((err) => {
        console.log(`!! Error refreshing GAPI token: ${err}`);
    });

    /*
        We need to await the whole JSON data to complete for some reason.
        Also the "token" returned is the token JSON plus some stuff we don't need.
    */
    token = await token.credentials;

    // Delete the old Photos instance just to be sure
    delete photos;

    // Initialize it just like at the start.
    photos = new Photos(token.access_token);

    // Verify that the token has been updated properly
    if (photos.transport.authToken != token.access_token) {
        console.log("!! Error refreshing GAPI token: Unable to update GPhotos token.");

        /*
            For the sake of simplicity, I'm leaving this empty. You can implement
            your own retry mechanics.
        */
    }

    // Write the token to a file for later use.
    await fs.promises.writeFile("./token.json", JSON.stringify(token))
        .catch(err => {
            m.exitErr(`!! Error saving token file, you must fix this first: ${err}`);
        });

    return;
}

In summary: Yes, you do need to initialize a new Photos() object, after making sure you delete the old one.

I have had the code running an evening, overnight, and a morning, (~16hrs) and it has uploaded without fail.

roopakv commented 3 years ago

hmmm maybe we should just build in a way into the photos client to do the refresh with the refresh token so that this is handled for the users.

skybldev commented 3 years ago

I agree, this 'replacing the photos object' thing might lead to memory leaks and is a little bit janky. There should be a way to replace the token like @janvda suggested.

individual8 commented 3 years ago

Any changes on this matter in the meantime?

skybldev commented 3 years ago

Update on my implementation: I've been running it for two months without user intervention and can report no memory leaks. Its memory usage has stayed at a constant 177K during that whole time. For now I am confident in saying that this is a reliable way to go about this while such a feature is not yet implemented.

roopakv commented 3 years ago

@skybldev I have support for this here: https://github.com/roopakv/google-photos/pull/47

Curious what you think