-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Fixes refresh_token not being obtained all the time. #10
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,8 +49,13 @@ module.exports.apifyGoogleAuth = async ({ scope, tokensStore, credentials, googl | |
const authorizeUrl = oAuth2Client.generateAuthUrl({ | ||
access_type: 'offline', | ||
scope: `https://www.googleapis.com/auth/${scope}`, | ||
// Always prompt for user consent otherwise, there is a chance to not obtain refresh_token | ||
// https://stackoverflow.com/a/10857806 | ||
prompt: 'consent' | ||
}); | ||
let code; | ||
|
||
const codeHolder = { code: null }; // To be able to access code as reference from any scope | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the scope we want to access this? We are sending it somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change in |
||
|
||
if (googleCredentials.email) { | ||
console.log('You provided an email so we will try to log in with puppeteer. You may have to allow for access after authorization in your email depending on the security level you have.'); | ||
let puppeteerOptions; | ||
|
@@ -93,7 +98,7 @@ module.exports.apifyGoogleAuth = async ({ scope, tokensStore, credentials, googl | |
await page.waitForSelector('#submit_approve_access'); | ||
await page.click('#submit_approve_access'); | ||
await page.waitForSelector('#code', { timeout: 120000 }); | ||
code = await page.$eval('#code', (el) => el.value); | ||
codeHolder.code = await page.$eval('#code', (el) => el.value); | ||
await page.close(); | ||
await browser.close(); | ||
} catch (e) { | ||
|
@@ -109,15 +114,22 @@ module.exports.apifyGoogleAuth = async ({ scope, tokensStore, credentials, googl | |
console.log(pleaseOpen); | ||
console.log(information); | ||
|
||
let codeBeenSet; | ||
const waitForCodeBeenSet = new Promise((resolve) => { | ||
codeBeenSet = resolve; | ||
}); | ||
|
||
const server = http.createServer((req, res) => { | ||
code = new URL(req.url, pickedCredentials.redirect_uri).searchParams.get('code'); | ||
if (code) { | ||
codeHolder.code = new URL(req.url, pickedCredentials.redirect_uri).searchParams.get('code'); | ||
if (codeHolder.code) { | ||
let data = ''; | ||
req.on('data', (body) => { | ||
if (body) data += body; | ||
}); | ||
req.on('end', () => { | ||
res.end(close()); | ||
codeBeenSet(); | ||
console.log("Code was successfully provided!") | ||
}); | ||
} else { | ||
res.end(authorize(authorizeUrl)); | ||
|
@@ -126,21 +138,34 @@ module.exports.apifyGoogleAuth = async ({ scope, tokensStore, credentials, googl | |
|
||
server.listen(port, () => console.log('server is listening on port', port)); | ||
|
||
const start = Date.now(); | ||
while (!code) { | ||
const now = Date.now(); | ||
if (now - start > 5 * 60 * 1000) { | ||
throw new Error('You did not provide the code in time!'); | ||
} | ||
console.log(`waiting for code...You have ${300 - Math.floor((now - start) / 1000)} seconds left`); | ||
await new Promise((resolve) => setTimeout(resolve, 10000)); | ||
} | ||
let timeoutInterval; | ||
const waitForTimeout = new Promise((resolve, reject) => { | ||
const start = Date.now(); | ||
|
||
timeoutInterval = setInterval(() => { | ||
const now = Date.now(); | ||
if (now - start > 5 * 60 * 1_000) { | ||
reject('You did not provide the code in time!'); | ||
} else { | ||
console.log(`waiting for code...You have ${300 - Math.floor((now - start) / 1000)} seconds left`); | ||
} | ||
}, 10000) | ||
}); | ||
|
||
// Wait for code being set by user or time runs out | ||
await Promise.race([ | ||
waitForTimeout.catch((errorMessage) => { throw new Error(errorMessage) }), | ||
waitForCodeBeenSet | ||
]); | ||
|
||
clearInterval(timeoutInterval); // clear Interval no matter what happened | ||
codeBeenSet(); // Resolve in case of timeout | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason for this rewrite, it seems like the same thing with more complicated code, am I missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have to wait another 10sec after the code was already set. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may use synchronize it with |
||
|
||
server.close(() => console.log('closing server')); | ||
} | ||
|
||
// Now that we have the code, use that to acquire tokens. | ||
const tokensResponse = await oAuth2Client.getToken(code); | ||
const tokensResponse = await oAuth2Client.getToken(codeHolder.code); | ||
console.log(`Storing the tokens to your store under key ${tokensRecordKey}`); | ||
await store.setValue(tokensRecordKey, tokensResponse.tokens); | ||
oAuth2Client.setCredentials(tokensResponse.tokens); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this help us in current situation? Because by default we will use the code stored in KV store even if it is not viable and it will fail. But I didn't really have problems with needing a refresh. What is the actual problem you are solving?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the stackoverflow - It helps in development when you are testing/using the same account for the same app and redirectUrl. The refresh_token seems to be returned only for the first time in this case. Then you have to manually go to your google account and remove the consent and then give it again to the application I noticed this because otherwise I had to give new consent every 30minutes (which is pretty annoying) since the credentials were saved without
refresh_token
which is required for obtaining the new tokens.See googleapis/google-api-nodejs-client#750 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are not storing the
code
, you are storing the received tokens.