@tocha688/browser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,235 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { faker } from "@faker-js/faker";
4
+ import { adsEncodeBase64, adsDecodeBase64 } from "./ads_tool";
5
+ import { PcScreen, WindowsWebGL, Cpus, Memorys, rSysFonts } from "../const/browser";
6
+ import type { BrowserResult, BrowserStartOptions } from "../types";
7
+ import { checkAutoProxy, rArrItem, rInt, uuidV4, rAutoDigitInt } from "@tocha688/utils";
8
+ import { launchBrowserBase } from "./base";
9
+
10
+ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<BrowserResult> {
11
+ return launchBrowserBase(opts, {
12
+ prepare: async (userDataDir, devtoolsPort) => {
13
+ const webGLFP_path = path.join(userDataDir, "webgl.json")
14
+ const dynamicConfig_path = path.join(userDataDir, "dynamic.config")
15
+ const staticConfig_path = path.join(userDataDir, "static.config")
16
+ const params_path = path.join(userDataDir, "params.config")
17
+
18
+ let params: string;
19
+ let screenSize: string;
20
+
21
+ if (fs.existsSync(params_path)) {
22
+ params = fs.readFileSync(params_path, "utf-8");
23
+ const decoded = JSON.parse(adsDecodeBase64(params));
24
+ screenSize = decoded.ScreenSize;
25
+ } else {
26
+ let ipinfo: any = {};
27
+ let proxyInfo: URL | undefined;
28
+
29
+ if (opts.proxy) {
30
+ ipinfo = await checkAutoProxy(opts.proxy);
31
+ proxyInfo = new URL(ipinfo.proxy)
32
+ }
33
+
34
+ // webgl指纹
35
+ const config = {
36
+ // 指纹随机数种子
37
+ fp: rInt(100000, 999999),
38
+ geop: ipinfo?.latitude ? ipinfo?.latitude + "," + ipinfo?.longitude : "",
39
+ // 加上随机字符串 MSBYH29R
40
+ device_name: "PC" + faker.string.alphanumeric(8).toUpperCase(),
41
+ timezone: ipinfo?.timezone || "America/Los_Angeles",
42
+ proxy: {
43
+ protocol: proxyInfo?.protocol.replace(":", "") || "http",
44
+ host: proxyInfo?.hostname || "127.0.0.1",
45
+ port: proxyInfo?.port || "8080",
46
+ username: proxyInfo?.username || "",
47
+ password: proxyInfo?.password || "",
48
+ },
49
+ mac: faker.internet.mac(),
50
+ scanPorts: "3389,1080",
51
+ screenSize: rArrItem(PcScreen),
52
+ langs: ipinfo?.languages ?? "en-US,en",
53
+ }
54
+ const webGL = rArrItem(WindowsWebGL);
55
+ const webGLFP = {
56
+ "UNMASKED_VENDOR_WEBGL": webGL.name,
57
+ "UNMASKED_RENDERER_WEBGL": rArrItem(webGL.values),
58
+ "GPUAdapterInfo": {
59
+ "vendor": webGL.vendor,
60
+ "architecture": "pascal"
61
+ },
62
+ "SUPPORTED_EXTENSIONS": [
63
+ ]
64
+ }
65
+ // 黑名单 在名单上的不会修改指纹
66
+ const dynamic = {
67
+ "BlockList": {
68
+ "Version": "1743148877",
69
+ // 白名单域名
70
+ "Domains": []
71
+ },
72
+ "StrongBlockList": {
73
+ "Version": "3",
74
+ "Domains": []
75
+ },
76
+ "TimeZone": config.timezone,
77
+ "Geoposition": config.geop
78
+ }
79
+ // 静态配置
80
+ const staticConfig = {
81
+ // "CookiesExportPath": "C:\\.ADSPOWER_GLOBAL\\cache\\k186hhvu_hwvl6k\\sf_cookie.txt",
82
+ "CookiesExportPath": "",
83
+ "SecurePreferenceSync": true,
84
+ // "PasswordExportPath": "C:\\.ADSPOWER_GLOBAL\\cache\\k186hhvu_hwvl6k\\e2776b7f6d0ee269e7a3d43987c7b103",
85
+ "PasswordExportPath": "",
86
+ // "PasswordImportPath": "C:\\.ADSPOWER_GLOBAL\\cache\\k186hhvu_hwvl6k\\6c13174a34c549425974d259f9589e45",
87
+ "PasswordImportPath": "",
88
+ "RestoreLastSession": true,
89
+ // 黑名单页面的html base64编码
90
+ "BlockPage": "NDA0",
91
+ "FoceSafeBrowsing": true,
92
+ "ExtensionSetting": [
93
+ // {
94
+ // "path": "C:\\Users\\Administrator\\AppData\\Roaming\\Electron\\cwd_global\\lib\\tab_assistant",
95
+ // "pinned": 0,
96
+ // "hide": true,
97
+ // "version": "0.0.1",
98
+ // "accessWeb": true
99
+ // }
100
+ ],
101
+ // 不修改指纹的域名列表 net-a-porter.com,nike.com,fedex.com,thenorthface,bybit.com,adidas.com,mrporter.com,lululemon.com,sephora.com,biglietti.italotreno.it,target.com,tangerine.ca,ipdog.io,dhl.com,spotify.com,vinted.com,pingpongx.com,uber.com,wildberries.ru
102
+ "DisCanvasUrl": "",
103
+ "MaxTouchPoints": 0,
104
+ "MediaDevices": [
105
+ {
106
+ "kind": "audioinput",
107
+ "label": "Microphone Array (Realtek(R) Audio)"
108
+ },
109
+ {
110
+ "kind": "videoinput",
111
+ "label": "Integrated Camera (98da:3959)"
112
+ },
113
+ {
114
+ "kind": "audiooutput",
115
+ "label": "Speakers (Realtek(R) Audio)"
116
+ }
117
+ ],
118
+ "AudioFp": rAutoDigitInt(980, config.fp),
119
+ "WebGLMark": rAutoDigitInt("5001", config.fp).toString(),
120
+ "StartTime": Math.floor(Date.now() / 1000),
121
+ "GeolocationSetting": "ask",
122
+ "FlashPluginSetting": "block",
123
+ "Platform": "Win32",
124
+ "ScreenSize": config.screenSize,
125
+ "DisableBackgroundMode": true,
126
+ "DisableWebRTC": true,
127
+ "HardwareConcurrency": rArrItem(Cpus),
128
+ "DeviceMemory": rArrItem(Memorys),
129
+ "LoadExtensionErrorBox": false,
130
+ "ClientRectFp": rAutoDigitInt("5001", config.fp).toString(),
131
+ "ForceProcessExit": true,
132
+ "ProxyUser": config.proxy.username,
133
+ "ProxyPassword": config.proxy.password,
134
+ "Langs": config.langs,
135
+ "AcceptLang": config.langs ? config.langs + ",q=0.9" : "en-US,en;q=0.9",
136
+ // 禁用字体
137
+ "DisabledFonts": (await rSysFonts()).join(","),
138
+ }
139
+
140
+ fs.writeFileSync(webGLFP_path, JSON.stringify(webGLFP))
141
+ fs.writeFileSync(dynamicConfig_path, adsEncodeBase64(JSON.stringify(dynamic)))
142
+ fs.writeFileSync(staticConfig_path, adsEncodeBase64(JSON.stringify(staticConfig)))
143
+
144
+ params = adsEncodeBase64(JSON.stringify({
145
+ "UserId": "1",
146
+ "CanvasMark": rAutoDigitInt("7949", config.fp).toString(),
147
+ "DisableContainer": true,
148
+ "WebGLFP": webGLFP_path,
149
+ "AudioFp": staticConfig.AudioFp,
150
+ "WebGLMark": staticConfig.WebGLMark,
151
+ "StartTime": staticConfig.StartTime,
152
+ "AllowScanPorts": config.scanPorts,
153
+ "GeolocationSetting": staticConfig.GeolocationSetting,
154
+ "FlashPluginSetting": staticConfig.FlashPluginSetting,
155
+ "Platform": staticConfig.Platform,
156
+ "ScreenSize": staticConfig.ScreenSize,
157
+ "DisableBackgroundMode": staticConfig.DisableBackgroundMode,
158
+ "DisableWebRTC": staticConfig.DisableWebRTC,
159
+ "HardwareConcurrency": staticConfig.HardwareConcurrency,
160
+ "DeviceMemory": staticConfig.DeviceMemory,
161
+ "LoadExtensionErrorBox": staticConfig.LoadExtensionErrorBox,
162
+ "ClientRectFp": staticConfig.ClientRectFp,
163
+ "CanvasMarkEx": rAutoDigitInt("7949", config.fp).toString(),
164
+ "WebGLMarkEx": rAutoDigitInt("5001", config.fp).toString(),
165
+ "command_line": {
166
+ "do-not-de-elevate": ""
167
+ },
168
+ "ProxyChain": [
169
+ {
170
+ "scheme": config.proxy.protocol,
171
+ "host": config.proxy.host,
172
+ "port": config.proxy.port,
173
+ "account": config.proxy.username,
174
+ "password": config.proxy.password
175
+ }
176
+ ],
177
+ "ForceProcessExit": staticConfig.ForceProcessExit,
178
+ "ProxyUser": staticConfig.ProxyUser,
179
+ "ProxyPassword": staticConfig.ProxyPassword,
180
+ "Langs": staticConfig.Langs,
181
+ "AcceptLang": staticConfig.AcceptLang,
182
+ "TimeZone": config.timezone,
183
+ "Geoposition": config.geop,
184
+ "DynamicConfig": dynamicConfig_path,
185
+ "StaticConfig": staticConfig_path
186
+ }));
187
+
188
+ fs.writeFileSync(params_path, params)
189
+ screenSize = staticConfig.ScreenSize;
190
+ }
191
+
192
+ const args = [
193
+ "--disable-background-networking",
194
+ "--disable-client-side-phishing-detection",
195
+ "--disable-default-apps",
196
+ "--disable-hang-monitor",
197
+ "--disable-popup-blocking",
198
+ "--disable-prompt-on-repost",
199
+ "--disable-sync",
200
+ "--metrics-recording-only",
201
+ "--no-first-run",
202
+ "--safebrowsing-disable-auto-update",
203
+ "--password-store=basic",
204
+ "--no-service-autorun",
205
+ "--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes",
206
+ "--force-color-profile=srgb",
207
+ "--use-mock-keychain",
208
+ "--export-tagged-pdf",
209
+ "--no-default-browser-check",
210
+ "--window-position=0,0",
211
+ "--disable-background-mode",
212
+ "--ash-host-window-bounds=" + screenSize,
213
+ "--window-size=" + screenSize,
214
+ "--disable-renderer-accessibility",
215
+ "--disable-legacy-window",
216
+ "--fake-variations-channel=stable",
217
+ "--variations-server-url=https://clientservices.googleapis.com/chrome-variations/seed",
218
+ "--component-updater=initial-delay=6e5",
219
+ "--lang=en-US",
220
+ "--enable-features=NetworkService,NetworkServiceInProcess,LoadCryptoTokenExtension,PermuteTLSExtensions",
221
+ "--enable-blink-features=IdleDetection,Fledge",
222
+ "--extended-parameters=" + params,
223
+ "--flag-switches-begin",
224
+ "--flag-switches-end",
225
+ "--load-extension=",
226
+ "--no-sandbox"
227
+ ];
228
+
229
+ return {
230
+ args,
231
+ startingUrl: "https://ipapi.co/json/"
232
+ }
233
+ }
234
+ });
235
+ }
@@ -0,0 +1,68 @@
1
+ import { makeUniqueBrowserConfig } from "../core/launcher";
2
+ import portfinder from 'portfinder';
3
+ import fs from "fs";
4
+ import * as cl from "chrome-launcher";
5
+ import { tryCatch, tryLoop } from "@tocha688/utils";
6
+ import type { BrowserResult, BrowserStartOptions } from "../types";
7
+
8
+ export interface BrowserLaunchConfig {
9
+ prepare: (userDataDir: string, devtoolsPort: number) => Promise<{
10
+ args: string[];
11
+ startingUrl?: string;
12
+ cleanup?: () => Promise<void>;
13
+ }>;
14
+ }
15
+
16
+ export async function launchBrowserBase(
17
+ opts: BrowserStartOptions,
18
+ config: BrowserLaunchConfig
19
+ ): Promise<BrowserResult> {
20
+ const { userDataDir } = makeUniqueBrowserConfig();
21
+ // Ensure userDataDir exists
22
+ fs.mkdirSync(userDataDir, { recursive: true });
23
+
24
+ let hookResult: Awaited<ReturnType<BrowserLaunchConfig['prepare']>> | undefined;
25
+ try {
26
+ const devtoolsPort = await portfinder.getPortPromise({ port: 41000, stopPort: 49999 });
27
+
28
+ hookResult = await config.prepare(userDataDir, devtoolsPort);
29
+
30
+ const args = [
31
+ `--user-data-dir=${userDataDir}`,
32
+ `--remote-debugging-port=${devtoolsPort}`,
33
+ `--remote-debugging-address=0.0.0.0`,
34
+ ...hookResult.args
35
+ ];
36
+
37
+ // Handle headless option if not already handled by provider
38
+ if (opts.headless !== false && !args.some(a => a.startsWith("--headless"))) {
39
+ args.push("--headless");
40
+ }
41
+
42
+ const brw = await cl.launch({
43
+ userDataDir,
44
+ port: devtoolsPort,
45
+ chromePath: opts.chromePath,
46
+ chromeFlags: args,
47
+ startingUrl: hookResult.startingUrl || "about:blank"
48
+ });
49
+
50
+ return {
51
+ userDataDir,
52
+ port: brw.port,
53
+ browser: brw,
54
+ close: async () => {
55
+ await tryCatch(async () => await brw.kill());
56
+ if (hookResult?.cleanup) {
57
+ await tryCatch(hookResult.cleanup);
58
+ }
59
+ await tryLoop(async () => fs.rmSync(userDataDir, { recursive: true, force: true }));
60
+ }
61
+ };
62
+ } catch (e) {
63
+ // cleanup immediately if launch fails
64
+ await tryLoop(async () => fs.rmSync(userDataDir, { recursive: true, force: true }));
65
+ if (hookResult?.cleanup) await tryCatch(hookResult.cleanup);
66
+ throw e;
67
+ }
68
+ }
@@ -0,0 +1,31 @@
1
+ import type { BrowserStartOptions, BrowserResult } from "../types";
2
+ import * as cl from "chrome-launcher";
3
+
4
+ export async function startLocalBrowser(opts: BrowserStartOptions): Promise<BrowserResult> {
5
+ const { chromePath, headless = false, proxy, userDataDir } = opts;
6
+
7
+ const chromeFlags = [
8
+ "--disable-blink-features=AutomationControlled",
9
+ "--no-sandbox",
10
+ "--disable-setuid-sandbox",
11
+ ];
12
+
13
+ if (headless) {
14
+ chromeFlags.push("--headless");
15
+ }
16
+
17
+ // Proxy handling would go here if needed, but usually handled by Playwright context or upstream
18
+
19
+ const browser = await cl.launch({
20
+ chromePath,
21
+ chromeFlags,
22
+ userDataDir,
23
+ });
24
+
25
+ return {
26
+ userDataDir: userDataDir || "",
27
+ port: browser.port,
28
+ browser: browser,
29
+ close: async () => await browser.kill(),
30
+ };
31
+ }
@@ -0,0 +1,96 @@
1
+ import { faker } from "@faker-js/faker";
2
+ import type { BrowserResult, BrowserStartOptions } from "../types";
3
+ import { startProxyServer, type ProxyServerResult } from "@tocha688/utils";
4
+ import { launchBrowserBase } from "./base";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ export async function OKBrowserStartMain(opts: BrowserStartOptions): Promise<BrowserResult> {
9
+ return launchBrowserBase(opts, {
10
+ prepare: async (userDataDir, devtoolsPort) => {
11
+ let proxy: ProxyServerResult | undefined;
12
+ if (opts.proxy) {
13
+ proxy = await startProxyServer(opts.proxy);
14
+ console.log(`Started proxy server on port ${proxy.port}`);
15
+ }
16
+
17
+ const fingerprintPath = path.join(userDataDir, "fingerprint.config");
18
+ let fingerprint: string;
19
+
20
+ if (fs.existsSync(fingerprintPath)) {
21
+ fingerprint = fs.readFileSync(fingerprintPath, "utf-8");
22
+ } else {
23
+ fingerprint = faker.number.int({ min: 10000000, max: 99999999 }).toString();
24
+ fs.writeFileSync(fingerprintPath, fingerprint);
25
+ }
26
+
27
+ const args = [
28
+ "--allow-pre-commit-input",
29
+ "--disable-background-networking",
30
+ "--disable-background-timer-throttling",
31
+ "--disable-backgrounding-occluded-windows",
32
+ "--disable-breakpad",
33
+ "--disable-client-side-phishing-detection",
34
+ "--disable-component-extensions-with-background-pages",
35
+ "--disable-crash-reporter",
36
+ "--disable-default-apps",
37
+ "--disable-dev-shm-usage",
38
+ "--disable-hang-monitor",
39
+ "--disable-infobars",
40
+ "--disable-ipc-flooding-protection",
41
+ "--disable-popup-blocking",
42
+ "--disable-prompt-on-repost",
43
+ "--disable-renderer-backgrounding",
44
+ "--disable-search-engine-choice-screen",
45
+ "--disable-sync",
46
+ "--export-tagged-pdf",
47
+ "--force-color-profile=srgb",
48
+ "--generate-pdf-document-outline",
49
+ "--metrics-recording-only",
50
+ "--no-first-run",
51
+ "--password-store=basic",
52
+ "--use-mock-keychain",
53
+ "--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes",
54
+ "--enable-features=PdfOopif",
55
+ // "--user-data-dir=" + userDataDir, // Handled by base
56
+ // "about:blank",
57
+ // "http://127.0.0.1:4252/index.html?id=1977283458602381314",
58
+ // "--fingerprints=37553310",
59
+ "--fingerprints=" + fingerprint,
60
+ "--window-size=1280,720",
61
+ "--window-position=0,0",
62
+ "--no-sandbox",
63
+ // "--disable-blink-features=AutomationControlled",
64
+ "--ignore-certificate-errors",
65
+ "--notice-number=1",
66
+ // "--notice-number=4",
67
+ // "--load-extension=",
68
+ // "--remote-debugging-port=" + devtoolsPort, // Handled by base
69
+ // "--remote-debugging-address=0.0.0.0", // Handled by base
70
+ // "--enable-password-manager",
71
+ "--ignores=tls",
72
+ "--enable-logging",
73
+ // "--proxy-server=http://127.0.0.1:" + proxy.port,
74
+ "--timezone=" + (proxy?.ipinfo?.timezone ?? "America/Los_Angeles"),
75
+ "--lang=en-US",
76
+ // "--headless", // Handled by base
77
+ ];
78
+
79
+ if (proxy && proxy.port) {
80
+ args.push(`--proxy-server=http://127.0.0.1:${proxy.port}`);
81
+ }
82
+
83
+ return {
84
+ args,
85
+ startingUrl: "https://ipapi.co/json/",
86
+ cleanup: async () => {
87
+ if (proxy) await proxy.server.close(true);
88
+ }
89
+ };
90
+ }
91
+ });
92
+ }
93
+
94
+ export async function OKBrowserCreate(opts: BrowserStartOptions) {
95
+ return OKBrowserStartMain(opts);
96
+ }
@@ -0,0 +1,21 @@
1
+ import type { ProxyFormatInfo } from "@tocha688/utils/ip";
2
+ import * as cl from "chrome-launcher"
3
+ import * as ProxyChain from "proxy-chain";
4
+
5
+ // 使用方式
6
+ export type BrowserStartOptions = {
7
+ proxy?: string,
8
+ headless?: boolean,
9
+ chromePath: string,
10
+ userDataDir?: string;
11
+ }
12
+ export type BrowserResult = {
13
+ userDataDir: string,
14
+ port: number,
15
+ browser: cl.LaunchedChrome,
16
+ close: Function,
17
+ }
18
+
19
+ export type StartBrowserFn = (opts: BrowserStartOptions) => Promise<BrowserResult>;
20
+
21
+ export type ProxyServerResult = { port: number, server: ProxyChain.Server, ipinfo: any }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ clean: true,
7
+ minify: true,
8
+ shims: true,
9
+ dts: true,
10
+ splitting: false,
11
+ sourcemap: false,
12
+ platform: 'node',
13
+ target: 'esnext',
14
+ });