scrapely 1.2.39 → 1.2.40

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/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.40] - 2026-10-30
8
+
9
+ - Actors on puppeteer 25 (ESM-only) are sent to the remote browser when puppeteer is loaded with `require()` before the SDK on a run without the preload. The SDK only recognised puppeteer's old `lib/cjs` build, so such a `puppeteer.launch()` tried to start a local browser; the named `launch` export (`const { launch } = require('puppeteer')`) is covered too. puppeteer 24 and older behave as before.
10
+
7
11
  ## [1.2.39] - 2026-10-29
8
12
 
9
13
  - A refused charge names the limit that refused it. When a charge is refused or cut short, the platform now says why (HTTP 402 `error.data.reason`, and `limitedBy` on a partial acceptance): the run's item limit (`maxItems`), its maximum total charge (`maxTotalChargeUsd`) or the account balance. The SDK used to call all of them the spending limit. Now its log lines name the real limit; `ChargingManager.getChargeLimitReason()` returns `'max-items'`, `'max-charge'` or `'insufficient-funds'` and `getChargeLimitMessage()` the stop message ("Stopped: reached the item limit you set (800 items)", "Stopped: reached the maximum charge you set ($3.00)", "Stopped: your account has no credit left for this run"); and once a limit was reached, a status message (`Actor.setStatusMessage`, `Actor.exit`, `Actor.fail`) whose clause blames the maximum charge gets that clause replaced by the real stop message. Platforms that do not send the reason yet: inferred from the 402 message and `ACTOR_MAX_PAID_DATASET_ITEMS`. What is charged and saved does not change.
@@ -20,6 +20,7 @@ const PBKDF2_ITERATIONS = 100_000;
20
20
  const PBKDF2_HASH = 'sha256';
21
21
  const SALT_LENGTH = 32;
22
22
  const KEY_LENGTH = 32;
23
+ const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
23
24
  // ─── Private Key Decryption ───────────────────────────────────────────────────
24
25
  /**
25
26
  * Read the private key file from disk and decrypt it with the run passphrase.
@@ -101,6 +102,9 @@ function decryptAllSecrets(encryptedSecrets, privateKeyPem) {
101
102
  function mergeDecryptedInput(input, decryptedSecrets) {
102
103
  const merged = { ...input };
103
104
  for (const [fieldName, value] of Object.entries(decryptedSecrets)) {
105
+ // Never walk into or write onto an object's prototype chain.
106
+ if (fieldName.split('.').some((part) => UNSAFE_KEYS.has(part)))
107
+ continue;
104
108
  if (fieldName.includes('.')) {
105
109
  // Handle nested paths
106
110
  const parts = fieldName.split('.');
@@ -791,8 +791,31 @@ function patchRawPuppeteerModule(mod, moduleName) {
791
791
  // read-only export binding — the default export is still patched
792
792
  }
793
793
  }
794
+ // ESM-only puppeteer (v25+) loaded through require(): the named
795
+ // `launch` is a read-only binding to the original method, which hands
796
+ // off to a BrowserLauncher. Patch that class so it redirects too.
797
+ if (mod.launch === origBoundLaunch)
798
+ patchPuppeteerBrowserLauncherClass(mod.BrowserLauncher, moduleName);
794
799
  }
795
800
  }
801
+ /**
802
+ * Patch puppeteer's BrowserLauncher class (base of ChromeLauncher and
803
+ * FirefoxLauncher), which every PuppeteerNode launch goes through. Calls that
804
+ * already went through a patched launch decide the same way again (no CDP URL
805
+ * → local launch). Idempotent per class.
806
+ */
807
+ function patchPuppeteerBrowserLauncherClass(BrowserLauncher, moduleName) {
808
+ const proto = BrowserLauncher?.prototype;
809
+ if (!proto || typeof proto.launch !== 'function')
810
+ return;
811
+ if (Object.prototype.hasOwnProperty.call(proto, '_scrapelyRawCdpPatched'))
812
+ return;
813
+ const origLaunch = proto.launch;
814
+ proto.launch = function browserLauncherLaunchPatched(launchOptions) {
815
+ return puppeteerLaunchPatched(this.puppeteer, (options) => origLaunch.call(this, options), moduleName, launchOptions);
816
+ };
817
+ proto._scrapelyRawCdpPatched = true;
818
+ }
796
819
  /**
797
820
  * Requests that load (a part of) some copy of @crawlee/browser-pool:
798
821
  * - the bare package name (require from @crawlee/playwright etc.),
@@ -804,8 +827,11 @@ const BROWSER_POOL_REQUEST_RE = /^@crawlee\/browser-pool$|[/\\]@crawlee[/\\]brow
804
827
  const BROWSER_POOL_FILENAME_RE = /[/\\]@crawlee[/\\]browser-pool[/\\](index|playwright[/\\]playwright-plugin|puppeteer[/\\]puppeteer-plugin)\.js$/;
805
828
  /** playwright / playwright-core package entry, by filename (ESM imports and require.cache). */
806
829
  const PLAYWRIGHT_ENTRY_FILENAME_RE = /[/\\]node_modules[/\\](playwright|playwright-core)[/\\]index\.js$/;
807
- /** puppeteer / puppeteer-core CJS package entry (default instance), by filename. */
808
- const PUPPETEER_ENTRY_FILENAME_RE = /[/\\]node_modules[/\\](puppeteer|puppeteer-core)[/\\]lib[/\\]cjs[/\\]puppeteer[/\\](puppeteer|puppeteer-core)\.js$/;
830
+ /**
831
+ * puppeteer / puppeteer-core package entry (default instance), by filename:
832
+ * `lib/cjs/puppeteer/…` up to v24, `lib/puppeteer/…` in the ESM-only v25+.
833
+ */
834
+ const PUPPETEER_ENTRY_FILENAME_RE = /[/\\]node_modules[/\\](puppeteer|puppeteer-core)[/\\]lib[/\\](?:cjs[/\\])?puppeteer[/\\](puppeteer|puppeteer-core)\.js$/;
809
835
  /** puppeteer-core's PuppeteerNode class file (CJS), as requested by node/node.js or cached. */
810
836
  const PUPPETEER_NODE_REQUEST_RE = /(^|[/\\])PuppeteerNode(\.js)?$/;
811
837
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapely",
3
- "version": "1.2.39",
3
+ "version": "1.2.40",
4
4
  "description": "Scrapely SDK for building JavaScript and TypeScript Actors",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -89,20 +89,20 @@
89
89
  "crawlee": "^3.16.0",
90
90
  "dockerode": "^4.0.9",
91
91
  "ioredis": "^6.0.0",
92
- "ip-address": "10.3.1",
92
+ "ip-address": "10.6.0",
93
93
  "lodash": "^4.18.1",
94
94
  "mysql2": "^3.23.1",
95
95
  "playwright": "^1.58.2",
96
96
  "scrapely-client": "^2.25.9",
97
97
  "tar-fs": "2.1.5",
98
- "ws": "^8.18.0"
98
+ "ws": "^8.21.0"
99
99
  },
100
100
  "devDependencies": {
101
101
  "@types/dockerode": "^4.0.1",
102
102
  "@types/node": "^25.5.0",
103
103
  "@types/ws": "^8.5.10",
104
104
  "dotenv": "^17.3.1",
105
- "puppeteer-core": "^24.43.1",
105
+ "puppeteer-core": "^25.16.0",
106
106
  "tsx": "^4.21.0",
107
107
  "typescript": "^5.7.0"
108
108
  },
@@ -113,8 +113,10 @@
113
113
  "axios": "^1.20.0",
114
114
  "brace-expansion": "5.0.9",
115
115
  "fast-xml-builder": "^1.2.1",
116
+ "strnum": "^2.4.2",
116
117
  "file-type": "^21.3.3",
117
118
  "follow-redirects": "^1.16.1",
119
+ "protobufjs": "^7.6.6",
118
120
  "@crawlee/core": {
119
121
  "ow": "0.28.2"
120
122
  },