n8n-nodes-crawlbrulee 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,10 +66,35 @@ turn on **Download Screenshot** — it's on **Scrape URL** and on **Get Scrape R
66
66
 
67
67
  ### what comes back
68
68
 
69
- one item in, one item out: the api's json body becomes the item's `json`, unchanged. scrape, map and finished-job responses carry `response_meta.usage`, so the credits a call cost are in front of you. **Map Website** adds `pagination` and `truncation` there, and its **Limit** starts at 50 in n8n, not the api's 5000 — n8n's rule for a field named Limit sets that, so raising it is usually the first thing you do. the [api docs](https://crawlbrulee.com/docs) have the full response shape.
69
+ one item in, one item out: the api's json body becomes the item's `json`, unchanged. **Map Website** adds `pagination` and `truncation` under `response_meta`, and its **Limit** starts at 50 in n8n, not the api's 5000 — n8n's rule for a field named Limit sets that, so raising it is usually the first thing you do. the [api docs](https://crawlbrulee.com/docs) have the full response shape.
70
70
 
71
71
  set the node's "On Error" setting to continue and a failed item comes back as `{ "error": "…" }` instead of stopping the run.
72
72
 
73
+ ### the page's own status
74
+
75
+ **Scrape URL** and **Get Scrape Result** put the HTTP status the site answered with in `page_status_code`, next to `url`. it is the status of the final page, after redirects.
76
+
77
+ a page the site really served is a normal item, whatever its status. a 404, 410 or 503 page comes back with its content, like any other page. it is not a node error, so "On Error" never sees it. when the status matters to your workflow, check it before you use the content — for example an **If** node on `{{ $json.page_status_code }}` is less than 400.
78
+
79
+ ### zero data retention
80
+
81
+ turn on **Zero Data Retention** under **Options** of **Scrape URL**, **Scrape URL (Async)** or **Map Website** to keep the result out of the shared cache. anything stored to deliver it is kept for 24 hours, then deleted. it adds 1 credit and must be enabled for your organization. if it is not, the call fails with a clear message and costs no credits. see [zero data retention](https://crawlbrulee.com/docs/zero-data-retention).
82
+
83
+ ### what a call cost
84
+
85
+ scrape, map and finished-job responses carry `response_meta.usage`, so the cost of each call is in front of you:
86
+
87
+ | field | what it is |
88
+ |---|---|
89
+ | `total_credit_cost` | credits this call cost |
90
+ | `engine_credit_cost` | the engine's base price: 1 for `http`, 3 for `browser`, 5 for `screenshot`, 0 for `cache` |
91
+ | `proxy_multiplier` | 1 for the Basic proxy tier, 5 for Advanced |
92
+ | `screenshot_slicing_credit_cost` | 1 when the screenshot was cut into slices on this call, else 0 (reusing slices from the cache costs 0). not on **Map Website** |
93
+ | `zero_data_retention_credit_cost` | 1 when **Zero Data Retention** added its credit to this call, else 0 |
94
+ | `engine`, `proxy` | the engine and the proxy tier that did the work |
95
+
96
+ `total_credit_cost` is always `engine_credit_cost × proxy_multiplier + screenshot_slicing_credit_cost + zero_data_retention_credit_cost` (map has no slicing part). a page we don't bill, such as one the site answered with a 5xx status, has 0 in every `*_credit_cost` field. the [credits and pricing](https://crawlbrulee.com/docs/credits-and-pricing) page lists what is billed.
97
+
73
98
  ## the crawlbrulee Trigger
74
99
 
75
100
  use it to react to a background scrape instead of polling for it:
@@ -78,13 +103,13 @@ use it to react to a background scrape instead of polling for it:
78
103
  2. paste that url into the **Webhook URL** field of a **Scrape URL (Async)** operation.
79
104
  3. when the job finishes, we post one `scrape.complete` event there and the trigger fires.
80
105
 
81
- the item carries the event id, the timestamp and the job data. turn on **Fetch Result** and, for a job that succeeded, the trigger fetches the page content into `result`; if that fetch fails, the reason lands in `result_error` and the item still comes through.
106
+ the item carries the event id, the timestamp and the job data. a job that got a page has status `success` and the page's HTTP status in `page_status_code`, so a 404 page arrives as `success` too. turn on **Fetch Result** and, for a job that succeeded, the trigger fetches the page content into `result`; if that fetch fails, the reason lands in `result_error` and the item still comes through.
82
107
 
83
108
  with the secret set, the trigger checks the HMAC-SHA256 signature on every delivery. it drops one that fails the check, one that isn't a `scrape.complete` event, and one whose event id is among the last 500 it saw: nothing runs, and we still answer 200 so it is not retried. that id list lives in the node's workflow data, so a fresh copy of the workflow starts empty.
84
109
 
85
110
  ## errors
86
111
 
87
- the node turns api errors into messages you can act on: a rate limit says how long to wait, out of credits and concurrency limit are each named, an anti-bot block says so with no retry hint, and a scrape error points you at the Advanced proxy tier and at Require JS. the raw response stays on the error for the n8n error view, and every error name is in the [error reference](https://crawlbrulee.com/docs/errors).
112
+ the node turns api errors into messages you can act on: a rate limit says how long to wait, a Zero Data Retention request your organization cannot make yet says to turn the option off or ask us to enable it, out of credits and concurrency limit are each named, an anti-bot block says so with no retry hint, a scrape error points you at the Advanced proxy tier and at Require JS, and a site we could not reach at all (for example it timed out, or its certificate is bad) tells you to check the url and try again later. the raw response stays on the error for the n8n error view, and every error name is in the [error reference](https://crawlbrulee.com/docs/errors).
88
113
 
89
114
  ## development
90
115
 
@@ -8,6 +8,8 @@ function buildMapBody(_node, _itemIndex, p) {
8
8
  const o = (_a = p.options) !== null && _a !== void 0 ? _a : {};
9
9
  if (o.sitemapOnly === true)
10
10
  body.sitemap_only = true;
11
+ if (o.zeroDataRetention === true)
12
+ body.zero_data_retention = true;
11
13
  const types = {};
12
14
  if (typeof o.internal === 'boolean')
13
15
  types.internal = o.internal;
@@ -88,6 +88,8 @@ function buildScrapeBody(node, itemIndex, p) {
88
88
  body.proxy = o.proxy;
89
89
  if (o.requireJs === true)
90
90
  body.require_js = true;
91
+ if (o.zeroDataRetention === true)
92
+ body.zero_data_retention = true;
91
93
  const cleanup = {};
92
94
  if (typeof o.removeAdsAndPopups === 'boolean')
93
95
  cleanup.ads_and_popups = o.removeAdsAndPopups;
@@ -92,6 +92,7 @@ exports.mapFields = [
92
92
  default: false,
93
93
  description: 'Whether to read only sitemap.xml and skip homepage link extraction',
94
94
  },
95
+ shared_1.zeroDataRetentionOption,
95
96
  ],
96
97
  },
97
98
  ];
@@ -17,7 +17,7 @@ exports.scrapeOperations = {
17
17
  name: 'Get Scrape Result',
18
18
  value: 'getScrapeResult',
19
19
  action: 'Get scrape result',
20
- description: 'Fetch the content of a finished async scrape',
20
+ description: "Fetch the content of a finished async scrape. The site's own HTTP status is in page_status_code.",
21
21
  },
22
22
  {
23
23
  name: 'Get Scrape Status',
@@ -29,7 +29,7 @@ exports.scrapeOperations = {
29
29
  name: 'Scrape URL',
30
30
  value: 'scrape',
31
31
  action: 'Scrape URL',
32
- description: 'Fetch one URL and return the requested content',
32
+ description: "Fetch one URL and return the requested content. The site's own HTTP status is in page_status_code.",
33
33
  },
34
34
  {
35
35
  name: 'Scrape URL (Async)',
@@ -41,6 +41,13 @@ exports.scrapeOperations = {
41
41
  default: 'scrape',
42
42
  };
43
43
  exports.scrapeFields = [
44
+ {
45
+ displayName: 'A page the site answers with an error, like a 404, still comes back as a normal item, not as a node error. Its HTTP status is in page_status_code. Check it, for example with an If node, before you use the content.',
46
+ name: 'pageStatusNotice',
47
+ type: 'notice',
48
+ default: '',
49
+ displayOptions: { show: { resource: ['scrape'], operation: ['scrape', 'getScrapeResult'] } },
50
+ },
44
51
  {
45
52
  displayName: 'Job ID',
46
53
  name: 'jobId',
@@ -237,6 +244,7 @@ exports.scrapeFields = [
237
244
  default: false,
238
245
  description: 'Whether to render JavaScript in a headless browser first. Adds latency and credits.',
239
246
  },
247
+ shared_1.zeroDataRetentionOption,
240
248
  ],
241
249
  },
242
250
  {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.localeOption = exports.countryOption = exports.cacheMaxAgeOption = exports.proxyOption = void 0;
3
+ exports.localeOption = exports.countryOption = exports.zeroDataRetentionOption = exports.cacheMaxAgeOption = exports.proxyOption = void 0;
4
4
  exports.proxyOption = {
5
5
  displayName: 'Proxy',
6
6
  name: 'proxy',
@@ -30,6 +30,13 @@ const cacheMaxAgeOption = (defaultText) => ({
30
30
  description: `Seconds, or an ISO-8601 cutoff. Cached results older than this are skipped. Default ${defaultText}.`,
31
31
  });
32
32
  exports.cacheMaxAgeOption = cacheMaxAgeOption;
33
+ exports.zeroDataRetentionOption = {
34
+ displayName: 'Zero Data Retention',
35
+ name: 'zeroDataRetention',
36
+ type: 'boolean',
37
+ default: false,
38
+ description: 'Whether to keep the result out of the shared cache. Anything stored to deliver it is kept for 24 hours, then deleted. Adds 1 credit. Must be enabled for your organization.',
39
+ };
33
40
  exports.countryOption = {
34
41
  displayName: 'Country',
35
42
  name: 'country',
@@ -4,6 +4,8 @@ exports.describeApiError = describeApiError;
4
4
  exports.toNodeApiError = toNodeApiError;
5
5
  const n8n_workflow_1 = require("n8n-workflow");
6
6
  const KEY_REJECTED = 'crawlbrulee rejected the API key. Check the key on the credential.';
7
+ const UNREACHABLE = 'crawlbrulee could not reach the target site. Check the URL. If it is right, the site may be down or slow, so try again later. This costs no credits.';
8
+ const ZDR_NOT_ENABLED = 'Zero data retention is not enabled for your organization. Turn the Zero Data Retention option off, or contact sales@crawlbrulee.com to have it turned on. This costs no credits.';
7
9
  const SCRAPE_HINT = 'The Advanced proxy tier has a higher success rate; enable Require JS for JavaScript-rendered content.';
8
10
  function isApiErrorBody(body) {
9
11
  return (typeof body === 'object' &&
@@ -39,6 +41,10 @@ function describeApiError(status, body) {
39
41
  if (details.reason === 'concurrency_limit')
40
42
  return { message: 'Concurrency limit reached.', description };
41
43
  return { message: body.message, description };
44
+ case 'target_unreachable':
45
+ return { message: UNREACHABLE, description };
46
+ case 'zero_data_retention_not_enabled':
47
+ return { message: ZDR_NOT_ENABLED, description };
42
48
  case 'scrape_error':
43
49
  return { message: `${body.message.replace(/[.\s]*$/, '')}. ${SCRAPE_HINT}`, description };
44
50
  default:
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.USER_AGENT = void 0;
4
4
  exports.crawlbruleeRequest = crawlbruleeRequest;
5
5
  const errors_1 = require("./errors");
6
- exports.USER_AGENT = 'n8n-nodes-crawlbrulee/0.1.0';
6
+ exports.USER_AGENT = 'n8n-nodes-crawlbrulee/0.3.0';
7
7
  function stripTrailingSlash(url) {
8
8
  return url.replace(/\/+$/, '');
9
9
  }
@@ -29,7 +29,7 @@ class CrawlbruleeTrigger {
29
29
  ],
30
30
  properties: [
31
31
  {
32
- displayName: "Copy this node's webhook URL into the Webhook URL field of a Scrape URL (Async) operation. Each finished job posts one scrape.complete event here. Set the Webhook Signing Secret on the credential to verify deliveries.",
32
+ displayName: "Copy this node's webhook URL into the Webhook URL field of a Scrape URL (Async) operation. Each finished job posts one scrape.complete event here. Set the Webhook Signing Secret on the credential to verify deliveries. A page the site answers with an error, like a 404, still arrives with status success; its HTTP status is in page_status_code.",
33
33
  name: 'notice',
34
34
  type: 'notice',
35
35
  default: '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-crawlbrulee",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "crawlbrulee for n8n — EU-native web scraping for AI agents & developers. scrape any url to markdown, html, links, images, metadata or a screenshot, map a site's urls, and react to finished background scrapes.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://crawlbrulee.com",
@@ -57,7 +57,7 @@
57
57
  ]
58
58
  },
59
59
  "devDependencies": {
60
- "@crawlbrulee/sdk": "^1.0.1",
60
+ "@crawlbrulee/sdk": "^1.2.0",
61
61
  "@n8n/node-cli": "0.48.6",
62
62
  "@types/node": "^22.0.0",
63
63
  "eslint": "9.32.0",