@tangle-network/browser-agent-driver 0.10.0 → 0.12.0
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/dist/brain/index.d.ts +3 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +103 -11
- package/dist/brain/index.js.map +1 -1
- package/dist/browser-launch.d.ts +3 -0
- package/dist/browser-launch.d.ts.map +1 -1
- package/dist/browser-launch.js +12 -2
- package/dist/browser-launch.js.map +1 -1
- package/dist/cli-auth.d.ts +19 -0
- package/dist/cli-auth.d.ts.map +1 -0
- package/dist/cli-auth.js +195 -0
- package/dist/cli-auth.js.map +1 -0
- package/dist/cli-design-audit.d.ts +8 -0
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +858 -31
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-showcase.d.ts +28 -0
- package/dist/cli-showcase.d.ts.map +1 -0
- package/dist/cli-showcase.js +131 -0
- package/dist/cli-showcase.js.map +1 -0
- package/dist/cli-ui.d.ts.map +1 -1
- package/dist/cli-ui.js +41 -2
- package/dist/cli-ui.js.map +1 -1
- package/dist/cli.js +418 -211
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/design/compare.d.ts +9 -0
- package/dist/design/compare.d.ts.map +1 -0
- package/dist/design/compare.js +339 -0
- package/dist/design/compare.js.map +1 -0
- package/dist/design/index.d.ts +6 -0
- package/dist/design/index.d.ts.map +1 -0
- package/dist/design/index.js +5 -0
- package/dist/design/index.js.map +1 -0
- package/dist/design/page-interaction.d.ts +24 -0
- package/dist/design/page-interaction.d.ts.map +1 -0
- package/dist/design/page-interaction.js +244 -0
- package/dist/design/page-interaction.js.map +1 -0
- package/dist/design/rip.d.ts +9 -0
- package/dist/design/rip.d.ts.map +1 -0
- package/dist/design/rip.js +362 -0
- package/dist/design/rip.js.map +1 -0
- package/dist/design/types.d.ts +126 -0
- package/dist/design/types.d.ts.map +1 -0
- package/dist/design/types.js +3 -0
- package/dist/design/types.js.map +1 -0
- package/dist/drivers/playwright.d.ts +1 -0
- package/dist/drivers/playwright.d.ts.map +1 -1
- package/dist/drivers/playwright.js +21 -5
- package/dist/drivers/playwright.js.map +1 -1
- package/dist/drivers/types.d.ts +8 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/effect-verification.d.ts +12 -0
- package/dist/runner/effect-verification.d.ts.map +1 -0
- package/dist/runner/effect-verification.js +169 -0
- package/dist/runner/effect-verification.js.map +1 -0
- package/dist/runner/index.d.ts +1 -0
- package/dist/runner/index.d.ts.map +1 -1
- package/dist/runner/index.js +2 -0
- package/dist/runner/index.js.map +1 -1
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +34 -57
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner.d.ts +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +2 -0
- package/dist/runner.js.map +1 -1
- package/dist/showcase/annotate.d.ts +28 -0
- package/dist/showcase/annotate.d.ts.map +1 -0
- package/dist/showcase/annotate.js +95 -0
- package/dist/showcase/annotate.js.map +1 -0
- package/dist/showcase/assemble.d.ts +31 -0
- package/dist/showcase/assemble.d.ts.map +1 -0
- package/dist/showcase/assemble.js +107 -0
- package/dist/showcase/assemble.js.map +1 -0
- package/dist/showcase/demo-player.d.ts +53 -0
- package/dist/showcase/demo-player.d.ts.map +1 -0
- package/dist/showcase/demo-player.js +325 -0
- package/dist/showcase/demo-player.js.map +1 -0
- package/dist/showcase/index.d.ts +21 -0
- package/dist/showcase/index.d.ts.map +1 -0
- package/dist/showcase/index.js +289 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/types.d.ts +111 -0
- package/dist/showcase/types.d.ts.map +1 -0
- package/dist/showcase/types.js +3 -0
- package/dist/showcase/types.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -1
package/dist/cli.js
CHANGED
|
@@ -101,6 +101,22 @@ async function main() {
|
|
|
101
101
|
'fork-run': { type: 'string' },
|
|
102
102
|
pages: { type: 'string' },
|
|
103
103
|
'extract-tokens': { type: 'boolean' },
|
|
104
|
+
rip: { type: 'boolean' },
|
|
105
|
+
'design-compare': { type: 'boolean' },
|
|
106
|
+
'compare-url': { type: 'string' },
|
|
107
|
+
evolve: { type: 'string' },
|
|
108
|
+
'evolve-rounds': { type: 'string' },
|
|
109
|
+
'project-dir': { type: 'string' },
|
|
110
|
+
reproducibility: { type: 'boolean' },
|
|
111
|
+
// showcase
|
|
112
|
+
script: { type: 'string' },
|
|
113
|
+
capture: { type: 'string' },
|
|
114
|
+
crop: { type: 'string' },
|
|
115
|
+
highlight: { type: 'string' },
|
|
116
|
+
format: { type: 'string' },
|
|
117
|
+
viewport: { type: 'string' },
|
|
118
|
+
scale: { type: 'string' },
|
|
119
|
+
'color-scheme': { type: 'string' },
|
|
104
120
|
'llm-timeout': { type: 'string' },
|
|
105
121
|
retries: { type: 'string' },
|
|
106
122
|
'retry-delay-ms': { type: 'string' },
|
|
@@ -116,6 +132,8 @@ async function main() {
|
|
|
116
132
|
timeout: { type: 'string' },
|
|
117
133
|
extension: { type: 'string', multiple: true },
|
|
118
134
|
'user-data-dir': { type: 'string' },
|
|
135
|
+
'profile-dir': { type: 'string' },
|
|
136
|
+
'cdp-url': { type: 'string' },
|
|
119
137
|
wallet: { type: 'boolean' },
|
|
120
138
|
'wallet-auto-approve': { type: 'boolean' },
|
|
121
139
|
'wallet-password': { type: 'string' },
|
|
@@ -141,6 +159,11 @@ async function main() {
|
|
|
141
159
|
'block-analytics': { type: 'boolean', default: false },
|
|
142
160
|
'block-images': { type: 'boolean', default: false },
|
|
143
161
|
'block-media': { type: 'boolean', default: false },
|
|
162
|
+
// Auth
|
|
163
|
+
fill: { type: 'string', multiple: true },
|
|
164
|
+
cookie: { type: 'string', multiple: true },
|
|
165
|
+
'wait-for': { type: 'string' },
|
|
166
|
+
'wait-timeout': { type: 'string' },
|
|
144
167
|
help: { type: 'boolean', short: 'h', default: false },
|
|
145
168
|
version: { type: 'boolean', short: 'v', default: false },
|
|
146
169
|
},
|
|
@@ -160,6 +183,32 @@ async function main() {
|
|
|
160
183
|
cliError('--url is required for design-audit.');
|
|
161
184
|
process.exit(1);
|
|
162
185
|
}
|
|
186
|
+
// --design-compare mode
|
|
187
|
+
if (values['design-compare']) {
|
|
188
|
+
if (!values['compare-url']) {
|
|
189
|
+
cliError('--compare-url is required with --design-compare.');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const { runDesignCompare } = await import('./design/compare.js');
|
|
193
|
+
await runDesignCompare({
|
|
194
|
+
urlA: values.url,
|
|
195
|
+
urlB: values['compare-url'],
|
|
196
|
+
headless: values.headless,
|
|
197
|
+
outputDir: values.sink,
|
|
198
|
+
});
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
// --rip mode
|
|
202
|
+
if (values.rip) {
|
|
203
|
+
const { ripSite } = await import('./design/rip.js');
|
|
204
|
+
await ripSite({
|
|
205
|
+
url: values.url,
|
|
206
|
+
pages: values.pages ? parseInt(values.pages) : undefined,
|
|
207
|
+
headless: values.headless,
|
|
208
|
+
outputDir: values.sink,
|
|
209
|
+
});
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
163
212
|
const { runDesignAudit } = await import('./cli-design-audit.js');
|
|
164
213
|
await runDesignAudit({
|
|
165
214
|
url: values.url,
|
|
@@ -173,6 +222,10 @@ async function main() {
|
|
|
173
222
|
headless: values.headless,
|
|
174
223
|
debug: values.debug,
|
|
175
224
|
extractTokens: values['extract-tokens'],
|
|
225
|
+
evolve: values.evolve,
|
|
226
|
+
evolveRounds: values['evolve-rounds'] ? parseInt(values['evolve-rounds']) : undefined,
|
|
227
|
+
projectDir: values['project-dir'],
|
|
228
|
+
reproducibility: values.reproducibility,
|
|
176
229
|
});
|
|
177
230
|
process.exit(0);
|
|
178
231
|
}
|
|
@@ -208,8 +261,61 @@ async function main() {
|
|
|
208
261
|
}
|
|
209
262
|
process.exit(0);
|
|
210
263
|
}
|
|
264
|
+
if (command === 'showcase') {
|
|
265
|
+
const { handleShowcase } = await import('./cli-showcase.js');
|
|
266
|
+
await handleShowcase({
|
|
267
|
+
url: values.url,
|
|
268
|
+
script: values.script,
|
|
269
|
+
capture: values.capture,
|
|
270
|
+
crop: values.crop,
|
|
271
|
+
highlight: values.highlight,
|
|
272
|
+
format: values.format,
|
|
273
|
+
viewport: values.viewport,
|
|
274
|
+
output: values.sink,
|
|
275
|
+
headless: values.headless ?? true,
|
|
276
|
+
colorScheme: values['color-scheme'],
|
|
277
|
+
scale: values.scale ? parseFloat(values.scale) : undefined,
|
|
278
|
+
storageState: values['storage-state'],
|
|
279
|
+
quality: values['quality-threshold'] ? parseInt(values['quality-threshold']) : undefined,
|
|
280
|
+
});
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
283
|
+
if (command === 'auth') {
|
|
284
|
+
const sub = positionals[1];
|
|
285
|
+
if (sub === 'save') {
|
|
286
|
+
const { handleAuthSave } = await import('./cli-auth.js');
|
|
287
|
+
await handleAuthSave({
|
|
288
|
+
url: values.url || positionals[2],
|
|
289
|
+
output: values['storage-state'] || positionals[3],
|
|
290
|
+
});
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
293
|
+
if (sub === 'login') {
|
|
294
|
+
const { handleAuthLogin } = await import('./cli-auth.js');
|
|
295
|
+
await handleAuthLogin({
|
|
296
|
+
url: values.url || positionals[2],
|
|
297
|
+
output: values['storage-state'],
|
|
298
|
+
fill: values.fill,
|
|
299
|
+
cookie: values.cookie,
|
|
300
|
+
waitFor: values['wait-for'],
|
|
301
|
+
waitTimeout: values['wait-timeout'] ? parseInt(values['wait-timeout'], 10) : undefined,
|
|
302
|
+
headless: values.headless,
|
|
303
|
+
});
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
if (sub === 'check') {
|
|
307
|
+
const { handleAuthCheck } = await import('./cli-auth.js');
|
|
308
|
+
await handleAuthCheck({
|
|
309
|
+
path: values['storage-state'] || positionals[2],
|
|
310
|
+
origin: positionals[3],
|
|
311
|
+
});
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
cliError(`Unknown auth subcommand: ${sub || '(none)'}. Use "auth save", "auth login", or "auth check".`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
211
317
|
if (command !== 'run') {
|
|
212
|
-
cliError(`Unknown command: ${command}. Use "run", "runs",
|
|
318
|
+
cliError(`Unknown command: ${command}. Use "run", "runs", "design-audit", "showcase", or "auth".`);
|
|
213
319
|
process.exit(1);
|
|
214
320
|
}
|
|
215
321
|
// Validate inputs
|
|
@@ -378,6 +484,10 @@ async function main() {
|
|
|
378
484
|
}
|
|
379
485
|
}
|
|
380
486
|
}
|
|
487
|
+
if (values['profile-dir'])
|
|
488
|
+
cliOverrides.profileDir = values['profile-dir'];
|
|
489
|
+
if (values['cdp-url'])
|
|
490
|
+
cliOverrides.cdpUrl = values['cdp-url'];
|
|
381
491
|
if (values.memory !== undefined || values['memory-dir']) {
|
|
382
492
|
cliOverrides.memory = {
|
|
383
493
|
...(cliOverrides.memory ?? {}),
|
|
@@ -438,12 +548,8 @@ async function main() {
|
|
|
438
548
|
cliOverrides.resourceBlocking = {
|
|
439
549
|
...(cliOverrides.resourceBlocking ?? {}),
|
|
440
550
|
blockAnalytics: true,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
blockImages: true,
|
|
444
|
-
blockMedia: true,
|
|
445
|
-
}
|
|
446
|
-
: {}),
|
|
551
|
+
blockImages: true,
|
|
552
|
+
blockMedia: true,
|
|
447
553
|
};
|
|
448
554
|
}
|
|
449
555
|
cliOverrides.microPlan = {
|
|
@@ -677,9 +783,9 @@ async function main() {
|
|
|
677
783
|
cliError(`storage state file not found: ${storageStatePath}`);
|
|
678
784
|
process.exit(1);
|
|
679
785
|
}
|
|
680
|
-
if (launchPlan.
|
|
681
|
-
|
|
682
|
-
|
|
786
|
+
if (launchPlan.persistentContext && !launchPlan.cdpUrl && browserName !== 'chromium') {
|
|
787
|
+
const feature = launchPlan.walletMode ? 'Wallet mode' : '--profile-dir';
|
|
788
|
+
throw new Error(`${feature} requires Chromium. Set --browser chromium.`);
|
|
683
789
|
}
|
|
684
790
|
// Ensure clean exit on interrupt
|
|
685
791
|
process.on('SIGINT', () => { renderer?.destroy(); process.exit(130); });
|
|
@@ -690,145 +796,179 @@ async function main() {
|
|
|
690
796
|
let persistentContext;
|
|
691
797
|
let stopWalletAutoApprover;
|
|
692
798
|
const launchDiagnostics = {};
|
|
693
|
-
|
|
799
|
+
// CDP connection — attach to an existing browser (Atlas, Chrome, Brave, etc.)
|
|
800
|
+
let cdpUrl = launchPlan.cdpUrl || process.env.BROWSER_ENDPOINT;
|
|
801
|
+
let cdpConnected = false;
|
|
802
|
+
if (cdpUrl) {
|
|
803
|
+
// Auto-discover WebSocket URL from HTTP endpoint
|
|
804
|
+
if (cdpUrl.startsWith('http://') || cdpUrl.startsWith('https://')) {
|
|
805
|
+
try {
|
|
806
|
+
const versionUrl = cdpUrl.replace(/\/$/, '') + '/json/version';
|
|
807
|
+
const res = await fetch(versionUrl);
|
|
808
|
+
const info = await res.json();
|
|
809
|
+
if (info.webSocketDebuggerUrl) {
|
|
810
|
+
if (info.Browser && !quiet)
|
|
811
|
+
cliLog('cdp', `connected to ${info.Browser}`);
|
|
812
|
+
cdpUrl = info.webSocketDebuggerUrl;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
// Fall through — try the URL as-is
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const cdpStartedAt = Date.now();
|
|
820
|
+
if (cdpUrl.includes('/devtools/') || browserName === 'chromium') {
|
|
821
|
+
browser = await chromium.connectOverCDP(cdpUrl);
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
const browserType = browserName === 'firefox' ? firefox : browserName === 'webkit' ? webkit : chromium;
|
|
825
|
+
browser = await browserType.connect(cdpUrl);
|
|
826
|
+
}
|
|
827
|
+
launchDiagnostics.browserLaunchMs = Date.now() - cdpStartedAt;
|
|
828
|
+
launchDiagnostics.cdpUrl = cdpUrl;
|
|
829
|
+
cdpConnected = true;
|
|
830
|
+
}
|
|
831
|
+
else if (launchPlan.persistentContext) {
|
|
694
832
|
for (const extensionPath of launchPlan.extensionPaths) {
|
|
695
833
|
if (!fs.existsSync(extensionPath)) {
|
|
696
834
|
throw new Error(`Wallet extension path does not exist: ${extensionPath}`);
|
|
697
835
|
}
|
|
698
836
|
}
|
|
699
|
-
const userDataDir = launchPlan.userDataDir ?? path.resolve('.agent-wallet-profile');
|
|
837
|
+
const userDataDir = launchPlan.userDataDir ?? path.resolve(launchPlan.walletMode ? '.agent-wallet-profile' : '.agent-profile');
|
|
700
838
|
fs.mkdirSync(userDataDir, { recursive: true });
|
|
701
839
|
const persistentLaunchStartedAt = Date.now();
|
|
702
840
|
persistentContext = await chromium.launchPersistentContext(userDataDir, {
|
|
703
841
|
channel: isStealthProfile ? 'chrome' : 'chromium',
|
|
704
|
-
headless:
|
|
842
|
+
headless: launchPlan.headless,
|
|
705
843
|
args: launchPlan.browserArgs,
|
|
706
844
|
viewport,
|
|
707
845
|
recordVideo: { dir: videoDir, size: viewport },
|
|
708
846
|
});
|
|
709
847
|
launchDiagnostics.browserLaunchMs = Date.now() - persistentLaunchStartedAt;
|
|
710
848
|
await applyStorageStateToPersistentContext(persistentContext, storageStatePath);
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
849
|
+
if (launchPlan.walletMode) {
|
|
850
|
+
const walletConfig = driverConfig.wallet ?? {};
|
|
851
|
+
// Intercept page-level JSON-RPC so dApps see wallet balances from the
|
|
852
|
+
// local Anvil fork. Only forward user-specific calls (eth_getBalance
|
|
853
|
+
// for the wallet, eth_call with wallet address in calldata). Pool data
|
|
854
|
+
// and protocol calls go to real endpoints for reliability.
|
|
855
|
+
const walletRpcUrl = walletConfig.preflight?.chain?.rpcUrl;
|
|
856
|
+
if (walletRpcUrl) {
|
|
857
|
+
// Default to Anvil's first derived address if no wallet address configured
|
|
858
|
+
const walletAddrFull = (walletConfig.address ?? '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266').toLowerCase();
|
|
859
|
+
const walletAddrHex = walletAddrFull.replace('0x', '');
|
|
860
|
+
await persistentContext.route('**/*', async (route) => {
|
|
861
|
+
try {
|
|
862
|
+
const frame = route.request().frame();
|
|
863
|
+
if (frame && frame.url().startsWith('chrome-extension://')) {
|
|
864
|
+
await route.continue();
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
725
869
|
await route.continue();
|
|
726
870
|
return;
|
|
727
871
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const items = Array.isArray(body) ? body : [body];
|
|
750
|
-
// Check if any item involves the wallet (balance, contract call, simulation)
|
|
751
|
-
const isUserQuery = items.some((item) => {
|
|
752
|
-
const method = item.method;
|
|
753
|
-
if (!method)
|
|
754
|
-
return false;
|
|
755
|
-
if (method === 'eth_getBalance') {
|
|
756
|
-
const params = item.params;
|
|
757
|
-
return params?.[0]?.toLowerCase() === walletAddrFull;
|
|
758
|
-
}
|
|
759
|
-
if (method === 'eth_call' || method === 'eth_estimateGas') {
|
|
760
|
-
const params = item.params;
|
|
761
|
-
const txObj = params?.[0];
|
|
762
|
-
if (!txObj)
|
|
872
|
+
if (route.request().method() !== 'POST') {
|
|
873
|
+
await route.continue();
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const ct = route.request().headers()['content-type'] ?? '';
|
|
877
|
+
if (!ct.includes('json')) {
|
|
878
|
+
await route.continue();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const postData = route.request().postData();
|
|
882
|
+
if (!postData) {
|
|
883
|
+
await route.continue();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
const body = JSON.parse(postData);
|
|
888
|
+
const items = Array.isArray(body) ? body : [body];
|
|
889
|
+
// Check if any item involves the wallet (balance, contract call, simulation)
|
|
890
|
+
const isUserQuery = items.some((item) => {
|
|
891
|
+
const method = item.method;
|
|
892
|
+
if (!method)
|
|
763
893
|
return false;
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
894
|
+
if (method === 'eth_getBalance') {
|
|
895
|
+
const params = item.params;
|
|
896
|
+
return params?.[0]?.toLowerCase() === walletAddrFull;
|
|
897
|
+
}
|
|
898
|
+
if (method === 'eth_call' || method === 'eth_estimateGas') {
|
|
899
|
+
const params = item.params;
|
|
900
|
+
const txObj = params?.[0];
|
|
901
|
+
if (!txObj)
|
|
902
|
+
return false;
|
|
903
|
+
const from = txObj.from?.toLowerCase() ?? '';
|
|
904
|
+
const data = txObj.data?.toLowerCase() ?? '';
|
|
905
|
+
return from === walletAddrFull || data.includes(walletAddrHex);
|
|
906
|
+
}
|
|
907
|
+
if (method === 'eth_getTransactionCount') {
|
|
908
|
+
const params = item.params;
|
|
909
|
+
return params?.[0]?.toLowerCase() === walletAddrFull;
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
});
|
|
913
|
+
if (!isUserQuery) {
|
|
914
|
+
await route.continue();
|
|
915
|
+
return;
|
|
771
916
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
917
|
+
// Normalize: some dApps (Aave) omit jsonrpc/id — Anvil requires them
|
|
918
|
+
let nextId = 1;
|
|
919
|
+
const normalized = items.map((item) => {
|
|
920
|
+
const out = { ...item, jsonrpc: '2.0', id: item.id ?? nextId++ };
|
|
921
|
+
delete out.chainId;
|
|
922
|
+
return out;
|
|
923
|
+
});
|
|
924
|
+
const payload = Array.isArray(body) ? normalized : normalized[0];
|
|
925
|
+
const res = await fetch(walletRpcUrl, {
|
|
926
|
+
method: 'POST',
|
|
927
|
+
headers: { 'Content-Type': 'application/json' },
|
|
928
|
+
body: JSON.stringify(payload),
|
|
929
|
+
});
|
|
930
|
+
await route.fulfill({
|
|
931
|
+
status: res.status,
|
|
932
|
+
contentType: 'application/json',
|
|
933
|
+
body: await res.text(),
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
775
937
|
await route.continue();
|
|
776
|
-
return;
|
|
777
938
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
const shouldAutoApprove = walletConfig.autoApprove ?? true;
|
|
942
|
+
if (shouldAutoApprove) {
|
|
943
|
+
stopWalletAutoApprover = await startWalletAutoApprover(persistentContext, {
|
|
944
|
+
password: walletConfig.password,
|
|
945
|
+
tickMs: walletConfig.tickMs,
|
|
946
|
+
actionSelectors: walletConfig.actionSelectors,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
const preflightEnabled = walletConfig.preflight?.enabled ?? true;
|
|
950
|
+
const preflightSeedUrls = walletConfig.preflight?.seedUrls && walletConfig.preflight.seedUrls.length > 0
|
|
951
|
+
? walletConfig.preflight.seedUrls
|
|
952
|
+
: [...new Set(cases.map((testCase) => testCase.startUrl).filter(Boolean))];
|
|
953
|
+
if (preflightEnabled && preflightSeedUrls.length > 0) {
|
|
954
|
+
const preflight = await runWalletPreflight(persistentContext, {
|
|
955
|
+
seedUrls: preflightSeedUrls,
|
|
956
|
+
password: walletConfig.password,
|
|
957
|
+
actionSelectors: walletConfig.actionSelectors,
|
|
958
|
+
promptPaths: walletConfig.promptPaths,
|
|
959
|
+
connectSelectors: walletConfig.connectSelectors,
|
|
960
|
+
connectorSelectors: walletConfig.connectorSelectors,
|
|
961
|
+
requestAccounts: walletConfig.preflight?.requestAccounts,
|
|
962
|
+
accountsTimeoutMs: walletConfig.preflight?.accountsTimeoutMs,
|
|
963
|
+
maxChainSwitchAttempts: walletConfig.preflight?.maxChainSwitchAttempts,
|
|
964
|
+
chain: walletConfig.preflight?.chain,
|
|
965
|
+
log: quiet ? undefined : (message) => cliLog('wallet', message),
|
|
966
|
+
});
|
|
967
|
+
if (!preflight.ok) {
|
|
968
|
+
const failed = preflight.results.find((resultEntry) => !resultEntry.ready);
|
|
969
|
+
const details = failed?.details ?? 'unknown reason';
|
|
970
|
+
throw new Error(`Wallet preflight failed for ${preflight.failedUrl ?? 'unknown origin'} (${details})`);
|
|
799
971
|
}
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
const shouldAutoApprove = walletConfig.autoApprove ?? true;
|
|
803
|
-
if (shouldAutoApprove) {
|
|
804
|
-
stopWalletAutoApprover = await startWalletAutoApprover(persistentContext, {
|
|
805
|
-
password: walletConfig.password,
|
|
806
|
-
tickMs: walletConfig.tickMs,
|
|
807
|
-
actionSelectors: walletConfig.actionSelectors,
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
const preflightEnabled = walletConfig.preflight?.enabled ?? true;
|
|
811
|
-
const preflightSeedUrls = walletConfig.preflight?.seedUrls && walletConfig.preflight.seedUrls.length > 0
|
|
812
|
-
? walletConfig.preflight.seedUrls
|
|
813
|
-
: [...new Set(cases.map((testCase) => testCase.startUrl).filter(Boolean))];
|
|
814
|
-
if (preflightEnabled && preflightSeedUrls.length > 0) {
|
|
815
|
-
const preflight = await runWalletPreflight(persistentContext, {
|
|
816
|
-
seedUrls: preflightSeedUrls,
|
|
817
|
-
password: walletConfig.password,
|
|
818
|
-
actionSelectors: walletConfig.actionSelectors,
|
|
819
|
-
promptPaths: walletConfig.promptPaths,
|
|
820
|
-
connectSelectors: walletConfig.connectSelectors,
|
|
821
|
-
connectorSelectors: walletConfig.connectorSelectors,
|
|
822
|
-
requestAccounts: walletConfig.preflight?.requestAccounts,
|
|
823
|
-
accountsTimeoutMs: walletConfig.preflight?.accountsTimeoutMs,
|
|
824
|
-
maxChainSwitchAttempts: walletConfig.preflight?.maxChainSwitchAttempts,
|
|
825
|
-
chain: walletConfig.preflight?.chain,
|
|
826
|
-
log: quiet ? undefined : (message) => cliLog('wallet', message),
|
|
827
|
-
});
|
|
828
|
-
if (!preflight.ok) {
|
|
829
|
-
const failed = preflight.results.find((resultEntry) => !resultEntry.ready);
|
|
830
|
-
const details = failed?.details ?? 'unknown reason';
|
|
831
|
-
throw new Error(`Wallet preflight failed for ${preflight.failedUrl ?? 'unknown origin'} (${details})`);
|
|
832
972
|
}
|
|
833
973
|
}
|
|
834
974
|
}
|
|
@@ -838,94 +978,160 @@ async function main() {
|
|
|
838
978
|
: browserName === 'webkit'
|
|
839
979
|
? webkit
|
|
840
980
|
: chromium;
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
981
|
+
const browserLaunchStartedAt = Date.now();
|
|
982
|
+
browser = await browserType.launch({
|
|
983
|
+
headless: launchPlan.headless,
|
|
984
|
+
...(browserName === 'chromium' ? { args: launchPlan.browserArgs } : {}),
|
|
985
|
+
// Use system Chrome for stealth profiles — real TLS/JA3 fingerprint vs bundled Chromium
|
|
986
|
+
...(isStealthProfile && browserName === 'chromium' ? { channel: 'chrome' } : {}),
|
|
987
|
+
});
|
|
988
|
+
launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
|
|
989
|
+
}
|
|
990
|
+
// Headless Chromium sends "HeadlessChrome/..." in the default User-Agent.
|
|
991
|
+
// CDNs like Akamai reject this with ERR_HTTP2_PROTOCOL_ERROR before any JS
|
|
992
|
+
// stealth patches can run. Build a clean UA from the browser version.
|
|
993
|
+
const headlessUserAgent = launchPlan.headless && browser
|
|
994
|
+
? (() => {
|
|
995
|
+
const ver = browser.version();
|
|
996
|
+
const plat = process.platform;
|
|
997
|
+
const platformToken = plat === 'win32'
|
|
998
|
+
? 'Windows NT 10.0; Win64; x64'
|
|
999
|
+
: plat === 'linux'
|
|
1000
|
+
? 'X11; Linux x86_64'
|
|
1001
|
+
: 'Macintosh; Intel Mac OS X 10_15_7';
|
|
1002
|
+
return `Mozilla/5.0 (${platformToken}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver} Safari/537.36`;
|
|
1003
|
+
})()
|
|
1004
|
+
: undefined;
|
|
1005
|
+
const driverFactory = async () => {
|
|
1006
|
+
const contextStartedAt = Date.now();
|
|
1007
|
+
// CDP: reuse the browser's default context (preserves user cookies/sessions).
|
|
1008
|
+
// Persistent context: use the already-opened persistent context.
|
|
1009
|
+
// Default: create a fresh isolated context.
|
|
1010
|
+
let context;
|
|
1011
|
+
if (cdpConnected) {
|
|
1012
|
+
// Reuse the user's existing browser context — cookies, localStorage, extensions intact
|
|
1013
|
+
const contexts = browser.contexts();
|
|
1014
|
+
context = contexts[0] ?? await browser.newContext({ viewport });
|
|
1015
|
+
}
|
|
1016
|
+
else if (persistentContext) {
|
|
1017
|
+
context = persistentContext;
|
|
852
1018
|
}
|
|
853
1019
|
else {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1020
|
+
context = await browser.newContext({
|
|
1021
|
+
viewport,
|
|
1022
|
+
recordVideo: { dir: videoDir, size: viewport },
|
|
1023
|
+
storageState: storageStatePath,
|
|
1024
|
+
locale: 'en-US',
|
|
1025
|
+
timezoneId: 'America/New_York',
|
|
1026
|
+
...(headlessUserAgent ? { userAgent: headlessUserAgent } : {}),
|
|
1027
|
+
extraHTTPHeaders: {
|
|
1028
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
1029
|
+
},
|
|
860
1030
|
});
|
|
861
|
-
launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
|
|
862
1031
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1032
|
+
// Stealth patches: only for Playwright-controlled contexts (not real user browsers)
|
|
1033
|
+
if (!cdpConnected) {
|
|
1034
|
+
await context.addInitScript(`
|
|
1035
|
+
// navigator.webdriver — explicit override (backup for --disable-blink-features)
|
|
1036
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
1037
|
+
// navigator.plugins — empty in headless, non-empty in real browsers
|
|
1038
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
1039
|
+
get: () => [
|
|
1040
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
1041
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
1042
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
1043
|
+
],
|
|
875
1044
|
});
|
|
876
|
-
//
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1045
|
+
// navigator.languages — must match Accept-Language header
|
|
1046
|
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
1047
|
+
// hardware signals — realistic desktop values
|
|
1048
|
+
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
|
|
1049
|
+
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
1050
|
+
// window.chrome — full stub matching real Chrome
|
|
1051
|
+
if (!window.chrome) window.chrome = {};
|
|
1052
|
+
if (!window.chrome.runtime) window.chrome.runtime = { id: undefined };
|
|
1053
|
+
if (!window.chrome.app) window.chrome.app = { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } };
|
|
1054
|
+
if (!window.chrome.csi) window.chrome.csi = function() { return { onloadT: Date.now(), startE: Date.now(), pageT: Date.now() - performance.timing.navigationStart }; };
|
|
1055
|
+
if (!window.chrome.loadTimes) window.chrome.loadTimes = function() { return { commitLoadTime: Date.now() / 1000, connectionInfo: 'h2', finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: Date.now() / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: Date.now() / 1000 - 0.16, startLoadTime: Date.now() / 1000 - 0.16, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true }; };
|
|
1056
|
+
// WebGL vendor/renderer — match real GPU values
|
|
1057
|
+
try {
|
|
1058
|
+
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
1059
|
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
1060
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
1061
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1062
|
+
return getParameter.call(this, parameter);
|
|
1063
|
+
};
|
|
1064
|
+
} catch (_) {}
|
|
1065
|
+
try {
|
|
1066
|
+
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
|
|
1067
|
+
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
|
|
1068
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
1069
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1070
|
+
return getParameter2.call(this, parameter);
|
|
1071
|
+
};
|
|
1072
|
+
} catch (_) {}
|
|
1073
|
+
// window.outerWidth/outerHeight — 0 in headless, match viewport in real browsers
|
|
1074
|
+
if (window.outerWidth === 0) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth });
|
|
1075
|
+
if (window.outerHeight === 0) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85 });
|
|
1076
|
+
// Patch permissions API — cover all permission types bots commonly mis-handle
|
|
1077
|
+
try {
|
|
1078
|
+
const origQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
1079
|
+
navigator.permissions.query = (params) => {
|
|
1080
|
+
const deny = ['notifications', 'geolocation', 'camera', 'microphone', 'payment-handler'];
|
|
1081
|
+
if (deny.includes(params.name))
|
|
1082
|
+
return Promise.resolve({ state: 'denied', onchange: null });
|
|
1083
|
+
return origQuery(params);
|
|
1084
|
+
};
|
|
1085
|
+
} catch (_) {}
|
|
1086
|
+
// Canvas fingerprint noise — add imperceptible per-session noise to canvas readback
|
|
1087
|
+
// so each session produces a unique fingerprint (defeats static fingerprint matching)
|
|
1088
|
+
try {
|
|
1089
|
+
const seed = Math.random() * 0xffff | 0;
|
|
1090
|
+
const noisify = (canvas) => {
|
|
1091
|
+
try {
|
|
1092
|
+
const ctx = canvas.getContext('2d');
|
|
1093
|
+
if (!ctx) return;
|
|
1094
|
+
const { width: w, height: h } = canvas;
|
|
1095
|
+
if (w === 0 || h === 0) return;
|
|
1096
|
+
const img = ctx.getImageData(0, 0, w, h);
|
|
1097
|
+
const d = img.data;
|
|
1098
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
1099
|
+
// deterministic per-pixel noise from seed + position
|
|
1100
|
+
d[i] = d[i] ^ ((seed + i) & 1);
|
|
1101
|
+
}
|
|
1102
|
+
ctx.putImageData(img, 0, 0);
|
|
1103
|
+
} catch (_) {}
|
|
1104
|
+
};
|
|
1105
|
+
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1106
|
+
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
1107
|
+
noisify(this);
|
|
1108
|
+
return origToDataURL.apply(this, args);
|
|
1109
|
+
};
|
|
1110
|
+
const origToBlob = HTMLCanvasElement.prototype.toBlob;
|
|
1111
|
+
HTMLCanvasElement.prototype.toBlob = function(...args) {
|
|
1112
|
+
noisify(this);
|
|
1113
|
+
return origToBlob.apply(this, args);
|
|
1114
|
+
};
|
|
1115
|
+
} catch (_) {}
|
|
1116
|
+
// Fix CDP screenX/screenY bug — CDP Input.dispatchMouseEvent sets
|
|
1117
|
+
// screenX=clientX, screenY=clientY which never happens in real browsers.
|
|
1118
|
+
// Cloudflare Turnstile actively checks this. Add a per-session window
|
|
1119
|
+
// offset so screenX/screenY are realistic and internally consistent.
|
|
1120
|
+
try {
|
|
1121
|
+
const winX = Math.floor(Math.random() * 200) + 50;
|
|
1122
|
+
const winY = Math.floor(Math.random() * 100) + 50;
|
|
1123
|
+
const chrome = 85;
|
|
1124
|
+
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
1125
|
+
get() { return this.clientX + winX; },
|
|
1126
|
+
configurable: true,
|
|
1127
|
+
});
|
|
1128
|
+
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
1129
|
+
get() { return this.clientY + winY + chrome; },
|
|
1130
|
+
configurable: true,
|
|
1131
|
+
});
|
|
1132
|
+
} catch (_) {}
|
|
1133
|
+
`);
|
|
1134
|
+
}
|
|
929
1135
|
const contextCreateMs = Date.now() - contextStartedAt;
|
|
930
1136
|
const pageStartedAt = Date.now();
|
|
931
1137
|
const page = await context.newPage();
|
|
@@ -957,8 +1163,9 @@ async function main() {
|
|
|
957
1163
|
contextCreateMs,
|
|
958
1164
|
pageCreateMs,
|
|
959
1165
|
resourceBlockingSetupMs,
|
|
960
|
-
storageStateApplied: Boolean(storageStatePath),
|
|
1166
|
+
storageStateApplied: Boolean(storageStatePath) && !cdpConnected,
|
|
961
1167
|
persistentContext: Boolean(persistentContext),
|
|
1168
|
+
cdpConnected,
|
|
962
1169
|
};
|
|
963
1170
|
// Wrap in a Driver that properly tears down context on close
|
|
964
1171
|
const wrappedDriver = {
|
|
@@ -970,7 +1177,7 @@ async function main() {
|
|
|
970
1177
|
async close() {
|
|
971
1178
|
await driver.close().catch(() => { });
|
|
972
1179
|
await page.close().catch(() => { });
|
|
973
|
-
if (!persistentContext) {
|
|
1180
|
+
if (!persistentContext && !cdpConnected) {
|
|
974
1181
|
await context.close().catch(() => { });
|
|
975
1182
|
}
|
|
976
1183
|
},
|