mirror of https://github.com/alibaba/ice.git
				
				
				
			
		
			
				
	
	
		
			140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
import * as http from 'http';
 | 
						|
import * as url from 'url';
 | 
						|
import * as path from 'path';
 | 
						|
import * as fse from 'fs-extra';
 | 
						|
import type {
 | 
						|
  Page as PuppeteerPage,
 | 
						|
  Browser as PuppeteerBrowser,
 | 
						|
  WaitForOptions,
 | 
						|
  HTTPResponse,
 | 
						|
} from 'puppeteer';
 | 
						|
import puppeteer from 'puppeteer';
 | 
						|
 | 
						|
export interface Page extends PuppeteerPage {
 | 
						|
  html: () => Promise<string>;
 | 
						|
  $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)[]>;
 | 
						|
  push: (url: string, options?: WaitForOptions & {
 | 
						|
    referer?: string;
 | 
						|
    referrerPolicy?: string;
 | 
						|
  }) => Promise<HTTPResponse | null>;
 | 
						|
}
 | 
						|
 | 
						|
interface BrowserOptions {
 | 
						|
  cwd?: string;
 | 
						|
  port?: number;
 | 
						|
  server?: http.Server;
 | 
						|
}
 | 
						|
 | 
						|
export default class Browser {
 | 
						|
  private server: http.Server;
 | 
						|
  private browser: PuppeteerBrowser;
 | 
						|
  private baseUrl: string;
 | 
						|
 | 
						|
  constructor(options: BrowserOptions) {
 | 
						|
    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 || '';
 | 
						|
      const pathname = `${cwd}${url.parse(reqUrl).pathname}`.split(path.sep).join('/');
 | 
						|
      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');
 | 
						|
  }
 | 
						|
 | 
						|
  async start() {
 | 
						|
    this.browser = await puppeteer.launch(
 | 
						|
      {
 | 
						|
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  async close() {
 | 
						|
    if (!this.browser) { return; }
 | 
						|
    await this.browser.close();
 | 
						|
    // @ts-ignore
 | 
						|
    if (this.server.stop) {
 | 
						|
      // @ts-ignore
 | 
						|
      await this.server.stop();
 | 
						|
    } else {
 | 
						|
      await this.server.close();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async page(baseUrl: string, path = '/', disableJS?: boolean): Promise<Page> {
 | 
						|
    this.baseUrl = baseUrl;
 | 
						|
 | 
						|
    if (!this.browser) { throw new Error('Please call start() before page(url)'); }
 | 
						|
    const page = (await this.browser.newPage()) as Page;
 | 
						|
 | 
						|
    if (disableJS) {
 | 
						|
      await page.setJavaScriptEnabled(false);
 | 
						|
    }
 | 
						|
 | 
						|
    await page.goto(`${this.baseUrl}${path}`);
 | 
						|
 | 
						|
    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) => {
 | 
						|
      return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
 | 
						|
    }, trim);
 | 
						|
    page.$$text = (selector, trim) =>
 | 
						|
      page.$$eval(selector, (els, trim) => els.map((el) => {
 | 
						|
        return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
 | 
						|
      }), trim);
 | 
						|
    page.$attr = (selector, attr) =>
 | 
						|
      page.$eval(selector, (el, attr) => el.getAttribute(attr as string), attr);
 | 
						|
    page.$$attr = (selector, attr) =>
 | 
						|
      page.$$eval(
 | 
						|
        selector,
 | 
						|
        (els, attr) => els.map(el => el.getAttribute(attr as string)),
 | 
						|
        attr,
 | 
						|
      );
 | 
						|
 | 
						|
    return page;
 | 
						|
  }
 | 
						|
}
 |