@proxyshard/shardx 0.1.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.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # @proxyshard/shardx (Node)
2
+
3
+ Self-contained Node/TypeScript SDK for the **ShardX anti-detect
4
+ browser** by the [ProxyShard](https://proxyshard.com) team.
5
+
6
+ Does **not** depend on the desktop launcher. On first use it downloads
7
+ the patched Chromium 148 engine, Widevine CDM, and the 170-profile
8
+ fingerprint library from our CDN into a local cache, then launches
9
+ isolated browser sessions on demand.
10
+
11
+ Driven by [patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright)
12
+ (stealth-patched Playwright) — `sdk.session()` returns a ready-to-use
13
+ `Browser` instance, no manual `connectOverCDP` plumbing.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @proxyshard/shardx
19
+ ```
20
+
21
+ Supported hosts: **macOS arm64**, **Windows x64**, **Linux x64**. Node ≥ 18.
22
+
23
+ ## Quick start
24
+
25
+ ```ts
26
+ import { ShardX } from "@proxyshard/shardx";
27
+
28
+ const sdk = new ShardX();
29
+ // Engine + Widevine + fingerprint library auto-download from CDN on
30
+ // the first session/launch/listProfiles call (~170 MB once, etag-cached
31
+ // afterward). No separate install step.
32
+
33
+ // Launch + drive in one call. Returns the patchright Browser.
34
+ const { browser, session, close } = await sdk.session({
35
+ fingerprint: "win-rtx4060",
36
+ proxy: "socks5://user:pass@host:port",
37
+ });
38
+ try {
39
+ const ctx = browser.contexts()[0];
40
+ const page = await ctx.newPage();
41
+ await page.goto("https://browserleaks.com/quic");
42
+ console.log(await page.title());
43
+
44
+ // Inspect what the SDK resolved before launch:
45
+ console.log(session.geo); // { countryCode: 'DE', timezone: 'Europe/Berlin', ... }
46
+ console.log(session.proxyUdpMs, // UDP RTT in ms or null
47
+ session.quicEnabled, // boolean
48
+ session.webrtcMode); // "auto" | "tcp_only" | "block"
49
+ } finally {
50
+ await close(); // tears down patchright + the engine
51
+ }
52
+ ```
53
+
54
+ ### Random profile when none specified
55
+
56
+ ```ts
57
+ const { browser, close } = await sdk.session({
58
+ platform: "Windows",
59
+ randomize: true, // re-rolls hw_concurrency / RAM / platform_version
60
+ });
61
+ try {
62
+ const page = await browser.contexts()[0].newPage();
63
+ // ...
64
+ } finally {
65
+ await close();
66
+ }
67
+ ```
68
+
69
+ ### Discover bundled profiles
70
+
71
+ ```ts
72
+ console.log(sdk.listProfiles().slice(0, 5));
73
+ // [ 'linux-gt1030', 'linux-gtx1050', 'mac-m1-air13', 'mac-m1-imac24', 'mac-m1-max-mbp14' ]
74
+
75
+ console.log(sdk.listProfiles({ platform: "Windows" }).slice(0, 5));
76
+
77
+ const profile = sdk.randomProfile({ platform: "macOS" });
78
+ console.log(profile.id, profile.config.webgl.renderer);
79
+ ```
80
+
81
+ ### Validate a proxy before binding
82
+
83
+ ```ts
84
+ console.log(await sdk.checkProxy("socks5://user:pass@host:port"));
85
+ // {
86
+ // udpMs: 142,
87
+ // geo: { countryCode: 'DE', timezone: 'Europe/Berlin', ... },
88
+ // wouldEnableQuic: true,
89
+ // wouldSetWebrtc: 'auto',
90
+ // }
91
+ ```
92
+
93
+ ## Pre-launch checks
94
+
95
+ Every call to `sdk.session()` / `sdk.launch()` runs the same pre-spawn
96
+ pipeline the desktop launcher uses:
97
+
98
+ 1. **`resolveAutoFields`** — if the profile has `"auto"` sentinels for
99
+ `timezone`, `navigator.language`, or `geolocation.mode`, the SDK
100
+ makes a live geo lookup through the bound proxy (`ip-api.com` by
101
+ default). Concrete values get written back: timezone (from the API,
102
+ never a static table), `accept_language` chain, `languages`,
103
+ `icu_locale` (always overwritten so `Intl.*` matches
104
+ `navigator.language`), and lat/lng. Proxy-via failure → direct geo
105
+ → host `Intl.DateTimeFormat().resolvedOptions().timeZone` as
106
+ last-resort fallback. The resolved geo is surfaced on
107
+ `session.geo`.
108
+ 2. **`applyScreenStrategy`** — see below.
109
+ 3. **`probeUdp`** — SOCKS5 UDP_ASSOCIATE round-trip. If it fails, QUIC
110
+ is force-disabled and WebRTC switches to `tcp_only` automatically.
111
+
112
+ ### Screen strategy
113
+
114
+ `screenMode` option to `session()` / `launch()`:
115
+
116
+ * **`"profile"`** — keep whatever the fingerprint claims.
117
+ * **`"cap_to_host"`** — *macOS default.* If the host monitor is smaller
118
+ than the FP screen, scale `screen.*` + `window.*` down proportionally;
119
+ otherwise no-op.
120
+ * **`"use_host"`** — *Windows / Linux default.* Overwrite `screen.*`
121
+ with the real monitor (minus a 40 px Windows taskbar) and recompute
122
+ `window.outer*` / `window.inner*`.
123
+
124
+ Default mode is picked from `navigator.platform`. Override per launch:
125
+
126
+ ```ts
127
+ await sdk.session({ fingerprint: "win-rtx4060", screenMode: "profile" });
128
+ ```
129
+
130
+ ### Host-aware hardware randomisation
131
+
132
+ `randomize: true` re-picks `hardware_concurrency`, `device_memory`, and
133
+ `platform_version` before the launch — using the same logic as the
134
+ desktop launcher:
135
+
136
+ * **macOS** profiles use the curated `MAC_HW_CONFIGS` table by id.
137
+ * **Windows / Linux** profiles bracket the host's logical CPU count
138
+ within `[host − 4, host + 2]` from the real x86 set
139
+ `[4, 6, 8, 12, 16, 20, 24, 28, 32]`; `device_memory` is floored by
140
+ core count (≥ 12 → 16, else 8) and capped by `hostRamBucketGb()`
141
+ (8 / 16 / 32 GiB bucketed from `sysctl hw.memsize` / `/proc/meminfo`
142
+ / `Get-CimInstance Win32_ComputerSystem`).
143
+
144
+ So a profile launched on an 8-core / 16 GB laptop will never claim
145
+ 32 cores / 128 GB of RAM.
146
+
147
+ ### Override fingerprint fields
148
+
149
+ ```ts
150
+ const profile = sdk.library
151
+ .load("win-rtx4060")
152
+ .withOverride({
153
+ name: "my-account",
154
+ timezone: "Europe/Berlin",
155
+ navigator: { language: "de-DE" },
156
+ });
157
+
158
+ const { browser, close } = await sdk.session({ fingerprint: profile, proxy: "socks5://..." });
159
+ ```
160
+
161
+ ### Use your own fingerprint JSON
162
+
163
+ ```ts
164
+ import { Profile } from "@proxyshard/shardx";
165
+
166
+ const profile = Profile.fromFile("/path/to/my-custom.json");
167
+ const { browser, close } = await sdk.session({ fingerprint: profile });
168
+ ```
169
+
170
+ ### WebRTC policy
171
+
172
+ ```ts
173
+ await sdk.session({
174
+ fingerprint: "win-rtx4060",
175
+ proxy: "socks5://...",
176
+ webrtc: "tcp_only", // "auto" (default) | "block" | "tcp_only"
177
+ webrtcPublicIp: "203.0.113.42", // advertised in ICE candidates
178
+ });
179
+ ```
180
+
181
+ ### Progress callback during the first-run download
182
+
183
+ The first `session`/`launch`/`listProfiles` triggers the download. Hook
184
+ it with a progress callback on the constructor:
185
+
186
+ ```ts
187
+ const sdk = new ShardX({
188
+ progress: (label, received, total) => {
189
+ const pct = total ? Math.floor((received / total) * 100) : 0;
190
+ console.log(`${label}: ${pct}%`);
191
+ },
192
+ });
193
+ const { browser, close } = await sdk.session({ fingerprint: "win-rtx4060" });
194
+ ```
195
+
196
+ ## Advanced: raw launch without patchright
197
+
198
+ If you'd rather drive the browser with a different CDP client (raw
199
+ `chrome-remote-interface`, puppeteer-core's `connect`, your own
200
+ WebSocket), skip `session()` and use `launch()` directly:
201
+
202
+ ```ts
203
+ const sess = await sdk.launch("win-rtx4060", { proxy: "socks5://...", cdp: true });
204
+ console.log(sess.cdpUrl); // ws://127.0.0.1:54113/devtools/browser/...
205
+ // ... drive it yourself ...
206
+ await sess.stop();
207
+ ```
208
+
209
+ `launch()` runs the same pre-launch pipeline (auto-resolve, screen
210
+ strategy, UDP probe, hw randomisation) and returns a `BrowserSession`
211
+ with `cdpUrl`, `geo`, `proxyUdpMs`, `quicEnabled`, `webrtcMode`,
212
+ `userDataDir`, and `stop()`.
213
+
214
+ ## Cache layout
215
+
216
+ ```
217
+ ~/Library/Application Support/shardx-sdk/ (mac)
218
+ %LOCALAPPDATA%\shardx-sdk\ (win)
219
+ ~/.cache/shardx-sdk/ (linux)
220
+ ├── manifest.json ← etag cache
221
+ ├── ShardX-Mac-arm64/ ← extracted engine
222
+ ├── fingerprints/ ← 170 bundled .json profiles
223
+ └── profiles/<profile-id>/ ← per-launch user-data-dir
224
+ ```
225
+
226
+ Override:
227
+
228
+ ```ts
229
+ const sdk = new ShardX({ cacheDir: "/data/shardx" });
230
+ ```
231
+
232
+ ## Update the runtime
233
+
234
+ The SDK auto-checks remote etags on the first `session`/`launch`/`listProfiles`
235
+ call of each process and re-downloads anything that changed. To force a
236
+ re-download mid-process:
237
+
238
+ ```ts
239
+ await sdk.runtime.install({ force: true });
240
+ ```
241
+
242
+ ## License
243
+
244
+ MIT (this SDK). The Chromium-fork engine binary it downloads at
245
+ runtime is a closed-source product — see the
246
+ [main repo](https://github.com/ProxyShard/ShardBrowser) for engine
247
+ licensing.
@@ -0,0 +1,10 @@
1
+ import { type GeoInfo } from "./geo.js";
2
+ import type { ParsedProxy } from "./proxy.js";
3
+ export declare function hasAutoFields(cfg: Record<string, unknown>): boolean;
4
+ /**
5
+ * Apply the launcher's "auto" resolution. Returns the GeoInfo that fed the
6
+ * resolution, or null when both proxy + direct probes failed and the host
7
+ * fallback was used.
8
+ */
9
+ export declare function resolveAutoFields(cfg: Record<string, unknown>, proxy: ParsedProxy | null): Promise<GeoInfo | null>;
10
+ //# sourceMappingURL=autoResolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoResolve.d.ts","sourceRoot":"","sources":["../src/autoResolve.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyD9C,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAOnE;AA4BD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,KAAK,EAAE,WAAW,GAAG,IAAI,GACxB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAmEzB"}
@@ -0,0 +1,181 @@
1
+ // Resolve `"auto"` sentinels in a profile config — port of
2
+ // `resolve_auto_fields` in `src-tauri/src/launch.rs`. Reads the live geo
3
+ // (through the bound proxy when present, direct otherwise), falls back to
4
+ // the host TZ/locale on failure, then mutates `cfg` in place to write
5
+ // concrete timezone / navigator.language / accept_language / languages /
6
+ // icu_locale / geolocation values.
7
+ import { readlinkSync } from "node:fs";
8
+ import { geoCheckVia } from "./geo.js";
9
+ /** ISO-3166 alpha-2 → BCP-47 locale. Ported 1:1 from launcher's Rust
10
+ * `country_to_locale` (src-tauri/src/proxy.rs). Authoritative table the
11
+ * desktop launcher uses — keep in sync if the Rust side ever changes. */
12
+ function countryToLocale(cc) {
13
+ return CC_TO_LOCALE[(cc ?? "").toUpperCase()] ?? "en-US";
14
+ }
15
+ const CC_TO_LOCALE = {
16
+ US: "en-US", GB: "en-GB", UK: "en-GB", CA: "en-CA",
17
+ AU: "en-AU", NZ: "en-NZ", IE: "en-IE", ZA: "en-ZA", IN: "en-IN",
18
+ DE: "de-DE", AT: "de-AT", CH: "de-CH",
19
+ FR: "fr-FR", BE: "fr-BE",
20
+ ES: "es-ES", MX: "es-MX", AR: "es-AR", CO: "es-CO", CL: "es-CL",
21
+ IT: "it-IT", NL: "nl-NL", PL: "pl-PL",
22
+ BR: "pt-BR", PT: "pt-PT",
23
+ RO: "ro-RO", RU: "ru-RU", BY: "be-BY", UA: "uk-UA",
24
+ TR: "tr-TR", GR: "el-GR",
25
+ CZ: "cs-CZ", SK: "sk-SK", HU: "hu-HU",
26
+ SE: "sv-SE", FI: "fi-FI", NO: "nb-NO", DK: "da-DK",
27
+ BG: "bg-BG", HR: "hr-HR", SI: "sl-SI", RS: "sr-RS",
28
+ IL: "he-IL",
29
+ SA: "ar-SA", AE: "ar-SA", EG: "ar-SA",
30
+ ID: "id-ID", MY: "ms-MY", PH: "fil-PH", VN: "vi-VN", TH: "th-TH",
31
+ CN: "zh-CN", HK: "zh-HK", TW: "zh-TW",
32
+ JP: "ja-JP", KR: "ko-KR",
33
+ };
34
+ /** Country → IANA timezone fallback for providers that omit timezone.
35
+ * Ported 1:1 from launcher's Rust `country_to_timezone`. */
36
+ function countryToTimezone(cc) {
37
+ return CC_TO_TZ[(cc ?? "").toUpperCase()] ?? "UTC";
38
+ }
39
+ const CC_TO_TZ = {
40
+ US: "America/New_York", CA: "America/Toronto",
41
+ GB: "Europe/London", UK: "Europe/London",
42
+ DE: "Europe/Berlin", FR: "Europe/Paris", ES: "Europe/Madrid",
43
+ IT: "Europe/Rome", NL: "Europe/Amsterdam", PL: "Europe/Warsaw",
44
+ PT: "Europe/Lisbon", RO: "Europe/Bucharest", RU: "Europe/Moscow",
45
+ UA: "Europe/Kyiv", TR: "Europe/Istanbul", GR: "Europe/Athens",
46
+ CZ: "Europe/Prague", HU: "Europe/Budapest",
47
+ SE: "Europe/Stockholm", FI: "Europe/Helsinki",
48
+ NO: "Europe/Oslo", DK: "Europe/Copenhagen",
49
+ CH: "Europe/Zurich", AT: "Europe/Vienna",
50
+ BR: "America/Sao_Paulo", AR: "America/Argentina/Buenos_Aires",
51
+ MX: "America/Mexico_City",
52
+ AU: "Australia/Sydney", NZ: "Pacific/Auckland",
53
+ IN: "Asia/Kolkata", ID: "Asia/Jakarta", MY: "Asia/Kuala_Lumpur",
54
+ SG: "Asia/Singapore", TH: "Asia/Bangkok", VN: "Asia/Ho_Chi_Minh",
55
+ CN: "Asia/Shanghai", HK: "Asia/Hong_Kong", TW: "Asia/Taipei",
56
+ JP: "Asia/Tokyo", KR: "Asia/Seoul",
57
+ IL: "Asia/Jerusalem", SA: "Asia/Riyadh", AE: "Asia/Dubai",
58
+ };
59
+ export function hasAutoFields(cfg) {
60
+ if (cfg["timezone"] === "auto")
61
+ return true;
62
+ const nav = cfg["navigator"];
63
+ if (nav && nav["language"] === "auto")
64
+ return true;
65
+ const geo = cfg["geolocation"];
66
+ if (geo && typeof geo === "object" && geo["mode"] === "auto")
67
+ return true;
68
+ return false;
69
+ }
70
+ function hostTimezone() {
71
+ const tz = (process.env.TZ ?? "").trim();
72
+ if (tz && tz.includes("/"))
73
+ return tz;
74
+ try {
75
+ const target = readlinkSync("/etc/localtime");
76
+ for (const prefix of ["/usr/share/zoneinfo/", "/var/db/timezone/zoneinfo/"]) {
77
+ const i = target.indexOf(prefix);
78
+ if (i >= 0)
79
+ return target.slice(i + prefix.length);
80
+ }
81
+ }
82
+ catch { /* not a symlink / not unix */ }
83
+ try {
84
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function hostLocale() {
91
+ for (const v of [process.env.LANG, process.env.LC_ALL, process.env.LC_MESSAGES]) {
92
+ if (!v)
93
+ continue;
94
+ const stripped = v.split(".", 1)[0].replace(/_/g, "-");
95
+ if (stripped.includes("-"))
96
+ return stripped;
97
+ }
98
+ return "en-US";
99
+ }
100
+ /**
101
+ * Apply the launcher's "auto" resolution. Returns the GeoInfo that fed the
102
+ * resolution, or null when both proxy + direct probes failed and the host
103
+ * fallback was used.
104
+ */
105
+ export async function resolveAutoFields(cfg, proxy) {
106
+ const wantTz = cfg["timezone"] === "auto";
107
+ const nav = cfg["navigator"];
108
+ const wantLang = !!(nav && nav["language"] === "auto");
109
+ const geoCfg = cfg["geolocation"];
110
+ const wantGeo = !!(geoCfg && typeof geoCfg === "object" && geoCfg["mode"] === "auto");
111
+ if (!wantTz && !wantLang && !wantGeo)
112
+ return null;
113
+ let geo = null;
114
+ if (proxy) {
115
+ try {
116
+ geo = await geoCheckVia(proxy);
117
+ }
118
+ catch {
119
+ geo = null;
120
+ }
121
+ }
122
+ if (!geo) {
123
+ try {
124
+ geo = await geoCheckVia(null);
125
+ }
126
+ catch {
127
+ geo = null;
128
+ }
129
+ }
130
+ let resolvedTz;
131
+ let resolvedLocale;
132
+ let lat;
133
+ let lng;
134
+ if (geo) {
135
+ // Timezone: always from API. If the provider didn't return one,
136
+ // fall back to the country-code table — NOT the host TZ (would
137
+ // leak the launcher's real zone).
138
+ resolvedTz = geo.timezone || countryToTimezone(geo.countryCode);
139
+ resolvedLocale = countryToLocale(geo.countryCode);
140
+ lat = geo.latitude !== 0 ? geo.latitude : null;
141
+ lng = geo.longitude !== 0 ? geo.longitude : null;
142
+ }
143
+ else {
144
+ resolvedTz = hostTimezone() ?? "UTC";
145
+ resolvedLocale = hostLocale();
146
+ lat = null;
147
+ lng = null;
148
+ }
149
+ if (wantTz)
150
+ cfg["timezone"] = resolvedTz;
151
+ if (wantLang) {
152
+ const base = resolvedLocale.split("-", 1)[0];
153
+ const accept = resolvedLocale === "en-US"
154
+ ? "en-US,en;q=0.9"
155
+ : `${resolvedLocale},${base};q=0.9,en-US;q=0.8,en;q=0.7`;
156
+ const languages = resolvedLocale === "en-US"
157
+ ? ["en-US", "en"]
158
+ : [resolvedLocale, base, "en-US", "en"];
159
+ const navObj = (cfg["navigator"] ??= {});
160
+ navObj["language"] = resolvedLocale;
161
+ navObj["accept_language"] = accept;
162
+ navObj["languages"] = languages;
163
+ // Always overwrite — matches launch.rs (even hardcoded values are replaced).
164
+ cfg["icu_locale"] = resolvedLocale;
165
+ }
166
+ if (wantGeo) {
167
+ if (lat !== null && lng !== null) {
168
+ cfg["geolocation"] = {
169
+ mode: "manual",
170
+ latitude: lat,
171
+ longitude: lng,
172
+ accuracy: 50.0,
173
+ };
174
+ }
175
+ else {
176
+ delete cfg["geolocation"];
177
+ }
178
+ }
179
+ return geo;
180
+ }
181
+ //# sourceMappingURL=autoResolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoResolve.js","sourceRoot":"","sources":["../src/autoResolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import { type GeoInfo } from "./geo.js";
3
+ import { Profile } from "./profile.js";
4
+ import type { Runtime } from "./runtime.js";
5
+ import { type ScreenStrategy } from "./screen.js";
6
+ export type WebRtcMode = "auto" | "block" | "tcp_only";
7
+ /** Legacy alias retained for back-compat; prefer `ScreenStrategy`. */
8
+ export type ScreenMode = ScreenStrategy;
9
+ export interface LaunchOptions {
10
+ proxy?: string;
11
+ cdp?: boolean;
12
+ headless?: boolean;
13
+ extraArgs?: string[];
14
+ env?: Record<string, string>;
15
+ webrtc?: WebRtcMode;
16
+ webrtcPublicIp?: string;
17
+ /** Override the UDP-probe auto-decision. */
18
+ quic?: boolean;
19
+ /** Defaults to "cap_to_host" on macOS, "use_host" on Win/Linux. */
20
+ screenMode?: ScreenStrategy;
21
+ probeTimeoutMs?: number;
22
+ /** Custom user-data-dir root. Defaults to ./shardx-profiles/<id>/. */
23
+ userDataDir?: string;
24
+ }
25
+ export declare class BrowserSession {
26
+ readonly pid: number;
27
+ readonly userDataDir: string;
28
+ readonly cdpUrl: string | null;
29
+ readonly process: ChildProcess;
30
+ readonly proxyUdpMs: number | null;
31
+ readonly quicEnabled: boolean;
32
+ readonly webrtcMode: WebRtcMode;
33
+ readonly geo: GeoInfo | null;
34
+ private _stopped;
35
+ constructor(pid: number, userDataDir: string, cdpUrl: string | null, process: ChildProcess, proxyUdpMs?: number | null, quicEnabled?: boolean, webrtcMode?: WebRtcMode, geo?: GeoInfo | null);
36
+ stop(timeoutMs?: number): Promise<void>;
37
+ }
38
+ export declare class Browser {
39
+ private readonly runtime;
40
+ constructor(runtime: Runtime);
41
+ launch(profile: Profile, opts?: LaunchOptions): Promise<BrowserSession>;
42
+ }
43
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAUA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,OAAO,EAAe,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAA6C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7F,MAAM,MAAM,UAAU,GAAK,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AACzD,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAK,cAAc,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,cAAc;IAIvB,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO;IAC7B,QAAQ,CAAC,UAAU,EAAE,UAAU;IAC/B,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAV9B,OAAO,CAAC,QAAQ,CAAS;gBAGd,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,YAAY,EACrB,UAAU,GAAE,MAAM,GAAG,IAAW,EAChC,WAAW,GAAE,OAAe,EAC5B,UAAU,GAAE,UAAmB,EAC/B,GAAG,GAAE,OAAO,GAAG,IAAW;IAG/B,IAAI,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAa5C;AAED,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,OAAO;IAEvC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;CAuFlF"}
@@ -0,0 +1,172 @@
1
+ // Browser launch + lifecycle. Spawns the ShardX engine with the same
2
+ // spoofing flags the desktop launcher uses, plus pre-launch:
3
+ //
4
+ // • resolveAutoFields — fill timezone/language/geolocation from a
5
+ // live geo lookup through the bound proxy.
6
+ // • applyScreenStrategy — cap to host monitor (macOS) or replace with
7
+ // the host monitor (Win/Linux), matching the launcher's
8
+ // `clamp_screen_to_real_display` / `--shardx-real-screen` switch.
9
+ // • probeUdp — decide QUIC + WebRTC policy from a live
10
+ // SOCKS5 UDP_ASSOCIATE probe.
11
+ import { spawn } from "node:child_process";
12
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { hasAutoFields, resolveAutoFields } from "./autoResolve.js";
15
+ import { geoCheckVia } from "./geo.js";
16
+ import { userDataDir } from "./profile.js";
17
+ import { parseProxy, probeUdp, proxyToArg } from "./proxy.js";
18
+ import { applyScreenStrategy, defaultScreenModeFor } from "./screen.js";
19
+ export class BrowserSession {
20
+ pid;
21
+ userDataDir;
22
+ cdpUrl;
23
+ process;
24
+ proxyUdpMs;
25
+ quicEnabled;
26
+ webrtcMode;
27
+ geo;
28
+ _stopped = false;
29
+ constructor(pid, userDataDir, cdpUrl, process, proxyUdpMs = null, quicEnabled = false, webrtcMode = "auto", geo = null) {
30
+ this.pid = pid;
31
+ this.userDataDir = userDataDir;
32
+ this.cdpUrl = cdpUrl;
33
+ this.process = process;
34
+ this.proxyUdpMs = proxyUdpMs;
35
+ this.quicEnabled = quicEnabled;
36
+ this.webrtcMode = webrtcMode;
37
+ this.geo = geo;
38
+ }
39
+ async stop(timeoutMs = 5000) {
40
+ if (this._stopped)
41
+ return;
42
+ this._stopped = true;
43
+ if (!this.process.pid)
44
+ return;
45
+ try {
46
+ this.process.kill("SIGTERM");
47
+ }
48
+ catch { /* already gone */ }
49
+ const exited = await new Promise((resolve) => {
50
+ const t = setTimeout(() => resolve(false), timeoutMs);
51
+ this.process.once("exit", () => { clearTimeout(t); resolve(true); });
52
+ });
53
+ if (!exited) {
54
+ try {
55
+ this.process.kill("SIGKILL");
56
+ }
57
+ catch { /* ignore */ }
58
+ }
59
+ }
60
+ }
61
+ export class Browser {
62
+ runtime;
63
+ constructor(runtime) {
64
+ this.runtime = runtime;
65
+ }
66
+ async launch(profile, opts = {}) {
67
+ // Auto-install on first use (high-level ShardX.launch already does
68
+ // this; the call is here too so low-level Browser.launch users
69
+ // don't have to remember).
70
+ await this.runtime.install();
71
+ const parsed = opts.proxy ? parseProxy(opts.proxy) : null;
72
+ // ---- pre-launch: auto-resolve, screen strategy, UDP probe ------
73
+ let geo = null;
74
+ if (hasAutoFields(profile.config)) {
75
+ geo = await resolveAutoFields(profile.config, parsed);
76
+ }
77
+ const mode = opts.screenMode ?? defaultScreenModeFor(profile.platform);
78
+ applyScreenStrategy(profile.config, mode);
79
+ let proxyUdpMs = null;
80
+ if (parsed && parsed.scheme === "socks5") {
81
+ proxyUdpMs = await probeUdp(parsed, opts.probeTimeoutMs ?? 6000);
82
+ }
83
+ const udpOk = proxyUdpMs !== null;
84
+ const quicEnabled = opts.quic ?? (parsed !== null && udpOk);
85
+ let webrtcMode = opts.webrtc ?? "auto";
86
+ if (webrtcMode === "auto" && parsed !== null && !udpOk)
87
+ webrtcMode = "tcp_only";
88
+ // ---- profile + udd ----------------------------------------------
89
+ const udd = userDataDir(this.runtime, profile.id, opts.userDataDir);
90
+ console.log(`[shardx] profile '${profile.id}' → ${udd}`);
91
+ const fpFile = join(udd, "fingerprint.json");
92
+ writeFileSync(fpFile, JSON.stringify(profile.config));
93
+ const argv = [
94
+ `--fingerprint-profile=${fpFile}`,
95
+ `--user-data-dir=${udd}`,
96
+ "--no-first-run",
97
+ ];
98
+ if (!profile.hasWebGPU)
99
+ argv.push("--disable-features=WebGPU");
100
+ if (!opts.headless && !opts.cdp) {
101
+ argv.push("--restore-last-session", "--hide-crash-restore-bubble");
102
+ }
103
+ // Engine-side real-screen switch only fires on use_host (where the SDK
104
+ // already rewrote screen.* — keep them in sync with the launcher).
105
+ if (mode === "use_host")
106
+ argv.push("--shardx-real-screen");
107
+ if (parsed) {
108
+ argv.push(`--proxy-server=${proxyToArg(parsed)}`);
109
+ argv.push(quicEnabled ? "--enable-quic" : "--disable-quic");
110
+ }
111
+ if (webrtcMode === "block") {
112
+ argv.push("--force-webrtc-ip-handling-policy=disable_non_proxied_udp", "--shardx-webrtc-policy=block");
113
+ }
114
+ else if (webrtcMode === "tcp_only") {
115
+ argv.push("--force-webrtc-ip-handling-policy=disable_non_proxied_udp", "--shardx-webrtc-policy=tcp_only");
116
+ // Engine spoofs the public side of ICE candidates with this IP.
117
+ // Match the launcher: ALWAYS resolve when proxy is bound — relying
118
+ // on `geo` from auto-resolve only works when the profile has auto
119
+ // sentinels, otherwise the engine falls back to the host IP.
120
+ let ip = opts.webrtcPublicIp ?? geo?.ip;
121
+ if (!ip && parsed) {
122
+ try {
123
+ ip = (await geoCheckVia(parsed)).ip || undefined;
124
+ }
125
+ catch { /* leave undefined */ }
126
+ }
127
+ if (ip)
128
+ argv.push(`--shardx-webrtc-public-ip=${ip}`);
129
+ }
130
+ const cdpMarker = join(udd, "DevToolsActivePort");
131
+ if (opts.cdp) {
132
+ if (existsSync(cdpMarker))
133
+ rmSync(cdpMarker, { force: true });
134
+ argv.push("--remote-debugging-port=0", "--remote-allow-origins=*");
135
+ }
136
+ if (opts.headless)
137
+ argv.push("--headless=new");
138
+ if (opts.extraArgs)
139
+ argv.push(...opts.extraArgs);
140
+ const child = spawn(this.runtime.binaryPath, argv, {
141
+ env: { ...process.env, ...(opts.env ?? {}) },
142
+ stdio: "ignore",
143
+ detached: process.platform !== "win32",
144
+ });
145
+ const cdpUrl = opts.cdp ? await readCdpEndpoint(udd, 15_000) : null;
146
+ return new BrowserSession(child.pid, udd, cdpUrl, child, proxyUdpMs, quicEnabled, webrtcMode, geo);
147
+ }
148
+ }
149
+ async function readCdpEndpoint(udd, timeoutMs) {
150
+ const marker = join(udd, "DevToolsActivePort");
151
+ const deadline = Date.now() + timeoutMs;
152
+ while (Date.now() < deadline) {
153
+ if (existsSync(marker)) {
154
+ try {
155
+ const firstLine = readFileSync(marker, "utf8").split("\n")[0].trim();
156
+ const port = parseInt(firstLine, 10);
157
+ if (!Number.isNaN(port)) {
158
+ const r = await fetch(`http://127.0.0.1:${port}/json/version`);
159
+ if (r.ok) {
160
+ const data = await r.json();
161
+ if (data.webSocketDebuggerUrl)
162
+ return data.webSocketDebuggerUrl;
163
+ }
164
+ }
165
+ }
166
+ catch { /* keep polling */ }
167
+ }
168
+ await new Promise((r) => setTimeout(r, 100));
169
+ }
170
+ return null;
171
+ }
172
+ //# sourceMappingURL=browser.js.map