From 14ea8de7094b97e9f2342690df3be7bfa16268ec Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 4 May 2021 11:30:12 +0200 Subject: [PATCH] Modified 'printResume' Firebase cloud function: implemented basic retry mechanism with waitUntil 'networkidle0' and 'networkidle2', enhanced error handling to return more detailed information --- functions/index.js | 125 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 28 deletions(-) diff --git a/functions/index.js b/functions/index.js index dc838dc4..0a5319fd 100644 --- a/functions/index.js +++ b/functions/index.js @@ -54,42 +54,100 @@ const deleteUserFunctionHandler = async (_, { auth }) => { } }; -exports.deleteUser = functions - .runWith({ memory: '256MB' }) - .https.onCall(deleteUserFunctionHandler); +/** + * Tries to navigate the page to a given URL. + * + * @param {puppeteer.Page} page Page. + * @param {string} url URL to navigate page to. + * @param {puppeteer.PuppeteerLifeCycleEvent} waitUntil When to consider navigation succeeded. + * @returns {Promise} Returns null if no error occurred, otherwise returns the error message. + */ +const tryGotoPage = async (page, url, waitUntil) => { + let httpResponse; -exports.printResume = functions - .runWith({ memory: '1GB' }) - .https.onCall(async ({ id, type }, { auth }) => { - if (!id) { - throw new functions.https.HttpsError( - 'invalid-argument', - 'The function must be called with argument "id" containing the resume ID.', - ); + try { + httpResponse = await page.goto(url, { + waitUntil, + }); + } catch (error) { + return `page.goto (waitUntil: "${waitUntil}") threw an error with message "${error.message}"`; + } + + if (httpResponse === null) { + return `page.goto (waitUntil: "${waitUntil}") returned a null response`; + } + + if (!httpResponse.ok()) { + return `page.goto (waitUntil: "${waitUntil}") returned a response with HTTP status ${httpResponse.status()} "${httpResponse.statusText()}"`; + } + + return null; +}; + +/** + * Creates a page and navigates to a given URL. + * + * @param {puppeteer.Browser} browser Browser. + * @param {string} url URL to navigate to. + * @returns {Promise<{page: puppeteer.Page, errors: string[]}>} Returns an object with the page if no error occurred, otherwise returns an object with the list of error messages. + */ +const gotoPage = async (browser, url) => { + const errors = []; + + const waitUntilArray = ['networkidle0', 'networkidle2']; + for (let index = 0; index < waitUntilArray.length; index++) { + /* eslint-disable no-await-in-loop */ + const waitUntil = waitUntilArray[index]; + + const page = await browser.newPage(); + await page.setCacheEnabled(false); + + const error = await tryGotoPage(page, url, waitUntil); + if (!error) { + return { page, errors: null }; } - if (!type) { - throw new functions.https.HttpsError( - 'invalid-argument', - 'The function must be called with argument "type" containing the type of resume.', - ); - } + errors.push(error); + await page.close(); + } - if (!auth) { - throw new functions.https.HttpsError( - 'failed-precondition', - 'The function must be called while authenticated.', - ); - } + return { page: null, errors }; +}; +const printResumeFunctionHandler = async ({ id, type }, { auth }) => { + if (!id) { + throw new functions.https.HttpsError( + 'invalid-argument', + 'The function must be called with argument "id" containing the resume ID.', + ); + } + + if (!type) { + throw new functions.https.HttpsError( + 'invalid-argument', + 'The function must be called with argument "type" containing the type of resume.', + ); + } + + if (!auth) { + throw new functions.https.HttpsError( + 'failed-precondition', + 'The function must be called while authenticated.', + ); + } + + try { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], }); - const page = await browser.newPage(); - await page.goto(BASE_URL + id, { - waitUntil: 'networkidle0', - }); + + const url = BASE_URL + id; + const { page, errors } = await gotoPage(browser, url); + if (errors && errors.length > 0) { + throw new Error(errors.join(' - ')); + } + await timeout(6000); await page.emulateMediaType('print'); let pdf; @@ -124,4 +182,15 @@ exports.printResume = functions await browser.close(); return Buffer.from(pdf).toString('base64'); - }); + } catch (error) { + throw new functions.https.HttpsError('internal', error.message); + } +}; + +exports.deleteUser = functions + .runWith({ memory: '256MB' }) + .https.onCall(deleteUserFunctionHandler); + +exports.printResume = functions + .runWith({ memory: '1GB' }) + .https.onCall(printResumeFunctionHandler);