Source: util/Puppeteer.js

/**
 * Expose a function to the page if it does not exist
 *
 * NOTE:
 * Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher
 * using page.removeExposedFunction
 * https://pptr.dev/api/puppeteer.page.removeexposedfunction
 *
 * @param {object} page - Puppeteer Page instance
 * @param {string} name
 * @param {Function} fn
 */
const routedFunctionsByPage = new WeakMap();

/**
 * Returns the known Puppeteer browser connection state across supported
 * releases. Unknown embedders intentionally return undefined so cleanup code
 * can preserve its conservative fallback.
 * Puppeteer 25 replaced Browser.isConnected() with the Browser.connected
 * getter, while older releases and embedders can still expose the method.
 *
 * @param {object} browser - Puppeteer Browser instance
 * @returns {boolean|undefined}
 */
function getBrowserConnectionState(browser) {
    if (!browser) return undefined;
    if (typeof browser.isConnected === 'function') {
        return browser.isConnected() === true;
    }
    if (typeof browser.connected === 'boolean') return browser.connected;
    return undefined;
}

const isBrowserConnected = (browser) =>
    getBrowserConnectionState(browser) === true;

const getRoutedFunctions = (page) => {
    let routedFunctions = routedFunctionsByPage.get(page);
    if (!routedFunctions) {
        routedFunctions = new Map();
        routedFunctionsByPage.set(page, routedFunctions);
    }
    return routedFunctions;
};

const hasExposedFunction = (page, name) =>
    page.evaluate((bindingName) => {
        return !!window[bindingName];
    }, name);

const isDuplicateBindingError = (error, name) => {
    const message = error instanceof Error ? error.message : '';
    return (
        message.includes('Failed to add page binding') &&
        (message.includes(`window['${name}'] already exists`) ||
            message.includes(`window["${name}"] already exists`))
    );
};

/**
 * Install a stable Puppeteer binding while the Page target is known to be
 * quiescent. A later exposeFunctionIfAbsent call only replaces the in-process
 * handler and never calls Page.addScriptToEvaluateOnNewDocument again.
 *
 * @param {object} page - Puppeteer Page instance
 * @param {string} name
 * @returns {Promise<boolean>} whether this process owns the routed binding
 */
async function prepareRoutedFunction(page, name) {
    const routedFunctions = getRoutedFunctions(page);
    const current = routedFunctions.get(name);
    if (current) {
        const owned = await current.ready;
        return owned && routedFunctions.get(name) === current;
    }

    const route = { handler: undefined, ready: undefined };
    route.ready = (async () => {
        if (await hasExposedFunction(page, name)) {
            return false;
        }
        try {
            await page.exposeFunction(name, (...args) => {
                return route.handler?.(...args);
            });
            return true;
        } catch (error) {
            if (
                !isDuplicateBindingError(error, name) ||
                !(await hasExposedFunction(page, name))
            ) {
                throw error;
            }
            return false;
        }
    })();
    routedFunctions.set(name, route);

    try {
        const owned = await route.ready;
        if (!owned && routedFunctions.get(name) === route) {
            routedFunctions.delete(name);
        }
        return owned;
    } catch (error) {
        if (routedFunctions.get(name) === route) {
            routedFunctions.delete(name);
        }
        throw error;
    }
}

async function exposeFunctionIfAbsent(page, name, fn) {
    const routedFunctions = routedFunctionsByPage.get(page);
    const route = routedFunctions?.get(name);
    if (route) {
        const owned = await route.ready;
        if (owned && routedFunctions.get(name) === route) {
            route.handler = fn;
            return;
        }
    }

    const exist = await hasExposedFunction(page, name);
    if (exist) {
        return;
    }

    try {
        await page.exposeFunction(name, fn);
    } catch (error) {
        if (
            !isDuplicateBindingError(error, name) ||
            !(await hasExposedFunction(page, name))
        ) {
            throw error;
        }
    }
}

module.exports = {
    exposeFunctionIfAbsent,
    getBrowserConnectionState,
    isBrowserConnected,
    prepareRoutedFunction,
};