'use strict';
const { randomUUID } = require('node:crypto');
const { openOriginalChannelTransport } = require('./ChannelSessionTransport');
/**
* Borrows a transport in an existing Puppeteer realm selected by the channel
* owner. Never creates a page, worker, browser, login or WhatsApp socket.
* assertOwned is mandatory and must fence the caller's original realm/session.
* The owner signals navigation, worker loss and lease revocation via signal.
* This is an internal transport API, not a call engine or readiness claim.
*/
async function borrowOriginalChannelTransport(realm, signal, assertOwned) {
const fail = () => new Error('wwebjs_original_transport_unavailable');
if (
typeof realm?.evaluateHandle !== 'function' ||
!(signal instanceof AbortSignal) ||
typeof assertOwned !== 'function'
)
throw fail();
const lifetime = new AbortController();
let handle;
let stopped = false;
let disposing = Promise.resolve();
const dispose = (value) => {
if (!value) return;
disposing = Promise.resolve()
.then(() => value.evaluate((source) => source.close()))
.catch(() => {})
.finally(() => value.dispose().catch(() => {}));
};
const close = () => {
if (stopped) return;
stopped = true;
signal.removeEventListener('abort', close);
lifetime.abort();
dispose(handle);
};
const check = () => {
try {
if (stopped || signal.aborted) throw fail();
assertOwned();
} catch {
close();
throw fail();
}
};
signal.addEventListener('abort', close, { once: true });
try {
check();
// Exactly once. A late handle is retired in its own original realm;
// acquisition is never retried in another page or replacement worker.
handle = await realm.evaluateHandle(openOriginalChannelTransport);
if (stopped) dispose(handle);
check();
const generation = await handle.evaluate((source) => {
source.check();
return source.generation;
});
check();
if (
typeof generation !== 'string' ||
!/^[0-9.]+:[1-9][0-9]*$/.test(generation)
)
throw fail();
// One pending socket event, not an HTTP loop or a status poll.
void handle.evaluate((source) => source.closed()).then(close, close);
const run = async (method, args, cancellation, operationId) => {
if (!(cancellation instanceof AbortSignal)) throw fail();
check();
if (cancellation.aborted) throw fail();
const combined = AbortSignal.any([lifetime.signal, cancellation]);
let abort;
const cancelled = new Promise((_, reject) => {
abort = () => {
// Cancelling an ACK/event wait does not cancel or repeat a
// socket write already dispatched to the original realm.
if (operationId)
void handle
.evaluate(
(source, id) => source.cancel(id),
operationId,
)
.catch(close);
reject(fail());
};
combined.addEventListener('abort', abort, { once: true });
});
try {
check();
if (combined.aborted) throw fail();
const operation = handle.evaluate(
(source, name, input) => {
source.check();
return source[name](...input);
},
method,
args,
);
const result = await Promise.race([operation, cancelled]);
check();
if (combined.aborted) throw fail();
return result;
} catch {
throw fail();
} finally {
combined.removeEventListener('abort', abort);
}
};
return Object.freeze({
generation,
signal: lifetime.signal,
check,
close,
disposed: () => disposing,
send: (node, cancellation) => run('send', [node], cancellation),
request: (node, id, cancellation) =>
run('request', [node, id], cancellation, id),
next: (cancellation) => {
const id = randomUUID();
return run('next', [id], cancellation, id);
},
});
} catch {
close();
throw fail();
}
}
module.exports = { borrowOriginalChannelTransport };