File size: 3,273 Bytes
8a8fe1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import {Browser, Page, PuppeteerLaunchOptions} from "puppeteer";
import path from "path";
import run from "node:test";
import * as fs from "fs";
import {shuffleArray, sleep} from "../utils";
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const runPath = path.join(__dirname, 'run');

export interface PageInfo<T> {
    id: string;
    ready: boolean;
    page?: Page;
    data?: T;
}

type PrepareFunc<T> = (id: string, browser: Browser) => Promise<[Page | undefined, T, string]>

export class BrowserPool<T> {
    private readonly pool: PageInfo<T>[] = [];
    private readonly size: number;
    private readonly prepare: PrepareFunc<T>

    constructor(size: number, initialIDs: string[], prepare: PrepareFunc<T>) {
        this.size = size
        this.prepare = prepare;
        this.init(initialIDs);
    }

    init(initialIDs: string[]) {
        for (let i = 0; i < this.size; i++) {
            const id = initialIDs[i];
            const info: PageInfo<T> = {
                id,
                ready: false,
            }
            this.initOne(id).then(([page, data, newID]) => {
                if (!page) {
                    return;
                }
                info.id = newID;
                info.page = page;
                info.data = data;
                info.ready = true;
            }).catch(e => {
                console.error(e);
            })
            this.pool.push(info)
        }
    }

    async initOne(id: string): Promise<[Page, T, string]> {
        const options: PuppeteerLaunchOptions = {
            headless: process.env.DEBUG === "1" ? false : 'new',
            args: ['--no-sandbox', '--disable-setuid-sandbox'],
            userDataDir: `run/${id}`,
        };
        const browser = await puppeteer.launch(options);
        const [page, data, newID] = await this.prepare(id, browser)
        if (!page) {
            console.log(`init ${id} failed, delete! init new ${newID}`);
            await browser.close();
            if (options.userDataDir) {
                fs.rmdirSync(options.userDataDir, {recursive: true});
            }
            await sleep(5000);
            return this.initOne(newID);
        }
        return [page, data, newID];
    }

    //@ts-ignore
    get(): [page: Page | undefined, data: T | undefined, done: (data: T) => void, destroy: (newID: string) => void] {
        for (const item of shuffleArray(this.pool)) {
            if (item.ready) {
                item.ready = false;
                return [
                    item.page,
                    item.data,
                    (data: T) => {
                        item.ready = true
                        item.data = data;
                    },
                    (newID: string) => {
                        item.page?.close();
                        this.initOne(newID).then(([page, data, newID]) => {
                            item.id = newID;
                            item.page = page
                            item.data = data;
                            item.ready = true;
                        })
                    }
                ]
            }
        }
        return [] as any;
    }
}