@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 +247 -0
- package/dist/autoResolve.d.ts +10 -0
- package/dist/autoResolve.d.ts.map +1 -0
- package/dist/autoResolve.js +181 -0
- package/dist/autoResolve.js.map +1 -0
- package/dist/browser.d.ts +43 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +172 -0
- package/dist/browser.js.map +1 -0
- package/dist/geo.d.ts +24 -0
- package/dist/geo.d.ts.map +1 -0
- package/dist/geo.js +129 -0
- package/dist/geo.js.map +1 -0
- package/dist/host.d.ts +10 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +142 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +130 -0
- package/dist/index.js.map +1 -0
- package/dist/profile.d.ts +26 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +89 -0
- package/dist/profile.js.map +1 -0
- package/dist/proxy.d.ts +18 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +212 -0
- package/dist/proxy.js.map +1 -0
- package/dist/randomize.d.ts +20 -0
- package/dist/randomize.d.ts.map +1 -0
- package/dist/randomize.js +123 -0
- package/dist/randomize.js.map +1 -0
- package/dist/runtime.d.ts +49 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +266 -0
- package/dist/runtime.js.map +1 -0
- package/dist/screen.d.ts +5 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +82 -0
- package/dist/screen.js.map +1 -0
- package/package.json +60 -0
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"}
|
package/dist/browser.js
ADDED
|
@@ -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
|