132 lines
4.3 KiB
JavaScript
132 lines
4.3 KiB
JavaScript
import { log } from '../lib/logger.js';
|
|
|
|
import IPCApi from '../lib/ipc.js';
|
|
import LocalCDP from '../lib/local/cdp.js';
|
|
|
|
import IdleApi from '../api/idle.js';
|
|
import ControlsApi from '../api/controls.js';
|
|
import V8CacheApi from '../api/v8Cache.js';
|
|
|
|
const acquireTarget = async (CDP, filter = () => true) => {
|
|
let target;
|
|
|
|
log('acquiring target...');
|
|
|
|
while (!target) {
|
|
process.stdout.write('.');
|
|
target = (await CDP.sendMessage('Target.getTargets')).targetInfos.filter(x => x.type === 'page').filter(filter)[0];
|
|
if (!target) await new Promise(res => setTimeout(res, 200));
|
|
}
|
|
|
|
console.log();
|
|
|
|
return (await CDP.sendMessage('Target.attachToTarget', {
|
|
targetId: target.targetId,
|
|
flatten: true
|
|
})).sessionId;
|
|
};
|
|
|
|
export default async (CDP, proc, injectionType = 'browser', { dataPath, browserName, browserType, openingLocal, localUrl, url, closeHandlers }) => {
|
|
let pageLoadCallback, pageLoadPromise = new Promise(res => pageLoadCallback = res);
|
|
let frameLoadCallback = () => {}, onWindowMessage = () => {};
|
|
CDP.onMessage(msg => {
|
|
if (msg.method === 'Runtime.bindingCalled' && msg.params.name === '_gluonSend') onWindowMessage(JSON.parse(msg.params.payload));
|
|
if (msg.method === 'Page.frameStoppedLoading') frameLoadCallback(msg.params);
|
|
if (msg.method === 'Page.loadEventFired') pageLoadCallback();
|
|
if (msg.method === 'Runtime.executionContextCreated') {
|
|
try {
|
|
injectIPC(); // ensure IPC injection again
|
|
} catch { }
|
|
}
|
|
});
|
|
|
|
const browserInfo = await CDP.sendMessage('Browser.getVersion');
|
|
log('browser:', browserInfo.product);
|
|
|
|
let sessionId;
|
|
if (injectionType === 'browser') sessionId = await acquireTarget(CDP, target => target.url !== 'about:blank');
|
|
|
|
if (openingLocal && browserType === 'chromium') await LocalCDP(CDP, { sessionId, localUrl, url });
|
|
|
|
await CDP.sendMessage('Runtime.enable', {}, sessionId); // enable runtime API
|
|
|
|
CDP.sendMessage('Runtime.addBinding', { // setup sending from window to Node via Binding
|
|
name: '_gluonSend'
|
|
}, sessionId);
|
|
|
|
const evalInWindow = async func => {
|
|
const reply = await CDP.sendMessage(`Runtime.evaluate`, {
|
|
expression: typeof func === 'string' ? func : `(${func.toString()})()`
|
|
}, sessionId);
|
|
|
|
if (reply.exceptionDetails) return new (global[reply.result?.className] ?? Error)((reply.result?.description?.split(':').slice(1).join(':').trim() ?? reply.exceptionDetails.text) + '\n');
|
|
|
|
return reply.result?.value ?? reply;
|
|
};
|
|
|
|
const [ ipcMessageCallback, injectIPC, IPC ] = await IPCApi({
|
|
browserName,
|
|
browserInfo,
|
|
browserType
|
|
}, {
|
|
evalInWindow,
|
|
evalOnNewDocument: source => CDP.sendMessage('Page.addScriptToEvaluateOnNewDocument', { source }, sessionId),
|
|
pageLoadPromise: new Promise(res => frameLoadCallback = res)
|
|
});
|
|
onWindowMessage = ipcMessageCallback;
|
|
|
|
log('finished setup');
|
|
|
|
evalInWindow('document.readyState').then(readyState => { // check if already loaded, if so trigger page load promise
|
|
if (readyState === 'complete' || readyState === 'ready') pageLoadCallback();
|
|
frameLoadCallback();
|
|
});
|
|
|
|
|
|
const generateVersionInfo = (name, version) => ({
|
|
name,
|
|
version,
|
|
major: parseInt(version.split('.')[0])
|
|
});
|
|
|
|
const versions = {
|
|
product: generateVersionInfo(browserName, browserInfo.product.split('/')[1]),
|
|
engine: generateVersionInfo(browserType, browserInfo.product.split('/')[1]),
|
|
jsEngine: generateVersionInfo(browserType === 'chromium' ? 'v8' : 'spidermonkey', browserInfo.jsVersion)
|
|
};
|
|
|
|
const Window = {
|
|
page: {
|
|
eval: evalInWindow,
|
|
loaded: pageLoadPromise,
|
|
|
|
title: val => {
|
|
if (!val) return evalInWindow('document.title');
|
|
return evalInWindow(`document.title = \`${val}\``);
|
|
}
|
|
},
|
|
|
|
ipc: IPC,
|
|
|
|
cdp: {
|
|
send: (method, params, useSessionId = true) => CDP.sendMessage(method, params, useSessionId ? sessionId : undefined)
|
|
},
|
|
|
|
close: () => {
|
|
for (const handler of closeHandlers) handler(); // extra api handlers which need to be closed
|
|
|
|
CDP.close();
|
|
proc.kill();
|
|
},
|
|
|
|
versions
|
|
};
|
|
|
|
proc.on('close', Window.close);
|
|
|
|
Window.idle = await IdleApi(Window.cdp, { browserType, closeHandlers });
|
|
Window.controls = await ControlsApi(Window.cdp);
|
|
Window.v8Cache = await V8CacheApi(Window.cdp, evalInWindow, { browserType, dataPath });
|
|
|
|
return Window;
|
|
}; |