2021-06-07 17:43:36 -07:00
|
|
|
const ws = require("ws");
|
|
|
|
const http = require("http");
|
|
|
|
const url = require("url");
|
|
|
|
const fs = require("fs");
|
2021-07-06 20:22:27 -07:00
|
|
|
const path = require("path");
|
2021-06-07 17:43:36 -07:00
|
|
|
|
|
|
|
const SingleBrowserImplementation = require("puppeteer-cluster/dist/concurrency/SingleBrowserImplementation").default;
|
|
|
|
|
2021-07-06 20:22:27 -07:00
|
|
|
const indexHTML = fs.readFileSync(path.join(__dirname, "..", "screencast", "index.html"), {encoding: "utf8"});
|
2021-06-07 17:43:36 -07:00
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
class ScreenCaster
|
|
|
|
{
|
|
|
|
constructor(cluster, port) {
|
|
|
|
this.cluster = cluster;
|
|
|
|
|
|
|
|
this.httpServer = http.createServer((req, res) => {
|
|
|
|
const pathname = url.parse(req.url).pathname;
|
|
|
|
if (pathname === "/") {
|
|
|
|
res.writeHead(200, {"Content-Type": "text/html"});
|
|
|
|
res.end(indexHTML);
|
|
|
|
} else {
|
|
|
|
res.writeHead(404, {"Content-Type": "text/html"});
|
|
|
|
res.end("Not Found");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.allWS = new Set();
|
|
|
|
|
|
|
|
this.targets = new Map();
|
|
|
|
this.caches = new Map();
|
|
|
|
this.urls = new Map();
|
|
|
|
|
|
|
|
this.wss = new ws.Server({ noServer: true });
|
|
|
|
|
|
|
|
this.wss.on("connection", (ws) => this.initWebSocket(ws));
|
|
|
|
|
|
|
|
this.httpServer.on("upgrade", (request, socket, head) => {
|
|
|
|
const pathname = url.parse(request.url).pathname;
|
|
|
|
|
|
|
|
if (pathname === "/ws") {
|
|
|
|
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
|
|
this.wss.emit("connection", ws, request);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.httpServer.listen(port);
|
|
|
|
}
|
|
|
|
|
|
|
|
initWebSocket(ws) {
|
|
|
|
for (const id of this.targets.keys()) {
|
|
|
|
const data = this.caches.get(id);
|
|
|
|
const url = this.urls.get(id);
|
|
|
|
const msg = {"msg": "newTarget", id, url, data};
|
|
|
|
ws.send(JSON.stringify(msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.allWS.add(ws);
|
|
|
|
|
|
|
|
if (this.allWS.size === 1) {
|
|
|
|
this.startCastAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
ws.on("close", () => {
|
0.4.1 Release! (#70)
* optimization: don't intercept requests if no blockRules set
* page load: set waitUntil to use networkidle2 instead of networkidle0 as reasonable default for most pages
* add --behaviorTimeout to set max running time for behaviors (defaults to 90 seconds)
* refactor profile loadProfile/saveProfile to util/browser.js
- support augmenting existing profile when creating a new profile
* screencasting: convert newContext to window instead of page by default, instead of just warning about it
* shared multiplatform image support:
- determine browser exe from list of options, getBrowserExe() returns current exe
- supports running with 'google-chrome' under amd64, and 'chromium-browser' under arm64
- update to multiplatform oldwebtoday/chrome:91 as browser image
- enable multiplatform build with latest build-push-action@v2
* seeds: add trim() to seed URLs
* logging: reduce initial debug logging, enable only if '--logging debug' is set. log if profile, text-extraction enabled, and post-processing stages automatically
* profile creation: add --windowSize flag, set default to 1600x900, default to loading Application tab, tweak UI styles
* extractLinks: support passing in custom property to get link, and also loading as an attribute via getAttribute. Fixes #25
* update CHANGES and README with new features
* bump version to 0.4.1
2021-07-22 14:24:51 -07:00
|
|
|
//console.log("Screencast WebSocket Disconnected");
|
2021-06-07 17:43:36 -07:00
|
|
|
this.allWS.delete(ws);
|
|
|
|
|
|
|
|
if (this.allWS.size === 0) {
|
|
|
|
this.stopCastAll();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
sendAll(msg) {
|
|
|
|
msg = JSON.stringify(msg);
|
|
|
|
for (const ws of this.allWS) {
|
|
|
|
ws.send(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async newTarget(target) {
|
|
|
|
const cdp = await target.createCDPSession();
|
|
|
|
const id = target._targetId;
|
|
|
|
const url = target.url();
|
|
|
|
|
|
|
|
this.targets.set(id, cdp);
|
|
|
|
this.urls.set(id, url);
|
|
|
|
|
|
|
|
this.sendAll({"msg": "newTarget", id, url});
|
|
|
|
|
|
|
|
cdp.on("Page.screencastFrame", async (resp) => {
|
|
|
|
const data = resp.data;
|
|
|
|
const sessionId = resp.sessionId;
|
|
|
|
|
|
|
|
this.sendAll({"msg": "screencast", id, data});
|
|
|
|
this.caches.set(id, data);
|
2021-07-20 15:45:51 -07:00
|
|
|
try {
|
|
|
|
await cdp.send("Page.screencastFrameAck", {sessionId});
|
|
|
|
} catch(e) {
|
0.4.1 Release! (#70)
* optimization: don't intercept requests if no blockRules set
* page load: set waitUntil to use networkidle2 instead of networkidle0 as reasonable default for most pages
* add --behaviorTimeout to set max running time for behaviors (defaults to 90 seconds)
* refactor profile loadProfile/saveProfile to util/browser.js
- support augmenting existing profile when creating a new profile
* screencasting: convert newContext to window instead of page by default, instead of just warning about it
* shared multiplatform image support:
- determine browser exe from list of options, getBrowserExe() returns current exe
- supports running with 'google-chrome' under amd64, and 'chromium-browser' under arm64
- update to multiplatform oldwebtoday/chrome:91 as browser image
- enable multiplatform build with latest build-push-action@v2
* seeds: add trim() to seed URLs
* logging: reduce initial debug logging, enable only if '--logging debug' is set. log if profile, text-extraction enabled, and post-processing stages automatically
* profile creation: add --windowSize flag, set default to 1600x900, default to loading Application tab, tweak UI styles
* extractLinks: support passing in custom property to get link, and also loading as an attribute via getAttribute. Fixes #25
* update CHANGES and README with new features
* bump version to 0.4.1
2021-07-22 14:24:51 -07:00
|
|
|
//console.log("Ack Failed, probably window/tab already closed", e);
|
2021-07-20 15:45:51 -07:00
|
|
|
}
|
2021-06-07 17:43:36 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
if (this.allWS.size) {
|
|
|
|
await this.startCast(cdp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async endTarget(target) {
|
|
|
|
const id = target._targetId;
|
|
|
|
const cdp = this.targets.get(id);
|
|
|
|
if (!cdp) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.stopCast(cdp);
|
|
|
|
|
|
|
|
this.sendAll({"msg": "endTarget", id});
|
|
|
|
|
|
|
|
this.targets.delete(id);
|
|
|
|
this.caches.delete(id);
|
|
|
|
this.urls.delete(id);
|
|
|
|
|
2021-07-20 15:45:51 -07:00
|
|
|
try {
|
|
|
|
await cdp.detach();
|
|
|
|
} catch (e) {
|
|
|
|
// already detached
|
|
|
|
}
|
2021-06-07 17:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async startCast(cdp) {
|
|
|
|
if (cdp._startedCast) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdp._startedCast = true;
|
|
|
|
|
|
|
|
await cdp.send("Page.startScreencast", {format: "png", everyNthFrame: 1, maxWidth: 1024, maxHeight: 768});
|
|
|
|
}
|
|
|
|
|
|
|
|
async stopCast(cdp) {
|
|
|
|
if (!cdp._startedCast) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdp._startedCast = false;
|
2021-07-20 15:45:51 -07:00
|
|
|
try {
|
|
|
|
await cdp.send("Page.stopScreencast");
|
|
|
|
} catch (e) {
|
|
|
|
// likely already stopped
|
|
|
|
}
|
2021-06-07 17:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
startCastAll() {
|
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
for (const cdp of this.targets.values()) {
|
|
|
|
promises.push(this.startCast(cdp));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.allSettled(promises);
|
|
|
|
}
|
|
|
|
|
|
|
|
stopCastAll() {
|
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
for (const cdp of this.targets.values()) {
|
|
|
|
promises.push(this.stopCast(cdp));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.allSettled(promises);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
class NewWindowPage extends SingleBrowserImplementation {
|
|
|
|
async init() {
|
|
|
|
await super.init();
|
|
|
|
|
|
|
|
this.newTargets = [];
|
|
|
|
|
|
|
|
this.nextPromise();
|
|
|
|
|
|
|
|
this.mainPage = await this.browser.newPage();
|
|
|
|
|
|
|
|
this.pages = [];
|
|
|
|
this.reuse = true;
|
|
|
|
|
|
|
|
await this.mainPage.goto("about:blank");
|
|
|
|
|
|
|
|
this.mainTarget = this.mainPage.target();
|
|
|
|
|
|
|
|
this.browser.on("targetcreated", (target) => {
|
|
|
|
if (this._nextTarget && target.opener() === this.mainTarget) {
|
|
|
|
this.newTargets.push(target);
|
|
|
|
this._nextTarget();
|
|
|
|
this.nextPromise();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
nextPromise() {
|
|
|
|
this._nextPromise = new Promise((resolve) => this._nextTarget = resolve);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getNewPage() {
|
|
|
|
const p = this._nextPromise;
|
|
|
|
|
|
|
|
await this.mainPage.evaluate("window.open('about:blank', '', 'resizable');");
|
|
|
|
|
|
|
|
await p;
|
|
|
|
|
|
|
|
const target = this.newTargets.shift();
|
|
|
|
|
|
|
|
return {page: await target.page() };
|
|
|
|
}
|
|
|
|
|
|
|
|
async createResources() {
|
|
|
|
if (this.pages.length) {
|
|
|
|
return {page: this.pages.shift()};
|
|
|
|
}
|
|
|
|
return await this.getNewPage();
|
|
|
|
}
|
|
|
|
|
|
|
|
async freeResources(resources) {
|
|
|
|
if (this.reuse) {
|
|
|
|
this.pages.push(resources.page);
|
|
|
|
} else {
|
|
|
|
await resources.page.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { ScreenCaster, NewWindowPage };
|