-
Couldn't load subscription status.
- Fork 45
Open
Description
I have a class creating a "client" profile:
import { createHash } from 'crypto';
import { existsSync } from 'fs';
import { join } from 'path';
import puppeteer, { Browser, Page } from 'puppeteer-core';
import { createPlugin, PuppeteerFingerprintPlugin } from 'puppeteer-with-fingerprints';
import { env } from '../config';
import {
AUTHORIZE_SELECTORS,
AuthorizeStatus,
BROWSER_WORKING_DIRECTORY,
DEFAULT_ACCOUNT_BEFORE_LOAD,
DEFAULT_CLIENT_STATE,
PROFILES_PATH,
WHITELIST_URLS_WHEN_CAPTCHA_ACTIVE,
} from '../constants';
import { Logger } from '../services';
import { IAccountInfo, IClientOptions, IClientRequest, IClientState } from '../types';
import { ClientResponse } from './client-response.core';
export class Client {
public static async create(accountData: IClientOptions) {
const plugin = await this.initPlugin(accountData.email, accountData.proxy);
if (!plugin) return null;
const browserAndPage = await this.initBrowserAndPage(plugin, accountData.email);
if (!browserAndPage) return null;
const { browser, page } = browserAndPage;
const client = new Client(plugin, browser, page, accountData);
await client.setupPage();
return client;
}
private static async initBrowserAndPage(plugin: PuppeteerFingerprintPlugin, email: string) {
let browser: Browser | null = null;
try {
browser = await plugin.launch({
userDataDir: this.getProfilePath(email),
args: ['--no-sandbox'],
headless: false,
});
const page = await browser.newPage();
return { browser, page };
} catch (error) {
Logger.fatal(email, error, 'BrowserAndPage');
await browser?.close();
}
}
private static async initPlugin(email: string, proxy: string) {
try {
const plugin = createPlugin(puppeteer);
const profilePath = this.getProfilePath(email);
const hasProfile = existsSync(profilePath);
plugin.setServiceKey(env.fingerprintSwitcherToken);
plugin.setWorkingFolder(BROWSER_WORKING_DIRECTORY);
if (!hasProfile) {
const fingerprint = await plugin.fetch({
tags: ['Desktop', 'Microsoft Windows', 'Chrome'],
timeLimit: '60 days',
});
plugin.useFingerprint(fingerprint);
plugin.useProxy(proxy);
return plugin;
}
plugin.useProfile(this.getProfilePath(email), {
loadFingerprint: true,
loadProxy: true,
});
return plugin;
} catch (error) {
Logger.fatal(email, error, 'InitPlugin');
return null;
}
}
private static getProfilePath(email: string) {
const hash = createHash('md5').update(email, 'utf-8').digest('hex');
return join(PROFILES_PATH, hash);
}
private accountInfo: IAccountInfo = DEFAULT_ACCOUNT_BEFORE_LOAD;
private state: IClientState = DEFAULT_CLIENT_STATE;
private logger: Logger;
private constructor(
private plugin: PuppeteerFingerprintPlugin,
private browser: Browser,
private page: Page,
private options: IClientOptions,
) {
this.logger = Logger.get(options.email);
}
public async authorize() {
// ===
}
public async close() {
this.state = {
inAuthorize: false,
isLoggedIn: false,
isBlockedByCaptcha: false,
isBrowserOpen: false,
isClientClosed: true,
};
await this.browser.close();
this.logger.info('Browser unloaded');
}
public async request(options: IClientRequest) {
if (this.browser.connected || this.page.isClosed() || !this.state.isLoggedIn) return null;
const data = await this.page.evaluate(async request => {
try {
const response = await fetch(request.url, {
credentials: 'include',
method: request.method,
body: request.body,
headers: request.headers,
});
const headers = Object.fromEntries(response.headers.entries());
const arrayBuffer = await response.arrayBuffer();
return {
request,
response: {
isOk: response.ok,
status: response.status,
statusText: response.statusText,
headers,
arrayBuffer,
},
};
} catch (error) {
return null;
}
}, options);
if (!data) return null;
return new ClientResponse(data.request, data.response);
}
public updateProxy(validatedProxy: string) {
this.options.proxy = validatedProxy;
this.plugin.useProxy(this.options.proxy);
this.logger.info(`Client: Proxy updated (Need restart browser)`);
}
public getState(): Readonly<IClientState> {
return this.state;
}
public getPlugin() {
return this.plugin;
}
public getBrowser() {
return this.browser;
}
public getPage() {
return this.page;
}
public getAccountInfo(): Readonly<IAccountInfo> {
return this.accountInfo;
}
public get profileName() {
return createHash('md5').update(this.options.email, 'utf-8').digest('hex');
}
private async setupPage() {
await this.page.setRequestInterception(true);
await this.page.emulateTimezone('Asia/Yakutsk');
this.page.on('request', request => {
const url = request.url();
if (this.state.isClientClosed) return request.abort('aborted');
if (this.state.isBlockedByCaptcha) {
if (!this.isRequestAllowedWhenCaptchaActive(url)) {
this.logger.info(`Requests: Request to ${url} blocked (Captcha whitelist)`);
return request.abort('blockedbyclient');
}
}
});
this.page.on('response', async response => {
const url = response.url();
if (url.includes('captcha')) {
this.state.isBlockedByCaptcha = true;
const captchaImage = await response.buffer();
this.logger.info('Responses: Need captcha solving');
}
});
}
private isRequestAllowedWhenCaptchaActive(url: string) {
return WHITELIST_URLS_WHEN_CAPTCHA_ACTIVE.some(chunk => url.includes(chunk));
}
}Am I doing the right thing by creating a new plugin for each profile? But at the same time, they all have one working engine folder.
Metadata
Metadata
Assignees
Labels
No labels