@crawlbrulee/sdk 0.6.0 → 0.7.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 +156 -76
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🍮 crawlbrulee js/ts sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@crawlbrulee/sdk)
|
|
4
|
+
[](https://www.npmjs.com/package/@crawlbrulee/sdk)
|
|
5
|
+
[](./LICENSE)
|
|
4
6
|
|
|
5
|
-
-
|
|
7
|
+
the official js/ts sdk for the [crawlbrulee](https://crawlbrulee.com) web-scraping api — published to npm as [`@crawlbrulee/sdk`](https://www.npmjs.com/package/@crawlbrulee/sdk). you
|
|
8
|
+
send a url, you get back markdown, cleaned html, links, images, metadata, or a screenshot.
|
|
9
|
+
|
|
10
|
+
- fully typed.
|
|
6
11
|
- ESM + CommonJS, ships its own `.d.ts`.
|
|
7
|
-
-
|
|
8
|
-
-
|
|
12
|
+
- zero runtime dependencies — just `fetch`.
|
|
13
|
+
- works on Node.js 22+, modern Deno, Bun, and runtimes where `fetch` is available.
|
|
14
|
+
|
|
15
|
+
this readme covers the sdk itself — the client, the types, and the js-side ergonomics. for how
|
|
16
|
+
the api behaves — endpoints, parameters, and error semantics — please see our
|
|
17
|
+
[api docs](https://crawlbrulee.com/docs).
|
|
9
18
|
|
|
10
|
-
> **
|
|
19
|
+
> **status:** v0.7.0 (beta). the api surface is stabilizing — expect minor breaking changes between 0.x releases.
|
|
20
|
+
|
|
21
|
+
**get a free api key** → [dashboard.crawlbrulee.com](https://dashboard.crawlbrulee.com)
|
|
11
22
|
|
|
12
23
|
---
|
|
13
24
|
|
|
14
|
-
##
|
|
25
|
+
## install
|
|
15
26
|
|
|
16
27
|
```bash
|
|
17
28
|
pnpm add @crawlbrulee/sdk
|
|
@@ -21,7 +32,7 @@ npm install @crawlbrulee/sdk
|
|
|
21
32
|
yarn add @crawlbrulee/sdk
|
|
22
33
|
```
|
|
23
34
|
|
|
24
|
-
##
|
|
35
|
+
## quickstart
|
|
25
36
|
|
|
26
37
|
```ts
|
|
27
38
|
import { Crawlbrulee } from '@crawlbrulee/sdk'
|
|
@@ -41,22 +52,38 @@ console.log(page.metadata?.title) // structured <head> metadata
|
|
|
41
52
|
console.log(page.response_meta.usage.credits, 'credits charged') // usage accounting
|
|
42
53
|
```
|
|
43
54
|
|
|
44
|
-
###
|
|
55
|
+
### authentication
|
|
56
|
+
|
|
57
|
+
every request carries your api key as `Authorization: Bearer <key>`. give it to the sdk one of two ways:
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| `timeoutMs` | `0` (no timeout) | Per-request timeout (covers headers + body). A per-call `timeoutMs` overrides this. |
|
|
59
|
+
```ts
|
|
60
|
+
// explicit — pass the key directly
|
|
61
|
+
const crawlbrulee = new Crawlbrulee({ apiKey: 'cwbl_…' })
|
|
50
62
|
|
|
51
|
-
|
|
63
|
+
// from the environment — reads CRAWLBRULEE_API_KEY
|
|
64
|
+
const crawlbrulee = Crawlbrulee.fromEnv()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`Crawlbrulee.fromEnv(overrides?)` reads the key from `CRAWLBRULEE_API_KEY` and forwards any other option through
|
|
68
|
+
`overrides` (e.g. `Crawlbrulee.fromEnv({ timeoutMs: 30_000 })`). it throws if the variable is unset or empty. keys are
|
|
69
|
+
minted in the dashboard; see [authentication](https://crawlbrulee.com/docs/authentication) for how the api consumes them.
|
|
70
|
+
|
|
71
|
+
### configuration
|
|
72
|
+
|
|
73
|
+
| option | default | description |
|
|
74
|
+
| ----------- | ----------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `apiKey` | — | api key, sent as `Authorization: Bearer …`. **required** — or use `Crawlbrulee.fromEnv()`. |
|
|
76
|
+
| `baseUrl` | `https://api.crawlbrulee.com` | override the target host (local dev / staging). trailing slashes are stripped. |
|
|
77
|
+
| `timeoutMs` | `0` (no timeout) | per-request timeout in milliseconds (covers headers + body). a per-call `timeoutMs` overrides this. |
|
|
52
78
|
|
|
53
79
|
---
|
|
54
80
|
|
|
55
|
-
##
|
|
81
|
+
## api reference
|
|
56
82
|
|
|
57
|
-
|
|
83
|
+
all methods return a `Promise` that resolves to the parsed json response, or rejects with a [`CrawlbruleeError`](#errors)
|
|
84
|
+
subclass.
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
every method accepts an optional second argument with per-call overrides:
|
|
60
87
|
|
|
61
88
|
```ts
|
|
62
89
|
crawlbrulee.scrape(request, {
|
|
@@ -65,11 +92,11 @@ crawlbrulee.scrape(request, {
|
|
|
65
92
|
})
|
|
66
93
|
```
|
|
67
94
|
|
|
68
|
-
###
|
|
95
|
+
### scraping
|
|
69
96
|
|
|
70
97
|
#### `crawlbrulee.scrape(request, options?)`
|
|
71
98
|
|
|
72
|
-
|
|
99
|
+
scrape a url synchronously. the request blocks until the server is done.
|
|
73
100
|
|
|
74
101
|
```ts
|
|
75
102
|
const page = await crawlbrulee.scrape({
|
|
@@ -93,43 +120,62 @@ const page = await crawlbrulee.scrape({
|
|
|
93
120
|
})
|
|
94
121
|
```
|
|
95
122
|
|
|
96
|
-
|
|
123
|
+
the response carries the extracted content alongside structured `metadata` (the parsed `<head>` tags — `title`,
|
|
124
|
+
`description`, OG/Twitter fields, …) and a `response_meta` envelope:
|
|
97
125
|
|
|
98
126
|
```ts
|
|
99
127
|
page.metadata?.title // structured <head> metadata (when extract.metadata, on by default)
|
|
100
128
|
|
|
101
129
|
page.response_meta.usage.credits // credits charged — 0 on a cache hit
|
|
102
|
-
page.response_meta.usage.proxy // the resolved proxy tier actually used: '
|
|
130
|
+
page.response_meta.usage.proxy // the resolved proxy tier actually used: 'basic' | 'advanced' (never 'auto')
|
|
103
131
|
page.response_meta.usage.cache_hit // true when the result was served from cache
|
|
104
132
|
```
|
|
105
133
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **`proxy
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
notes:
|
|
135
|
+
|
|
136
|
+
- **`proxy`**: defaults to `auto` when omitted — it starts at the basic tier and escalates to advanced on failure,
|
|
137
|
+
billed at the delivered tier. pass `'basic'` or `'advanced'` to pin a tier. on the response,
|
|
138
|
+
`response_meta.usage.proxy` reports the tier we resolved and used — never `'auto'`. see
|
|
139
|
+
[proxies & location](https://crawlbrulee.com/docs/proxies) for what each tier does.
|
|
140
|
+
- **`screenshot`**: custom `viewport.width`/`height` are integers in `[16, 10000]` and `device_scale_factor` is in
|
|
141
|
+
`[1, 4]`; out-of-range values are rejected with a `400`. full capture options:
|
|
142
|
+
[screenshots](https://crawlbrulee.com/docs/scrape/screenshots).
|
|
143
|
+
- **`extract.images`**: urls preserve their query string and resolve document-relative `src`s against the full page url
|
|
144
|
+
(browser parity) — the same rules as `links`. every extract field is documented under
|
|
145
|
+
[extraction](https://crawlbrulee.com/docs/scrape/extraction).
|
|
146
|
+
- **`warnings`**: when we complete a scrape but something is worth flagging — e.g. `screenshot_truncated` when a long
|
|
147
|
+
page exceeded the scrolling-screenshot height cap — the codes land on `page.warnings`. they're stable, so you can switch
|
|
148
|
+
on them. fresh scrapes only; cache hits omit warnings.
|
|
149
|
+
- **`unsupported_fields`**: if you request an extract that doesn't apply to the content type (e.g. `markdown` of a pdf),
|
|
150
|
+
that field name comes back on `page.unsupported_fields` and the rest of your payload is still returned.
|
|
151
|
+
|
|
152
|
+
see [`ScrapeRequest`](src/types/scrape.ts) and [`ScrapeResponse`](src/types/scrape.ts) for every field, with inline
|
|
153
|
+
documentation — and the [scrape endpoint](https://crawlbrulee.com/docs/scrape) reference for the api-side contract those
|
|
154
|
+
types mirror.
|
|
113
155
|
|
|
114
156
|
#### `crawlbrulee.scrapeAsync(request, options?)`
|
|
115
157
|
|
|
116
|
-
|
|
158
|
+
submit a scrape job in the background. returns immediately with a `job_id`.
|
|
117
159
|
|
|
118
160
|
```ts
|
|
119
161
|
const { job_id } = await crawlbrulee.scrapeAsync({ url: 'https://example.com' })
|
|
120
162
|
```
|
|
121
163
|
|
|
164
|
+
pass a `webhook` to be notified on completion instead of polling — see [webhooks](#webhooks).
|
|
165
|
+
|
|
122
166
|
#### `crawlbrulee.getScrapeStatus(jobId, options?)`
|
|
123
167
|
|
|
124
|
-
|
|
168
|
+
look up the current state of an async job — `pending`, `running`, `done`, or `failed`. the response carries `job_id` and
|
|
169
|
+
`created_at` (snake_case, straight off the wire). once the job is `done`, it also carries usage accounting on
|
|
170
|
+
`response_meta.usage` (`credits`, `proxy`, `cache_hit`).
|
|
125
171
|
|
|
126
172
|
#### `crawlbrulee.getScrapeResult(jobId, options?)`
|
|
127
173
|
|
|
128
|
-
|
|
174
|
+
fetch the result of a completed async job. throws if the job hasn't finished yet.
|
|
129
175
|
|
|
130
176
|
#### `crawlbrulee.waitForScrape(jobId, options?)`
|
|
131
177
|
|
|
132
|
-
|
|
178
|
+
poll an async job until it reaches a terminal state, then return the scrape result.
|
|
133
179
|
|
|
134
180
|
```ts
|
|
135
181
|
const { job_id } = await crawlbrulee.scrapeAsync({ url: 'https://example.com' })
|
|
@@ -140,13 +186,16 @@ const page = await crawlbrulee.waitForScrape(job_id, {
|
|
|
140
186
|
})
|
|
141
187
|
```
|
|
142
188
|
|
|
143
|
-
|
|
189
|
+
throws a `CrawlbruleeError` with `errorName: 'job_failed'` if the job ends in `failed`, or `errorName: 'request_timeout'`
|
|
190
|
+
if the wait expires. the job lifecycle itself — states, retention, and when to prefer async over sync — is documented
|
|
191
|
+
under [async scrape](https://crawlbrulee.com/docs/scrape/async).
|
|
144
192
|
|
|
145
|
-
###
|
|
193
|
+
### mapping
|
|
146
194
|
|
|
147
195
|
#### `crawlbrulee.map(request, options?)`
|
|
148
196
|
|
|
149
|
-
|
|
197
|
+
build (or return a cached) link map for a website. combines sitemap discovery with the freshest cached homepage scrape
|
|
198
|
+
when available.
|
|
150
199
|
|
|
151
200
|
```ts
|
|
152
201
|
const result = await crawlbrulee.map({
|
|
@@ -162,25 +211,41 @@ console.log(result.links.length, 'urls on page 1 of', result.response_meta.pagin
|
|
|
162
211
|
console.log(result.response_meta.usage.credits, 'credits charged') // usage accounting, alongside pagination + truncation
|
|
163
212
|
```
|
|
164
213
|
|
|
165
|
-
|
|
214
|
+
`result.response_meta` carries `usage` (credits / resolved `proxy` / `cache_hit`) alongside the map-specific `pagination`
|
|
215
|
+
and `truncation` blocks. see the [map endpoint](https://crawlbrulee.com/docs/map) for discovery rules and pagination
|
|
216
|
+
semantics.
|
|
217
|
+
|
|
218
|
+
### account
|
|
166
219
|
|
|
167
220
|
#### `crawlbrulee.usage(options?)`
|
|
168
221
|
|
|
169
|
-
|
|
222
|
+
return the current billing-cycle snapshot — `total_credits`, `used_credits`, `available_credits`, `used_quota_percent`,
|
|
223
|
+
`max_concurrency`, and the `usage_reset` timestamp.
|
|
170
224
|
|
|
171
225
|
#### `crawlbrulee.whoami(options?)`
|
|
172
226
|
|
|
173
|
-
|
|
227
|
+
return the organization name and token identity behind the api key (`organization_name`, `token_name`, and a
|
|
228
|
+
safe-to-display `token_preview`). use it to confirm which key is in play before a destructive operation.
|
|
229
|
+
|
|
230
|
+
what a call costs, and how credits are counted, is documented under
|
|
231
|
+
[credits & pricing](https://crawlbrulee.com/docs/credits-and-pricing).
|
|
174
232
|
|
|
175
233
|
---
|
|
176
234
|
|
|
177
|
-
##
|
|
235
|
+
## webhooks
|
|
236
|
+
|
|
237
|
+
when an async scrape job finishes, crawlbrulee can `POST` a `scrape.complete` webhook to your configured endpoint. the
|
|
238
|
+
sdk ships two helpers for it.
|
|
178
239
|
|
|
179
|
-
|
|
240
|
+
the delivery contract and payload shape live under [webhooks](https://crawlbrulee.com/docs/scrape/webhooks); the
|
|
241
|
+
signature scheme is specified in [webhook verification](https://crawlbrulee.com/docs/webhook-verification). what follows
|
|
242
|
+
is how this sdk helps you consume them.
|
|
180
243
|
|
|
181
|
-
###
|
|
244
|
+
### triggering a webhook (`scrapeAsync`)
|
|
182
245
|
|
|
183
|
-
|
|
246
|
+
pass a `webhook` to `scrapeAsync` to have us deliver a single signed `scrape.complete` `POST` when the job reaches a
|
|
247
|
+
terminal state. this is **async-only** — the synchronous `scrape()` response _is_ the notification, so it does not accept
|
|
248
|
+
a `webhook`.
|
|
184
249
|
|
|
185
250
|
```ts
|
|
186
251
|
const { job_id } = await crawlbrulee.scrapeAsync({
|
|
@@ -195,11 +260,16 @@ const { job_id } = await crawlbrulee.scrapeAsync({
|
|
|
195
260
|
})
|
|
196
261
|
```
|
|
197
262
|
|
|
198
|
-
|
|
263
|
+
configure the signing secret used for these deliveries in the dashboard (**account → webhooks**). there is no per-request
|
|
264
|
+
secret - when the delivery arrives, verify it with [`verifyWebhookSignature`](#verifywebhooksignatureoptions) and read your `metadata` back from
|
|
265
|
+
`webhook.data.metadata`. the delivery also carries usage accounting on `webhook.data.response_meta.usage` (`credits`,
|
|
266
|
+
`proxy`, `cache_hit`). see [`AsyncScrapeWebhook`](src/types/scrape.ts) for the full field documentation.
|
|
199
267
|
|
|
200
268
|
### `verifyWebhookSignature(options)`
|
|
201
269
|
|
|
202
|
-
|
|
270
|
+
a standalone, network-free helper (built on Web Crypto, so it runs on Node.js 22+, browsers, Bun, Deno, and edge) that
|
|
271
|
+
verifies the `X-Cwbl-Signature` header. **it returns a result object rather than throwing** — a failed verification is
|
|
272
|
+
normal control flow.
|
|
203
273
|
|
|
204
274
|
```ts
|
|
205
275
|
import { verifyWebhookSignature } from '@crawlbrulee/sdk'
|
|
@@ -218,11 +288,15 @@ if (result.verified) {
|
|
|
218
288
|
}
|
|
219
289
|
```
|
|
220
290
|
|
|
221
|
-
|
|
291
|
+
during a **signing-secret rotation grace window** we send a second `X-Cwbl-Signature-Rotated` header signed with the
|
|
292
|
+
previous secret. `verifyWebhookSignature` tries your `secret` against the primary header first, then the rotated one,
|
|
293
|
+
and reports which matched via `signedWith` — so verification keeps working whether you still hold the old secret or have
|
|
294
|
+
already rotated to the new one.
|
|
222
295
|
|
|
223
296
|
### `crawlbrulee.fetchScrapeResultFromWebhook(webhook, options?)`
|
|
224
297
|
|
|
225
|
-
|
|
298
|
+
given a verified `scrape.complete` body, fetch the scrape result. returns `getScrapeResult(job_id)` for a `success` job;
|
|
299
|
+
throws a `CrawlbruleeError` for `failed` (carrying the failure message) or `cancelled` jobs.
|
|
226
300
|
|
|
227
301
|
```ts
|
|
228
302
|
import { Crawlbrulee, verifyWebhookSignature, type ScrapeCompleteWebhook } from '@crawlbrulee/sdk'
|
|
@@ -246,23 +320,25 @@ app.post('/webhooks/crawlbrulee', async (req, res) => {
|
|
|
246
320
|
})
|
|
247
321
|
```
|
|
248
322
|
|
|
249
|
-
|
|
323
|
+
always verify the signature **before** parsing or trusting the body. the `X-Cwbl-Event-Id` header (also
|
|
324
|
+
`webhook.event_id`) is a stable id you can use to de-duplicate deliveries.
|
|
250
325
|
|
|
251
326
|
---
|
|
252
327
|
|
|
253
|
-
##
|
|
328
|
+
## errors
|
|
254
329
|
|
|
255
|
-
|
|
330
|
+
every failure raised by the sdk extends [`CrawlbruleeError`](src/errors.ts). typed subclasses are exported for the most actionable
|
|
331
|
+
cases:
|
|
256
332
|
|
|
257
|
-
|
|
|
333
|
+
| class | when it's raised |
|
|
258
334
|
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
259
|
-
| `AuthenticationError` | 401 / 403 responses (missing, invalid, or unauthorized
|
|
260
|
-
| `RateLimitError` | 429 responses.
|
|
261
|
-
| `UsageAllocationError` |
|
|
335
|
+
| `AuthenticationError` | 401 / 403 responses (missing, invalid, or unauthorized api key). |
|
|
336
|
+
| `RateLimitError` | 429 responses. exposes `retryAfterMs` and `limitedBy` when the server provided them. |
|
|
337
|
+
| `UsageAllocationError` | the org's plan limit was hit. exposes `reason` (`credit_limit`, `concurrency_limit`, …) and `usage`. |
|
|
262
338
|
| `ValidationError` | 4xx caused by a bad request (`invalid_url`, `url_too_long`, `blocked_url`, …). |
|
|
263
339
|
| `NotFoundError` | 404 responses (e.g. unknown async `jobId`). |
|
|
264
|
-
| `TransportError` |
|
|
265
|
-
| `CrawlbruleeError` |
|
|
340
|
+
| `TransportError` | network failures, aborts, non-json responses, request body read failures. |
|
|
341
|
+
| `CrawlbruleeError` | base class — used for any other api error. always has `status`, `errorName`, `message`. |
|
|
266
342
|
|
|
267
343
|
```ts
|
|
268
344
|
import { Crawlbrulee, RateLimitError, UsageAllocationError } from '@crawlbrulee/sdk'
|
|
@@ -282,26 +358,13 @@ try {
|
|
|
282
358
|
}
|
|
283
359
|
```
|
|
284
360
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
Limits are per-plan, with **separate buckets for sync and async** (requests per minute). Synchronous `scrape()` **and** `map()` count against the sync bucket; `scrapeAsync()` submissions have their own bucket.
|
|
290
|
-
|
|
291
|
-
| Plan | Sync (rpm) | Async (rpm) |
|
|
292
|
-
| -------- | ---------- | ----------- |
|
|
293
|
-
| Free | 50 | 100 |
|
|
294
|
-
| Starter | 100 | 300 |
|
|
295
|
-
| Pro | 350 | 1000 |
|
|
296
|
-
| Advanced | 1000 | 3000 |
|
|
297
|
-
|
|
298
|
-
When you exceed a bucket the API returns `429` and the SDK throws a `RateLimitError` — read `retryAfterMs` to back off.
|
|
299
|
-
|
|
300
|
-
---
|
|
361
|
+
for exhaustive branching, switch on `err.errorName` — the literal-typed union is exported as `ApiErrorName`. the
|
|
362
|
+
`isCrawlbruleeError(err)` type guard narrows an `unknown` to the base error. the api docs carry the canonical
|
|
363
|
+
[error reference](https://crawlbrulee.com/docs/errors) — every `errorName`, what causes it, and how to recover.
|
|
301
364
|
|
|
302
|
-
##
|
|
365
|
+
## cancellation and timeouts
|
|
303
366
|
|
|
304
|
-
|
|
367
|
+
every method accepts an `AbortSignal`:
|
|
305
368
|
|
|
306
369
|
```ts
|
|
307
370
|
const controller = new AbortController()
|
|
@@ -310,11 +373,12 @@ const page = crawlbrulee.scrape({ url: 'https://slow.example.com' }, { signal: c
|
|
|
310
373
|
setTimeout(() => controller.abort(), 5_000)
|
|
311
374
|
```
|
|
312
375
|
|
|
313
|
-
|
|
376
|
+
the per-call `timeoutMs` and the caller's signal are composed — whichever fires first wins. a fired timeout surfaces as
|
|
377
|
+
a `TransportError` with `errorName: 'request_timeout'`; an aborted signal as `errorName: 'client_closed_request'`.
|
|
314
378
|
|
|
315
379
|
---
|
|
316
380
|
|
|
317
|
-
##
|
|
381
|
+
## development
|
|
318
382
|
|
|
319
383
|
```bash
|
|
320
384
|
pnpm install
|
|
@@ -324,4 +388,20 @@ pnpm lint # eslint
|
|
|
324
388
|
pnpm build # tsup → dist/
|
|
325
389
|
```
|
|
326
390
|
|
|
327
|
-
|
|
391
|
+
the sdk has zero runtime dependencies on purpose. please keep it that way when contributing.
|
|
392
|
+
|
|
393
|
+
## part of the crawlbrulee toolkit
|
|
394
|
+
|
|
395
|
+
one api, many ways to call it:
|
|
396
|
+
|
|
397
|
+
- **[js/ts sdk](https://github.com/crawlbrulee/crawlbrulee-js)** — `@crawlbrulee/sdk` (this one)
|
|
398
|
+
- **[python sdk](https://github.com/crawlbrulee/crawlbrulee-py)** — `crawlbrulee` on pypi
|
|
399
|
+
- **[cli](https://github.com/crawlbrulee/crawlbrulee-cli)** — `npx crawlbrulee`
|
|
400
|
+
- **[mcp server](https://github.com/crawlbrulee/crawlbrulee-mcp)** — `@crawlbrulee/mcp`, for ai agents
|
|
401
|
+
- **[agent skills](https://github.com/crawlbrulee/crawlbrulee-skills)** — for skills-aware coding agents
|
|
402
|
+
|
|
403
|
+
docs: [crawlbrulee.com/docs](https://crawlbrulee.com/docs) · dashboard: [dashboard.crawlbrulee.com](https://dashboard.crawlbrulee.com)
|
|
404
|
+
|
|
405
|
+
## license
|
|
406
|
+
|
|
407
|
+
[AGPL-3.0-only](./LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var DEFAULT_BASE_URL = "https://api.crawlbrulee.com";
|
|
5
5
|
var DEFAULT_REQUEST_TIMEOUT_MS = 0;
|
|
6
6
|
var ENV_API_KEY = "CRAWLBRULEE_API_KEY";
|
|
7
|
-
var USER_AGENT = "@crawlbrulee/sdk/0.
|
|
7
|
+
var USER_AGENT = "@crawlbrulee/sdk/0.7.0 (node)";
|
|
8
8
|
|
|
9
9
|
// src/errors.ts
|
|
10
10
|
var CrawlbruleeError = class extends Error {
|
|
@@ -134,7 +134,7 @@ var CwblInstrumentation = {
|
|
|
134
134
|
const g = globalThis;
|
|
135
135
|
if (typeof g.fetch !== "function") {
|
|
136
136
|
throw new CrawlbruleeError(
|
|
137
|
-
"No global fetch is available in this runtime. crawlbrulee requires Node.js
|
|
137
|
+
"No global fetch is available in this runtime. crawlbrulee requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.",
|
|
138
138
|
{ status: 0, errorName: null }
|
|
139
139
|
);
|
|
140
140
|
}
|
|
@@ -679,7 +679,7 @@ function getSubtle() {
|
|
|
679
679
|
const subtle = globalThis.crypto?.subtle;
|
|
680
680
|
if (!subtle) {
|
|
681
681
|
throw new Error(
|
|
682
|
-
"Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js
|
|
682
|
+
"Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime."
|
|
683
683
|
);
|
|
684
684
|
}
|
|
685
685
|
return subtle;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/instrumentation.ts","../src/http.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Production base URL of the crawlbrulee API. Used by default when the caller\n * doesn't pass a `baseUrl` to {@link Crawlbrulee}. Local development and\n * staging callers point at their own host via that option.\n */\nexport const DEFAULT_BASE_URL = 'https://api.crawlbrulee.com'\n\n/** Default request timeout when the caller doesn't specify one (0 disables the timeout). */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 0\n\n/** Environment variable read by `Crawlbrulee.fromEnv()` to source the API key. */\nexport const ENV_API_KEY = 'CRAWLBRULEE_API_KEY'\n\n/** Identifies the SDK in the `User-Agent` header. Kept in one place for easy bumping. */\nexport const USER_AGENT = '@crawlbrulee/sdk/0.4.0 (node)'\n","import type {\n ApiErrorDetails,\n ApiErrorName,\n ApiErrorResponse,\n RateLimitErrorDetails,\n UsageAllocationErrorDetails,\n} from './types/common.js'\n\n/**\n * Base error class for every failure raised by the SDK.\n *\n * Two kinds of failures end up here:\n *\n * 1. **API errors** — the server returned a non-2xx response with a well-formed\n * JSON body. In that case `status`, `errorName` and (sometimes) `details`\n * are populated.\n * 2. **Transport errors** — the request never produced a structured response\n * (network failure, abort, timeout, non-JSON body, etc.). In that case\n * `status` may be `0` and `errorName` is one of the synthetic transport\n * names (`request_timeout`, `client_closed_request`) or `null`.\n *\n * Typed subclasses are exported for the most common cases. To branch on more\n * specific server-side errors, switch on `err.errorName` or use the\n * {@link isCrawlbruleeError} helper.\n */\nexport class CrawlbruleeError extends Error {\n /** HTTP status code; `0` for transport-level failures with no response. */\n readonly status: number\n /** The `name` field from the API error body, or `null` for transport errors. */\n readonly errorName: ApiErrorName | null\n /** Structured detail block from the API error body, if any. */\n readonly details?: ApiErrorDetails\n /** The original parsed error body, when one was received. */\n readonly response?: ApiErrorResponse\n\n constructor(\n message: string,\n options: {\n status: number\n errorName: ApiErrorName | null\n details?: ApiErrorDetails\n response?: ApiErrorResponse\n cause?: unknown\n }\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = 'CrawlbruleeError'\n this.status = options.status\n this.errorName = options.errorName\n this.details = options.details\n this.response = options.response\n }\n}\n\n/** Raised for 401 / 403 responses (missing, invalid, or unauthorized API key). */\nexport class AuthenticationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'AuthenticationError'\n }\n}\n\n/**\n * Raised for HTTP 429 responses. When the server included a `retry_after_ms`\n * hint in `details` it is surfaced directly on the instance.\n *\n * `errorName` is always the literal `'too_many_requests'` — the SDK normalizes\n * this even when the server returns a 429 with a different `name` field\n * (e.g. a CDN coalescing upstream rate limiting). The original body is still\n * available on `response`.\n */\nexport class RateLimitError extends CrawlbruleeError {\n override readonly errorName: 'too_many_requests'\n /** Suggested delay (ms) before retrying, when the server provided one. */\n readonly retryAfterMs?: number\n /** Which rate limit was tripped (e.g. `org`, `ip`), when provided. */\n readonly limitedBy?: string\n\n constructor(\n message: string,\n options: {\n status: number\n details?: RateLimitErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'too_many_requests', details: options.details })\n this.name = 'RateLimitError'\n this.errorName = 'too_many_requests'\n this.retryAfterMs = options.details?.retry_after_ms\n this.limitedBy = options.details?.limited_by\n }\n}\n\n/**\n * Raised when the API rejects a request because the org's plan limits would\n * be exceeded (credit limit, concurrency cap, overage hard cap, etc.).\n *\n * `errorName` is always the literal `'usage_allocation_error'`.\n */\nexport class UsageAllocationError extends CrawlbruleeError {\n override readonly errorName: 'usage_allocation_error'\n /** Specific reason the allocation was denied. */\n readonly reason: UsageAllocationErrorDetails['reason']\n /** Current usage / limit snapshot at the time of the rejection. */\n readonly usage?: UsageAllocationErrorDetails['details']\n\n constructor(\n message: string,\n options: {\n status: number\n details: UsageAllocationErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'usage_allocation_error' })\n this.name = 'UsageAllocationError'\n this.errorName = 'usage_allocation_error'\n this.reason = options.details.reason\n this.usage = options.details.details\n }\n}\n\n/** Raised for 4xx responses caused by an invalid request shape or arguments. */\nexport class ValidationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'ValidationError'\n }\n}\n\n/** Raised for 404 responses (e.g. unknown async job ID). */\nexport class NotFoundError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Raised when a request cannot be sent or no structured response is parsed.\n *\n * The `errorName` discriminates the cause:\n * - `'request_timeout'` — the per-request timeout fired.\n * - `'client_closed_request'` — the caller's `AbortSignal` fired.\n * - `null` — generic transport failure (network error, non-JSON body, etc.).\n */\nexport class TransportError extends CrawlbruleeError {\n constructor(\n message: string,\n options: {\n status?: number\n errorName?: 'request_timeout' | 'client_closed_request' | null\n cause?: unknown\n } = {}\n ) {\n super(message, {\n status: options.status ?? 0,\n errorName: options.errorName ?? null,\n cause: options.cause,\n })\n this.name = 'TransportError'\n }\n}\n\n/** Narrow `unknown` to the SDK's base error type. */\nexport function isCrawlbruleeError(err: unknown): err is CrawlbruleeError {\n return err instanceof CrawlbruleeError\n}\n\n/**\n * Map an API error body + HTTP status to the most specific error class.\n *\n * Dispatch is **name-first**: the body's `name` field is the most reliable\n * signal of what went wrong. Status code is used only as a fallback when the\n * name is unrecognized (e.g. a CDN-synthesized error). This avoids\n * miscategorizing things like a 403 with `name: 'not_found'` as an auth error.\n *\n * Internal — used by the HTTP layer.\n */\nexport function createApiError(body: ApiErrorResponse, status: number): CrawlbruleeError {\n const { name, message, details } = body\n const response = body\n\n switch (name) {\n case 'too_many_requests':\n return new RateLimitError(message, {\n status,\n details: details?.error_name === 'too_many_requests' ? details : undefined,\n response,\n })\n\n case 'usage_allocation_error': {\n // Without a structured details block we still want a typed error — fall\n // back to a synthetic `internal_error` reason so callers can branch.\n const usageDetails: UsageAllocationErrorDetails =\n details?.error_name === 'usage_allocation_error'\n ? details\n : { error_name: 'usage_allocation_error', reason: 'internal_error' }\n return new UsageAllocationError(message, { status, details: usageDetails, response })\n }\n\n case 'invalid_credentials':\n case 'access_denied':\n return new AuthenticationError(message, { status, errorName: name, response })\n\n case 'not_found':\n return new NotFoundError(message, { status, errorName: name, response })\n\n case 'validation_error':\n case 'invalid_url':\n case 'url_too_long':\n case 'unsupported_url_schema':\n case 'url_credentials_not_supported':\n case 'blocked_url':\n case 'unsupported_content':\n return new ValidationError(message, { status, errorName: name, response })\n }\n\n // Name was not specific enough — fall back to status-based heuristics, but\n // never override what the name said. A 429 with an unrecognized name still\n // promotes to RateLimitError (the class invariant normalizes errorName).\n if (status === 429) {\n return new RateLimitError(message, { status, response })\n }\n if (status === 401 || status === 403) {\n return new AuthenticationError(message, { status, errorName: name, response })\n }\n if (status === 404) {\n return new NotFoundError(message, { status, errorName: name, response })\n }\n\n return new CrawlbruleeError(message, { status, errorName: name, details, response })\n}\n","import { DEFAULT_BASE_URL } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\n\n/** Function shape compatible with the global `fetch`. */\nexport type FetchLike = typeof fetch\n\n/**\n * Centralized factory for the low-level dependencies the SDK injects into its\n * HTTP layer. Production code resolves these to the runtime's global `fetch`\n * and the burned-in production base URL; tests stub this module to swap in\n * mocks and alternate hosts.\n *\n * This is internal — it is not exported from the package's public entry. Tests\n * import it from `src/instrumentation.js` directly and use `vi.spyOn` to\n * substitute behavior.\n */\nexport const CwblInstrumentation = {\n /**\n * Resolve the `fetch` implementation the SDK should use. Throws a\n * {@link CrawlbruleeError} if the runtime does not expose a global `fetch`.\n */\n getFetch(): FetchLike {\n const g = globalThis as { fetch?: FetchLike }\n if (typeof g.fetch !== 'function') {\n throw new CrawlbruleeError(\n 'No global fetch is available in this runtime. crawlbrulee requires Node.js 20+, Bun, Deno, or a modern browser/edge runtime.',\n { status: 0, errorName: null }\n )\n }\n return g.fetch.bind(globalThis)\n },\n\n /**\n * Resolve the base URL the SDK should target. Returns the production host by\n * default; tests stub this to point at a mock origin.\n */\n getBaseUrl(): string {\n return DEFAULT_BASE_URL\n },\n}\n","import { DEFAULT_REQUEST_TIMEOUT_MS, USER_AGENT } from './config.js'\nimport { TransportError, createApiError, type CrawlbruleeError } from './errors.js'\nimport { CwblInstrumentation, type FetchLike } from './instrumentation.js'\nimport type { ApiErrorResponse } from './types/common.js'\n\n/** HTTP methods used by the SDK. */\nexport type HttpMethod = 'GET' | 'POST'\n\n/** Options the SDK accepts at construction time for the HTTP layer. */\nexport interface HttpClientOptions {\n /** API key sent as `Authorization: Bearer <key>`. */\n apiKey: string\n /**\n * Override the base URL. Trailing slashes are stripped. Falls back to\n * {@link CwblInstrumentation.getBaseUrl} (which resolves to the production\n * host) when unset.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Pass `0` (or omit) to disable the\n * timeout entirely.\n */\n timeoutMs?: number\n}\n\n/** Per-call overrides accepted on every resource method. */\nexport interface RequestOptions {\n /** Abort the request when this signal fires. Composable with the timeout. */\n signal?: AbortSignal\n /**\n * Override the constructor-level `timeoutMs` for this call. Pass `0` to\n * disable the timeout for this call.\n */\n timeoutMs?: number\n}\n\ninterface SendArgs extends RequestOptions {\n method: HttpMethod\n path: string\n body?: unknown\n}\n\ninterface ComposedSignal {\n signal: AbortSignal | undefined\n /** Returns `true` if the abort was triggered by the per-request timeout. */\n timedOut: () => boolean\n /** Releases the timer and any listeners attached to the caller's signal. */\n cleanup: () => void\n}\n\n/**\n * Minimal `fetch`-based HTTP layer used by {@link Crawlbrulee}. Handles:\n *\n * - URL composition (joining `baseUrl` and path safely).\n * - JSON serialization and parsing.\n * - The `Authorization: Bearer …` header.\n * - Composing the caller's `AbortSignal` with an internal timeout signal. The\n * timeout covers the WHOLE request, including the response body read — not\n * just the time-to-headers.\n * - Mapping non-2xx responses to typed `CrawlbruleeError` subclasses via\n * {@link createApiError}.\n *\n * The base URL and `fetch` implementation are sourced from\n * {@link CwblInstrumentation} at construction time so tests can stub the\n * module.\n */\nexport class HttpClient {\n readonly baseUrl: string\n private readonly apiKey: string\n private readonly fetch: FetchLike\n private readonly timeoutMs: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = stripTrailingSlash(options.baseUrl ?? CwblInstrumentation.getBaseUrl())\n this.apiKey = options.apiKey\n this.fetch = CwblInstrumentation.getFetch()\n this.timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n }\n\n /** Send a `GET` request and parse the response as `T`. */\n get<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'GET', path, ...options })\n }\n\n /** Send a `POST` request with a JSON body and parse the response as `T`. */\n post<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'POST', path, body, ...options })\n }\n\n private async send<T>(args: SendArgs): Promise<T> {\n const url = this.buildUrl(args.path)\n const headers = this.buildHeaders(args)\n const body = args.body === undefined ? undefined : JSON.stringify(args.body)\n const composed = this.composeSignal(args.signal, args.timeoutMs)\n\n try {\n let res: Response\n try {\n res = await this.fetch(url, {\n method: args.method,\n headers,\n body,\n signal: composed.signal,\n })\n } catch (cause: unknown) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n\n let text: string\n try {\n text = await res.text()\n } catch (cause: unknown) {\n if (isAbortError(cause)) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n throw new TransportError(`Failed to read response body (status ${res.status}).`, {\n status: res.status,\n cause,\n })\n }\n\n const parsed = parseJsonOrThrow(text, res.status)\n if (!res.ok) throw toApiError(parsed, res.status, text)\n return parsed as T\n } finally {\n composed.cleanup()\n }\n }\n\n private buildUrl(path: string): string {\n if (!path.startsWith('/')) {\n throw new TypeError(`crawlbrulee SDK: path must start with '/' (received '${path}')`)\n }\n return `${this.baseUrl}${path}`\n }\n\n private buildHeaders(args: SendArgs): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'user-agent': USER_AGENT,\n authorization: `Bearer ${this.apiKey}`,\n }\n if (args.body !== undefined) headers['content-type'] = 'application/json'\n return headers\n }\n\n /**\n * Build a single `AbortSignal` that fires when either the caller-supplied\n * signal aborts OR the per-request timeout elapses. The returned `cleanup`\n * callback MUST be invoked on every exit path so we don't leak timers or\n * dead listeners on long-lived caller signals.\n */\n private composeSignal(\n callerSignal: AbortSignal | undefined,\n overrideTimeoutMs: number | undefined\n ): ComposedSignal {\n const timeoutMs = overrideTimeoutMs ?? this.timeoutMs\n const hasTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0\n\n if (!hasTimeout && !callerSignal) {\n return { signal: undefined, timedOut: () => false, cleanup: () => {} }\n }\n\n if (!hasTimeout) {\n return { signal: callerSignal, timedOut: () => false, cleanup: () => {} }\n }\n\n const controller = new AbortController()\n let didTimeout = false\n const timer = setTimeout(() => {\n didTimeout = true\n controller.abort(new Error('request_timeout'))\n }, timeoutMs)\n\n let onCallerAbort: (() => void) | undefined\n if (callerSignal) {\n if (callerSignal.aborted) {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n } else {\n onCallerAbort = () => {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n }\n callerSignal.addEventListener('abort', onCallerAbort, { once: true })\n }\n }\n\n const cleanup = () => {\n clearTimeout(timer)\n if (onCallerAbort && callerSignal) {\n callerSignal.removeEventListener('abort', onCallerAbort)\n }\n }\n\n return { signal: controller.signal, timedOut: () => didTimeout, cleanup }\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError'\n}\n\nfunction abortOrNetworkError(cause: unknown, timedOut: boolean, timeoutMs: number): TransportError {\n if (isAbortError(cause)) {\n if (timedOut) {\n return new TransportError(`Request timed out after ${timeoutMs}ms.`, {\n errorName: 'request_timeout',\n cause,\n })\n }\n return new TransportError('Request aborted by caller.', {\n errorName: 'client_closed_request',\n cause,\n })\n }\n return new TransportError(formatNetworkErrorMessage(cause), { cause })\n}\n\nfunction formatNetworkErrorMessage(cause: unknown): string {\n if (cause instanceof Error) {\n return `Network error: ${cause.message}`\n }\n return 'Network error: unknown failure while sending the request.'\n}\n\nfunction parseJsonOrThrow(text: string, status: number): unknown {\n if (text === '') return {}\n try {\n return JSON.parse(text)\n } catch (cause: unknown) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text\n throw new TransportError(`Unexpected non-JSON response (status ${status}): ${preview}`, {\n status,\n cause,\n })\n }\n}\n\nfunction toApiError(parsed: unknown, status: number, rawText: string): CrawlbruleeError {\n if (isApiErrorResponse(parsed)) {\n return createApiError(parsed, status)\n }\n const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}…` : rawText\n return new TransportError(`HTTP ${status}: ${preview || '(empty body)'}`, { status })\n}\n\nfunction isApiErrorResponse(value: unknown): value is ApiErrorResponse {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return typeof v.name === 'string' && typeof v.message === 'string'\n}\n","import { ENV_API_KEY } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\nimport { HttpClient, type RequestOptions } from './http.js'\nimport type {\n AsyncJobStatusResponse,\n AsyncScrapeRequest,\n AsyncScrapeResponse,\n MapRequest,\n MapResponse,\n ScrapeCompleteWebhook,\n ScrapeRequest,\n ScrapeResponse,\n UsageResponse,\n WhoamiResponse,\n} from './types/index.js'\n\n/** Options accepted by the {@link Crawlbrulee} constructor. */\nexport interface CrawlbruleeOptions {\n /**\n * API key sent as `Authorization: Bearer <key>`. Required — to read from the\n * environment instead, use {@link Crawlbrulee.fromEnv}. Leading and trailing\n * whitespace is stripped; an empty / whitespace-only value is rejected.\n */\n apiKey: string\n /**\n * Override the base URL the SDK targets. Defaults to the production host\n * ({@link DEFAULT_BASE_URL}). Intended for local development and staging\n * (e.g. `https://api.staging.crawlbrulee.com`) — production callers should\n * leave it unset. Trailing slashes are stripped.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Defaults to `0` (no timeout). Set to a\n * positive number to abort slow requests; a per-call `timeoutMs` override\n * takes precedence. The timeout covers the WHOLE request, including the\n * response body read.\n */\n timeoutMs?: number\n}\n\n/**\n * Options accepted by {@link Crawlbrulee.waitForScrape}.\n *\n * Note: `timeoutMs` here is the OVERALL wait budget across all polls — not the\n * per-HTTP-request timeout. The per-poll HTTP timeout is whatever the client\n * was constructed with; if you want to bound each individual poll, construct\n * the client with `timeoutMs` set.\n */\nexport interface WaitForScrapeOptions extends Omit<RequestOptions, 'timeoutMs'> {\n /** Time between status polls in milliseconds. Default `2000`. */\n intervalMs?: number\n /**\n * Maximum total time to wait before giving up, in milliseconds. Default\n * `300_000` (5 minutes). Pass `0` to wait indefinitely.\n */\n timeoutMs?: number\n}\n\n/**\n * Official client for the crawlbrulee API.\n *\n * @example\n * ```ts\n * import { Crawlbrulee } from '@crawlbrulee/sdk'\n *\n * const crawlbrulee = new Crawlbrulee({ apiKey: 'cwbl_…' })\n * // or read CRAWLBRULEE_API_KEY from the environment:\n * const crawlbrulee = Crawlbrulee.fromEnv()\n *\n * const page = await crawlbrulee.scrape({\n * url: 'https://example.com',\n * extract: { markdown: true, links: true },\n * })\n * console.log(page.markdown)\n * ```\n */\nexport class Crawlbrulee {\n /** Resolved base URL — trailing slash already stripped. */\n readonly baseUrl: string\n /** Underlying HTTP layer. Exposed for advanced use cases (custom endpoints). */\n readonly http: HttpClient\n\n constructor(options: CrawlbruleeOptions) {\n const apiKey = options.apiKey?.trim()\n if (!apiKey) {\n throw new CrawlbruleeError(\n `Missing API key. Pass { apiKey } to Crawlbrulee or call Crawlbrulee.fromEnv() to read ${ENV_API_KEY}.`,\n { status: 0, errorName: null }\n )\n }\n this.http = new HttpClient({ apiKey, baseUrl: options.baseUrl, timeoutMs: options.timeoutMs })\n this.baseUrl = this.http.baseUrl\n }\n\n /**\n * Build a {@link Crawlbrulee} reading the API key from\n * `process.env.CRAWLBRULEE_API_KEY`. Throws if the variable is unset, empty,\n * or whitespace.\n *\n * Any other constructor option can be passed via `overrides`.\n *\n * @example\n * ```ts\n * const crawlbrulee = Crawlbrulee.fromEnv()\n * const crawlbrulee = Crawlbrulee.fromEnv({ timeoutMs: 30_000 })\n * ```\n */\n static fromEnv(overrides: Omit<CrawlbruleeOptions, 'apiKey'> = {}): Crawlbrulee {\n const apiKey = readEnv(ENV_API_KEY)\n if (!apiKey) {\n throw new CrawlbruleeError(\n `${ENV_API_KEY} is not set. Export it in your shell, or pass apiKey to new Crawlbrulee({ apiKey }).`,\n { status: 0, errorName: null }\n )\n }\n return new Crawlbrulee({ ...overrides, apiKey })\n }\n\n // ------------------------------------------------------------------\n // Scraping\n // ------------------------------------------------------------------\n\n /**\n * Scrape a URL synchronously and return the extracted content.\n *\n * The request blocks until the scrape is finished. For long-running jobs\n * (heavy JS rendering, screenshots of long pages) prefer\n * {@link Crawlbrulee.scrapeAsync} so the connection isn't held open.\n *\n * @param request — body for `POST /api/scrape`.\n * @param options — per-call timeout and abort signal.\n */\n scrape(request: ScrapeRequest, options?: RequestOptions): Promise<ScrapeResponse> {\n return this.http.post<ScrapeResponse>('/api/scrape', request, options)\n }\n\n /**\n * Submit an asynchronous scrape job and return its `job_id`. Poll the job\n * with {@link Crawlbrulee.getScrapeStatus} or wait for completion with\n * {@link Crawlbrulee.waitForScrape}.\n *\n * Pass an optional `webhook` to have the API deliver a signed\n * `scrape.complete` `POST` to your endpoint when the job finishes (see\n * {@link AsyncScrapeWebhook}). This field is async-only.\n */\n scrapeAsync(request: AsyncScrapeRequest, options?: RequestOptions): Promise<AsyncScrapeResponse> {\n return this.http.post<AsyncScrapeResponse>('/api/scrape/async', request, options)\n }\n\n /** Look up the current status of an async scrape job. */\n getScrapeStatus(jobId: string, options?: RequestOptions): Promise<AsyncJobStatusResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<AsyncJobStatusResponse>(\n `/api/scrape/status/${encodeURIComponent(jobId)}`,\n options\n )\n }\n\n /**\n * Fetch the result of a completed async scrape job. Throws if the job is\n * still pending/running — call {@link Crawlbrulee.getScrapeStatus}\n * first, or use {@link Crawlbrulee.waitForScrape} to poll-then-fetch.\n */\n getScrapeResult(jobId: string, options?: RequestOptions): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<ScrapeResponse>(`/api/scrape/result/${encodeURIComponent(jobId)}`, options)\n }\n\n /**\n * Fetch the scrape result referenced by a `scrape.complete` webhook body.\n *\n * Always verify the webhook signature with `verifyWebhookSignature` before\n * acting on it; this method trusts the parsed body it is handed.\n *\n * Behavior by `data.status`:\n * - `success` — delegates to {@link Crawlbrulee.getScrapeResult} for the\n * webhook's `job_id` and returns the parsed result.\n * - `failed` — throws a {@link CrawlbruleeError} carrying `data.error`\n * (`errorName: 'job_failed'`); there is no result to fetch.\n * - `cancelled` — throws a {@link CrawlbruleeError}\n * (`errorName: 'client_closed_request'`).\n *\n * A non-`scrape.complete` envelope throws a {@link CrawlbruleeError}\n * defensively. Any HTTP error from the underlying fetch propagates as the\n * usual typed `CrawlbruleeError` subclass.\n */\n async fetchScrapeResultFromWebhook(\n webhook: ScrapeCompleteWebhook,\n options?: RequestOptions\n ): Promise<ScrapeResponse> {\n if (webhook?.event !== 'scrape.complete') {\n throw new CrawlbruleeError(\n `Expected a 'scrape.complete' webhook but received '${String(webhook?.event)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n\n const { job_id: jobId, status, error } = webhook.data\n\n switch (status) {\n case 'success':\n return this.getScrapeResult(jobId, options)\n\n case 'failed':\n throw new CrawlbruleeError(error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'cancelled':\n throw new CrawlbruleeError(`Async scrape job ${jobId} was cancelled.`, {\n status: 0,\n errorName: 'client_closed_request',\n })\n\n default:\n throw new CrawlbruleeError(\n `Async scrape webhook for job ${jobId} carried an unexpected status '${String(status)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n }\n\n /**\n * Poll an async scrape job until it reaches a terminal state, then return\n * the scrape result.\n *\n * Throws a {@link CrawlbruleeError} when:\n * - the job ends in `failed` (`errorName: 'job_failed'`),\n * - the server reports an unexpected status (`errorName: 'job_failed'`),\n * - the overall wait exceeds `timeoutMs` (`errorName: 'request_timeout'`),\n * - the caller's `signal` aborts (`errorName: 'client_closed_request'`).\n */\n async waitForScrape(jobId: string, options: WaitForScrapeOptions = {}): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n const intervalMs = options.intervalMs ?? 2000\n const timeoutMs = options.timeoutMs ?? 300_000\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY\n\n while (true) {\n throwIfAborted(options.signal)\n if (Date.now() >= deadline) {\n throw new CrawlbruleeError(\n `Timed out after ${timeoutMs}ms waiting for async scrape job ${jobId}.`,\n { status: 0, errorName: 'request_timeout' }\n )\n }\n\n const status = await this.getScrapeStatus(jobId, { signal: options.signal })\n\n switch (status.status) {\n case 'done':\n return this.getScrapeResult(jobId, { signal: options.signal })\n\n case 'failed':\n throw new CrawlbruleeError(status.error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'pending':\n case 'running':\n break\n\n default:\n throw new CrawlbruleeError(\n `Async scrape job ${jobId} returned unexpected status '${String(status.status)}'.`,\n { status: 0, errorName: 'job_failed' }\n )\n }\n\n await sleep(intervalMs, options.signal)\n }\n }\n\n // ------------------------------------------------------------------\n // Mapping\n // ------------------------------------------------------------------\n\n /**\n * Build (or return a cached) site link-map for a domain. Combines sitemap\n * discovery with the freshest cached homepage scrape when available.\n */\n map(request: MapRequest, options?: RequestOptions): Promise<MapResponse> {\n return this.http.post<MapResponse>('/api/map', request, options)\n }\n\n // ------------------------------------------------------------------\n // Account\n // ------------------------------------------------------------------\n\n /**\n * Return the current billing-cycle usage: total/used/available credits,\n * used quota percentage, max concurrency, and when the cycle resets.\n */\n usage(options?: RequestOptions): Promise<UsageResponse> {\n return this.http.get<UsageResponse>('/api/usage', options)\n }\n\n /**\n * Return the organization name and identifying details of the API token\n * used to authenticate this request. Useful for confirming which key is in\n * use before performing destructive operations.\n */\n whoami(options?: RequestOptions): Promise<WhoamiResponse> {\n return this.http.get<WhoamiResponse>('/api/whoami', options)\n }\n}\n\n/**\n * Defensive read of `process.env[name]`. Guards both the absence of `process`\n * (browser / edge runtimes) and Deno's permission throw on env access without\n * `--allow-env`.\n */\nfunction readEnv(name: string): string | undefined {\n try {\n if (typeof process === 'undefined' || !process.env) return undefined\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined\n } catch {\n return undefined\n }\n}\n\nfunction assertNonEmptyJobId(jobId: string): void {\n if (typeof jobId !== 'string' || jobId.trim().length === 0) {\n throw new CrawlbruleeError('jobId must be a non-empty string.', {\n status: 0,\n errorName: null,\n })\n }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal.reason,\n })\n }\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer)\n reject(\n new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal?.reason,\n })\n )\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n onAbort()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n","/**\n * Verification for async scrape completion webhooks.\n *\n * {@link verifyWebhookSignature} validates the signature crawlbrulee attaches to\n * every webhook delivery. It is a standalone, network-free helper built on Web\n * Crypto (`globalThis.crypto.subtle`) so it runs unchanged on Node.js 22+,\n * browsers, Bun, Deno, and edge runtimes — it never touches `node:crypto`.\n */\n\n/** HTTP header carrying the primary webhook signature (always present). */\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Cwbl-Signature'\n\n/**\n * HTTP header carrying a signature produced with the previous signing secret.\n * Present only during a signing-secret rotation grace window.\n */\nexport const WEBHOOK_SIGNATURE_ROTATED_HEADER = 'X-Cwbl-Signature-Rotated'\n\n/** HTTP header carrying the unique event id, useful for delivery de-duplication. */\nexport const WEBHOOK_EVENT_ID_HEADER = 'X-Cwbl-Event-Id'\n\n/** Default replay-protection window (seconds) applied to the signed timestamp. */\nexport const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300\n\n/** Which signature header satisfied verification. */\nexport type WebhookSignatureSource = 'primary' | 'rotated'\n\n/**\n * Why a webhook signature failed to verify.\n *\n * - `missing_signature` — neither the primary nor the rotated header was present.\n * - `malformed_signature` — a header was present but not in the expected\n * `t=<unix_seconds>,v1=<64_hex>` format.\n * - `timestamp_out_of_tolerance` — the signed timestamp drifted further from now\n * than `toleranceSeconds` allows (replay protection).\n * - `signature_mismatch` — a well-formed, in-tolerance signature did not match\n * the one computed from the payload and secret.\n */\nexport type WebhookVerificationFailureReason =\n | 'missing_signature'\n | 'malformed_signature'\n | 'timestamp_out_of_tolerance'\n | 'signature_mismatch'\n\n/** Result of {@link verifyWebhookSignature}. Verification failure is returned, not thrown. */\nexport type WebhookVerificationResult =\n | { verified: true; signedWith: WebhookSignatureSource }\n | { verified: false; reason: WebhookVerificationFailureReason }\n\n/** Options for {@link verifyWebhookSignature}. */\nexport interface VerifyWebhookSignatureOptions {\n /**\n * The raw request body, exactly as received. Pass the bytes/string the server\n * signed — do NOT re-serialize parsed JSON, or the signature will not match.\n */\n payload: string | Uint8Array\n /**\n * The request headers. Accepts a fetch `Headers` instance or a plain object\n * (Express/Node give lowercased keys, values possibly arrays). Lookup is\n * case-insensitive.\n */\n headers: Headers | Record<string, string | string[] | undefined>\n /** The current signing secret (`whsec_…`). */\n secret: string\n /**\n * Replay-protection window in seconds. Defaults to\n * {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS} (300). Pass `0` (or any falsy\n * value) to disable the timestamp check entirely.\n */\n toleranceSeconds?: number\n}\n\nconst SIGNATURE_FORMAT = /^t=(\\d+),v1=([0-9a-f]{64})$/\n\ninterface ParsedSignature {\n timestamp: number\n signature: string\n}\n\n/**\n * Verify a crawlbrulee webhook signature against the primary and rotated\n * headers.\n *\n * The signing scheme matches the backend:\n * - the signed payload is `` `${t}.${rawBody}` `` where `t` is the unix-seconds\n * integer from the header and `rawBody` is the raw request body,\n * - the signature is `HMAC-SHA256(secret, signedPayload)` as lowercase hex,\n * - the header value is `t=<unix_seconds>,v1=<64_hex>`.\n *\n * The supplied `secret` is tried against the primary header first, then the\n * rotated header (which the API emits during a signing-secret rotation grace\n * window). Whichever matches wins, and the result reports which header it was.\n *\n * This NEVER throws on a verification failure — failures are normal control\n * flow and are returned as `{ verified: false, reason }`.\n *\n * @example\n * ```ts\n * const result = await verifyWebhookSignature({\n * payload: rawBody,\n * headers: req.headers,\n * secret: process.env.CRAWLBRULEE_WEBHOOK_SECRET!,\n * })\n * if (!result.verified) return res.status(400).end()\n * ```\n */\nexport async function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): Promise<WebhookVerificationResult> {\n const { payload, headers, secret } = options\n const toleranceSeconds = options.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS\n\n const primaryHeader = getHeader(headers, WEBHOOK_SIGNATURE_HEADER)\n const rotatedHeader = getHeader(headers, WEBHOOK_SIGNATURE_ROTATED_HEADER)\n\n if (primaryHeader === undefined && rotatedHeader === undefined) {\n return { verified: false, reason: 'missing_signature' }\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000)\n const body = toBytes(payload)\n const key = await importHmacKey(secret)\n\n // Track the \"best\" failure reason so the result is informative: a real\n // mismatch should win over a malformed sibling header. Order from least to\n // most specific.\n let failure: WebhookVerificationFailureReason = 'malformed_signature'\n\n for (const source of ['primary', 'rotated'] as const) {\n const raw = source === 'primary' ? primaryHeader : rotatedHeader\n if (raw === undefined) continue\n\n const parsed = parseSignatureHeader(raw)\n if (!parsed) {\n // A malformed header can't verify; keep looking at the other one.\n continue\n }\n\n if (toleranceSeconds && Math.abs(nowSeconds - parsed.timestamp) > toleranceSeconds) {\n failure = mostSpecificFailure(failure, 'timestamp_out_of_tolerance')\n continue\n }\n\n const expected = await computeSignatureHex(key, parsed.timestamp, body)\n if (constantTimeEqualHex(expected, parsed.signature)) {\n return { verified: true, signedWith: source }\n }\n\n failure = mostSpecificFailure(failure, 'signature_mismatch')\n }\n\n return { verified: false, reason: failure }\n}\n\n/**\n * Rank verification failures so the returned reason reflects the most\n * actionable problem encountered across the two headers.\n */\nfunction mostSpecificFailure(\n current: WebhookVerificationFailureReason,\n candidate: WebhookVerificationFailureReason\n): WebhookVerificationFailureReason {\n const rank: Record<WebhookVerificationFailureReason, number> = {\n missing_signature: 0,\n malformed_signature: 1,\n timestamp_out_of_tolerance: 2,\n signature_mismatch: 3,\n }\n return rank[candidate] > rank[current] ? candidate : current\n}\n\n/** Case-insensitive header lookup over `Headers` or a plain object. */\nfunction getHeader(\n headers: Headers | Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n if (typeof Headers !== 'undefined' && headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const target = name.toLowerCase()\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() !== target) continue\n const value = (headers as Record<string, string | string[] | undefined>)[key]\n if (Array.isArray(value)) return value[0]\n return value ?? undefined\n }\n return undefined\n}\n\nfunction parseSignatureHeader(value: string): ParsedSignature | null {\n const match = SIGNATURE_FORMAT.exec(value.trim())\n if (!match) return null\n const timestamp = Number(match[1])\n if (!Number.isSafeInteger(timestamp)) return null\n return { timestamp, signature: match[2]! }\n}\n\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload\n}\n\n/**\n * Web Crypto types, derived from the runtime global so we don't have to pull in\n * the DOM `lib` (the SDK compiles against `lib: ES2022` + `@types/node`).\n */\ntype SubtleCryptoLike = typeof globalThis.crypto.subtle\ntype CryptoKeyLike = Awaited<ReturnType<SubtleCryptoLike['importKey']>>\n\nfunction importHmacKey(secret: string): Promise<CryptoKeyLike> {\n return getSubtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n}\n\nasync function computeSignatureHex(\n key: CryptoKeyLike,\n timestamp: number,\n body: Uint8Array\n): Promise<string> {\n const prefix = new TextEncoder().encode(`${timestamp}.`)\n const message = new Uint8Array(prefix.length + body.length)\n message.set(prefix, 0)\n message.set(body, prefix.length)\n const digest = await getSubtle().sign('HMAC', key, message)\n return toHex(new Uint8Array(digest))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Length-checked, constant-time comparison of two lowercase hex strings. Folds\n * every byte into an accumulator with XOR — never early-returns on the first\n * mismatch — so timing does not leak how much of the signature matched.\n */\nfunction constantTimeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\nfunction getSubtle(): SubtleCryptoLike {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) {\n throw new Error(\n 'Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 20+, Bun, Deno, or a modern browser/edge runtime.'\n )\n }\n return subtle\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/instrumentation.ts","../src/http.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Production base URL of the crawlbrulee API. Used by default when the caller\n * doesn't pass a `baseUrl` to {@link Crawlbrulee}. Local development and\n * staging callers point at their own host via that option.\n */\nexport const DEFAULT_BASE_URL = 'https://api.crawlbrulee.com'\n\n/** Default request timeout when the caller doesn't specify one (0 disables the timeout). */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 0\n\n/** Environment variable read by `Crawlbrulee.fromEnv()` to source the API key. */\nexport const ENV_API_KEY = 'CRAWLBRULEE_API_KEY'\n\n/** Identifies the SDK in the `User-Agent` header. Kept in one place for easy bumping. */\nexport const USER_AGENT = '@crawlbrulee/sdk/0.7.0 (node)'\n","import type {\n ApiErrorDetails,\n ApiErrorName,\n ApiErrorResponse,\n RateLimitErrorDetails,\n UsageAllocationErrorDetails,\n} from './types/common.js'\n\n/**\n * Base error class for every failure raised by the SDK.\n *\n * Two kinds of failures end up here:\n *\n * 1. **API errors** — the server returned a non-2xx response with a well-formed\n * JSON body. In that case `status`, `errorName` and (sometimes) `details`\n * are populated.\n * 2. **Transport errors** — the request never produced a structured response\n * (network failure, abort, timeout, non-JSON body, etc.). In that case\n * `status` may be `0` and `errorName` is one of the synthetic transport\n * names (`request_timeout`, `client_closed_request`) or `null`.\n *\n * Typed subclasses are exported for the most common cases. To branch on more\n * specific server-side errors, switch on `err.errorName` or use the\n * {@link isCrawlbruleeError} helper.\n */\nexport class CrawlbruleeError extends Error {\n /** HTTP status code; `0` for transport-level failures with no response. */\n readonly status: number\n /** The `name` field from the API error body, or `null` for transport errors. */\n readonly errorName: ApiErrorName | null\n /** Structured detail block from the API error body, if any. */\n readonly details?: ApiErrorDetails\n /** The original parsed error body, when one was received. */\n readonly response?: ApiErrorResponse\n\n constructor(\n message: string,\n options: {\n status: number\n errorName: ApiErrorName | null\n details?: ApiErrorDetails\n response?: ApiErrorResponse\n cause?: unknown\n }\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = 'CrawlbruleeError'\n this.status = options.status\n this.errorName = options.errorName\n this.details = options.details\n this.response = options.response\n }\n}\n\n/** Raised for 401 / 403 responses (missing, invalid, or unauthorized API key). */\nexport class AuthenticationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'AuthenticationError'\n }\n}\n\n/**\n * Raised for HTTP 429 responses. When the server included a `retry_after_ms`\n * hint in `details` it is surfaced directly on the instance.\n *\n * `errorName` is always the literal `'too_many_requests'` — the SDK normalizes\n * this even when the server returns a 429 with a different `name` field\n * (e.g. a CDN coalescing upstream rate limiting). The original body is still\n * available on `response`.\n */\nexport class RateLimitError extends CrawlbruleeError {\n override readonly errorName: 'too_many_requests'\n /** Suggested delay (ms) before retrying, when the server provided one. */\n readonly retryAfterMs?: number\n /** Which rate limit was tripped (e.g. `org`, `ip`), when provided. */\n readonly limitedBy?: string\n\n constructor(\n message: string,\n options: {\n status: number\n details?: RateLimitErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'too_many_requests', details: options.details })\n this.name = 'RateLimitError'\n this.errorName = 'too_many_requests'\n this.retryAfterMs = options.details?.retry_after_ms\n this.limitedBy = options.details?.limited_by\n }\n}\n\n/**\n * Raised when the API rejects a request because the org's plan limits would\n * be exceeded (credit limit, concurrency cap, overage hard cap, etc.).\n *\n * `errorName` is always the literal `'usage_allocation_error'`.\n */\nexport class UsageAllocationError extends CrawlbruleeError {\n override readonly errorName: 'usage_allocation_error'\n /** Specific reason the allocation was denied. */\n readonly reason: UsageAllocationErrorDetails['reason']\n /** Current usage / limit snapshot at the time of the rejection. */\n readonly usage?: UsageAllocationErrorDetails['details']\n\n constructor(\n message: string,\n options: {\n status: number\n details: UsageAllocationErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'usage_allocation_error' })\n this.name = 'UsageAllocationError'\n this.errorName = 'usage_allocation_error'\n this.reason = options.details.reason\n this.usage = options.details.details\n }\n}\n\n/** Raised for 4xx responses caused by an invalid request shape or arguments. */\nexport class ValidationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'ValidationError'\n }\n}\n\n/** Raised for 404 responses (e.g. unknown async job ID). */\nexport class NotFoundError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Raised when a request cannot be sent or no structured response is parsed.\n *\n * The `errorName` discriminates the cause:\n * - `'request_timeout'` — the per-request timeout fired.\n * - `'client_closed_request'` — the caller's `AbortSignal` fired.\n * - `null` — generic transport failure (network error, non-JSON body, etc.).\n */\nexport class TransportError extends CrawlbruleeError {\n constructor(\n message: string,\n options: {\n status?: number\n errorName?: 'request_timeout' | 'client_closed_request' | null\n cause?: unknown\n } = {}\n ) {\n super(message, {\n status: options.status ?? 0,\n errorName: options.errorName ?? null,\n cause: options.cause,\n })\n this.name = 'TransportError'\n }\n}\n\n/** Narrow `unknown` to the SDK's base error type. */\nexport function isCrawlbruleeError(err: unknown): err is CrawlbruleeError {\n return err instanceof CrawlbruleeError\n}\n\n/**\n * Map an API error body + HTTP status to the most specific error class.\n *\n * Dispatch is **name-first**: the body's `name` field is the most reliable\n * signal of what went wrong. Status code is used only as a fallback when the\n * name is unrecognized (e.g. a CDN-synthesized error). This avoids\n * miscategorizing things like a 403 with `name: 'not_found'` as an auth error.\n *\n * Internal — used by the HTTP layer.\n */\nexport function createApiError(body: ApiErrorResponse, status: number): CrawlbruleeError {\n const { name, message, details } = body\n const response = body\n\n switch (name) {\n case 'too_many_requests':\n return new RateLimitError(message, {\n status,\n details: details?.error_name === 'too_many_requests' ? details : undefined,\n response,\n })\n\n case 'usage_allocation_error': {\n // Without a structured details block we still want a typed error — fall\n // back to a synthetic `internal_error` reason so callers can branch.\n const usageDetails: UsageAllocationErrorDetails =\n details?.error_name === 'usage_allocation_error'\n ? details\n : { error_name: 'usage_allocation_error', reason: 'internal_error' }\n return new UsageAllocationError(message, { status, details: usageDetails, response })\n }\n\n case 'invalid_credentials':\n case 'access_denied':\n return new AuthenticationError(message, { status, errorName: name, response })\n\n case 'not_found':\n return new NotFoundError(message, { status, errorName: name, response })\n\n case 'validation_error':\n case 'invalid_url':\n case 'url_too_long':\n case 'unsupported_url_schema':\n case 'url_credentials_not_supported':\n case 'blocked_url':\n case 'unsupported_content':\n return new ValidationError(message, { status, errorName: name, response })\n }\n\n // Name was not specific enough — fall back to status-based heuristics, but\n // never override what the name said. A 429 with an unrecognized name still\n // promotes to RateLimitError (the class invariant normalizes errorName).\n if (status === 429) {\n return new RateLimitError(message, { status, response })\n }\n if (status === 401 || status === 403) {\n return new AuthenticationError(message, { status, errorName: name, response })\n }\n if (status === 404) {\n return new NotFoundError(message, { status, errorName: name, response })\n }\n\n return new CrawlbruleeError(message, { status, errorName: name, details, response })\n}\n","import { DEFAULT_BASE_URL } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\n\n/** Function shape compatible with the global `fetch`. */\nexport type FetchLike = typeof fetch\n\n/**\n * Centralized factory for the low-level dependencies the SDK injects into its\n * HTTP layer. Production code resolves these to the runtime's global `fetch`\n * and the burned-in production base URL; tests stub this module to swap in\n * mocks and alternate hosts.\n *\n * This is internal — it is not exported from the package's public entry. Tests\n * import it from `src/instrumentation.js` directly and use `vi.spyOn` to\n * substitute behavior.\n */\nexport const CwblInstrumentation = {\n /**\n * Resolve the `fetch` implementation the SDK should use. Throws a\n * {@link CrawlbruleeError} if the runtime does not expose a global `fetch`.\n */\n getFetch(): FetchLike {\n const g = globalThis as { fetch?: FetchLike }\n if (typeof g.fetch !== 'function') {\n throw new CrawlbruleeError(\n 'No global fetch is available in this runtime. crawlbrulee requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.',\n { status: 0, errorName: null }\n )\n }\n return g.fetch.bind(globalThis)\n },\n\n /**\n * Resolve the base URL the SDK should target. Returns the production host by\n * default; tests stub this to point at a mock origin.\n */\n getBaseUrl(): string {\n return DEFAULT_BASE_URL\n },\n}\n","import { DEFAULT_REQUEST_TIMEOUT_MS, USER_AGENT } from './config.js'\nimport { TransportError, createApiError, type CrawlbruleeError } from './errors.js'\nimport { CwblInstrumentation, type FetchLike } from './instrumentation.js'\nimport type { ApiErrorResponse } from './types/common.js'\n\n/** HTTP methods used by the SDK. */\nexport type HttpMethod = 'GET' | 'POST'\n\n/** Options the SDK accepts at construction time for the HTTP layer. */\nexport interface HttpClientOptions {\n /** API key sent as `Authorization: Bearer <key>`. */\n apiKey: string\n /**\n * Override the base URL. Trailing slashes are stripped. Falls back to\n * {@link CwblInstrumentation.getBaseUrl} (which resolves to the production\n * host) when unset.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Pass `0` (or omit) to disable the\n * timeout entirely.\n */\n timeoutMs?: number\n}\n\n/** Per-call overrides accepted on every resource method. */\nexport interface RequestOptions {\n /** Abort the request when this signal fires. Composable with the timeout. */\n signal?: AbortSignal\n /**\n * Override the constructor-level `timeoutMs` for this call. Pass `0` to\n * disable the timeout for this call.\n */\n timeoutMs?: number\n}\n\ninterface SendArgs extends RequestOptions {\n method: HttpMethod\n path: string\n body?: unknown\n}\n\ninterface ComposedSignal {\n signal: AbortSignal | undefined\n /** Returns `true` if the abort was triggered by the per-request timeout. */\n timedOut: () => boolean\n /** Releases the timer and any listeners attached to the caller's signal. */\n cleanup: () => void\n}\n\n/**\n * Minimal `fetch`-based HTTP layer used by {@link Crawlbrulee}. Handles:\n *\n * - URL composition (joining `baseUrl` and path safely).\n * - JSON serialization and parsing.\n * - The `Authorization: Bearer …` header.\n * - Composing the caller's `AbortSignal` with an internal timeout signal. The\n * timeout covers the WHOLE request, including the response body read — not\n * just the time-to-headers.\n * - Mapping non-2xx responses to typed `CrawlbruleeError` subclasses via\n * {@link createApiError}.\n *\n * The base URL and `fetch` implementation are sourced from\n * {@link CwblInstrumentation} at construction time so tests can stub the\n * module.\n */\nexport class HttpClient {\n readonly baseUrl: string\n private readonly apiKey: string\n private readonly fetch: FetchLike\n private readonly timeoutMs: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = stripTrailingSlash(options.baseUrl ?? CwblInstrumentation.getBaseUrl())\n this.apiKey = options.apiKey\n this.fetch = CwblInstrumentation.getFetch()\n this.timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n }\n\n /** Send a `GET` request and parse the response as `T`. */\n get<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'GET', path, ...options })\n }\n\n /** Send a `POST` request with a JSON body and parse the response as `T`. */\n post<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'POST', path, body, ...options })\n }\n\n private async send<T>(args: SendArgs): Promise<T> {\n const url = this.buildUrl(args.path)\n const headers = this.buildHeaders(args)\n const body = args.body === undefined ? undefined : JSON.stringify(args.body)\n const composed = this.composeSignal(args.signal, args.timeoutMs)\n\n try {\n let res: Response\n try {\n res = await this.fetch(url, {\n method: args.method,\n headers,\n body,\n signal: composed.signal,\n })\n } catch (cause: unknown) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n\n let text: string\n try {\n text = await res.text()\n } catch (cause: unknown) {\n if (isAbortError(cause)) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n throw new TransportError(`Failed to read response body (status ${res.status}).`, {\n status: res.status,\n cause,\n })\n }\n\n const parsed = parseJsonOrThrow(text, res.status)\n if (!res.ok) throw toApiError(parsed, res.status, text)\n return parsed as T\n } finally {\n composed.cleanup()\n }\n }\n\n private buildUrl(path: string): string {\n if (!path.startsWith('/')) {\n throw new TypeError(`crawlbrulee SDK: path must start with '/' (received '${path}')`)\n }\n return `${this.baseUrl}${path}`\n }\n\n private buildHeaders(args: SendArgs): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'user-agent': USER_AGENT,\n authorization: `Bearer ${this.apiKey}`,\n }\n if (args.body !== undefined) headers['content-type'] = 'application/json'\n return headers\n }\n\n /**\n * Build a single `AbortSignal` that fires when either the caller-supplied\n * signal aborts OR the per-request timeout elapses. The returned `cleanup`\n * callback MUST be invoked on every exit path so we don't leak timers or\n * dead listeners on long-lived caller signals.\n */\n private composeSignal(\n callerSignal: AbortSignal | undefined,\n overrideTimeoutMs: number | undefined\n ): ComposedSignal {\n const timeoutMs = overrideTimeoutMs ?? this.timeoutMs\n const hasTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0\n\n if (!hasTimeout && !callerSignal) {\n return { signal: undefined, timedOut: () => false, cleanup: () => {} }\n }\n\n if (!hasTimeout) {\n return { signal: callerSignal, timedOut: () => false, cleanup: () => {} }\n }\n\n const controller = new AbortController()\n let didTimeout = false\n const timer = setTimeout(() => {\n didTimeout = true\n controller.abort(new Error('request_timeout'))\n }, timeoutMs)\n\n let onCallerAbort: (() => void) | undefined\n if (callerSignal) {\n if (callerSignal.aborted) {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n } else {\n onCallerAbort = () => {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n }\n callerSignal.addEventListener('abort', onCallerAbort, { once: true })\n }\n }\n\n const cleanup = () => {\n clearTimeout(timer)\n if (onCallerAbort && callerSignal) {\n callerSignal.removeEventListener('abort', onCallerAbort)\n }\n }\n\n return { signal: controller.signal, timedOut: () => didTimeout, cleanup }\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError'\n}\n\nfunction abortOrNetworkError(cause: unknown, timedOut: boolean, timeoutMs: number): TransportError {\n if (isAbortError(cause)) {\n if (timedOut) {\n return new TransportError(`Request timed out after ${timeoutMs}ms.`, {\n errorName: 'request_timeout',\n cause,\n })\n }\n return new TransportError('Request aborted by caller.', {\n errorName: 'client_closed_request',\n cause,\n })\n }\n return new TransportError(formatNetworkErrorMessage(cause), { cause })\n}\n\nfunction formatNetworkErrorMessage(cause: unknown): string {\n if (cause instanceof Error) {\n return `Network error: ${cause.message}`\n }\n return 'Network error: unknown failure while sending the request.'\n}\n\nfunction parseJsonOrThrow(text: string, status: number): unknown {\n if (text === '') return {}\n try {\n return JSON.parse(text)\n } catch (cause: unknown) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text\n throw new TransportError(`Unexpected non-JSON response (status ${status}): ${preview}`, {\n status,\n cause,\n })\n }\n}\n\nfunction toApiError(parsed: unknown, status: number, rawText: string): CrawlbruleeError {\n if (isApiErrorResponse(parsed)) {\n return createApiError(parsed, status)\n }\n const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}…` : rawText\n return new TransportError(`HTTP ${status}: ${preview || '(empty body)'}`, { status })\n}\n\nfunction isApiErrorResponse(value: unknown): value is ApiErrorResponse {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return typeof v.name === 'string' && typeof v.message === 'string'\n}\n","import { ENV_API_KEY } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\nimport { HttpClient, type RequestOptions } from './http.js'\nimport type {\n AsyncJobStatusResponse,\n AsyncScrapeRequest,\n AsyncScrapeResponse,\n MapRequest,\n MapResponse,\n ScrapeCompleteWebhook,\n ScrapeRequest,\n ScrapeResponse,\n UsageResponse,\n WhoamiResponse,\n} from './types/index.js'\n\n/** Options accepted by the {@link Crawlbrulee} constructor. */\nexport interface CrawlbruleeOptions {\n /**\n * API key sent as `Authorization: Bearer <key>`. Required — to read from the\n * environment instead, use {@link Crawlbrulee.fromEnv}. Leading and trailing\n * whitespace is stripped; an empty / whitespace-only value is rejected.\n */\n apiKey: string\n /**\n * Override the base URL the SDK targets. Defaults to the production host\n * ({@link DEFAULT_BASE_URL}). Intended for local development and staging\n * (e.g. `https://api.staging.crawlbrulee.com`) — production callers should\n * leave it unset. Trailing slashes are stripped.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Defaults to `0` (no timeout). Set to a\n * positive number to abort slow requests; a per-call `timeoutMs` override\n * takes precedence. The timeout covers the WHOLE request, including the\n * response body read.\n */\n timeoutMs?: number\n}\n\n/**\n * Options accepted by {@link Crawlbrulee.waitForScrape}.\n *\n * Note: `timeoutMs` here is the OVERALL wait budget across all polls — not the\n * per-HTTP-request timeout. The per-poll HTTP timeout is whatever the client\n * was constructed with; if you want to bound each individual poll, construct\n * the client with `timeoutMs` set.\n */\nexport interface WaitForScrapeOptions extends Omit<RequestOptions, 'timeoutMs'> {\n /** Time between status polls in milliseconds. Default `2000`. */\n intervalMs?: number\n /**\n * Maximum total time to wait before giving up, in milliseconds. Default\n * `300_000` (5 minutes). Pass `0` to wait indefinitely.\n */\n timeoutMs?: number\n}\n\n/**\n * Official client for the crawlbrulee API.\n *\n * @example\n * ```ts\n * import { Crawlbrulee } from '@crawlbrulee/sdk'\n *\n * const crawlbrulee = new Crawlbrulee({ apiKey: 'cwbl_…' })\n * // or read CRAWLBRULEE_API_KEY from the environment:\n * const crawlbrulee = Crawlbrulee.fromEnv()\n *\n * const page = await crawlbrulee.scrape({\n * url: 'https://example.com',\n * extract: { markdown: true, links: true },\n * })\n * console.log(page.markdown)\n * ```\n */\nexport class Crawlbrulee {\n /** Resolved base URL — trailing slash already stripped. */\n readonly baseUrl: string\n /** Underlying HTTP layer. Exposed for advanced use cases (custom endpoints). */\n readonly http: HttpClient\n\n constructor(options: CrawlbruleeOptions) {\n const apiKey = options.apiKey?.trim()\n if (!apiKey) {\n throw new CrawlbruleeError(\n `Missing API key. Pass { apiKey } to Crawlbrulee or call Crawlbrulee.fromEnv() to read ${ENV_API_KEY}.`,\n { status: 0, errorName: null }\n )\n }\n this.http = new HttpClient({ apiKey, baseUrl: options.baseUrl, timeoutMs: options.timeoutMs })\n this.baseUrl = this.http.baseUrl\n }\n\n /**\n * Build a {@link Crawlbrulee} reading the API key from\n * `process.env.CRAWLBRULEE_API_KEY`. Throws if the variable is unset, empty,\n * or whitespace.\n *\n * Any other constructor option can be passed via `overrides`.\n *\n * @example\n * ```ts\n * const crawlbrulee = Crawlbrulee.fromEnv()\n * const crawlbrulee = Crawlbrulee.fromEnv({ timeoutMs: 30_000 })\n * ```\n */\n static fromEnv(overrides: Omit<CrawlbruleeOptions, 'apiKey'> = {}): Crawlbrulee {\n const apiKey = readEnv(ENV_API_KEY)\n if (!apiKey) {\n throw new CrawlbruleeError(\n `${ENV_API_KEY} is not set. Export it in your shell, or pass apiKey to new Crawlbrulee({ apiKey }).`,\n { status: 0, errorName: null }\n )\n }\n return new Crawlbrulee({ ...overrides, apiKey })\n }\n\n // ------------------------------------------------------------------\n // Scraping\n // ------------------------------------------------------------------\n\n /**\n * Scrape a URL synchronously and return the extracted content.\n *\n * The request blocks until the scrape is finished. For long-running jobs\n * (heavy JS rendering, screenshots of long pages) prefer\n * {@link Crawlbrulee.scrapeAsync} so the connection isn't held open.\n *\n * @param request — body for `POST /api/scrape`.\n * @param options — per-call timeout and abort signal.\n */\n scrape(request: ScrapeRequest, options?: RequestOptions): Promise<ScrapeResponse> {\n return this.http.post<ScrapeResponse>('/api/scrape', request, options)\n }\n\n /**\n * Submit an asynchronous scrape job and return its `job_id`. Poll the job\n * with {@link Crawlbrulee.getScrapeStatus} or wait for completion with\n * {@link Crawlbrulee.waitForScrape}.\n *\n * Pass an optional `webhook` to have the API deliver a signed\n * `scrape.complete` `POST` to your endpoint when the job finishes (see\n * {@link AsyncScrapeWebhook}). This field is async-only.\n */\n scrapeAsync(request: AsyncScrapeRequest, options?: RequestOptions): Promise<AsyncScrapeResponse> {\n return this.http.post<AsyncScrapeResponse>('/api/scrape/async', request, options)\n }\n\n /** Look up the current status of an async scrape job. */\n getScrapeStatus(jobId: string, options?: RequestOptions): Promise<AsyncJobStatusResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<AsyncJobStatusResponse>(\n `/api/scrape/status/${encodeURIComponent(jobId)}`,\n options\n )\n }\n\n /**\n * Fetch the result of a completed async scrape job. Throws if the job is\n * still pending/running — call {@link Crawlbrulee.getScrapeStatus}\n * first, or use {@link Crawlbrulee.waitForScrape} to poll-then-fetch.\n */\n getScrapeResult(jobId: string, options?: RequestOptions): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<ScrapeResponse>(`/api/scrape/result/${encodeURIComponent(jobId)}`, options)\n }\n\n /**\n * Fetch the scrape result referenced by a `scrape.complete` webhook body.\n *\n * Always verify the webhook signature with `verifyWebhookSignature` before\n * acting on it; this method trusts the parsed body it is handed.\n *\n * Behavior by `data.status`:\n * - `success` — delegates to {@link Crawlbrulee.getScrapeResult} for the\n * webhook's `job_id` and returns the parsed result.\n * - `failed` — throws a {@link CrawlbruleeError} carrying `data.error`\n * (`errorName: 'job_failed'`); there is no result to fetch.\n * - `cancelled` — throws a {@link CrawlbruleeError}\n * (`errorName: 'client_closed_request'`).\n *\n * A non-`scrape.complete` envelope throws a {@link CrawlbruleeError}\n * defensively. Any HTTP error from the underlying fetch propagates as the\n * usual typed `CrawlbruleeError` subclass.\n */\n async fetchScrapeResultFromWebhook(\n webhook: ScrapeCompleteWebhook,\n options?: RequestOptions\n ): Promise<ScrapeResponse> {\n if (webhook?.event !== 'scrape.complete') {\n throw new CrawlbruleeError(\n `Expected a 'scrape.complete' webhook but received '${String(webhook?.event)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n\n const { job_id: jobId, status, error } = webhook.data\n\n switch (status) {\n case 'success':\n return this.getScrapeResult(jobId, options)\n\n case 'failed':\n throw new CrawlbruleeError(error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'cancelled':\n throw new CrawlbruleeError(`Async scrape job ${jobId} was cancelled.`, {\n status: 0,\n errorName: 'client_closed_request',\n })\n\n default:\n throw new CrawlbruleeError(\n `Async scrape webhook for job ${jobId} carried an unexpected status '${String(status)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n }\n\n /**\n * Poll an async scrape job until it reaches a terminal state, then return\n * the scrape result.\n *\n * Throws a {@link CrawlbruleeError} when:\n * - the job ends in `failed` (`errorName: 'job_failed'`),\n * - the server reports an unexpected status (`errorName: 'job_failed'`),\n * - the overall wait exceeds `timeoutMs` (`errorName: 'request_timeout'`),\n * - the caller's `signal` aborts (`errorName: 'client_closed_request'`).\n */\n async waitForScrape(jobId: string, options: WaitForScrapeOptions = {}): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n const intervalMs = options.intervalMs ?? 2000\n const timeoutMs = options.timeoutMs ?? 300_000\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY\n\n while (true) {\n throwIfAborted(options.signal)\n if (Date.now() >= deadline) {\n throw new CrawlbruleeError(\n `Timed out after ${timeoutMs}ms waiting for async scrape job ${jobId}.`,\n { status: 0, errorName: 'request_timeout' }\n )\n }\n\n const status = await this.getScrapeStatus(jobId, { signal: options.signal })\n\n switch (status.status) {\n case 'done':\n return this.getScrapeResult(jobId, { signal: options.signal })\n\n case 'failed':\n throw new CrawlbruleeError(status.error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'pending':\n case 'running':\n break\n\n default:\n throw new CrawlbruleeError(\n `Async scrape job ${jobId} returned unexpected status '${String(status.status)}'.`,\n { status: 0, errorName: 'job_failed' }\n )\n }\n\n await sleep(intervalMs, options.signal)\n }\n }\n\n // ------------------------------------------------------------------\n // Mapping\n // ------------------------------------------------------------------\n\n /**\n * Build (or return a cached) site link-map for a domain. Combines sitemap\n * discovery with the freshest cached homepage scrape when available.\n */\n map(request: MapRequest, options?: RequestOptions): Promise<MapResponse> {\n return this.http.post<MapResponse>('/api/map', request, options)\n }\n\n // ------------------------------------------------------------------\n // Account\n // ------------------------------------------------------------------\n\n /**\n * Return the current billing-cycle usage: total/used/available credits,\n * used quota percentage, max concurrency, and when the cycle resets.\n */\n usage(options?: RequestOptions): Promise<UsageResponse> {\n return this.http.get<UsageResponse>('/api/usage', options)\n }\n\n /**\n * Return the organization name and identifying details of the API token\n * used to authenticate this request. Useful for confirming which key is in\n * use before performing destructive operations.\n */\n whoami(options?: RequestOptions): Promise<WhoamiResponse> {\n return this.http.get<WhoamiResponse>('/api/whoami', options)\n }\n}\n\n/**\n * Defensive read of `process.env[name]`. Guards both the absence of `process`\n * (browser / edge runtimes) and Deno's permission throw on env access without\n * `--allow-env`.\n */\nfunction readEnv(name: string): string | undefined {\n try {\n if (typeof process === 'undefined' || !process.env) return undefined\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined\n } catch {\n return undefined\n }\n}\n\nfunction assertNonEmptyJobId(jobId: string): void {\n if (typeof jobId !== 'string' || jobId.trim().length === 0) {\n throw new CrawlbruleeError('jobId must be a non-empty string.', {\n status: 0,\n errorName: null,\n })\n }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal.reason,\n })\n }\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer)\n reject(\n new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal?.reason,\n })\n )\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n onAbort()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n","/**\n * Verification for async scrape completion webhooks.\n *\n * {@link verifyWebhookSignature} validates the signature crawlbrulee attaches to\n * every webhook delivery. It is a standalone, network-free helper built on Web\n * Crypto (`globalThis.crypto.subtle`) so it runs unchanged on Node.js 22+,\n * browsers, Bun, Deno, and edge runtimes — it never touches `node:crypto`.\n */\n\n/** HTTP header carrying the primary webhook signature (always present). */\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Cwbl-Signature'\n\n/**\n * HTTP header carrying a signature produced with the previous signing secret.\n * Present only during a signing-secret rotation grace window.\n */\nexport const WEBHOOK_SIGNATURE_ROTATED_HEADER = 'X-Cwbl-Signature-Rotated'\n\n/** HTTP header carrying the unique event id, useful for delivery de-duplication. */\nexport const WEBHOOK_EVENT_ID_HEADER = 'X-Cwbl-Event-Id'\n\n/** Default replay-protection window (seconds) applied to the signed timestamp. */\nexport const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300\n\n/** Which signature header satisfied verification. */\nexport type WebhookSignatureSource = 'primary' | 'rotated'\n\n/**\n * Why a webhook signature failed to verify.\n *\n * - `missing_signature` — neither the primary nor the rotated header was present.\n * - `malformed_signature` — a header was present but not in the expected\n * `t=<unix_seconds>,v1=<64_hex>` format.\n * - `timestamp_out_of_tolerance` — the signed timestamp drifted further from now\n * than `toleranceSeconds` allows (replay protection).\n * - `signature_mismatch` — a well-formed, in-tolerance signature did not match\n * the one computed from the payload and secret.\n */\nexport type WebhookVerificationFailureReason =\n | 'missing_signature'\n | 'malformed_signature'\n | 'timestamp_out_of_tolerance'\n | 'signature_mismatch'\n\n/** Result of {@link verifyWebhookSignature}. Verification failure is returned, not thrown. */\nexport type WebhookVerificationResult =\n | { verified: true; signedWith: WebhookSignatureSource }\n | { verified: false; reason: WebhookVerificationFailureReason }\n\n/** Options for {@link verifyWebhookSignature}. */\nexport interface VerifyWebhookSignatureOptions {\n /**\n * The raw request body, exactly as received. Pass the bytes/string the server\n * signed — do NOT re-serialize parsed JSON, or the signature will not match.\n */\n payload: string | Uint8Array\n /**\n * The request headers. Accepts a fetch `Headers` instance or a plain object\n * (Express/Node give lowercased keys, values possibly arrays). Lookup is\n * case-insensitive.\n */\n headers: Headers | Record<string, string | string[] | undefined>\n /** The current signing secret (`whsec_…`). */\n secret: string\n /**\n * Replay-protection window in seconds. Defaults to\n * {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS} (300). Pass `0` (or any falsy\n * value) to disable the timestamp check entirely.\n */\n toleranceSeconds?: number\n}\n\nconst SIGNATURE_FORMAT = /^t=(\\d+),v1=([0-9a-f]{64})$/\n\ninterface ParsedSignature {\n timestamp: number\n signature: string\n}\n\n/**\n * Verify a crawlbrulee webhook signature against the primary and rotated\n * headers.\n *\n * The signing scheme matches the backend:\n * - the signed payload is `` `${t}.${rawBody}` `` where `t` is the unix-seconds\n * integer from the header and `rawBody` is the raw request body,\n * - the signature is `HMAC-SHA256(secret, signedPayload)` as lowercase hex,\n * - the header value is `t=<unix_seconds>,v1=<64_hex>`.\n *\n * The supplied `secret` is tried against the primary header first, then the\n * rotated header (which the API emits during a signing-secret rotation grace\n * window). Whichever matches wins, and the result reports which header it was.\n *\n * This NEVER throws on a verification failure — failures are normal control\n * flow and are returned as `{ verified: false, reason }`.\n *\n * @example\n * ```ts\n * const result = await verifyWebhookSignature({\n * payload: rawBody,\n * headers: req.headers,\n * secret: process.env.CRAWLBRULEE_WEBHOOK_SECRET!,\n * })\n * if (!result.verified) return res.status(400).end()\n * ```\n */\nexport async function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): Promise<WebhookVerificationResult> {\n const { payload, headers, secret } = options\n const toleranceSeconds = options.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS\n\n const primaryHeader = getHeader(headers, WEBHOOK_SIGNATURE_HEADER)\n const rotatedHeader = getHeader(headers, WEBHOOK_SIGNATURE_ROTATED_HEADER)\n\n if (primaryHeader === undefined && rotatedHeader === undefined) {\n return { verified: false, reason: 'missing_signature' }\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000)\n const body = toBytes(payload)\n const key = await importHmacKey(secret)\n\n // Track the \"best\" failure reason so the result is informative: a real\n // mismatch should win over a malformed sibling header. Order from least to\n // most specific.\n let failure: WebhookVerificationFailureReason = 'malformed_signature'\n\n for (const source of ['primary', 'rotated'] as const) {\n const raw = source === 'primary' ? primaryHeader : rotatedHeader\n if (raw === undefined) continue\n\n const parsed = parseSignatureHeader(raw)\n if (!parsed) {\n // A malformed header can't verify; keep looking at the other one.\n continue\n }\n\n if (toleranceSeconds && Math.abs(nowSeconds - parsed.timestamp) > toleranceSeconds) {\n failure = mostSpecificFailure(failure, 'timestamp_out_of_tolerance')\n continue\n }\n\n const expected = await computeSignatureHex(key, parsed.timestamp, body)\n if (constantTimeEqualHex(expected, parsed.signature)) {\n return { verified: true, signedWith: source }\n }\n\n failure = mostSpecificFailure(failure, 'signature_mismatch')\n }\n\n return { verified: false, reason: failure }\n}\n\n/**\n * Rank verification failures so the returned reason reflects the most\n * actionable problem encountered across the two headers.\n */\nfunction mostSpecificFailure(\n current: WebhookVerificationFailureReason,\n candidate: WebhookVerificationFailureReason\n): WebhookVerificationFailureReason {\n const rank: Record<WebhookVerificationFailureReason, number> = {\n missing_signature: 0,\n malformed_signature: 1,\n timestamp_out_of_tolerance: 2,\n signature_mismatch: 3,\n }\n return rank[candidate] > rank[current] ? candidate : current\n}\n\n/** Case-insensitive header lookup over `Headers` or a plain object. */\nfunction getHeader(\n headers: Headers | Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n if (typeof Headers !== 'undefined' && headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const target = name.toLowerCase()\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() !== target) continue\n const value = (headers as Record<string, string | string[] | undefined>)[key]\n if (Array.isArray(value)) return value[0]\n return value ?? undefined\n }\n return undefined\n}\n\nfunction parseSignatureHeader(value: string): ParsedSignature | null {\n const match = SIGNATURE_FORMAT.exec(value.trim())\n if (!match) return null\n const timestamp = Number(match[1])\n if (!Number.isSafeInteger(timestamp)) return null\n return { timestamp, signature: match[2]! }\n}\n\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload\n}\n\n/**\n * Web Crypto types, derived from the runtime global so we don't have to pull in\n * the DOM `lib` (the SDK compiles against `lib: ES2022` + `@types/node`).\n */\ntype SubtleCryptoLike = typeof globalThis.crypto.subtle\ntype CryptoKeyLike = Awaited<ReturnType<SubtleCryptoLike['importKey']>>\n\nfunction importHmacKey(secret: string): Promise<CryptoKeyLike> {\n return getSubtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n}\n\nasync function computeSignatureHex(\n key: CryptoKeyLike,\n timestamp: number,\n body: Uint8Array\n): Promise<string> {\n const prefix = new TextEncoder().encode(`${timestamp}.`)\n const message = new Uint8Array(prefix.length + body.length)\n message.set(prefix, 0)\n message.set(body, prefix.length)\n const digest = await getSubtle().sign('HMAC', key, message)\n return toHex(new Uint8Array(digest))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Length-checked, constant-time comparison of two lowercase hex strings. Folds\n * every byte into an accumulator with XOR — never early-returns on the first\n * mismatch — so timing does not leak how much of the signature matched.\n */\nfunction constantTimeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\nfunction getSubtle(): SubtleCryptoLike {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) {\n throw new Error(\n 'Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.'\n )\n }\n return subtle\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -74,17 +74,15 @@ declare class HttpClient {
|
|
|
74
74
|
* - `advanced` — residential proxy, higher success rate on protected sites.
|
|
75
75
|
* - `auto` — start at the basic tier and escalate to advanced on failure;
|
|
76
76
|
* billed at the delivered tier. This is the default when `proxy` is omitted.
|
|
77
|
-
* - `none` — skip the proxy entirely. Rejected in production; available on
|
|
78
|
-
* staging only as a debug/perf-test toggle.
|
|
79
77
|
*/
|
|
80
|
-
type ProxyTier = 'basic' | 'advanced' | 'auto'
|
|
78
|
+
type ProxyTier = 'basic' | 'advanced' | 'auto';
|
|
81
79
|
/**
|
|
82
80
|
* Proxy tier the server actually used to route a fetch, as reported back in
|
|
83
81
|
* {@link Usage.proxy}. Unlike the request-side {@link ProxyTier}, this never
|
|
84
82
|
* includes `auto` — when a request asks for `auto`, the server resolves it to a
|
|
85
83
|
* concrete tier and echoes the resolved value here.
|
|
86
84
|
*/
|
|
87
|
-
type ResolvedProxyTier = '
|
|
85
|
+
type ResolvedProxyTier = 'basic' | 'advanced';
|
|
88
86
|
/**
|
|
89
87
|
* Usage accounting for a single billable operation, returned on the response
|
|
90
88
|
* envelope of scrape, map, and async-status (when terminal). All crawlbrulee
|
package/dist/index.d.ts
CHANGED
|
@@ -74,17 +74,15 @@ declare class HttpClient {
|
|
|
74
74
|
* - `advanced` — residential proxy, higher success rate on protected sites.
|
|
75
75
|
* - `auto` — start at the basic tier and escalate to advanced on failure;
|
|
76
76
|
* billed at the delivered tier. This is the default when `proxy` is omitted.
|
|
77
|
-
* - `none` — skip the proxy entirely. Rejected in production; available on
|
|
78
|
-
* staging only as a debug/perf-test toggle.
|
|
79
77
|
*/
|
|
80
|
-
type ProxyTier = 'basic' | 'advanced' | 'auto'
|
|
78
|
+
type ProxyTier = 'basic' | 'advanced' | 'auto';
|
|
81
79
|
/**
|
|
82
80
|
* Proxy tier the server actually used to route a fetch, as reported back in
|
|
83
81
|
* {@link Usage.proxy}. Unlike the request-side {@link ProxyTier}, this never
|
|
84
82
|
* includes `auto` — when a request asks for `auto`, the server resolves it to a
|
|
85
83
|
* concrete tier and echoes the resolved value here.
|
|
86
84
|
*/
|
|
87
|
-
type ResolvedProxyTier = '
|
|
85
|
+
type ResolvedProxyTier = 'basic' | 'advanced';
|
|
88
86
|
/**
|
|
89
87
|
* Usage accounting for a single billable operation, returned on the response
|
|
90
88
|
* envelope of scrape, map, and async-status (when terminal). All crawlbrulee
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var DEFAULT_BASE_URL = "https://api.crawlbrulee.com";
|
|
3
3
|
var DEFAULT_REQUEST_TIMEOUT_MS = 0;
|
|
4
4
|
var ENV_API_KEY = "CRAWLBRULEE_API_KEY";
|
|
5
|
-
var USER_AGENT = "@crawlbrulee/sdk/0.
|
|
5
|
+
var USER_AGENT = "@crawlbrulee/sdk/0.7.0 (node)";
|
|
6
6
|
|
|
7
7
|
// src/errors.ts
|
|
8
8
|
var CrawlbruleeError = class extends Error {
|
|
@@ -132,7 +132,7 @@ var CwblInstrumentation = {
|
|
|
132
132
|
const g = globalThis;
|
|
133
133
|
if (typeof g.fetch !== "function") {
|
|
134
134
|
throw new CrawlbruleeError(
|
|
135
|
-
"No global fetch is available in this runtime. crawlbrulee requires Node.js
|
|
135
|
+
"No global fetch is available in this runtime. crawlbrulee requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.",
|
|
136
136
|
{ status: 0, errorName: null }
|
|
137
137
|
);
|
|
138
138
|
}
|
|
@@ -677,7 +677,7 @@ function getSubtle() {
|
|
|
677
677
|
const subtle = globalThis.crypto?.subtle;
|
|
678
678
|
if (!subtle) {
|
|
679
679
|
throw new Error(
|
|
680
|
-
"Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js
|
|
680
|
+
"Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime."
|
|
681
681
|
);
|
|
682
682
|
}
|
|
683
683
|
return subtle;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/instrumentation.ts","../src/http.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Production base URL of the crawlbrulee API. Used by default when the caller\n * doesn't pass a `baseUrl` to {@link Crawlbrulee}. Local development and\n * staging callers point at their own host via that option.\n */\nexport const DEFAULT_BASE_URL = 'https://api.crawlbrulee.com'\n\n/** Default request timeout when the caller doesn't specify one (0 disables the timeout). */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 0\n\n/** Environment variable read by `Crawlbrulee.fromEnv()` to source the API key. */\nexport const ENV_API_KEY = 'CRAWLBRULEE_API_KEY'\n\n/** Identifies the SDK in the `User-Agent` header. Kept in one place for easy bumping. */\nexport const USER_AGENT = '@crawlbrulee/sdk/0.4.0 (node)'\n","import type {\n ApiErrorDetails,\n ApiErrorName,\n ApiErrorResponse,\n RateLimitErrorDetails,\n UsageAllocationErrorDetails,\n} from './types/common.js'\n\n/**\n * Base error class for every failure raised by the SDK.\n *\n * Two kinds of failures end up here:\n *\n * 1. **API errors** — the server returned a non-2xx response with a well-formed\n * JSON body. In that case `status`, `errorName` and (sometimes) `details`\n * are populated.\n * 2. **Transport errors** — the request never produced a structured response\n * (network failure, abort, timeout, non-JSON body, etc.). In that case\n * `status` may be `0` and `errorName` is one of the synthetic transport\n * names (`request_timeout`, `client_closed_request`) or `null`.\n *\n * Typed subclasses are exported for the most common cases. To branch on more\n * specific server-side errors, switch on `err.errorName` or use the\n * {@link isCrawlbruleeError} helper.\n */\nexport class CrawlbruleeError extends Error {\n /** HTTP status code; `0` for transport-level failures with no response. */\n readonly status: number\n /** The `name` field from the API error body, or `null` for transport errors. */\n readonly errorName: ApiErrorName | null\n /** Structured detail block from the API error body, if any. */\n readonly details?: ApiErrorDetails\n /** The original parsed error body, when one was received. */\n readonly response?: ApiErrorResponse\n\n constructor(\n message: string,\n options: {\n status: number\n errorName: ApiErrorName | null\n details?: ApiErrorDetails\n response?: ApiErrorResponse\n cause?: unknown\n }\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = 'CrawlbruleeError'\n this.status = options.status\n this.errorName = options.errorName\n this.details = options.details\n this.response = options.response\n }\n}\n\n/** Raised for 401 / 403 responses (missing, invalid, or unauthorized API key). */\nexport class AuthenticationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'AuthenticationError'\n }\n}\n\n/**\n * Raised for HTTP 429 responses. When the server included a `retry_after_ms`\n * hint in `details` it is surfaced directly on the instance.\n *\n * `errorName` is always the literal `'too_many_requests'` — the SDK normalizes\n * this even when the server returns a 429 with a different `name` field\n * (e.g. a CDN coalescing upstream rate limiting). The original body is still\n * available on `response`.\n */\nexport class RateLimitError extends CrawlbruleeError {\n override readonly errorName: 'too_many_requests'\n /** Suggested delay (ms) before retrying, when the server provided one. */\n readonly retryAfterMs?: number\n /** Which rate limit was tripped (e.g. `org`, `ip`), when provided. */\n readonly limitedBy?: string\n\n constructor(\n message: string,\n options: {\n status: number\n details?: RateLimitErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'too_many_requests', details: options.details })\n this.name = 'RateLimitError'\n this.errorName = 'too_many_requests'\n this.retryAfterMs = options.details?.retry_after_ms\n this.limitedBy = options.details?.limited_by\n }\n}\n\n/**\n * Raised when the API rejects a request because the org's plan limits would\n * be exceeded (credit limit, concurrency cap, overage hard cap, etc.).\n *\n * `errorName` is always the literal `'usage_allocation_error'`.\n */\nexport class UsageAllocationError extends CrawlbruleeError {\n override readonly errorName: 'usage_allocation_error'\n /** Specific reason the allocation was denied. */\n readonly reason: UsageAllocationErrorDetails['reason']\n /** Current usage / limit snapshot at the time of the rejection. */\n readonly usage?: UsageAllocationErrorDetails['details']\n\n constructor(\n message: string,\n options: {\n status: number\n details: UsageAllocationErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'usage_allocation_error' })\n this.name = 'UsageAllocationError'\n this.errorName = 'usage_allocation_error'\n this.reason = options.details.reason\n this.usage = options.details.details\n }\n}\n\n/** Raised for 4xx responses caused by an invalid request shape or arguments. */\nexport class ValidationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'ValidationError'\n }\n}\n\n/** Raised for 404 responses (e.g. unknown async job ID). */\nexport class NotFoundError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Raised when a request cannot be sent or no structured response is parsed.\n *\n * The `errorName` discriminates the cause:\n * - `'request_timeout'` — the per-request timeout fired.\n * - `'client_closed_request'` — the caller's `AbortSignal` fired.\n * - `null` — generic transport failure (network error, non-JSON body, etc.).\n */\nexport class TransportError extends CrawlbruleeError {\n constructor(\n message: string,\n options: {\n status?: number\n errorName?: 'request_timeout' | 'client_closed_request' | null\n cause?: unknown\n } = {}\n ) {\n super(message, {\n status: options.status ?? 0,\n errorName: options.errorName ?? null,\n cause: options.cause,\n })\n this.name = 'TransportError'\n }\n}\n\n/** Narrow `unknown` to the SDK's base error type. */\nexport function isCrawlbruleeError(err: unknown): err is CrawlbruleeError {\n return err instanceof CrawlbruleeError\n}\n\n/**\n * Map an API error body + HTTP status to the most specific error class.\n *\n * Dispatch is **name-first**: the body's `name` field is the most reliable\n * signal of what went wrong. Status code is used only as a fallback when the\n * name is unrecognized (e.g. a CDN-synthesized error). This avoids\n * miscategorizing things like a 403 with `name: 'not_found'` as an auth error.\n *\n * Internal — used by the HTTP layer.\n */\nexport function createApiError(body: ApiErrorResponse, status: number): CrawlbruleeError {\n const { name, message, details } = body\n const response = body\n\n switch (name) {\n case 'too_many_requests':\n return new RateLimitError(message, {\n status,\n details: details?.error_name === 'too_many_requests' ? details : undefined,\n response,\n })\n\n case 'usage_allocation_error': {\n // Without a structured details block we still want a typed error — fall\n // back to a synthetic `internal_error` reason so callers can branch.\n const usageDetails: UsageAllocationErrorDetails =\n details?.error_name === 'usage_allocation_error'\n ? details\n : { error_name: 'usage_allocation_error', reason: 'internal_error' }\n return new UsageAllocationError(message, { status, details: usageDetails, response })\n }\n\n case 'invalid_credentials':\n case 'access_denied':\n return new AuthenticationError(message, { status, errorName: name, response })\n\n case 'not_found':\n return new NotFoundError(message, { status, errorName: name, response })\n\n case 'validation_error':\n case 'invalid_url':\n case 'url_too_long':\n case 'unsupported_url_schema':\n case 'url_credentials_not_supported':\n case 'blocked_url':\n case 'unsupported_content':\n return new ValidationError(message, { status, errorName: name, response })\n }\n\n // Name was not specific enough — fall back to status-based heuristics, but\n // never override what the name said. A 429 with an unrecognized name still\n // promotes to RateLimitError (the class invariant normalizes errorName).\n if (status === 429) {\n return new RateLimitError(message, { status, response })\n }\n if (status === 401 || status === 403) {\n return new AuthenticationError(message, { status, errorName: name, response })\n }\n if (status === 404) {\n return new NotFoundError(message, { status, errorName: name, response })\n }\n\n return new CrawlbruleeError(message, { status, errorName: name, details, response })\n}\n","import { DEFAULT_BASE_URL } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\n\n/** Function shape compatible with the global `fetch`. */\nexport type FetchLike = typeof fetch\n\n/**\n * Centralized factory for the low-level dependencies the SDK injects into its\n * HTTP layer. Production code resolves these to the runtime's global `fetch`\n * and the burned-in production base URL; tests stub this module to swap in\n * mocks and alternate hosts.\n *\n * This is internal — it is not exported from the package's public entry. Tests\n * import it from `src/instrumentation.js` directly and use `vi.spyOn` to\n * substitute behavior.\n */\nexport const CwblInstrumentation = {\n /**\n * Resolve the `fetch` implementation the SDK should use. Throws a\n * {@link CrawlbruleeError} if the runtime does not expose a global `fetch`.\n */\n getFetch(): FetchLike {\n const g = globalThis as { fetch?: FetchLike }\n if (typeof g.fetch !== 'function') {\n throw new CrawlbruleeError(\n 'No global fetch is available in this runtime. crawlbrulee requires Node.js 20+, Bun, Deno, or a modern browser/edge runtime.',\n { status: 0, errorName: null }\n )\n }\n return g.fetch.bind(globalThis)\n },\n\n /**\n * Resolve the base URL the SDK should target. Returns the production host by\n * default; tests stub this to point at a mock origin.\n */\n getBaseUrl(): string {\n return DEFAULT_BASE_URL\n },\n}\n","import { DEFAULT_REQUEST_TIMEOUT_MS, USER_AGENT } from './config.js'\nimport { TransportError, createApiError, type CrawlbruleeError } from './errors.js'\nimport { CwblInstrumentation, type FetchLike } from './instrumentation.js'\nimport type { ApiErrorResponse } from './types/common.js'\n\n/** HTTP methods used by the SDK. */\nexport type HttpMethod = 'GET' | 'POST'\n\n/** Options the SDK accepts at construction time for the HTTP layer. */\nexport interface HttpClientOptions {\n /** API key sent as `Authorization: Bearer <key>`. */\n apiKey: string\n /**\n * Override the base URL. Trailing slashes are stripped. Falls back to\n * {@link CwblInstrumentation.getBaseUrl} (which resolves to the production\n * host) when unset.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Pass `0` (or omit) to disable the\n * timeout entirely.\n */\n timeoutMs?: number\n}\n\n/** Per-call overrides accepted on every resource method. */\nexport interface RequestOptions {\n /** Abort the request when this signal fires. Composable with the timeout. */\n signal?: AbortSignal\n /**\n * Override the constructor-level `timeoutMs` for this call. Pass `0` to\n * disable the timeout for this call.\n */\n timeoutMs?: number\n}\n\ninterface SendArgs extends RequestOptions {\n method: HttpMethod\n path: string\n body?: unknown\n}\n\ninterface ComposedSignal {\n signal: AbortSignal | undefined\n /** Returns `true` if the abort was triggered by the per-request timeout. */\n timedOut: () => boolean\n /** Releases the timer and any listeners attached to the caller's signal. */\n cleanup: () => void\n}\n\n/**\n * Minimal `fetch`-based HTTP layer used by {@link Crawlbrulee}. Handles:\n *\n * - URL composition (joining `baseUrl` and path safely).\n * - JSON serialization and parsing.\n * - The `Authorization: Bearer …` header.\n * - Composing the caller's `AbortSignal` with an internal timeout signal. The\n * timeout covers the WHOLE request, including the response body read — not\n * just the time-to-headers.\n * - Mapping non-2xx responses to typed `CrawlbruleeError` subclasses via\n * {@link createApiError}.\n *\n * The base URL and `fetch` implementation are sourced from\n * {@link CwblInstrumentation} at construction time so tests can stub the\n * module.\n */\nexport class HttpClient {\n readonly baseUrl: string\n private readonly apiKey: string\n private readonly fetch: FetchLike\n private readonly timeoutMs: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = stripTrailingSlash(options.baseUrl ?? CwblInstrumentation.getBaseUrl())\n this.apiKey = options.apiKey\n this.fetch = CwblInstrumentation.getFetch()\n this.timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n }\n\n /** Send a `GET` request and parse the response as `T`. */\n get<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'GET', path, ...options })\n }\n\n /** Send a `POST` request with a JSON body and parse the response as `T`. */\n post<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'POST', path, body, ...options })\n }\n\n private async send<T>(args: SendArgs): Promise<T> {\n const url = this.buildUrl(args.path)\n const headers = this.buildHeaders(args)\n const body = args.body === undefined ? undefined : JSON.stringify(args.body)\n const composed = this.composeSignal(args.signal, args.timeoutMs)\n\n try {\n let res: Response\n try {\n res = await this.fetch(url, {\n method: args.method,\n headers,\n body,\n signal: composed.signal,\n })\n } catch (cause: unknown) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n\n let text: string\n try {\n text = await res.text()\n } catch (cause: unknown) {\n if (isAbortError(cause)) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n throw new TransportError(`Failed to read response body (status ${res.status}).`, {\n status: res.status,\n cause,\n })\n }\n\n const parsed = parseJsonOrThrow(text, res.status)\n if (!res.ok) throw toApiError(parsed, res.status, text)\n return parsed as T\n } finally {\n composed.cleanup()\n }\n }\n\n private buildUrl(path: string): string {\n if (!path.startsWith('/')) {\n throw new TypeError(`crawlbrulee SDK: path must start with '/' (received '${path}')`)\n }\n return `${this.baseUrl}${path}`\n }\n\n private buildHeaders(args: SendArgs): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'user-agent': USER_AGENT,\n authorization: `Bearer ${this.apiKey}`,\n }\n if (args.body !== undefined) headers['content-type'] = 'application/json'\n return headers\n }\n\n /**\n * Build a single `AbortSignal` that fires when either the caller-supplied\n * signal aborts OR the per-request timeout elapses. The returned `cleanup`\n * callback MUST be invoked on every exit path so we don't leak timers or\n * dead listeners on long-lived caller signals.\n */\n private composeSignal(\n callerSignal: AbortSignal | undefined,\n overrideTimeoutMs: number | undefined\n ): ComposedSignal {\n const timeoutMs = overrideTimeoutMs ?? this.timeoutMs\n const hasTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0\n\n if (!hasTimeout && !callerSignal) {\n return { signal: undefined, timedOut: () => false, cleanup: () => {} }\n }\n\n if (!hasTimeout) {\n return { signal: callerSignal, timedOut: () => false, cleanup: () => {} }\n }\n\n const controller = new AbortController()\n let didTimeout = false\n const timer = setTimeout(() => {\n didTimeout = true\n controller.abort(new Error('request_timeout'))\n }, timeoutMs)\n\n let onCallerAbort: (() => void) | undefined\n if (callerSignal) {\n if (callerSignal.aborted) {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n } else {\n onCallerAbort = () => {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n }\n callerSignal.addEventListener('abort', onCallerAbort, { once: true })\n }\n }\n\n const cleanup = () => {\n clearTimeout(timer)\n if (onCallerAbort && callerSignal) {\n callerSignal.removeEventListener('abort', onCallerAbort)\n }\n }\n\n return { signal: controller.signal, timedOut: () => didTimeout, cleanup }\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError'\n}\n\nfunction abortOrNetworkError(cause: unknown, timedOut: boolean, timeoutMs: number): TransportError {\n if (isAbortError(cause)) {\n if (timedOut) {\n return new TransportError(`Request timed out after ${timeoutMs}ms.`, {\n errorName: 'request_timeout',\n cause,\n })\n }\n return new TransportError('Request aborted by caller.', {\n errorName: 'client_closed_request',\n cause,\n })\n }\n return new TransportError(formatNetworkErrorMessage(cause), { cause })\n}\n\nfunction formatNetworkErrorMessage(cause: unknown): string {\n if (cause instanceof Error) {\n return `Network error: ${cause.message}`\n }\n return 'Network error: unknown failure while sending the request.'\n}\n\nfunction parseJsonOrThrow(text: string, status: number): unknown {\n if (text === '') return {}\n try {\n return JSON.parse(text)\n } catch (cause: unknown) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text\n throw new TransportError(`Unexpected non-JSON response (status ${status}): ${preview}`, {\n status,\n cause,\n })\n }\n}\n\nfunction toApiError(parsed: unknown, status: number, rawText: string): CrawlbruleeError {\n if (isApiErrorResponse(parsed)) {\n return createApiError(parsed, status)\n }\n const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}…` : rawText\n return new TransportError(`HTTP ${status}: ${preview || '(empty body)'}`, { status })\n}\n\nfunction isApiErrorResponse(value: unknown): value is ApiErrorResponse {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return typeof v.name === 'string' && typeof v.message === 'string'\n}\n","import { ENV_API_KEY } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\nimport { HttpClient, type RequestOptions } from './http.js'\nimport type {\n AsyncJobStatusResponse,\n AsyncScrapeRequest,\n AsyncScrapeResponse,\n MapRequest,\n MapResponse,\n ScrapeCompleteWebhook,\n ScrapeRequest,\n ScrapeResponse,\n UsageResponse,\n WhoamiResponse,\n} from './types/index.js'\n\n/** Options accepted by the {@link Crawlbrulee} constructor. */\nexport interface CrawlbruleeOptions {\n /**\n * API key sent as `Authorization: Bearer <key>`. Required — to read from the\n * environment instead, use {@link Crawlbrulee.fromEnv}. Leading and trailing\n * whitespace is stripped; an empty / whitespace-only value is rejected.\n */\n apiKey: string\n /**\n * Override the base URL the SDK targets. Defaults to the production host\n * ({@link DEFAULT_BASE_URL}). Intended for local development and staging\n * (e.g. `https://api.staging.crawlbrulee.com`) — production callers should\n * leave it unset. Trailing slashes are stripped.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Defaults to `0` (no timeout). Set to a\n * positive number to abort slow requests; a per-call `timeoutMs` override\n * takes precedence. The timeout covers the WHOLE request, including the\n * response body read.\n */\n timeoutMs?: number\n}\n\n/**\n * Options accepted by {@link Crawlbrulee.waitForScrape}.\n *\n * Note: `timeoutMs` here is the OVERALL wait budget across all polls — not the\n * per-HTTP-request timeout. The per-poll HTTP timeout is whatever the client\n * was constructed with; if you want to bound each individual poll, construct\n * the client with `timeoutMs` set.\n */\nexport interface WaitForScrapeOptions extends Omit<RequestOptions, 'timeoutMs'> {\n /** Time between status polls in milliseconds. Default `2000`. */\n intervalMs?: number\n /**\n * Maximum total time to wait before giving up, in milliseconds. Default\n * `300_000` (5 minutes). Pass `0` to wait indefinitely.\n */\n timeoutMs?: number\n}\n\n/**\n * Official client for the crawlbrulee API.\n *\n * @example\n * ```ts\n * import { Crawlbrulee } from '@crawlbrulee/sdk'\n *\n * const crawlbrulee = new Crawlbrulee({ apiKey: 'cwbl_…' })\n * // or read CRAWLBRULEE_API_KEY from the environment:\n * const crawlbrulee = Crawlbrulee.fromEnv()\n *\n * const page = await crawlbrulee.scrape({\n * url: 'https://example.com',\n * extract: { markdown: true, links: true },\n * })\n * console.log(page.markdown)\n * ```\n */\nexport class Crawlbrulee {\n /** Resolved base URL — trailing slash already stripped. */\n readonly baseUrl: string\n /** Underlying HTTP layer. Exposed for advanced use cases (custom endpoints). */\n readonly http: HttpClient\n\n constructor(options: CrawlbruleeOptions) {\n const apiKey = options.apiKey?.trim()\n if (!apiKey) {\n throw new CrawlbruleeError(\n `Missing API key. Pass { apiKey } to Crawlbrulee or call Crawlbrulee.fromEnv() to read ${ENV_API_KEY}.`,\n { status: 0, errorName: null }\n )\n }\n this.http = new HttpClient({ apiKey, baseUrl: options.baseUrl, timeoutMs: options.timeoutMs })\n this.baseUrl = this.http.baseUrl\n }\n\n /**\n * Build a {@link Crawlbrulee} reading the API key from\n * `process.env.CRAWLBRULEE_API_KEY`. Throws if the variable is unset, empty,\n * or whitespace.\n *\n * Any other constructor option can be passed via `overrides`.\n *\n * @example\n * ```ts\n * const crawlbrulee = Crawlbrulee.fromEnv()\n * const crawlbrulee = Crawlbrulee.fromEnv({ timeoutMs: 30_000 })\n * ```\n */\n static fromEnv(overrides: Omit<CrawlbruleeOptions, 'apiKey'> = {}): Crawlbrulee {\n const apiKey = readEnv(ENV_API_KEY)\n if (!apiKey) {\n throw new CrawlbruleeError(\n `${ENV_API_KEY} is not set. Export it in your shell, or pass apiKey to new Crawlbrulee({ apiKey }).`,\n { status: 0, errorName: null }\n )\n }\n return new Crawlbrulee({ ...overrides, apiKey })\n }\n\n // ------------------------------------------------------------------\n // Scraping\n // ------------------------------------------------------------------\n\n /**\n * Scrape a URL synchronously and return the extracted content.\n *\n * The request blocks until the scrape is finished. For long-running jobs\n * (heavy JS rendering, screenshots of long pages) prefer\n * {@link Crawlbrulee.scrapeAsync} so the connection isn't held open.\n *\n * @param request — body for `POST /api/scrape`.\n * @param options — per-call timeout and abort signal.\n */\n scrape(request: ScrapeRequest, options?: RequestOptions): Promise<ScrapeResponse> {\n return this.http.post<ScrapeResponse>('/api/scrape', request, options)\n }\n\n /**\n * Submit an asynchronous scrape job and return its `job_id`. Poll the job\n * with {@link Crawlbrulee.getScrapeStatus} or wait for completion with\n * {@link Crawlbrulee.waitForScrape}.\n *\n * Pass an optional `webhook` to have the API deliver a signed\n * `scrape.complete` `POST` to your endpoint when the job finishes (see\n * {@link AsyncScrapeWebhook}). This field is async-only.\n */\n scrapeAsync(request: AsyncScrapeRequest, options?: RequestOptions): Promise<AsyncScrapeResponse> {\n return this.http.post<AsyncScrapeResponse>('/api/scrape/async', request, options)\n }\n\n /** Look up the current status of an async scrape job. */\n getScrapeStatus(jobId: string, options?: RequestOptions): Promise<AsyncJobStatusResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<AsyncJobStatusResponse>(\n `/api/scrape/status/${encodeURIComponent(jobId)}`,\n options\n )\n }\n\n /**\n * Fetch the result of a completed async scrape job. Throws if the job is\n * still pending/running — call {@link Crawlbrulee.getScrapeStatus}\n * first, or use {@link Crawlbrulee.waitForScrape} to poll-then-fetch.\n */\n getScrapeResult(jobId: string, options?: RequestOptions): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<ScrapeResponse>(`/api/scrape/result/${encodeURIComponent(jobId)}`, options)\n }\n\n /**\n * Fetch the scrape result referenced by a `scrape.complete` webhook body.\n *\n * Always verify the webhook signature with `verifyWebhookSignature` before\n * acting on it; this method trusts the parsed body it is handed.\n *\n * Behavior by `data.status`:\n * - `success` — delegates to {@link Crawlbrulee.getScrapeResult} for the\n * webhook's `job_id` and returns the parsed result.\n * - `failed` — throws a {@link CrawlbruleeError} carrying `data.error`\n * (`errorName: 'job_failed'`); there is no result to fetch.\n * - `cancelled` — throws a {@link CrawlbruleeError}\n * (`errorName: 'client_closed_request'`).\n *\n * A non-`scrape.complete` envelope throws a {@link CrawlbruleeError}\n * defensively. Any HTTP error from the underlying fetch propagates as the\n * usual typed `CrawlbruleeError` subclass.\n */\n async fetchScrapeResultFromWebhook(\n webhook: ScrapeCompleteWebhook,\n options?: RequestOptions\n ): Promise<ScrapeResponse> {\n if (webhook?.event !== 'scrape.complete') {\n throw new CrawlbruleeError(\n `Expected a 'scrape.complete' webhook but received '${String(webhook?.event)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n\n const { job_id: jobId, status, error } = webhook.data\n\n switch (status) {\n case 'success':\n return this.getScrapeResult(jobId, options)\n\n case 'failed':\n throw new CrawlbruleeError(error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'cancelled':\n throw new CrawlbruleeError(`Async scrape job ${jobId} was cancelled.`, {\n status: 0,\n errorName: 'client_closed_request',\n })\n\n default:\n throw new CrawlbruleeError(\n `Async scrape webhook for job ${jobId} carried an unexpected status '${String(status)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n }\n\n /**\n * Poll an async scrape job until it reaches a terminal state, then return\n * the scrape result.\n *\n * Throws a {@link CrawlbruleeError} when:\n * - the job ends in `failed` (`errorName: 'job_failed'`),\n * - the server reports an unexpected status (`errorName: 'job_failed'`),\n * - the overall wait exceeds `timeoutMs` (`errorName: 'request_timeout'`),\n * - the caller's `signal` aborts (`errorName: 'client_closed_request'`).\n */\n async waitForScrape(jobId: string, options: WaitForScrapeOptions = {}): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n const intervalMs = options.intervalMs ?? 2000\n const timeoutMs = options.timeoutMs ?? 300_000\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY\n\n while (true) {\n throwIfAborted(options.signal)\n if (Date.now() >= deadline) {\n throw new CrawlbruleeError(\n `Timed out after ${timeoutMs}ms waiting for async scrape job ${jobId}.`,\n { status: 0, errorName: 'request_timeout' }\n )\n }\n\n const status = await this.getScrapeStatus(jobId, { signal: options.signal })\n\n switch (status.status) {\n case 'done':\n return this.getScrapeResult(jobId, { signal: options.signal })\n\n case 'failed':\n throw new CrawlbruleeError(status.error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'pending':\n case 'running':\n break\n\n default:\n throw new CrawlbruleeError(\n `Async scrape job ${jobId} returned unexpected status '${String(status.status)}'.`,\n { status: 0, errorName: 'job_failed' }\n )\n }\n\n await sleep(intervalMs, options.signal)\n }\n }\n\n // ------------------------------------------------------------------\n // Mapping\n // ------------------------------------------------------------------\n\n /**\n * Build (or return a cached) site link-map for a domain. Combines sitemap\n * discovery with the freshest cached homepage scrape when available.\n */\n map(request: MapRequest, options?: RequestOptions): Promise<MapResponse> {\n return this.http.post<MapResponse>('/api/map', request, options)\n }\n\n // ------------------------------------------------------------------\n // Account\n // ------------------------------------------------------------------\n\n /**\n * Return the current billing-cycle usage: total/used/available credits,\n * used quota percentage, max concurrency, and when the cycle resets.\n */\n usage(options?: RequestOptions): Promise<UsageResponse> {\n return this.http.get<UsageResponse>('/api/usage', options)\n }\n\n /**\n * Return the organization name and identifying details of the API token\n * used to authenticate this request. Useful for confirming which key is in\n * use before performing destructive operations.\n */\n whoami(options?: RequestOptions): Promise<WhoamiResponse> {\n return this.http.get<WhoamiResponse>('/api/whoami', options)\n }\n}\n\n/**\n * Defensive read of `process.env[name]`. Guards both the absence of `process`\n * (browser / edge runtimes) and Deno's permission throw on env access without\n * `--allow-env`.\n */\nfunction readEnv(name: string): string | undefined {\n try {\n if (typeof process === 'undefined' || !process.env) return undefined\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined\n } catch {\n return undefined\n }\n}\n\nfunction assertNonEmptyJobId(jobId: string): void {\n if (typeof jobId !== 'string' || jobId.trim().length === 0) {\n throw new CrawlbruleeError('jobId must be a non-empty string.', {\n status: 0,\n errorName: null,\n })\n }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal.reason,\n })\n }\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer)\n reject(\n new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal?.reason,\n })\n )\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n onAbort()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n","/**\n * Verification for async scrape completion webhooks.\n *\n * {@link verifyWebhookSignature} validates the signature crawlbrulee attaches to\n * every webhook delivery. It is a standalone, network-free helper built on Web\n * Crypto (`globalThis.crypto.subtle`) so it runs unchanged on Node.js 22+,\n * browsers, Bun, Deno, and edge runtimes — it never touches `node:crypto`.\n */\n\n/** HTTP header carrying the primary webhook signature (always present). */\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Cwbl-Signature'\n\n/**\n * HTTP header carrying a signature produced with the previous signing secret.\n * Present only during a signing-secret rotation grace window.\n */\nexport const WEBHOOK_SIGNATURE_ROTATED_HEADER = 'X-Cwbl-Signature-Rotated'\n\n/** HTTP header carrying the unique event id, useful for delivery de-duplication. */\nexport const WEBHOOK_EVENT_ID_HEADER = 'X-Cwbl-Event-Id'\n\n/** Default replay-protection window (seconds) applied to the signed timestamp. */\nexport const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300\n\n/** Which signature header satisfied verification. */\nexport type WebhookSignatureSource = 'primary' | 'rotated'\n\n/**\n * Why a webhook signature failed to verify.\n *\n * - `missing_signature` — neither the primary nor the rotated header was present.\n * - `malformed_signature` — a header was present but not in the expected\n * `t=<unix_seconds>,v1=<64_hex>` format.\n * - `timestamp_out_of_tolerance` — the signed timestamp drifted further from now\n * than `toleranceSeconds` allows (replay protection).\n * - `signature_mismatch` — a well-formed, in-tolerance signature did not match\n * the one computed from the payload and secret.\n */\nexport type WebhookVerificationFailureReason =\n | 'missing_signature'\n | 'malformed_signature'\n | 'timestamp_out_of_tolerance'\n | 'signature_mismatch'\n\n/** Result of {@link verifyWebhookSignature}. Verification failure is returned, not thrown. */\nexport type WebhookVerificationResult =\n | { verified: true; signedWith: WebhookSignatureSource }\n | { verified: false; reason: WebhookVerificationFailureReason }\n\n/** Options for {@link verifyWebhookSignature}. */\nexport interface VerifyWebhookSignatureOptions {\n /**\n * The raw request body, exactly as received. Pass the bytes/string the server\n * signed — do NOT re-serialize parsed JSON, or the signature will not match.\n */\n payload: string | Uint8Array\n /**\n * The request headers. Accepts a fetch `Headers` instance or a plain object\n * (Express/Node give lowercased keys, values possibly arrays). Lookup is\n * case-insensitive.\n */\n headers: Headers | Record<string, string | string[] | undefined>\n /** The current signing secret (`whsec_…`). */\n secret: string\n /**\n * Replay-protection window in seconds. Defaults to\n * {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS} (300). Pass `0` (or any falsy\n * value) to disable the timestamp check entirely.\n */\n toleranceSeconds?: number\n}\n\nconst SIGNATURE_FORMAT = /^t=(\\d+),v1=([0-9a-f]{64})$/\n\ninterface ParsedSignature {\n timestamp: number\n signature: string\n}\n\n/**\n * Verify a crawlbrulee webhook signature against the primary and rotated\n * headers.\n *\n * The signing scheme matches the backend:\n * - the signed payload is `` `${t}.${rawBody}` `` where `t` is the unix-seconds\n * integer from the header and `rawBody` is the raw request body,\n * - the signature is `HMAC-SHA256(secret, signedPayload)` as lowercase hex,\n * - the header value is `t=<unix_seconds>,v1=<64_hex>`.\n *\n * The supplied `secret` is tried against the primary header first, then the\n * rotated header (which the API emits during a signing-secret rotation grace\n * window). Whichever matches wins, and the result reports which header it was.\n *\n * This NEVER throws on a verification failure — failures are normal control\n * flow and are returned as `{ verified: false, reason }`.\n *\n * @example\n * ```ts\n * const result = await verifyWebhookSignature({\n * payload: rawBody,\n * headers: req.headers,\n * secret: process.env.CRAWLBRULEE_WEBHOOK_SECRET!,\n * })\n * if (!result.verified) return res.status(400).end()\n * ```\n */\nexport async function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): Promise<WebhookVerificationResult> {\n const { payload, headers, secret } = options\n const toleranceSeconds = options.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS\n\n const primaryHeader = getHeader(headers, WEBHOOK_SIGNATURE_HEADER)\n const rotatedHeader = getHeader(headers, WEBHOOK_SIGNATURE_ROTATED_HEADER)\n\n if (primaryHeader === undefined && rotatedHeader === undefined) {\n return { verified: false, reason: 'missing_signature' }\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000)\n const body = toBytes(payload)\n const key = await importHmacKey(secret)\n\n // Track the \"best\" failure reason so the result is informative: a real\n // mismatch should win over a malformed sibling header. Order from least to\n // most specific.\n let failure: WebhookVerificationFailureReason = 'malformed_signature'\n\n for (const source of ['primary', 'rotated'] as const) {\n const raw = source === 'primary' ? primaryHeader : rotatedHeader\n if (raw === undefined) continue\n\n const parsed = parseSignatureHeader(raw)\n if (!parsed) {\n // A malformed header can't verify; keep looking at the other one.\n continue\n }\n\n if (toleranceSeconds && Math.abs(nowSeconds - parsed.timestamp) > toleranceSeconds) {\n failure = mostSpecificFailure(failure, 'timestamp_out_of_tolerance')\n continue\n }\n\n const expected = await computeSignatureHex(key, parsed.timestamp, body)\n if (constantTimeEqualHex(expected, parsed.signature)) {\n return { verified: true, signedWith: source }\n }\n\n failure = mostSpecificFailure(failure, 'signature_mismatch')\n }\n\n return { verified: false, reason: failure }\n}\n\n/**\n * Rank verification failures so the returned reason reflects the most\n * actionable problem encountered across the two headers.\n */\nfunction mostSpecificFailure(\n current: WebhookVerificationFailureReason,\n candidate: WebhookVerificationFailureReason\n): WebhookVerificationFailureReason {\n const rank: Record<WebhookVerificationFailureReason, number> = {\n missing_signature: 0,\n malformed_signature: 1,\n timestamp_out_of_tolerance: 2,\n signature_mismatch: 3,\n }\n return rank[candidate] > rank[current] ? candidate : current\n}\n\n/** Case-insensitive header lookup over `Headers` or a plain object. */\nfunction getHeader(\n headers: Headers | Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n if (typeof Headers !== 'undefined' && headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const target = name.toLowerCase()\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() !== target) continue\n const value = (headers as Record<string, string | string[] | undefined>)[key]\n if (Array.isArray(value)) return value[0]\n return value ?? undefined\n }\n return undefined\n}\n\nfunction parseSignatureHeader(value: string): ParsedSignature | null {\n const match = SIGNATURE_FORMAT.exec(value.trim())\n if (!match) return null\n const timestamp = Number(match[1])\n if (!Number.isSafeInteger(timestamp)) return null\n return { timestamp, signature: match[2]! }\n}\n\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload\n}\n\n/**\n * Web Crypto types, derived from the runtime global so we don't have to pull in\n * the DOM `lib` (the SDK compiles against `lib: ES2022` + `@types/node`).\n */\ntype SubtleCryptoLike = typeof globalThis.crypto.subtle\ntype CryptoKeyLike = Awaited<ReturnType<SubtleCryptoLike['importKey']>>\n\nfunction importHmacKey(secret: string): Promise<CryptoKeyLike> {\n return getSubtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n}\n\nasync function computeSignatureHex(\n key: CryptoKeyLike,\n timestamp: number,\n body: Uint8Array\n): Promise<string> {\n const prefix = new TextEncoder().encode(`${timestamp}.`)\n const message = new Uint8Array(prefix.length + body.length)\n message.set(prefix, 0)\n message.set(body, prefix.length)\n const digest = await getSubtle().sign('HMAC', key, message)\n return toHex(new Uint8Array(digest))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Length-checked, constant-time comparison of two lowercase hex strings. Folds\n * every byte into an accumulator with XOR — never early-returns on the first\n * mismatch — so timing does not leak how much of the signature matched.\n */\nfunction constantTimeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\nfunction getSubtle(): SubtleCryptoLike {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) {\n throw new Error(\n 'Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 20+, Bun, Deno, or a modern browser/edge runtime.'\n )\n }\n return subtle\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/instrumentation.ts","../src/http.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Production base URL of the crawlbrulee API. Used by default when the caller\n * doesn't pass a `baseUrl` to {@link Crawlbrulee}. Local development and\n * staging callers point at their own host via that option.\n */\nexport const DEFAULT_BASE_URL = 'https://api.crawlbrulee.com'\n\n/** Default request timeout when the caller doesn't specify one (0 disables the timeout). */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 0\n\n/** Environment variable read by `Crawlbrulee.fromEnv()` to source the API key. */\nexport const ENV_API_KEY = 'CRAWLBRULEE_API_KEY'\n\n/** Identifies the SDK in the `User-Agent` header. Kept in one place for easy bumping. */\nexport const USER_AGENT = '@crawlbrulee/sdk/0.7.0 (node)'\n","import type {\n ApiErrorDetails,\n ApiErrorName,\n ApiErrorResponse,\n RateLimitErrorDetails,\n UsageAllocationErrorDetails,\n} from './types/common.js'\n\n/**\n * Base error class for every failure raised by the SDK.\n *\n * Two kinds of failures end up here:\n *\n * 1. **API errors** — the server returned a non-2xx response with a well-formed\n * JSON body. In that case `status`, `errorName` and (sometimes) `details`\n * are populated.\n * 2. **Transport errors** — the request never produced a structured response\n * (network failure, abort, timeout, non-JSON body, etc.). In that case\n * `status` may be `0` and `errorName` is one of the synthetic transport\n * names (`request_timeout`, `client_closed_request`) or `null`.\n *\n * Typed subclasses are exported for the most common cases. To branch on more\n * specific server-side errors, switch on `err.errorName` or use the\n * {@link isCrawlbruleeError} helper.\n */\nexport class CrawlbruleeError extends Error {\n /** HTTP status code; `0` for transport-level failures with no response. */\n readonly status: number\n /** The `name` field from the API error body, or `null` for transport errors. */\n readonly errorName: ApiErrorName | null\n /** Structured detail block from the API error body, if any. */\n readonly details?: ApiErrorDetails\n /** The original parsed error body, when one was received. */\n readonly response?: ApiErrorResponse\n\n constructor(\n message: string,\n options: {\n status: number\n errorName: ApiErrorName | null\n details?: ApiErrorDetails\n response?: ApiErrorResponse\n cause?: unknown\n }\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = 'CrawlbruleeError'\n this.status = options.status\n this.errorName = options.errorName\n this.details = options.details\n this.response = options.response\n }\n}\n\n/** Raised for 401 / 403 responses (missing, invalid, or unauthorized API key). */\nexport class AuthenticationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'AuthenticationError'\n }\n}\n\n/**\n * Raised for HTTP 429 responses. When the server included a `retry_after_ms`\n * hint in `details` it is surfaced directly on the instance.\n *\n * `errorName` is always the literal `'too_many_requests'` — the SDK normalizes\n * this even when the server returns a 429 with a different `name` field\n * (e.g. a CDN coalescing upstream rate limiting). The original body is still\n * available on `response`.\n */\nexport class RateLimitError extends CrawlbruleeError {\n override readonly errorName: 'too_many_requests'\n /** Suggested delay (ms) before retrying, when the server provided one. */\n readonly retryAfterMs?: number\n /** Which rate limit was tripped (e.g. `org`, `ip`), when provided. */\n readonly limitedBy?: string\n\n constructor(\n message: string,\n options: {\n status: number\n details?: RateLimitErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'too_many_requests', details: options.details })\n this.name = 'RateLimitError'\n this.errorName = 'too_many_requests'\n this.retryAfterMs = options.details?.retry_after_ms\n this.limitedBy = options.details?.limited_by\n }\n}\n\n/**\n * Raised when the API rejects a request because the org's plan limits would\n * be exceeded (credit limit, concurrency cap, overage hard cap, etc.).\n *\n * `errorName` is always the literal `'usage_allocation_error'`.\n */\nexport class UsageAllocationError extends CrawlbruleeError {\n override readonly errorName: 'usage_allocation_error'\n /** Specific reason the allocation was denied. */\n readonly reason: UsageAllocationErrorDetails['reason']\n /** Current usage / limit snapshot at the time of the rejection. */\n readonly usage?: UsageAllocationErrorDetails['details']\n\n constructor(\n message: string,\n options: {\n status: number\n details: UsageAllocationErrorDetails\n response?: ApiErrorResponse\n }\n ) {\n super(message, { ...options, errorName: 'usage_allocation_error' })\n this.name = 'UsageAllocationError'\n this.errorName = 'usage_allocation_error'\n this.reason = options.details.reason\n this.usage = options.details.details\n }\n}\n\n/** Raised for 4xx responses caused by an invalid request shape or arguments. */\nexport class ValidationError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'ValidationError'\n }\n}\n\n/** Raised for 404 responses (e.g. unknown async job ID). */\nexport class NotFoundError extends CrawlbruleeError {\n constructor(\n message: string,\n options: { status: number; errorName: ApiErrorName; response?: ApiErrorResponse }\n ) {\n super(message, options)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Raised when a request cannot be sent or no structured response is parsed.\n *\n * The `errorName` discriminates the cause:\n * - `'request_timeout'` — the per-request timeout fired.\n * - `'client_closed_request'` — the caller's `AbortSignal` fired.\n * - `null` — generic transport failure (network error, non-JSON body, etc.).\n */\nexport class TransportError extends CrawlbruleeError {\n constructor(\n message: string,\n options: {\n status?: number\n errorName?: 'request_timeout' | 'client_closed_request' | null\n cause?: unknown\n } = {}\n ) {\n super(message, {\n status: options.status ?? 0,\n errorName: options.errorName ?? null,\n cause: options.cause,\n })\n this.name = 'TransportError'\n }\n}\n\n/** Narrow `unknown` to the SDK's base error type. */\nexport function isCrawlbruleeError(err: unknown): err is CrawlbruleeError {\n return err instanceof CrawlbruleeError\n}\n\n/**\n * Map an API error body + HTTP status to the most specific error class.\n *\n * Dispatch is **name-first**: the body's `name` field is the most reliable\n * signal of what went wrong. Status code is used only as a fallback when the\n * name is unrecognized (e.g. a CDN-synthesized error). This avoids\n * miscategorizing things like a 403 with `name: 'not_found'` as an auth error.\n *\n * Internal — used by the HTTP layer.\n */\nexport function createApiError(body: ApiErrorResponse, status: number): CrawlbruleeError {\n const { name, message, details } = body\n const response = body\n\n switch (name) {\n case 'too_many_requests':\n return new RateLimitError(message, {\n status,\n details: details?.error_name === 'too_many_requests' ? details : undefined,\n response,\n })\n\n case 'usage_allocation_error': {\n // Without a structured details block we still want a typed error — fall\n // back to a synthetic `internal_error` reason so callers can branch.\n const usageDetails: UsageAllocationErrorDetails =\n details?.error_name === 'usage_allocation_error'\n ? details\n : { error_name: 'usage_allocation_error', reason: 'internal_error' }\n return new UsageAllocationError(message, { status, details: usageDetails, response })\n }\n\n case 'invalid_credentials':\n case 'access_denied':\n return new AuthenticationError(message, { status, errorName: name, response })\n\n case 'not_found':\n return new NotFoundError(message, { status, errorName: name, response })\n\n case 'validation_error':\n case 'invalid_url':\n case 'url_too_long':\n case 'unsupported_url_schema':\n case 'url_credentials_not_supported':\n case 'blocked_url':\n case 'unsupported_content':\n return new ValidationError(message, { status, errorName: name, response })\n }\n\n // Name was not specific enough — fall back to status-based heuristics, but\n // never override what the name said. A 429 with an unrecognized name still\n // promotes to RateLimitError (the class invariant normalizes errorName).\n if (status === 429) {\n return new RateLimitError(message, { status, response })\n }\n if (status === 401 || status === 403) {\n return new AuthenticationError(message, { status, errorName: name, response })\n }\n if (status === 404) {\n return new NotFoundError(message, { status, errorName: name, response })\n }\n\n return new CrawlbruleeError(message, { status, errorName: name, details, response })\n}\n","import { DEFAULT_BASE_URL } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\n\n/** Function shape compatible with the global `fetch`. */\nexport type FetchLike = typeof fetch\n\n/**\n * Centralized factory for the low-level dependencies the SDK injects into its\n * HTTP layer. Production code resolves these to the runtime's global `fetch`\n * and the burned-in production base URL; tests stub this module to swap in\n * mocks and alternate hosts.\n *\n * This is internal — it is not exported from the package's public entry. Tests\n * import it from `src/instrumentation.js` directly and use `vi.spyOn` to\n * substitute behavior.\n */\nexport const CwblInstrumentation = {\n /**\n * Resolve the `fetch` implementation the SDK should use. Throws a\n * {@link CrawlbruleeError} if the runtime does not expose a global `fetch`.\n */\n getFetch(): FetchLike {\n const g = globalThis as { fetch?: FetchLike }\n if (typeof g.fetch !== 'function') {\n throw new CrawlbruleeError(\n 'No global fetch is available in this runtime. crawlbrulee requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.',\n { status: 0, errorName: null }\n )\n }\n return g.fetch.bind(globalThis)\n },\n\n /**\n * Resolve the base URL the SDK should target. Returns the production host by\n * default; tests stub this to point at a mock origin.\n */\n getBaseUrl(): string {\n return DEFAULT_BASE_URL\n },\n}\n","import { DEFAULT_REQUEST_TIMEOUT_MS, USER_AGENT } from './config.js'\nimport { TransportError, createApiError, type CrawlbruleeError } from './errors.js'\nimport { CwblInstrumentation, type FetchLike } from './instrumentation.js'\nimport type { ApiErrorResponse } from './types/common.js'\n\n/** HTTP methods used by the SDK. */\nexport type HttpMethod = 'GET' | 'POST'\n\n/** Options the SDK accepts at construction time for the HTTP layer. */\nexport interface HttpClientOptions {\n /** API key sent as `Authorization: Bearer <key>`. */\n apiKey: string\n /**\n * Override the base URL. Trailing slashes are stripped. Falls back to\n * {@link CwblInstrumentation.getBaseUrl} (which resolves to the production\n * host) when unset.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Pass `0` (or omit) to disable the\n * timeout entirely.\n */\n timeoutMs?: number\n}\n\n/** Per-call overrides accepted on every resource method. */\nexport interface RequestOptions {\n /** Abort the request when this signal fires. Composable with the timeout. */\n signal?: AbortSignal\n /**\n * Override the constructor-level `timeoutMs` for this call. Pass `0` to\n * disable the timeout for this call.\n */\n timeoutMs?: number\n}\n\ninterface SendArgs extends RequestOptions {\n method: HttpMethod\n path: string\n body?: unknown\n}\n\ninterface ComposedSignal {\n signal: AbortSignal | undefined\n /** Returns `true` if the abort was triggered by the per-request timeout. */\n timedOut: () => boolean\n /** Releases the timer and any listeners attached to the caller's signal. */\n cleanup: () => void\n}\n\n/**\n * Minimal `fetch`-based HTTP layer used by {@link Crawlbrulee}. Handles:\n *\n * - URL composition (joining `baseUrl` and path safely).\n * - JSON serialization and parsing.\n * - The `Authorization: Bearer …` header.\n * - Composing the caller's `AbortSignal` with an internal timeout signal. The\n * timeout covers the WHOLE request, including the response body read — not\n * just the time-to-headers.\n * - Mapping non-2xx responses to typed `CrawlbruleeError` subclasses via\n * {@link createApiError}.\n *\n * The base URL and `fetch` implementation are sourced from\n * {@link CwblInstrumentation} at construction time so tests can stub the\n * module.\n */\nexport class HttpClient {\n readonly baseUrl: string\n private readonly apiKey: string\n private readonly fetch: FetchLike\n private readonly timeoutMs: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = stripTrailingSlash(options.baseUrl ?? CwblInstrumentation.getBaseUrl())\n this.apiKey = options.apiKey\n this.fetch = CwblInstrumentation.getFetch()\n this.timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n }\n\n /** Send a `GET` request and parse the response as `T`. */\n get<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'GET', path, ...options })\n }\n\n /** Send a `POST` request with a JSON body and parse the response as `T`. */\n post<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n return this.send<T>({ method: 'POST', path, body, ...options })\n }\n\n private async send<T>(args: SendArgs): Promise<T> {\n const url = this.buildUrl(args.path)\n const headers = this.buildHeaders(args)\n const body = args.body === undefined ? undefined : JSON.stringify(args.body)\n const composed = this.composeSignal(args.signal, args.timeoutMs)\n\n try {\n let res: Response\n try {\n res = await this.fetch(url, {\n method: args.method,\n headers,\n body,\n signal: composed.signal,\n })\n } catch (cause: unknown) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n\n let text: string\n try {\n text = await res.text()\n } catch (cause: unknown) {\n if (isAbortError(cause)) {\n throw abortOrNetworkError(cause, composed.timedOut(), args.timeoutMs ?? this.timeoutMs)\n }\n throw new TransportError(`Failed to read response body (status ${res.status}).`, {\n status: res.status,\n cause,\n })\n }\n\n const parsed = parseJsonOrThrow(text, res.status)\n if (!res.ok) throw toApiError(parsed, res.status, text)\n return parsed as T\n } finally {\n composed.cleanup()\n }\n }\n\n private buildUrl(path: string): string {\n if (!path.startsWith('/')) {\n throw new TypeError(`crawlbrulee SDK: path must start with '/' (received '${path}')`)\n }\n return `${this.baseUrl}${path}`\n }\n\n private buildHeaders(args: SendArgs): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'user-agent': USER_AGENT,\n authorization: `Bearer ${this.apiKey}`,\n }\n if (args.body !== undefined) headers['content-type'] = 'application/json'\n return headers\n }\n\n /**\n * Build a single `AbortSignal` that fires when either the caller-supplied\n * signal aborts OR the per-request timeout elapses. The returned `cleanup`\n * callback MUST be invoked on every exit path so we don't leak timers or\n * dead listeners on long-lived caller signals.\n */\n private composeSignal(\n callerSignal: AbortSignal | undefined,\n overrideTimeoutMs: number | undefined\n ): ComposedSignal {\n const timeoutMs = overrideTimeoutMs ?? this.timeoutMs\n const hasTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0\n\n if (!hasTimeout && !callerSignal) {\n return { signal: undefined, timedOut: () => false, cleanup: () => {} }\n }\n\n if (!hasTimeout) {\n return { signal: callerSignal, timedOut: () => false, cleanup: () => {} }\n }\n\n const controller = new AbortController()\n let didTimeout = false\n const timer = setTimeout(() => {\n didTimeout = true\n controller.abort(new Error('request_timeout'))\n }, timeoutMs)\n\n let onCallerAbort: (() => void) | undefined\n if (callerSignal) {\n if (callerSignal.aborted) {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n } else {\n onCallerAbort = () => {\n clearTimeout(timer)\n controller.abort(callerSignal.reason)\n }\n callerSignal.addEventListener('abort', onCallerAbort, { once: true })\n }\n }\n\n const cleanup = () => {\n clearTimeout(timer)\n if (onCallerAbort && callerSignal) {\n callerSignal.removeEventListener('abort', onCallerAbort)\n }\n }\n\n return { signal: controller.signal, timedOut: () => didTimeout, cleanup }\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError'\n}\n\nfunction abortOrNetworkError(cause: unknown, timedOut: boolean, timeoutMs: number): TransportError {\n if (isAbortError(cause)) {\n if (timedOut) {\n return new TransportError(`Request timed out after ${timeoutMs}ms.`, {\n errorName: 'request_timeout',\n cause,\n })\n }\n return new TransportError('Request aborted by caller.', {\n errorName: 'client_closed_request',\n cause,\n })\n }\n return new TransportError(formatNetworkErrorMessage(cause), { cause })\n}\n\nfunction formatNetworkErrorMessage(cause: unknown): string {\n if (cause instanceof Error) {\n return `Network error: ${cause.message}`\n }\n return 'Network error: unknown failure while sending the request.'\n}\n\nfunction parseJsonOrThrow(text: string, status: number): unknown {\n if (text === '') return {}\n try {\n return JSON.parse(text)\n } catch (cause: unknown) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text\n throw new TransportError(`Unexpected non-JSON response (status ${status}): ${preview}`, {\n status,\n cause,\n })\n }\n}\n\nfunction toApiError(parsed: unknown, status: number, rawText: string): CrawlbruleeError {\n if (isApiErrorResponse(parsed)) {\n return createApiError(parsed, status)\n }\n const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}…` : rawText\n return new TransportError(`HTTP ${status}: ${preview || '(empty body)'}`, { status })\n}\n\nfunction isApiErrorResponse(value: unknown): value is ApiErrorResponse {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return typeof v.name === 'string' && typeof v.message === 'string'\n}\n","import { ENV_API_KEY } from './config.js'\nimport { CrawlbruleeError } from './errors.js'\nimport { HttpClient, type RequestOptions } from './http.js'\nimport type {\n AsyncJobStatusResponse,\n AsyncScrapeRequest,\n AsyncScrapeResponse,\n MapRequest,\n MapResponse,\n ScrapeCompleteWebhook,\n ScrapeRequest,\n ScrapeResponse,\n UsageResponse,\n WhoamiResponse,\n} from './types/index.js'\n\n/** Options accepted by the {@link Crawlbrulee} constructor. */\nexport interface CrawlbruleeOptions {\n /**\n * API key sent as `Authorization: Bearer <key>`. Required — to read from the\n * environment instead, use {@link Crawlbrulee.fromEnv}. Leading and trailing\n * whitespace is stripped; an empty / whitespace-only value is rejected.\n */\n apiKey: string\n /**\n * Override the base URL the SDK targets. Defaults to the production host\n * ({@link DEFAULT_BASE_URL}). Intended for local development and staging\n * (e.g. `https://api.staging.crawlbrulee.com`) — production callers should\n * leave it unset. Trailing slashes are stripped.\n */\n baseUrl?: string\n /**\n * Per-request timeout in milliseconds. Defaults to `0` (no timeout). Set to a\n * positive number to abort slow requests; a per-call `timeoutMs` override\n * takes precedence. The timeout covers the WHOLE request, including the\n * response body read.\n */\n timeoutMs?: number\n}\n\n/**\n * Options accepted by {@link Crawlbrulee.waitForScrape}.\n *\n * Note: `timeoutMs` here is the OVERALL wait budget across all polls — not the\n * per-HTTP-request timeout. The per-poll HTTP timeout is whatever the client\n * was constructed with; if you want to bound each individual poll, construct\n * the client with `timeoutMs` set.\n */\nexport interface WaitForScrapeOptions extends Omit<RequestOptions, 'timeoutMs'> {\n /** Time between status polls in milliseconds. Default `2000`. */\n intervalMs?: number\n /**\n * Maximum total time to wait before giving up, in milliseconds. Default\n * `300_000` (5 minutes). Pass `0` to wait indefinitely.\n */\n timeoutMs?: number\n}\n\n/**\n * Official client for the crawlbrulee API.\n *\n * @example\n * ```ts\n * import { Crawlbrulee } from '@crawlbrulee/sdk'\n *\n * const crawlbrulee = new Crawlbrulee({ apiKey: 'cwbl_…' })\n * // or read CRAWLBRULEE_API_KEY from the environment:\n * const crawlbrulee = Crawlbrulee.fromEnv()\n *\n * const page = await crawlbrulee.scrape({\n * url: 'https://example.com',\n * extract: { markdown: true, links: true },\n * })\n * console.log(page.markdown)\n * ```\n */\nexport class Crawlbrulee {\n /** Resolved base URL — trailing slash already stripped. */\n readonly baseUrl: string\n /** Underlying HTTP layer. Exposed for advanced use cases (custom endpoints). */\n readonly http: HttpClient\n\n constructor(options: CrawlbruleeOptions) {\n const apiKey = options.apiKey?.trim()\n if (!apiKey) {\n throw new CrawlbruleeError(\n `Missing API key. Pass { apiKey } to Crawlbrulee or call Crawlbrulee.fromEnv() to read ${ENV_API_KEY}.`,\n { status: 0, errorName: null }\n )\n }\n this.http = new HttpClient({ apiKey, baseUrl: options.baseUrl, timeoutMs: options.timeoutMs })\n this.baseUrl = this.http.baseUrl\n }\n\n /**\n * Build a {@link Crawlbrulee} reading the API key from\n * `process.env.CRAWLBRULEE_API_KEY`. Throws if the variable is unset, empty,\n * or whitespace.\n *\n * Any other constructor option can be passed via `overrides`.\n *\n * @example\n * ```ts\n * const crawlbrulee = Crawlbrulee.fromEnv()\n * const crawlbrulee = Crawlbrulee.fromEnv({ timeoutMs: 30_000 })\n * ```\n */\n static fromEnv(overrides: Omit<CrawlbruleeOptions, 'apiKey'> = {}): Crawlbrulee {\n const apiKey = readEnv(ENV_API_KEY)\n if (!apiKey) {\n throw new CrawlbruleeError(\n `${ENV_API_KEY} is not set. Export it in your shell, or pass apiKey to new Crawlbrulee({ apiKey }).`,\n { status: 0, errorName: null }\n )\n }\n return new Crawlbrulee({ ...overrides, apiKey })\n }\n\n // ------------------------------------------------------------------\n // Scraping\n // ------------------------------------------------------------------\n\n /**\n * Scrape a URL synchronously and return the extracted content.\n *\n * The request blocks until the scrape is finished. For long-running jobs\n * (heavy JS rendering, screenshots of long pages) prefer\n * {@link Crawlbrulee.scrapeAsync} so the connection isn't held open.\n *\n * @param request — body for `POST /api/scrape`.\n * @param options — per-call timeout and abort signal.\n */\n scrape(request: ScrapeRequest, options?: RequestOptions): Promise<ScrapeResponse> {\n return this.http.post<ScrapeResponse>('/api/scrape', request, options)\n }\n\n /**\n * Submit an asynchronous scrape job and return its `job_id`. Poll the job\n * with {@link Crawlbrulee.getScrapeStatus} or wait for completion with\n * {@link Crawlbrulee.waitForScrape}.\n *\n * Pass an optional `webhook` to have the API deliver a signed\n * `scrape.complete` `POST` to your endpoint when the job finishes (see\n * {@link AsyncScrapeWebhook}). This field is async-only.\n */\n scrapeAsync(request: AsyncScrapeRequest, options?: RequestOptions): Promise<AsyncScrapeResponse> {\n return this.http.post<AsyncScrapeResponse>('/api/scrape/async', request, options)\n }\n\n /** Look up the current status of an async scrape job. */\n getScrapeStatus(jobId: string, options?: RequestOptions): Promise<AsyncJobStatusResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<AsyncJobStatusResponse>(\n `/api/scrape/status/${encodeURIComponent(jobId)}`,\n options\n )\n }\n\n /**\n * Fetch the result of a completed async scrape job. Throws if the job is\n * still pending/running — call {@link Crawlbrulee.getScrapeStatus}\n * first, or use {@link Crawlbrulee.waitForScrape} to poll-then-fetch.\n */\n getScrapeResult(jobId: string, options?: RequestOptions): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n return this.http.get<ScrapeResponse>(`/api/scrape/result/${encodeURIComponent(jobId)}`, options)\n }\n\n /**\n * Fetch the scrape result referenced by a `scrape.complete` webhook body.\n *\n * Always verify the webhook signature with `verifyWebhookSignature` before\n * acting on it; this method trusts the parsed body it is handed.\n *\n * Behavior by `data.status`:\n * - `success` — delegates to {@link Crawlbrulee.getScrapeResult} for the\n * webhook's `job_id` and returns the parsed result.\n * - `failed` — throws a {@link CrawlbruleeError} carrying `data.error`\n * (`errorName: 'job_failed'`); there is no result to fetch.\n * - `cancelled` — throws a {@link CrawlbruleeError}\n * (`errorName: 'client_closed_request'`).\n *\n * A non-`scrape.complete` envelope throws a {@link CrawlbruleeError}\n * defensively. Any HTTP error from the underlying fetch propagates as the\n * usual typed `CrawlbruleeError` subclass.\n */\n async fetchScrapeResultFromWebhook(\n webhook: ScrapeCompleteWebhook,\n options?: RequestOptions\n ): Promise<ScrapeResponse> {\n if (webhook?.event !== 'scrape.complete') {\n throw new CrawlbruleeError(\n `Expected a 'scrape.complete' webhook but received '${String(webhook?.event)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n\n const { job_id: jobId, status, error } = webhook.data\n\n switch (status) {\n case 'success':\n return this.getScrapeResult(jobId, options)\n\n case 'failed':\n throw new CrawlbruleeError(error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'cancelled':\n throw new CrawlbruleeError(`Async scrape job ${jobId} was cancelled.`, {\n status: 0,\n errorName: 'client_closed_request',\n })\n\n default:\n throw new CrawlbruleeError(\n `Async scrape webhook for job ${jobId} carried an unexpected status '${String(status)}'.`,\n { status: 0, errorName: 'validation_error' }\n )\n }\n }\n\n /**\n * Poll an async scrape job until it reaches a terminal state, then return\n * the scrape result.\n *\n * Throws a {@link CrawlbruleeError} when:\n * - the job ends in `failed` (`errorName: 'job_failed'`),\n * - the server reports an unexpected status (`errorName: 'job_failed'`),\n * - the overall wait exceeds `timeoutMs` (`errorName: 'request_timeout'`),\n * - the caller's `signal` aborts (`errorName: 'client_closed_request'`).\n */\n async waitForScrape(jobId: string, options: WaitForScrapeOptions = {}): Promise<ScrapeResponse> {\n assertNonEmptyJobId(jobId)\n const intervalMs = options.intervalMs ?? 2000\n const timeoutMs = options.timeoutMs ?? 300_000\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY\n\n while (true) {\n throwIfAborted(options.signal)\n if (Date.now() >= deadline) {\n throw new CrawlbruleeError(\n `Timed out after ${timeoutMs}ms waiting for async scrape job ${jobId}.`,\n { status: 0, errorName: 'request_timeout' }\n )\n }\n\n const status = await this.getScrapeStatus(jobId, { signal: options.signal })\n\n switch (status.status) {\n case 'done':\n return this.getScrapeResult(jobId, { signal: options.signal })\n\n case 'failed':\n throw new CrawlbruleeError(status.error ?? `Async scrape job ${jobId} failed.`, {\n status: 0,\n errorName: 'job_failed',\n })\n\n case 'pending':\n case 'running':\n break\n\n default:\n throw new CrawlbruleeError(\n `Async scrape job ${jobId} returned unexpected status '${String(status.status)}'.`,\n { status: 0, errorName: 'job_failed' }\n )\n }\n\n await sleep(intervalMs, options.signal)\n }\n }\n\n // ------------------------------------------------------------------\n // Mapping\n // ------------------------------------------------------------------\n\n /**\n * Build (or return a cached) site link-map for a domain. Combines sitemap\n * discovery with the freshest cached homepage scrape when available.\n */\n map(request: MapRequest, options?: RequestOptions): Promise<MapResponse> {\n return this.http.post<MapResponse>('/api/map', request, options)\n }\n\n // ------------------------------------------------------------------\n // Account\n // ------------------------------------------------------------------\n\n /**\n * Return the current billing-cycle usage: total/used/available credits,\n * used quota percentage, max concurrency, and when the cycle resets.\n */\n usage(options?: RequestOptions): Promise<UsageResponse> {\n return this.http.get<UsageResponse>('/api/usage', options)\n }\n\n /**\n * Return the organization name and identifying details of the API token\n * used to authenticate this request. Useful for confirming which key is in\n * use before performing destructive operations.\n */\n whoami(options?: RequestOptions): Promise<WhoamiResponse> {\n return this.http.get<WhoamiResponse>('/api/whoami', options)\n }\n}\n\n/**\n * Defensive read of `process.env[name]`. Guards both the absence of `process`\n * (browser / edge runtimes) and Deno's permission throw on env access without\n * `--allow-env`.\n */\nfunction readEnv(name: string): string | undefined {\n try {\n if (typeof process === 'undefined' || !process.env) return undefined\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined\n } catch {\n return undefined\n }\n}\n\nfunction assertNonEmptyJobId(jobId: string): void {\n if (typeof jobId !== 'string' || jobId.trim().length === 0) {\n throw new CrawlbruleeError('jobId must be a non-empty string.', {\n status: 0,\n errorName: null,\n })\n }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal.reason,\n })\n }\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer)\n reject(\n new CrawlbruleeError('Request aborted by caller.', {\n status: 0,\n errorName: 'client_closed_request',\n cause: signal?.reason,\n })\n )\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n onAbort()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n","/**\n * Verification for async scrape completion webhooks.\n *\n * {@link verifyWebhookSignature} validates the signature crawlbrulee attaches to\n * every webhook delivery. It is a standalone, network-free helper built on Web\n * Crypto (`globalThis.crypto.subtle`) so it runs unchanged on Node.js 22+,\n * browsers, Bun, Deno, and edge runtimes — it never touches `node:crypto`.\n */\n\n/** HTTP header carrying the primary webhook signature (always present). */\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Cwbl-Signature'\n\n/**\n * HTTP header carrying a signature produced with the previous signing secret.\n * Present only during a signing-secret rotation grace window.\n */\nexport const WEBHOOK_SIGNATURE_ROTATED_HEADER = 'X-Cwbl-Signature-Rotated'\n\n/** HTTP header carrying the unique event id, useful for delivery de-duplication. */\nexport const WEBHOOK_EVENT_ID_HEADER = 'X-Cwbl-Event-Id'\n\n/** Default replay-protection window (seconds) applied to the signed timestamp. */\nexport const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300\n\n/** Which signature header satisfied verification. */\nexport type WebhookSignatureSource = 'primary' | 'rotated'\n\n/**\n * Why a webhook signature failed to verify.\n *\n * - `missing_signature` — neither the primary nor the rotated header was present.\n * - `malformed_signature` — a header was present but not in the expected\n * `t=<unix_seconds>,v1=<64_hex>` format.\n * - `timestamp_out_of_tolerance` — the signed timestamp drifted further from now\n * than `toleranceSeconds` allows (replay protection).\n * - `signature_mismatch` — a well-formed, in-tolerance signature did not match\n * the one computed from the payload and secret.\n */\nexport type WebhookVerificationFailureReason =\n | 'missing_signature'\n | 'malformed_signature'\n | 'timestamp_out_of_tolerance'\n | 'signature_mismatch'\n\n/** Result of {@link verifyWebhookSignature}. Verification failure is returned, not thrown. */\nexport type WebhookVerificationResult =\n | { verified: true; signedWith: WebhookSignatureSource }\n | { verified: false; reason: WebhookVerificationFailureReason }\n\n/** Options for {@link verifyWebhookSignature}. */\nexport interface VerifyWebhookSignatureOptions {\n /**\n * The raw request body, exactly as received. Pass the bytes/string the server\n * signed — do NOT re-serialize parsed JSON, or the signature will not match.\n */\n payload: string | Uint8Array\n /**\n * The request headers. Accepts a fetch `Headers` instance or a plain object\n * (Express/Node give lowercased keys, values possibly arrays). Lookup is\n * case-insensitive.\n */\n headers: Headers | Record<string, string | string[] | undefined>\n /** The current signing secret (`whsec_…`). */\n secret: string\n /**\n * Replay-protection window in seconds. Defaults to\n * {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS} (300). Pass `0` (or any falsy\n * value) to disable the timestamp check entirely.\n */\n toleranceSeconds?: number\n}\n\nconst SIGNATURE_FORMAT = /^t=(\\d+),v1=([0-9a-f]{64})$/\n\ninterface ParsedSignature {\n timestamp: number\n signature: string\n}\n\n/**\n * Verify a crawlbrulee webhook signature against the primary and rotated\n * headers.\n *\n * The signing scheme matches the backend:\n * - the signed payload is `` `${t}.${rawBody}` `` where `t` is the unix-seconds\n * integer from the header and `rawBody` is the raw request body,\n * - the signature is `HMAC-SHA256(secret, signedPayload)` as lowercase hex,\n * - the header value is `t=<unix_seconds>,v1=<64_hex>`.\n *\n * The supplied `secret` is tried against the primary header first, then the\n * rotated header (which the API emits during a signing-secret rotation grace\n * window). Whichever matches wins, and the result reports which header it was.\n *\n * This NEVER throws on a verification failure — failures are normal control\n * flow and are returned as `{ verified: false, reason }`.\n *\n * @example\n * ```ts\n * const result = await verifyWebhookSignature({\n * payload: rawBody,\n * headers: req.headers,\n * secret: process.env.CRAWLBRULEE_WEBHOOK_SECRET!,\n * })\n * if (!result.verified) return res.status(400).end()\n * ```\n */\nexport async function verifyWebhookSignature(\n options: VerifyWebhookSignatureOptions\n): Promise<WebhookVerificationResult> {\n const { payload, headers, secret } = options\n const toleranceSeconds = options.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS\n\n const primaryHeader = getHeader(headers, WEBHOOK_SIGNATURE_HEADER)\n const rotatedHeader = getHeader(headers, WEBHOOK_SIGNATURE_ROTATED_HEADER)\n\n if (primaryHeader === undefined && rotatedHeader === undefined) {\n return { verified: false, reason: 'missing_signature' }\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000)\n const body = toBytes(payload)\n const key = await importHmacKey(secret)\n\n // Track the \"best\" failure reason so the result is informative: a real\n // mismatch should win over a malformed sibling header. Order from least to\n // most specific.\n let failure: WebhookVerificationFailureReason = 'malformed_signature'\n\n for (const source of ['primary', 'rotated'] as const) {\n const raw = source === 'primary' ? primaryHeader : rotatedHeader\n if (raw === undefined) continue\n\n const parsed = parseSignatureHeader(raw)\n if (!parsed) {\n // A malformed header can't verify; keep looking at the other one.\n continue\n }\n\n if (toleranceSeconds && Math.abs(nowSeconds - parsed.timestamp) > toleranceSeconds) {\n failure = mostSpecificFailure(failure, 'timestamp_out_of_tolerance')\n continue\n }\n\n const expected = await computeSignatureHex(key, parsed.timestamp, body)\n if (constantTimeEqualHex(expected, parsed.signature)) {\n return { verified: true, signedWith: source }\n }\n\n failure = mostSpecificFailure(failure, 'signature_mismatch')\n }\n\n return { verified: false, reason: failure }\n}\n\n/**\n * Rank verification failures so the returned reason reflects the most\n * actionable problem encountered across the two headers.\n */\nfunction mostSpecificFailure(\n current: WebhookVerificationFailureReason,\n candidate: WebhookVerificationFailureReason\n): WebhookVerificationFailureReason {\n const rank: Record<WebhookVerificationFailureReason, number> = {\n missing_signature: 0,\n malformed_signature: 1,\n timestamp_out_of_tolerance: 2,\n signature_mismatch: 3,\n }\n return rank[candidate] > rank[current] ? candidate : current\n}\n\n/** Case-insensitive header lookup over `Headers` or a plain object. */\nfunction getHeader(\n headers: Headers | Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n if (typeof Headers !== 'undefined' && headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const target = name.toLowerCase()\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() !== target) continue\n const value = (headers as Record<string, string | string[] | undefined>)[key]\n if (Array.isArray(value)) return value[0]\n return value ?? undefined\n }\n return undefined\n}\n\nfunction parseSignatureHeader(value: string): ParsedSignature | null {\n const match = SIGNATURE_FORMAT.exec(value.trim())\n if (!match) return null\n const timestamp = Number(match[1])\n if (!Number.isSafeInteger(timestamp)) return null\n return { timestamp, signature: match[2]! }\n}\n\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload\n}\n\n/**\n * Web Crypto types, derived from the runtime global so we don't have to pull in\n * the DOM `lib` (the SDK compiles against `lib: ES2022` + `@types/node`).\n */\ntype SubtleCryptoLike = typeof globalThis.crypto.subtle\ntype CryptoKeyLike = Awaited<ReturnType<SubtleCryptoLike['importKey']>>\n\nfunction importHmacKey(secret: string): Promise<CryptoKeyLike> {\n return getSubtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n}\n\nasync function computeSignatureHex(\n key: CryptoKeyLike,\n timestamp: number,\n body: Uint8Array\n): Promise<string> {\n const prefix = new TextEncoder().encode(`${timestamp}.`)\n const message = new Uint8Array(prefix.length + body.length)\n message.set(prefix, 0)\n message.set(body, prefix.length)\n const digest = await getSubtle().sign('HMAC', key, message)\n return toHex(new Uint8Array(digest))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Length-checked, constant-time comparison of two lowercase hex strings. Folds\n * every byte into an accumulator with XOR — never early-returns on the first\n * mismatch — so timing does not leak how much of the signature matched.\n */\nfunction constantTimeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return diff === 0\n}\n\nfunction getSubtle(): SubtleCryptoLike {\n const subtle = globalThis.crypto?.subtle\n if (!subtle) {\n throw new Error(\n 'Web Crypto (globalThis.crypto.subtle) is not available in this runtime. crawlbrulee webhook verification requires Node.js 22+, Bun, Deno, or a modern browser/edge runtime.'\n )\n }\n return subtle\n}\n"]}
|