@taurgis/bonsai 3.0.1 → 3.0.3
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/commands/fetch.js +7 -1
- package/dist/lib/research/browser.d.ts +13 -0
- package/dist/lib/research/browser.js +60 -2
- package/dist/lib/research/proxy.d.ts +36 -0
- package/dist/lib/research/proxy.js +81 -0
- package/dist/sites/salesforce-doc-fetch.js +14 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/fetch.js
CHANGED
|
@@ -10,7 +10,7 @@ import { evaluateFreshness, checkMaxAgeExpired, durationFlagError, } from '../li
|
|
|
10
10
|
import { revalidateCache, createArtifactFromFetch } from '../lib/research/revalidate.js';
|
|
11
11
|
import { fetchStaticHtml, fetchText } from '../lib/research/fetcher.js';
|
|
12
12
|
import { PROXY_TUNNEL_REJECTION_PATTERN } from '../lib/research/proxy.js';
|
|
13
|
-
import { fetchRenderedHtml } from '../lib/research/browser.js';
|
|
13
|
+
import { fetchRenderedHtml, SANDBOX_EGRESS_ERROR_MARKER } from '../lib/research/browser.js';
|
|
14
14
|
import { capturePage } from '../lib/research/capture.js';
|
|
15
15
|
import { persistSectionArtifacts } from '../lib/research/docs/section-artifacts.js';
|
|
16
16
|
import { applyAutoTags } from '../lib/research/keywords.js';
|
|
@@ -387,6 +387,12 @@ export function fetchFailureGuidance(message, url) {
|
|
|
387
387
|
suggestions: ['Check the hostname is spelled correctly and resolves on a public network.'],
|
|
388
388
|
};
|
|
389
389
|
}
|
|
390
|
+
// Chrome's own network stack hit a proxy-enforced sandbox egress block (browser.ts's
|
|
391
|
+
// describeNavigationFailure already explains the cause and the fix); only a workaround and the
|
|
392
|
+
// docs ref are added here.
|
|
393
|
+
if (message.includes(SANDBOX_EGRESS_ERROR_MARKER)) {
|
|
394
|
+
return { suggestions: [importHint], ref };
|
|
395
|
+
}
|
|
390
396
|
// A configured proxy (HTTPS_PROXY/HTTP_PROXY) refused to tunnel to this host — distinct from
|
|
391
397
|
// the destination's own 401/403 above, which is the site itself rejecting the request. Shared
|
|
392
398
|
// with fetcher.ts's retry-fallback check so the two stay in sync by construction.
|
|
@@ -76,4 +76,17 @@ export declare class ResponseCapture {
|
|
|
76
76
|
/** Resolves with the response body text once a matching response finishes, or null on timeout. */
|
|
77
77
|
waitFor(key: string, timeoutMs: number): Promise<string | null>;
|
|
78
78
|
}
|
|
79
|
+
export declare const SANDBOX_EGRESS_ERROR_MARKER = "sandboxed execution environment";
|
|
80
|
+
/**
|
|
81
|
+
* Builds the error thrown for a failed Page.navigate. When a proxy is configured (proxy.ts's
|
|
82
|
+
* signal for "this is a sandboxed execution environment") and Chrome's failure code indicates the
|
|
83
|
+
* proxy itself declined the tunnel, the destination isn't reachable because of the sandbox's
|
|
84
|
+
* network egress gateway rejecting it, not a real navigation problem — so the message says that
|
|
85
|
+
* explicitly, rather than surfacing a bare Chrome net-error code that looks like a generic
|
|
86
|
+
* connectivity failure. It deliberately doesn't claim the fix is "ask an admin to allowlist this
|
|
87
|
+
* host": the gateway that rejects the CONNECT tunnel can disagree with an org's configured domain
|
|
88
|
+
* policy (a different layer may not reach it), so the safer, always-correct advice is to verify
|
|
89
|
+
* against the environment directly and fall back to a manual fetch if it's still unreachable.
|
|
90
|
+
*/
|
|
91
|
+
export declare function describeNavigationFailure(errorText: string, url: string): Error;
|
|
79
92
|
export declare function fetchRenderedHtml(url: string, options?: BrowserFetchOptions): Promise<BrowserFetchResult>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
|
+
import { join } from 'node:path';
|
|
4
5
|
import { checkDnsSafety } from './fetcher.js';
|
|
5
|
-
import { getChromeProxyArgs } from './proxy.js';
|
|
6
|
+
import { getChromeProxyArgs, getChromeSpkiArgs, getChromeTlsCompatibilityArgs, isProxyConfigured, } from './proxy.js';
|
|
6
7
|
import { normalizeUrl } from './url.js';
|
|
7
8
|
export class CdpClient {
|
|
8
9
|
ws;
|
|
@@ -65,6 +66,13 @@ export function findChromePath() {
|
|
|
65
66
|
return process.env.CHROME_PATH;
|
|
66
67
|
}
|
|
67
68
|
const paths = [];
|
|
69
|
+
// Claude Code's remote sandbox (and other Playwright-provisioned environments) pre-installs a
|
|
70
|
+
// Chromium build under PLAYWRIGHT_BROWSERS_PATH rather than a system package manager location,
|
|
71
|
+
// with a `chromium` symlink at its root pointing at the actual browser binary. It's headless-Chrome
|
|
72
|
+
// capable (needed for the CDP `--headless=new` flag this module uses), so it's checked first.
|
|
73
|
+
if (process.env.PLAYWRIGHT_BROWSERS_PATH) {
|
|
74
|
+
paths.push(join(process.env.PLAYWRIGHT_BROWSERS_PATH, 'chromium'));
|
|
75
|
+
}
|
|
68
76
|
if (process.platform === 'darwin') {
|
|
69
77
|
paths.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium');
|
|
70
78
|
}
|
|
@@ -108,6 +116,15 @@ export function buildChromeArgs() {
|
|
|
108
116
|
// CDP navigation uses Chrome's own network stack, not Node's fetch/undici, so a sandbox
|
|
109
117
|
// proxy detected via *_PROXY env vars must be passed to Chrome separately (see proxy.ts).
|
|
110
118
|
...getChromeProxyArgs(),
|
|
119
|
+
// The sandbox proxy above re-terminates TLS with its own CA, which Chrome's Chrome-Root-Store
|
|
120
|
+
// verifier doesn't trust by default (unlike Node/curl/Python, which read NODE_EXTRA_CA_CERTS/
|
|
121
|
+
// SSL_CERT_FILE/CURL_CA_BUNDLE). Pin trust to that specific CA rather than disabling
|
|
122
|
+
// verification wholesale (see proxy.ts's getChromeSpkiArgs).
|
|
123
|
+
...getChromeSpkiArgs(),
|
|
124
|
+
// Separately from cert trust: some sandbox proxies' TLS terminator never responds to Chrome's
|
|
125
|
+
// default TLS 1.3 ClientHello and the connection is eventually reset (see proxy.ts's
|
|
126
|
+
// getChromeTlsCompatibilityArgs for how this was diagnosed).
|
|
127
|
+
...getChromeTlsCompatibilityArgs(),
|
|
111
128
|
];
|
|
112
129
|
}
|
|
113
130
|
async function spawnChrome(chromePath) {
|
|
@@ -303,6 +320,47 @@ export class ResponseCapture {
|
|
|
303
320
|
});
|
|
304
321
|
}
|
|
305
322
|
}
|
|
323
|
+
// Chrome has its own network stack (see getChromeProxyArgs), so when a sandbox's egress-enforcing
|
|
324
|
+
// proxy declines to tunnel to a destination, Chrome surfaces one of these net-error codes instead
|
|
325
|
+
// of the HTTP-level rejection undici sees in fetcher.ts (PROXY_TUNNEL_REJECTION_PATTERN). Matched
|
|
326
|
+
// against Page.navigate's `errorText`, which arrives as e.g. "net::ERR_TUNNEL_CONNECTION_FAILED".
|
|
327
|
+
const PROXY_BLOCKED_CHROME_NET_ERRORS = [
|
|
328
|
+
'ERR_TUNNEL_CONNECTION_FAILED',
|
|
329
|
+
'ERR_PROXY_CONNECTION_FAILED',
|
|
330
|
+
'ERR_SOCKS_CONNECTION_FAILED',
|
|
331
|
+
];
|
|
332
|
+
// Shared substring fetch.ts's fetchFailureGuidance matches on to attach suggestions/ref to this
|
|
333
|
+
// error, without duplicating the message text it needs to match against.
|
|
334
|
+
export const SANDBOX_EGRESS_ERROR_MARKER = 'sandboxed execution environment';
|
|
335
|
+
/**
|
|
336
|
+
* Builds the error thrown for a failed Page.navigate. When a proxy is configured (proxy.ts's
|
|
337
|
+
* signal for "this is a sandboxed execution environment") and Chrome's failure code indicates the
|
|
338
|
+
* proxy itself declined the tunnel, the destination isn't reachable because of the sandbox's
|
|
339
|
+
* network egress gateway rejecting it, not a real navigation problem — so the message says that
|
|
340
|
+
* explicitly, rather than surfacing a bare Chrome net-error code that looks like a generic
|
|
341
|
+
* connectivity failure. It deliberately doesn't claim the fix is "ask an admin to allowlist this
|
|
342
|
+
* host": the gateway that rejects the CONNECT tunnel can disagree with an org's configured domain
|
|
343
|
+
* policy (a different layer may not reach it), so the safer, always-correct advice is to verify
|
|
344
|
+
* against the environment directly and fall back to a manual fetch if it's still unreachable.
|
|
345
|
+
*/
|
|
346
|
+
export function describeNavigationFailure(errorText, url) {
|
|
347
|
+
const isProxyBlock = isProxyConfigured() && PROXY_BLOCKED_CHROME_NET_ERRORS.some((code) => errorText.includes(code));
|
|
348
|
+
if (!isProxyBlock) {
|
|
349
|
+
return new Error(`Navigation failed: ${errorText}`);
|
|
350
|
+
}
|
|
351
|
+
let host = url;
|
|
352
|
+
try {
|
|
353
|
+
host = new URL(url).hostname;
|
|
354
|
+
}
|
|
355
|
+
catch { }
|
|
356
|
+
return new Error(`Chrome could not reach "${host}" (${errorText}). This looks like a ${SANDBOX_EGRESS_ERROR_MARKER} ` +
|
|
357
|
+
`(e.g. Claude Code's remote sandbox) whose outbound network gateway rejected the connection to ` +
|
|
358
|
+
`this host, via the configured proxy (HTTPS_PROXY/HTTP_PROXY). This can happen even when the ` +
|
|
359
|
+
`environment's domain policy looks unrestricted, since the gateway enforcing egress can be a ` +
|
|
360
|
+
`separate layer from that policy. It can't be worked around from inside the sandbox by retrying ` +
|
|
361
|
+
`or bypassing the proxy: confirm "${host}" is reachable from this environment (e.g. test the ` +
|
|
362
|
+
`proxy directly), or fetch the page from a network with access and import it instead.`);
|
|
363
|
+
}
|
|
306
364
|
// Chrome can emit these on first navigation in headless CI when the cert verifier reloads mid-run.
|
|
307
365
|
const TRANSIENT_NAV_ERROR_CODES = ['ERR_CERT_VERIFIER_CHANGED'];
|
|
308
366
|
function isTransientBrowserNavigationError(err) {
|
|
@@ -339,7 +397,7 @@ async function fetchRenderedHtmlOnce(url, options = {}) {
|
|
|
339
397
|
// A network-level failure (DNS, refused/closed connection, TLS) never fires a load event, so
|
|
340
398
|
// fail fast here instead of waiting out the full navigation timeout on a dead page.
|
|
341
399
|
if (nav.errorText) {
|
|
342
|
-
throw
|
|
400
|
+
throw describeNavigationFailure(nav.errorText, currentUrl);
|
|
343
401
|
}
|
|
344
402
|
await waitForLoad(page.client, page.sessionId, timeout, options.settleMs ?? 1000);
|
|
345
403
|
const mainDoc = nav.frameId ? documentResponses.get(nav.frameId) : undefined;
|
|
@@ -33,3 +33,39 @@ export declare function getProxyDispatcher(): Dispatcher | undefined;
|
|
|
33
33
|
* be set, silently disagreeing with how fetch() routes the same traffic.
|
|
34
34
|
*/
|
|
35
35
|
export declare function getChromeProxyArgs(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Caps Chrome's TLS version at 1.2 for connections made through a detected sandbox proxy, or []
|
|
38
|
+
* when no proxy is configured. Some sandbox proxies' TLS-terminating layer never responds to
|
|
39
|
+
* Chrome's default TLS 1.3 ClientHello (BoringSSL sends ECH-GREASE by default, which most tools'
|
|
40
|
+
* TLS stacks — curl/OpenSSL's included — don't add): captured via --log-net-log, Chrome sends its
|
|
41
|
+
* ClientHello, the proxy sends nothing back, and ~6s later the socket read fails with
|
|
42
|
+
* net::ERR_CONNECTION_RESET. Capping to TLS 1.2 avoids that ClientHello shape entirely. Scoped to
|
|
43
|
+
* the proxied path only (via isProxyConfigured, the same signal every other sandbox-specific flag
|
|
44
|
+
* in this module uses) so ordinary developer machines get Chrome's normal TLS 1.3 negotiation
|
|
45
|
+
* unchanged; this doesn't affect certificate verification (see getChromeSpkiArgs), only which
|
|
46
|
+
* protocol version Chrome offers.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getChromeTlsCompatibilityArgs(): string[];
|
|
49
|
+
export declare const CA_BUNDLE_ENV_VARS: string[];
|
|
50
|
+
/**
|
|
51
|
+
* Base64-encoded SHA-256 SPKI fingerprints (RFC 7469 §2.4) for every certificate in the sandbox's
|
|
52
|
+
* discoverable CA bundle, in the exact encoding Chromium's --ignore-certificate-errors-spki-list
|
|
53
|
+
* expects — equivalent to `openssl x509 -pubkey -noout | openssl pkey -pubin -outform der |
|
|
54
|
+
* openssl dgst -sha256 -binary | openssl enc -base64` for each cert. Returns [] when no bundle is
|
|
55
|
+
* discoverable or none of its entries parse, so a missing/malformed bundle degrades to Chrome's
|
|
56
|
+
* normal (unpinned) verification rather than breaking the browser launch.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getCaBundleSpkiHashes(): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Chrome CLI flag that trusts exactly the sandbox's injected CA(s), pinned by SPKI hash rather
|
|
61
|
+
* than by disabling verification. Chromium's TLS stack (the Chrome Root Store) never reads
|
|
62
|
+
* NODE_EXTRA_CA_CERTS/SSL_CERT_FILE/CURL_CA_BUNDLE the way Node/curl/Python do, so a sandbox that
|
|
63
|
+
* MITMs HTTPS through its proxy (see isProxyConfigured) makes every other tool trust the proxy's
|
|
64
|
+
* cert while Chrome's handshake with it fails with ERR_CERT_AUTHORITY_INVALID. This is scoped
|
|
65
|
+
* narrowly to the specific CA(s) discovered via the CA bundle env vars — never
|
|
66
|
+
* --ignore-certificate-errors, which would disable verification for every host, including ones
|
|
67
|
+
* that have nothing to do with the sandbox's proxy. A no-op (empty array) when no proxy is
|
|
68
|
+
* configured or no CA bundle is discoverable, so default Chrome trust is untouched on ordinary
|
|
69
|
+
* developer machines.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getChromeSpkiArgs(): string[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash, X509Certificate } from 'node:crypto';
|
|
1
3
|
import { EnvHttpProxyAgent } from 'undici';
|
|
2
4
|
// Order matters: undici's own EnvHttpProxyAgent (github.com/nodejs/undici lib/dispatcher/
|
|
3
5
|
// env-http-proxy-agent.js) prefers the lowercase variant of each var over its uppercase twin
|
|
@@ -105,3 +107,82 @@ export function getChromeProxyArgs() {
|
|
|
105
107
|
args.push(`--proxy-bypass-list=${toChromeBypassList(noProxy)}`);
|
|
106
108
|
return args;
|
|
107
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Caps Chrome's TLS version at 1.2 for connections made through a detected sandbox proxy, or []
|
|
112
|
+
* when no proxy is configured. Some sandbox proxies' TLS-terminating layer never responds to
|
|
113
|
+
* Chrome's default TLS 1.3 ClientHello (BoringSSL sends ECH-GREASE by default, which most tools'
|
|
114
|
+
* TLS stacks — curl/OpenSSL's included — don't add): captured via --log-net-log, Chrome sends its
|
|
115
|
+
* ClientHello, the proxy sends nothing back, and ~6s later the socket read fails with
|
|
116
|
+
* net::ERR_CONNECTION_RESET. Capping to TLS 1.2 avoids that ClientHello shape entirely. Scoped to
|
|
117
|
+
* the proxied path only (via isProxyConfigured, the same signal every other sandbox-specific flag
|
|
118
|
+
* in this module uses) so ordinary developer machines get Chrome's normal TLS 1.3 negotiation
|
|
119
|
+
* unchanged; this doesn't affect certificate verification (see getChromeSpkiArgs), only which
|
|
120
|
+
* protocol version Chrome offers.
|
|
121
|
+
*/
|
|
122
|
+
export function getChromeTlsCompatibilityArgs() {
|
|
123
|
+
return isProxyConfigured() ? ['--ssl-version-max=tls1.2'] : [];
|
|
124
|
+
}
|
|
125
|
+
// Order matters for the same reason as the *_PROXY lists above: read consistently so this module
|
|
126
|
+
// and every caller agree on which file is "the" CA bundle. Node itself only honors
|
|
127
|
+
// NODE_EXTRA_CA_CERTS; SSL_CERT_FILE and CURL_CA_BUNDLE are the conventions OpenSSL-linked tools
|
|
128
|
+
// (curl, Python, etc.) use instead. A sandbox that MITMs TLS to route through its proxy sets all
|
|
129
|
+
// three to the same bundle so every tool picks it up regardless of which convention it follows.
|
|
130
|
+
export const CA_BUNDLE_ENV_VARS = ['NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'CURL_CA_BUNDLE'];
|
|
131
|
+
function findCaBundlePath() {
|
|
132
|
+
const path = firstEnv(CA_BUNDLE_ENV_VARS);
|
|
133
|
+
return path && existsSync(path) ? path : undefined;
|
|
134
|
+
}
|
|
135
|
+
function extractPemCertificates(bundle) {
|
|
136
|
+
return bundle.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? [];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Base64-encoded SHA-256 SPKI fingerprints (RFC 7469 §2.4) for every certificate in the sandbox's
|
|
140
|
+
* discoverable CA bundle, in the exact encoding Chromium's --ignore-certificate-errors-spki-list
|
|
141
|
+
* expects — equivalent to `openssl x509 -pubkey -noout | openssl pkey -pubin -outform der |
|
|
142
|
+
* openssl dgst -sha256 -binary | openssl enc -base64` for each cert. Returns [] when no bundle is
|
|
143
|
+
* discoverable or none of its entries parse, so a missing/malformed bundle degrades to Chrome's
|
|
144
|
+
* normal (unpinned) verification rather than breaking the browser launch.
|
|
145
|
+
*/
|
|
146
|
+
export function getCaBundleSpkiHashes() {
|
|
147
|
+
const bundlePath = findCaBundlePath();
|
|
148
|
+
if (!bundlePath)
|
|
149
|
+
return [];
|
|
150
|
+
let bundle;
|
|
151
|
+
try {
|
|
152
|
+
bundle = readFileSync(bundlePath, 'utf8');
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
const hashes = new Set();
|
|
158
|
+
for (const pem of extractPemCertificates(bundle)) {
|
|
159
|
+
try {
|
|
160
|
+
const spkiDer = new X509Certificate(pem).publicKey.export({ type: 'spki', format: 'der' });
|
|
161
|
+
hashes.add(createHash('sha256').update(spkiDer).digest('base64'));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Skip entries that don't parse as certificates rather than failing the whole bundle.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return Array.from(hashes);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Chrome CLI flag that trusts exactly the sandbox's injected CA(s), pinned by SPKI hash rather
|
|
171
|
+
* than by disabling verification. Chromium's TLS stack (the Chrome Root Store) never reads
|
|
172
|
+
* NODE_EXTRA_CA_CERTS/SSL_CERT_FILE/CURL_CA_BUNDLE the way Node/curl/Python do, so a sandbox that
|
|
173
|
+
* MITMs HTTPS through its proxy (see isProxyConfigured) makes every other tool trust the proxy's
|
|
174
|
+
* cert while Chrome's handshake with it fails with ERR_CERT_AUTHORITY_INVALID. This is scoped
|
|
175
|
+
* narrowly to the specific CA(s) discovered via the CA bundle env vars — never
|
|
176
|
+
* --ignore-certificate-errors, which would disable verification for every host, including ones
|
|
177
|
+
* that have nothing to do with the sandbox's proxy. A no-op (empty array) when no proxy is
|
|
178
|
+
* configured or no CA bundle is discoverable, so default Chrome trust is untouched on ordinary
|
|
179
|
+
* developer machines.
|
|
180
|
+
*/
|
|
181
|
+
export function getChromeSpkiArgs() {
|
|
182
|
+
if (!isProxyConfigured())
|
|
183
|
+
return [];
|
|
184
|
+
const hashes = getCaBundleSpkiHashes();
|
|
185
|
+
if (!hashes.length)
|
|
186
|
+
return [];
|
|
187
|
+
return [`--ignore-certificate-errors-spki-list=${hashes.join(',')}`];
|
|
188
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { openCdpPage, waitForLoad, ResponseCapture, } from '../lib/research/browser.js';
|
|
1
|
+
import { openCdpPage, waitForLoad, ResponseCapture, describeNavigationFailure, } from '../lib/research/browser.js';
|
|
2
2
|
import { checkDnsSafety } from '../lib/research/fetcher.js';
|
|
3
3
|
import { htmlToMarkdown } from '../lib/research/markdown.js';
|
|
4
4
|
import { contentMetrics, contentRicherThan } from '../lib/research/regression.js';
|
|
@@ -44,6 +44,10 @@ const BASE_REMOVE_SELECTORS = [
|
|
|
44
44
|
'dx-scroll-manager',
|
|
45
45
|
'dx-skip-nav-link',
|
|
46
46
|
'dw-instrumentation',
|
|
47
|
+
// The left-hand TOC/search drawer nested inside doc-content-layout's shadow DOM. It renders the
|
|
48
|
+
// entire site's navigation tree (hundreds of <a> links) as a sibling of the real article body,
|
|
49
|
+
// so left unremoved it dwarfs and precedes the actual content in the captured Markdown.
|
|
50
|
+
'dx-sidebar-old',
|
|
47
51
|
'[role="navigation"]',
|
|
48
52
|
'[aria-label*="Table of Contents" i]',
|
|
49
53
|
'#onetrust-consent-sdk',
|
|
@@ -279,7 +283,15 @@ async function captureSalesforceAttempt(page, url, captureScript, contentSelecto
|
|
|
279
283
|
accept: (body) => parseDocsApiPayload(body) !== null,
|
|
280
284
|
},
|
|
281
285
|
]);
|
|
282
|
-
|
|
286
|
+
// A network-level failure (DNS, refused/closed connection, TLS, a proxy declining to tunnel) never
|
|
287
|
+
// fires a load event and renders Chrome's own "site can't be reached" interstitial instead. Without
|
|
288
|
+
// this check that interstitial sails through pollSalesforceContentReady/assertReadableSalesforceContent
|
|
289
|
+
// as if it were real article content — it's plausible-length HTML that isn't a 404/loading shell — so
|
|
290
|
+
// the fetch would silently "succeed" with Chrome's error page cached as the documentation.
|
|
291
|
+
const nav = await page.client.send('Page.navigate', { url }, page.sessionId);
|
|
292
|
+
if (nav.errorText) {
|
|
293
|
+
throw describeNavigationFailure(nav.errorText, url);
|
|
294
|
+
}
|
|
283
295
|
await waitForLoad(page.client, page.sessionId, TIMEOUT_MS, 0).catch(() => { });
|
|
284
296
|
await page.client
|
|
285
297
|
.send('Runtime.evaluate', { expression: ACCEPT_CONSENT_EXPRESSION }, page.sessionId)
|
package/oclif.manifest.json
CHANGED