wb-browser-runtime 0.5.0 → 0.5.2
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 +21 -3
- package/bin/wb-browser-runtime.js +63 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,9 +25,27 @@ specific run.
|
|
|
25
25
|
- `BROWSERBASE_API_KEY`
|
|
26
26
|
- `BROWSERBASE_PROJECT_ID`
|
|
27
27
|
|
|
28
|
-
Verb arguments support
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
Verb arguments support two substitutions at dispatch time:
|
|
29
|
+
|
|
30
|
+
- `{{ env.NAME }}` — reads `process.env.NAME`. Use for static secrets injected via Doppler / the agent's env.
|
|
31
|
+
- `{{ artifacts.NAME }}` — reads `$WB_ARTIFACTS_DIR/NAME.txt` (falling back to `$WB_ARTIFACTS_DIR/NAME`). Use for dynamic values produced by an earlier bash cell — OTPs, magic-link URLs, export IDs, anything polled from an external system mid-run. Each read happens per-verb with no caching, so writes land immediately.
|
|
32
|
+
|
|
33
|
+
Both forms are redacted in stdout summaries — only the verb name + selector make it into the log.
|
|
34
|
+
|
|
35
|
+
## Optional: anti-detection
|
|
36
|
+
|
|
37
|
+
Targets behind Cloudflare / Kasada / DataDome (e.g. Airbase) will reject the
|
|
38
|
+
default Browserbase session fingerprint and serve a non-interactive challenge
|
|
39
|
+
page. Flip either flag on for the affected runs.
|
|
40
|
+
|
|
41
|
+
| Env var | Default | Purpose |
|
|
42
|
+
|------------------------------------|---------|--------------------------------------------------|
|
|
43
|
+
| `BROWSERBASE_ADVANCED_STEALTH` | *(off)* | Send `browserSettings.advancedStealth: true`. Browserbase Scale-plan-gated — API errors on lower plans. |
|
|
44
|
+
| `BROWSERBASE_PROXIES` | *(off)* | Send `proxies: true`. Routes through Browserbase residential proxy pool. Incurs extra per-session cost. |
|
|
45
|
+
|
|
46
|
+
Set `=1` (or `=true`) to enable. `proxies: true` alone clears most Cloudflare
|
|
47
|
+
challenges; add `advancedStealth: true` on top when the target still blocks.
|
|
48
|
+
The sidecar logs the resolved config at session create.
|
|
31
49
|
|
|
32
50
|
## Optional: session recording (rrweb + CDP screencast)
|
|
33
51
|
|
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
// BROWSERBASE_API_KEY
|
|
15
15
|
// BROWSERBASE_PROJECT_ID
|
|
16
16
|
//
|
|
17
|
-
// Verb args support
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// Verb args support two substitutions, expanded recursively at dispatch time:
|
|
18
|
+
// {{ env.NAME }} → process.env.NAME
|
|
19
|
+
// {{ artifacts.NAME }} → contents of $WB_ARTIFACTS_DIR/NAME.txt (or .../NAME)
|
|
20
|
+
// The artifacts form lets an earlier bash cell compute a value — OTP, magic
|
|
21
|
+
// link, export id — and feed it into a later browser verb without a sidecar
|
|
22
|
+
// round-trip. Credentials passed via either form never hit stdout — only the
|
|
23
|
+
// verb name + selector make it into the summary.
|
|
20
24
|
|
|
21
25
|
import readline from "node:readline";
|
|
22
26
|
import { chromium } from "playwright-core";
|
|
@@ -168,15 +172,35 @@ async function bbCreateSession() {
|
|
|
168
172
|
"BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID must be set",
|
|
169
173
|
);
|
|
170
174
|
}
|
|
175
|
+
|
|
176
|
+
// Both flags opt-in per session. advancedStealth is Scale-plan-gated on
|
|
177
|
+
// Browserbase's side; proxies adds residential-IP cost. Default off so a
|
|
178
|
+
// misconfigured plan doesn't break unrelated runs (HN, Google Sheets, etc.);
|
|
179
|
+
// flip per vendor when the target sits behind Cloudflare / similar bot
|
|
180
|
+
// detection (e.g., Airbase).
|
|
181
|
+
const envBool = (v) => v === "1" || (typeof v === "string" && v.toLowerCase() === "true");
|
|
182
|
+
const advancedStealth = envBool(process.env.BROWSERBASE_ADVANCED_STEALTH);
|
|
183
|
+
const proxies = envBool(process.env.BROWSERBASE_PROXIES);
|
|
184
|
+
|
|
185
|
+
// keepAlive:false — slice lifetime is tied to wb process; on shutdown
|
|
186
|
+
// we explicitly REQUEST_RELEASE so quota isn't burned by orphans.
|
|
187
|
+
const body = { projectId, keepAlive: false };
|
|
188
|
+
if (advancedStealth) {
|
|
189
|
+
body.browserSettings = { advancedStealth: true };
|
|
190
|
+
}
|
|
191
|
+
if (proxies) {
|
|
192
|
+
body.proxies = true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
log(`[bb] session create advancedStealth=${advancedStealth} proxies=${proxies}`);
|
|
196
|
+
|
|
171
197
|
const res = await fetch(`${BB_BASE}/v1/sessions`, {
|
|
172
198
|
method: "POST",
|
|
173
199
|
headers: {
|
|
174
200
|
"X-BB-API-Key": apiKey,
|
|
175
201
|
"Content-Type": "application/json",
|
|
176
202
|
},
|
|
177
|
-
|
|
178
|
-
// we explicitly REQUEST_RELEASE so quota isn't burned by orphans.
|
|
179
|
-
body: JSON.stringify({ projectId, keepAlive: false }),
|
|
203
|
+
body: JSON.stringify(body),
|
|
180
204
|
});
|
|
181
205
|
if (!res.ok) {
|
|
182
206
|
throw new Error(
|
|
@@ -556,22 +580,44 @@ function sanitize(s) {
|
|
|
556
580
|
return String(s || "default").replace(/[^A-Za-z0-9_-]+/g, "_");
|
|
557
581
|
}
|
|
558
582
|
|
|
559
|
-
// --- {{ env.X }} substitution
|
|
583
|
+
// --- {{ env.X }} / {{ artifacts.X }} substitution --------------------------
|
|
560
584
|
|
|
561
585
|
const ENV_RE = /\{\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
|
586
|
+
const ARTIFACT_RE = /\{\{\s*artifacts\.([A-Za-z_][A-Za-z0-9_.-]*)\s*\}\}/g;
|
|
587
|
+
|
|
588
|
+
function readArtifact(name) {
|
|
589
|
+
const dir = (process.env.WB_ARTIFACTS_DIR || "").trim();
|
|
590
|
+
if (!dir) {
|
|
591
|
+
log(`[warn] artifacts.${name} referenced but WB_ARTIFACTS_DIR is not set`);
|
|
592
|
+
return "";
|
|
593
|
+
}
|
|
594
|
+
// Per-verb read (no cache) so a bash cell that writes the artifact between
|
|
595
|
+
// slices is always picked up by the next browser verb.
|
|
596
|
+
for (const p of [path.join(dir, `${name}.txt`), path.join(dir, name)]) {
|
|
597
|
+
try {
|
|
598
|
+
return readFileSync(p, "utf8").trimEnd();
|
|
599
|
+
} catch {
|
|
600
|
+
// try next candidate
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
log(`[warn] artifact ${name} not found in ${dir}; leaving placeholder`);
|
|
604
|
+
return "";
|
|
605
|
+
}
|
|
562
606
|
|
|
563
607
|
function expand(value) {
|
|
564
608
|
if (typeof value === "string") {
|
|
565
|
-
return value
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
609
|
+
return value
|
|
610
|
+
.replace(ENV_RE, (_, name) => {
|
|
611
|
+
const v = process.env[name];
|
|
612
|
+
if (v === undefined) {
|
|
613
|
+
// Leave the placeholder visible so failures surface in stderr
|
|
614
|
+
// summaries instead of silently turning into empty strings.
|
|
615
|
+
log(`[warn] env var ${name} is not set; leaving placeholder`);
|
|
616
|
+
return "";
|
|
617
|
+
}
|
|
618
|
+
return v;
|
|
619
|
+
})
|
|
620
|
+
.replace(ARTIFACT_RE, (_, name) => readArtifact(name));
|
|
575
621
|
}
|
|
576
622
|
if (Array.isArray(value)) return value.map(expand);
|
|
577
623
|
if (value && typeof value === "object") {
|
package/package.json
CHANGED