@ultimat3/scraping 20.2.1 → 22.0.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/README.md CHANGED
@@ -298,6 +298,15 @@ Every code carries a cause, a runnable `fix:` and a retry classification — see
298
298
  `X_SCRAPE_YIELD_COLLAPSED`, `X_SCRAPE_AUTH_FAILED`, `X_SCRAPE_BLOCKED` and `X_SCRAPE_PAGE_CRASHED`
299
299
  are the four worth knowing by heart.
300
300
 
301
+ ### Error classes
302
+
303
+ Every error class `src/index.ts` exports, for `instanceof` inside one process. Across a wire or
304
+ a job boundary the class is gone and the `code` is what survives — match on that.
305
+
306
+ | Class | Code | Declared in |
307
+ |---|---|---|
308
+ | `ScrapeError` | any `ScrapeErrorCode` — `SCRAPE_ERROR_CODES` | `src/errors.ts` |
309
+
301
310
  ## Boundary
302
311
 
303
312
  Tier 5. May import tiers 0-4 only — enforced by `bun run scripts/boundaries.ts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/scraping",
3
- "version": "20.2.1",
3
+ "version": "22.0.0",
4
4
  "description": "Browser automation as a job: scrape() returns a JobHandle",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  "test": "bun test"
31
31
  },
32
32
  "dependencies": {
33
- "@ultimat3/core": "20.2.1",
34
- "@ultimat3/jobs": "20.2.1",
35
- "@ultimat3/schema": "20.2.1",
36
- "@ultimat3/storage": "20.2.1"
33
+ "@ultimat3/core": "22.0.0",
34
+ "@ultimat3/jobs": "22.0.0",
35
+ "@ultimat3/schema": "22.0.0",
36
+ "@ultimat3/storage": "22.0.0"
37
37
  }
38
38
  }
package/src/cdp-arm.ts CHANGED
@@ -135,11 +135,14 @@ export async function arm(init: CdpArmInit, sinks: CdpSinks): Promise<void> {
135
135
  const at = init.clock.now().getTime();
136
136
  if (verdict === 'allow') {
137
137
  network.push({ method, url, resourceType: type, at });
138
- void request.continue();
138
+ // Caught, never floated: both reject when the target closed mid-request (and on any other
139
+ // protocol error), and a floated rejection is one Bun ends the process on. The request is
140
+ // already recorded; there is nothing left to do for a target that is gone.
141
+ request.continue().catch(() => undefined);
139
142
  return;
140
143
  }
141
144
  network.push(refusalEntry(url, type, verdict, at, method));
142
- void request.abort();
145
+ request.abort().catch(() => undefined);
143
146
  });
144
147
  init.page.on('console', (payload) => {
145
148
  console_.push({
package/src/hosts.ts CHANGED
@@ -1,70 +1,4 @@
1
- // `allowHosts`, as a decision every driver asks before a request leaves — never a note in a
2
- // README. A headless browser inside your network is the widest SSRF surface an app can own: one
3
- // injected `<img src="http://169.254.169.254/…">` on a page you do not control is a credential
4
- // read, and no amount of "we only visit example.com" in prose intercepts it.
5
-
6
- export type HostRule = string;
7
-
8
- /** The one spelling that means "every host", written out so it is visible in review. */
9
- export const ANY_HOST: HostRule = '*';
10
-
11
- /**
12
- * Schemes with no host to match. `about:blank` is where every browser starts, `data:` and `blob:`
13
- * never leave the process — refusing them would refuse the first page load of every run.
14
- */
15
- const HOSTLESS_SCHEMES = new Set(['about:', 'data:', 'blob:']);
16
-
17
- /**
18
- * Hostless AND refused, which is why it is not on the line above — it sat there until 2026-09.
19
- * `javascript:` has no host for the same reason `data:` has none, and that is the whole
20
- * resemblance: the other three are inert content this process renders, while this one is code
21
- * EXECUTED in the current document's origin, with the session's cookies and the session's
22
- * `localStorage` already in scope. `allowHosts` cannot say anything about a URL with no host to
23
- * name, so "no host, therefore allowed" was the allow list opting itself out of the one navigation
24
- * that needs no host to exfiltrate through — `javascript:fetch('/admin').then(post_elsewhere)` is
25
- * a same-origin read on an allow-listed site. Fail closed; there is no legitimate scrape verb that
26
- * needs it (`page.eval` is the declared seam).
27
- */
28
- const REFUSED_SCHEMES = new Set(['javascript:']);
29
-
30
- export interface HostDecision {
31
- readonly allowed: boolean;
32
- /** The host the URL resolved to, `''` for a hostless scheme. */
33
- readonly host: string;
34
- }
35
-
36
- /**
37
- * `example.com` matches that host EXACTLY. `*.example.com` matches any subdomain and NOT the
38
- * apex — the two are written separately on purpose: an allow list that silently included every
39
- * subdomain would let a `cdn-user-content.example.com` (whose contents somebody else controls)
40
- * through a rule an author wrote for the apex.
41
- */
42
- export function hostMatches(host: string, rule: HostRule): boolean {
43
- if (rule === ANY_HOST) return true;
44
- const normalised = host.toLowerCase();
45
- const cleaned = rule.trim().toLowerCase();
46
- if (cleaned.startsWith('*.')) {
47
- const suffix = cleaned.slice(1);
48
- return normalised.endsWith(suffix) && normalised.length > suffix.length;
49
- }
50
- return normalised === cleaned;
51
- }
52
-
53
- /**
54
- * Fails CLOSED: a URL that cannot be parsed is refused. A driver handed a malformed request has
55
- * no way to know where it would have gone, and "we could not tell, so we let it through" is the
56
- * decision that makes the whole list advisory.
57
- */
58
- export function hostDecision(url: string, allowHosts: readonly HostRule[]): HostDecision {
59
- const scheme = url.slice(0, Math.max(0, url.indexOf(':') + 1)).toLowerCase();
60
- if (REFUSED_SCHEMES.has(scheme)) return { allowed: false, host: '' };
61
- if (HOSTLESS_SCHEMES.has(scheme)) return { allowed: true, host: '' };
62
- let host: string;
63
- try {
64
- host = new URL(url).hostname;
65
- } catch {
66
- return { allowed: false, host: '' };
67
- }
68
- if (host === '') return { allowed: false, host };
69
- return { allowed: allowHosts.some((rule) => hostMatches(host, rule)), host };
70
- }
1
+ // `allowHosts` matching is `@ultimat3/core`'s (`host-rules.ts`), shared with `x shot`'s browser;
2
+ // re-exported here so this package's own modules and its public names are unchanged.
3
+ export type { HostDecision, HostRule } from '@ultimat3/core';
4
+ export { ANY_HOST, hostDecision, hostMatches } from '@ultimat3/core';
package/src/index.ts CHANGED
@@ -3,12 +3,10 @@
3
3
  // the seam is not a seam.
4
4
 
5
5
  export type { ActionabilityState, ActionabilityWait } from './actionability';
6
- export { actionabilityProblem, awaitActionable, DEFAULT_POLL_MS, isStable } from './actionability';
6
+ export { awaitActionable } from './actionability';
7
7
  export type { ArtifactRef, ArtifactWriter, ArtifactWriterInit } from './artifacts';
8
8
  export {
9
- contentTypeFor,
10
9
  createArtifactWriter,
11
- DEFAULT_ARTIFACT_PREFIX,
12
10
  DEFAULT_CONTENT_TYPE,
13
11
  } from './artifacts';
14
12
  export type {
@@ -21,7 +19,6 @@ export { burnSession, createPrompt, ensureAuthenticated, restorableSession } fro
21
19
  export { browserRecord } from './browser-record';
22
20
  export type { CaptureClip, CaptureFraming } from './capture-clip';
23
21
  export { assertCaptureFraming } from './capture-clip';
24
- export { axNodesFor } from './cdp-a11y';
25
22
  export type {
26
23
  CdpBrowserLike,
27
24
  CdpFrameLike,
@@ -32,24 +29,14 @@ export type {
32
29
  CdpScreenshotOptions,
33
30
  CdpSessionLike,
34
31
  } from './cdp-port';
35
- export {
36
- clearExpression,
37
- focusExpression,
38
- parseSnapshots,
39
- snapshotExpression,
40
- } from './cdp-snapshot';
32
+ export { snapshotExpression } from './cdp-snapshot';
41
33
  export type { CdpTargetInit } from './cdp-target';
42
34
  export { CDP_DRIVER, cdpTarget } from './cdp-target';
43
35
  export type { Deadline, ScrapeClock, TestScrapeClock } from './clock';
44
36
  export { deadline, systemScrapeClock, testClock, throwIfAborted } from './clock';
45
37
  export type { ColorScheme } from './color-scheme';
46
- export { COLOR_SCHEME_FEATURE, COLOR_SCHEMES, isColorScheme } from './color-scheme';
47
- export {
48
- cookieDomainMatches,
49
- cookieHeaderFor,
50
- cookiePathMatches,
51
- cookiesForUrl,
52
- } from './cookie-scope';
38
+ export { COLOR_SCHEMES, isColorScheme } from './color-scheme';
39
+ export { cookieHeaderFor } from './cookie-scope';
53
40
  export type { ScrapeDriver, ScrapeSession, SessionInit } from './driver';
54
41
  export { resetScrapeDriver, scrapeDriver, setScrapeDriver } from './driver';
55
42
  export type { BrowserOptions, LocalBrowserOptions, RemoteBrowserOptions } from './driver-cdp';
@@ -57,7 +44,7 @@ export { localBrowser, remoteBrowser } from './driver-cdp';
57
44
  export type { FakeBrowserOptions, FakePageOptions, FakePages } from './driver-fake';
58
45
  export { FAKE_DRIVER, FAKE_PAGE_URL, fakeBrowser, fakePage, recordingsOf } from './driver-fake';
59
46
  export type { FixtureBrowserOptions } from './driver-fixture';
60
- export { FIXTURE_DRIVER, fixtureBrowser, recordingFilename } from './driver-fixture';
47
+ export { FIXTURE_DRIVER, fixtureBrowser } from './driver-fixture';
61
48
  export {
62
49
  authFailed,
63
50
  blocked,
@@ -100,32 +87,28 @@ export {
100
87
  ScrapeError,
101
88
  } from './errors';
102
89
  export type { ScrapeEventFields, StepEvent } from './events';
103
- export { scrapeLogger, withStepEvent } from './events';
104
90
  export type { YieldCheck, YieldExpectation, YieldGuardInput, YieldHistory } from './expect';
105
91
  export {
106
- DEFAULT_YIELD_WINDOW,
107
92
  guardYield,
108
93
  MIN_BASELINE_RUNS,
109
94
  median,
110
95
  memoryYieldHistory,
111
- yieldProblem,
112
96
  } from './expect';
113
- export { BURNS_SESSION, burnsSession, errorCode, NEVER_RETRIED, neverRetried } from './failures';
97
+ export { BURNS_SESSION, errorCode, NEVER_RETRIED } from './failures';
114
98
  export type { HostDecision, HostRule } from './hosts';
115
99
  export { ANY_HOST, hostDecision, hostMatches } from './hosts';
116
- export { markupEnabled, markupVisible, queryHtml } from './html-query';
100
+ export { queryHtml } from './html-query';
117
101
  export type { MarkupRequest } from './html-requests';
118
- export { markupRequests } from './html-requests';
119
102
  export type { HtmlTargetInit, RecordingLookup } from './html-target';
120
103
  export { htmlTarget } from './html-target';
121
104
  export type { HttpRequestInit, HttpTransportInit, ScrapeHttp, ScrapeResponse } from './http';
122
105
  export { DEFAULT_HTTP_MAX_BYTES, httpOverFetch, responseOver } from './http';
123
106
  export type { HttpRecordingLookup, RecordedHttpInit } from './http-recorded';
124
- export { httpRecordingFilename, httpRecordingsOf, recordedHttp } from './http-recorded';
107
+ export { httpRecordingsOf, recordedHttp } from './http-recorded';
125
108
  export type { RedirectHop } from './http-redirect';
126
- export { MAX_REDIRECT_HOPS, redirectHop } from './http-redirect';
109
+ export { MAX_REDIRECT_HOPS } from './http-redirect';
127
110
  export type { InterceptRules, InterceptVerdict } from './intercept';
128
- export { interceptVerdict, refusalEntry } from './intercept';
111
+ export { interceptVerdict } from './intercept';
129
112
  export type { KeyChord, KeyModifier } from './key-chord';
130
113
  export { KEY_MODIFIERS, parseKeyChord } from './key-chord';
131
114
  export type { OfflineSessionInit } from './offline-session';
@@ -139,18 +122,16 @@ export type {
139
122
  ScrapePage,
140
123
  WaitOptions,
141
124
  } from './page';
142
- export { DEFAULT_ACCESSIBILITY_MAX } from './page';
143
125
  export type { PageContext } from './page-over-target';
144
126
  export { pageOverTarget } from './page-over-target';
145
127
  export type { Pacer } from './rate';
146
- export { createPacer, DEFAULT_NAVIGATION_RATE } from './rate';
128
+ export { createPacer } from './rate';
147
129
  export type { HttpRecording, PageRecording } from './recording';
148
130
  export {
149
131
  httpRecordingSchema,
150
132
  pageRecordingSchema,
151
133
  parseHttpRecording,
152
134
  parseRecording,
153
- splitDownload,
154
135
  } from './recording';
155
136
  export type { AgentRecovery, Recovery, RecoveryAttempt, RecoveryHook } from './recover';
156
137
  export { runRecovery } from './recover';
@@ -166,13 +147,12 @@ export type {
166
147
  } from './rings';
167
148
  export {
168
149
  createRing,
169
- DEFAULT_RING_CAPACITY,
170
150
  MAX_PAGE_ERROR_CHARS,
171
151
  pageErrorEntry,
172
152
  RESOURCE_TYPES,
173
153
  } from './rings';
174
154
  export type { RobotsFetch, RobotsGate, RobotsGateInit, RobotsPolicy, RobotsRules } from './robots';
175
- export { createRobotsGate, DEFAULT_ROBOTS_AGENT, parseRobots, robotsAllows } from './robots';
155
+ export { createRobotsGate, parseRobots, robotsAllows } from './robots';
176
156
  export type { RobotsFetchInit } from './robots-fetch';
177
157
  export {
178
158
  DEFAULT_ROBOTS_MAX_BYTES,
@@ -189,11 +169,9 @@ export { scrape } from './scrape';
189
169
  export { DEFAULT_PAGE_TIMEOUT_MS, runScrape } from './scrape-run';
190
170
  export type { ScrapeSecrets, SecretResolver } from './secrets';
191
171
  export {
192
- blankPasswordFields,
193
172
  createSecretBag,
194
173
  MIN_REDACTABLE_LENGTH,
195
174
  redactSecrets,
196
- SECRET_PLACEHOLDER,
197
175
  safeConsole,
198
176
  safeHtml,
199
177
  safeNetwork,
@@ -201,8 +179,6 @@ export {
201
179
  } from './secrets';
202
180
  export type { ScrapeSessionStore, SessionSnapshot, SessionState } from './session-state';
203
181
  export {
204
- DEFAULT_SESSION_PREFIX,
205
- EMPTY_SESSION,
206
182
  memorySessionStore,
207
183
  parseSessionState,
208
184
  sessionDigest,
@@ -220,6 +196,5 @@ export type {
220
196
  ScrapeDownloadFile,
221
197
  ScrapeTarget,
222
198
  } from './target';
223
- export { ROOT_SELECTOR } from './target';
224
199
  export type { WedgeGuard, WedgeGuardInit } from './watchdog';
225
- export { createWedgeGuard, DEFAULT_GRACE_MS, DEFAULT_IDLE_MS } from './watchdog';
200
+ export { DEFAULT_GRACE_MS } from './watchdog';