aboutsummaryrefslogtreecommitdiffstats
path: root/src/trace.js
blob: 8717ff18ecbd19e6004cfa6ceedc2a9042957cfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Daemon diagnostic traces (WA_CLI_TRACE=1 or "trace": true in config.json).
// Logs: CDP/evaluate calls still pending after >15 s, exceptions inside functions exposed
// to the page (puppeteer swallows them), navigations and client events.
// Useful to see where whatsapp-web.js hangs when it never reaches `ready`.

import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const QUIET = new Set(['loading_screen', 'message', 'message_create', 'message_ack', 'unread_count', 'media_uploaded']);

export function enableTrace(log) {
    const { CdpPage } = require('puppeteer-core/lib/cjs/puppeteer/cdp/Page.js');
    const { CdpCDPSession } = require('puppeteer-core/lib/cjs/puppeteer/cdp/CdpSession.js');
    const wweb = require('whatsapp-web.js');
    const t = (...a) => log('trace:', ...a);

    const pending = new Map();
    let seq = 0;
    const track = (desc, p) => {
        const id = ++seq;
        pending.set(id, { desc, start: Date.now() });
        const done = () => pending.delete(id);
        p.then(done, (e) => {
            done();
            t('error', desc, String(e?.message ?? e).slice(0, 200));
        });
        return p;
    };
    setInterval(() => {
        for (const p of pending.values()) {
            const s = Math.round((Date.now() - p.start) / 1000);
            if (s >= 15 && s % 15 === 0) t(`pendiente ${s}s:`, p.desc);
        }
    }, 1000).unref();

    const send = CdpCDPSession.prototype.send;
    CdpCDPSession.prototype.send = function (method, ...args) {
        return track(`cdp ${method}`, send.call(this, method, ...args));
    };

    const expose = CdpPage.prototype.exposeFunction;
    CdpPage.prototype.exposeFunction = function (name, fn) {
        const wrapped = async (...args) => {
            try {
                return await fn(...args);
            } catch (e) {
                t(`exception in ${name}:`, String(e?.stack ?? e).split('\n').slice(0, 4).join(' | '));
                throw e;
            }
        };
        return track(`exposeFunction(${name})`, expose.call(this, name, wrapped));
    };

    const emit = wweb.Client.prototype.emit;
    wweb.Client.prototype.emit = function (event, ...args) {
        if (!QUIET.has(event)) t('evento', event, event === 'disconnected' || event === 'change_state' ? args[0] : '');
        return emit.call(this, event, ...args);
    };

    const init = wweb.Client.prototype.initialize;
    wweb.Client.prototype.initialize = function (...args) {
        const iv = setInterval(() => {
            if (!this.pupPage) return;
            clearInterval(iv);
            const page = this.pupPage;
            page.on('framenavigated', (f) => t('framenavigated', f === page.mainFrame() ? 'main' : 'iframe', f.url().slice(0, 60)));
            page.on('load', () => t('load'));
            page.on('pageerror', (e) => t('pageerror', String(e?.message ?? e).slice(0, 150)));
        }, 20);
        return init.apply(this, args);
    };

    const inject = wweb.Client.prototype.inject;
    wweb.Client.prototype.inject = function (...args) {
        t('inject()');
        return track('inject()', inject.apply(this, args));
    };
    if (inject.waCompat) wweb.Client.prototype.inject.waCompat = true;
}