2022-03-01 10:40:46 +08:00
|
|
|
import http from 'http';
|
|
|
|
import url from 'url';
|
|
|
|
import path from 'path';
|
2022-09-07 14:49:54 +08:00
|
|
|
import fse from 'fs-extra';
|
2022-03-01 10:40:46 +08:00
|
|
|
import puppeteer from 'puppeteer';
|
2022-01-27 14:32:38 +08:00
|
|
|
|
|
|
|
export interface IPage extends puppeteer.Page {
|
|
|
|
html?: () => Promise<string>;
|
2022-09-07 14:49:54 +08:00
|
|
|
$text?: (selector: string, trim?: boolean) => Promise<string | null>;
|
|
|
|
$$text?: (selector: string, trim?: boolean) => Promise<(string | null)[]>;
|
|
|
|
$attr?: (selector: string, attr: string) => Promise<string | null>;
|
|
|
|
$$attr?: (selector: string, attr: string) => Promise<(string | null)[]>;
|
2022-01-27 14:32:38 +08:00
|
|
|
push?: (url: string, options?: puppeteer.WaitForOptions & { referer?: string }) => Promise<puppeteer.HTTPResponse>;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IBrowserOptions {
|
|
|
|
cwd?: string;
|
|
|
|
port?: number;
|
|
|
|
server?: http.Server;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Browser {
|
|
|
|
private server: http.Server;
|
|
|
|
private browser: puppeteer.Browser;
|
|
|
|
private baseUrl: string;
|
|
|
|
|
2022-09-07 14:49:54 +08:00
|
|
|
constructor(options: BrowserOptions) {
|
2022-01-27 14:32:38 +08:00
|
|
|
const { server } = options;
|
|
|
|
if (server) {
|
|
|
|
this.server = server;
|
|
|
|
} else {
|
|
|
|
const { cwd, port } = options;
|
|
|
|
this.server = this.createServer(cwd, port);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
createServer(cwd: string, port: number) {
|
|
|
|
return http.createServer((req, res) => {
|
|
|
|
const requrl: string = req.url || '';
|
2022-08-25 18:43:08 +08:00
|
|
|
const pathname = `${cwd}${url.parse(requrl).pathname}`.split(path.sep).join('/');
|
2022-01-27 14:32:38 +08:00
|
|
|
if (fse.existsSync(pathname)) {
|
|
|
|
switch (path.extname(pathname)) { // set HTTP HEAD
|
|
|
|
case '.html':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
|
|
break;
|
|
|
|
case '.js':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/javascript' });
|
|
|
|
break;
|
|
|
|
case '.css':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/css' });
|
|
|
|
break;
|
|
|
|
case '.gif':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'image/gif' });
|
|
|
|
break;
|
|
|
|
case '.jpg':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'image/jpeg' });
|
|
|
|
break;
|
|
|
|
case '.png':
|
|
|
|
res.writeHead(200, { 'Content-Type': 'image/png' });
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
res.writeHead(200, {
|
|
|
|
'Content-Type': 'application/octet-stream',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
fse.readFile(pathname, (_err, data) => {
|
|
|
|
res.end(data);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
|
|
res.end('<h1>404 Not Found</h1>');
|
|
|
|
console.log(`${pathname} Not Found.`);
|
|
|
|
}
|
|
|
|
}).listen(port, '127.0.0.1');
|
|
|
|
}
|
|
|
|
|
2022-09-07 14:49:54 +08:00
|
|
|
async start() {
|
2022-01-27 14:32:38 +08:00
|
|
|
this.browser = await puppeteer.launch();
|
|
|
|
}
|
|
|
|
|
2022-09-07 14:49:54 +08:00
|
|
|
async close() {
|
|
|
|
if (!this.browser) { return; }
|
2022-01-27 14:32:38 +08:00
|
|
|
await this.browser.close();
|
|
|
|
this.server.close();
|
|
|
|
}
|
|
|
|
|
2022-09-07 14:49:54 +08:00
|
|
|
async page(url: string, disableJS?: boolean) {
|
2022-01-27 14:32:38 +08:00
|
|
|
this.baseUrl = url;
|
|
|
|
if (!this.browser) { throw new Error('Please call start() before page(url)'); }
|
2022-03-09 15:20:08 +08:00
|
|
|
const page: Page = await this.browser.newPage();
|
|
|
|
|
|
|
|
if (disableJS) {
|
|
|
|
page.setJavaScriptEnabled(false);
|
|
|
|
}
|
|
|
|
|
2022-01-27 14:32:38 +08:00
|
|
|
await page.goto(url);
|
|
|
|
page.push = (url, options) => page.goto(`${this.baseUrl}${url}`, options);
|
|
|
|
page.html = () =>
|
|
|
|
page.evaluate(() => window.document.documentElement.outerHTML);
|
|
|
|
page.$text = (selector, trim) => page.$eval(selector, (el, trim) => {
|
2022-09-07 14:49:54 +08:00
|
|
|
return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
|
2022-01-27 14:32:38 +08:00
|
|
|
}, trim);
|
|
|
|
page.$$text = (selector, trim) =>
|
|
|
|
page.$$eval(selector, (els, trim) => els.map((el) => {
|
2022-09-07 14:49:54 +08:00
|
|
|
return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
|
2022-01-27 14:32:38 +08:00
|
|
|
}), trim);
|
2022-04-11 10:38:04 +08:00
|
|
|
|
|
|
|
page.$attr = (selector, attr) =>
|
|
|
|
page.$eval(selector, (el, attr) => el.getAttribute(attr as string), attr);
|
|
|
|
|
|
|
|
page.$$attr = (selector, attr) =>
|
|
|
|
page.$$eval(
|
2022-01-27 14:32:38 +08:00
|
|
|
selector,
|
2022-04-11 10:38:04 +08:00
|
|
|
(els, attr) => els.map(el => el.getAttribute(attr as string)),
|
|
|
|
attr,
|
2022-01-27 14:32:38 +08:00
|
|
|
);
|
|
|
|
return page;
|
|
|
|
}
|
|
|
|
}
|