scrapely 1.2.6 → 1.2.8
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 +43 -96
- package/dist/actor.d.ts +2 -2
- package/dist/actor.js +2 -2
- package/dist/proxy-configuration.d.ts +8 -4
- package/dist/proxy-configuration.js +16 -11
- package/dist/scrapely-proxy-configuration.d.ts +4 -1
- package/dist/scrapely-proxy-configuration.js +25 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,118 +1,65 @@
|
|
|
1
|
-
# Scrapely
|
|
2
|
-
|
|
3
|
-
Scrapely is an Actor-based web scraping platform. Developers push an Actor (a scraping program), the platform builds it into a Docker image, runs it with structured input, and keeps its output in datasets, key-value stores and request queues. The API and SDKs follow Apify's shape, so most Apify Actors run with few or no changes.
|
|
4
|
-
|
|
5
|
-
This repo holds the platform server, the Actor SDKs, the API clients, the CLI and the MCP servers.
|
|
6
|
-
|
|
7
|
-
## Repo layout
|
|
8
|
-
|
|
9
|
-
| Path | What it is |
|
|
10
|
-
|------|------------|
|
|
11
|
-
| `src/` → `dist/` | Node.js Actor SDK, published as `scrapely`. `dist/` is committed because it is the package output. |
|
|
12
|
-
| `scrapely-python/` | Python Actor SDK (`scrapely` on PyPI) |
|
|
13
|
-
| `scrapely-server/` | API server, build/run workers, autoscaler, egress proxy |
|
|
14
|
-
| `scrapely-cli/` | `scrapely` CLI: login, create/init, push, pull, run, builds, runs, storages, secrets |
|
|
15
|
-
| `scrapely-client/` | JavaScript API client (`scrapely-client`, drop-in for `apify-client`) |
|
|
16
|
-
| `scrapely-client-python/` | Python API client (`scrapely-client-python`) |
|
|
17
|
-
| `scrapely-mcp/` | MCP server (TypeScript) |
|
|
18
|
-
| `scrapely-mcp-python/` | MCP server (Python) |
|
|
19
|
-
| `docker/` | Runner and Actor base images, Dragonfly compose file, Kaniko patch |
|
|
20
|
-
| `docs/` | Platform documentation. Start at [docs/README.md](docs/README.md). |
|
|
21
|
-
| `scripts/` | Server setup, deploy, migrations, benchmarks |
|
|
22
|
-
| `tests/` | SDK/runtime tests |
|
|
23
|
-
| `examples/basic-actor/` | Smallest working Actor |
|
|
24
|
-
| `npm-claim-scrapely/` | Old placeholder for the `scrapely` npm name, now superseded by the SDK itself (1.2.4+). |
|
|
25
|
-
|
|
26
|
-
Each package has its own README with install and usage details.
|
|
27
|
-
|
|
28
|
-
## How it fits together
|
|
29
|
-
|
|
30
|
-
- The **API server** (`scrapely-server/`) serves the Apify-style `/v2` API: auth, Actors and versions, builds, runs, tasks, schedules, webhooks, storages, billing and admin routes.
|
|
31
|
-
- **Builds and runs** are queued with BullMQ and picked up by workers. Builds produce Docker images that go to a private registry. Runs execute in Docker on worker servers.
|
|
32
|
-
- **The autoscaler** adds and removes worker servers based on load. See [docs/autoscaling.md](docs/autoscaling.md).
|
|
33
|
-
- **Storage**: run state and storages live in DragonflyDB (Redis-compatible). Accounts, Actors, runs and billing live in SingleStore. Both are backed up to S3.
|
|
34
|
-
- **Browsers**: remote browsers come from Evomi over CDP. See [docs/evomi-integration.md](docs/evomi-integration.md).
|
|
35
|
-
- **Actor code** imports the SDK, which patches Crawlee storage to go through the platform:
|
|
1
|
+
# Scrapely Node.js SDK
|
|
36
2
|
|
|
37
|
-
|
|
38
|
-
import { Actor, PlaywrightCrawler, Dataset } from 'scrapely';
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
```python
|
|
42
|
-
from scrapely import Actor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Local development
|
|
3
|
+
Build JavaScript and TypeScript Actors for the [Scrapely](https://scrape.ly) platform. The SDK gives you the Actor lifecycle, input and output, datasets, key-value stores and request queues, Crawlee crawlers, remote browsers over CDP, platform events, run control, webhooks and pay-per-event charging.
|
|
46
4
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Node.js 22
|
|
50
|
-
- Docker
|
|
51
|
-
- Doppler CLI with access to the `scrapely` project (all secrets come from Doppler; there are no `.env.example` files)
|
|
52
|
-
- Reachable DragonflyDB and SingleStore instances (Doppler `stg` points at staging)
|
|
53
|
-
|
|
54
|
-
### Install
|
|
5
|
+
## Install
|
|
55
6
|
|
|
56
7
|
```bash
|
|
57
|
-
npm install
|
|
58
|
-
cd scrapely-server && npm install
|
|
59
|
-
cd ../scrapely-cli && npm install
|
|
8
|
+
npm install scrapely
|
|
60
9
|
```
|
|
61
10
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
cd scrapely-server
|
|
66
|
-
doppler run -p scrapely -c stg -- npm run dev
|
|
67
|
-
curl http://localhost:3000/health
|
|
68
|
-
```
|
|
11
|
+
## Quick start
|
|
69
12
|
|
|
70
|
-
|
|
13
|
+
```ts
|
|
14
|
+
import { Actor } from 'scrapely';
|
|
71
15
|
|
|
72
|
-
|
|
16
|
+
await Actor.main(async () => {
|
|
17
|
+
const input = await Actor.getInputOrThrow<{ url: string }>();
|
|
18
|
+
await Actor.pushData({ url: input.url, status: 'processed' });
|
|
19
|
+
});
|
|
20
|
+
```
|
|
73
21
|
|
|
74
|
-
|
|
22
|
+
`Actor.main()` initializes the Actor, awaits your function, closes events and storage, and exits the process. Always `await` writes so they reach the platform before the process ends.
|
|
75
23
|
|
|
76
|
-
|
|
77
|
-
make build-runner
|
|
78
|
-
```
|
|
24
|
+
## Crawling
|
|
79
25
|
|
|
80
|
-
|
|
26
|
+
```ts
|
|
27
|
+
import { Actor, PlaywrightCrawler } from 'scrapely';
|
|
81
28
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
cd examples/basic-actor
|
|
85
|
-
scrapely push
|
|
86
|
-
scrapely run --wait
|
|
87
|
-
```
|
|
29
|
+
await Actor.main(async () => {
|
|
30
|
+
const { startUrls } = await Actor.getInputOrThrow<{ startUrls: string[] }>();
|
|
88
31
|
|
|
89
|
-
|
|
32
|
+
const crawler = new PlaywrightCrawler({
|
|
33
|
+
maxConcurrency: 5,
|
|
34
|
+
async requestHandler({ request, page }) {
|
|
35
|
+
await Actor.pushData({ url: request.url, title: await page.title() });
|
|
36
|
+
},
|
|
37
|
+
});
|
|
90
38
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
cd scrapely-server && npm test
|
|
39
|
+
await crawler.run(startUrls);
|
|
40
|
+
});
|
|
94
41
|
```
|
|
95
42
|
|
|
96
|
-
|
|
43
|
+
On the platform, browser crawlers connect to a remote Chromium over CDP instead of launching a local browser. Locally, they launch a normal browser unless you set `CDP_URL` or call `setCdpUrl()`.
|
|
97
44
|
|
|
98
|
-
##
|
|
45
|
+
## What's included
|
|
99
46
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
47
|
+
- **Actor lifecycle:** `Actor.main()`, `init()`, `exit()`, `fail()`, status messages, reboot, metamorph and abort
|
|
48
|
+
- **Input and output:** `getInput()`, `getInputOrThrow()`, `pushData()`, `getValue()`, `setValue()`
|
|
49
|
+
- **Storage:** `Dataset`, `KeyValueStore` and `RequestQueue`, backed by the Scrapely API on the platform and by local files during development
|
|
50
|
+
- **Crawlers:** `PlaywrightCrawler`, `PuppeteerCrawler`, `CheerioCrawler`, `HttpCrawler`, `BasicCrawler`, `BrowserCrawler`, `JSDOMCrawler` and `LinkeDOMCrawler`
|
|
51
|
+
- **Remote browsers:** automatic CDP wiring through `CDP_URL`, or `setCdpUrl()`
|
|
52
|
+
- **Events:** state persistence, migration and graceful shutdown
|
|
53
|
+
- **Run control:** `Actor.call()`, `Actor.callTask()`, `Actor.start()` and ad-hoc webhooks
|
|
54
|
+
- **Charging:** `Actor.charge()`, `ChargingManager` and `pushDataAndCharge()` for pay-per-event Actors
|
|
55
|
+
- **Logging and utilities:** `log`, `utils` and `enqueueLinks`
|
|
107
56
|
|
|
108
|
-
|
|
57
|
+
## Related packages
|
|
109
58
|
|
|
110
|
-
|
|
59
|
+
- [`scrapely-client`](https://www.npmjs.com/package/scrapely-client): JavaScript API client
|
|
60
|
+
- [`scrapely-cli`](https://www.npmjs.com/package/scrapely-cli): create, run and push Actors from your terminal
|
|
61
|
+
- [`scrapely-python`](https://pypi.org/project/scrapely-python/): the Python SDK
|
|
111
62
|
|
|
112
|
-
##
|
|
63
|
+
## License
|
|
113
64
|
|
|
114
|
-
|
|
115
|
-
- [scrapely-server/docs/API_ENDPOINTS.md](scrapely-server/docs/API_ENDPOINTS.md): API reference
|
|
116
|
-
- [docs/network-architecture.md](docs/network-architecture.md): fleet topology and data path
|
|
117
|
-
- `scrapely-server/src/index.ts`: server startup and route wiring
|
|
118
|
-
- `src/actor.ts`: Node SDK Actor runtime
|
|
65
|
+
ISC
|
package/dist/actor.d.ts
CHANGED
|
@@ -546,8 +546,8 @@ export declare class Actor {
|
|
|
546
546
|
* });
|
|
547
547
|
* ```
|
|
548
548
|
*
|
|
549
|
-
*
|
|
550
|
-
* `undefined` when `{
|
|
549
|
+
* Pass the proxy input field (`editor: "proxy"`) as is: this function returns
|
|
550
|
+
* `undefined` when `{ useScrapelyProxy: false }` is passed without custom proxies.
|
|
551
551
|
*
|
|
552
552
|
* @param options - Proxy configuration options
|
|
553
553
|
* @returns Promise resolving to ProxyConfiguration instance, or undefined
|
package/dist/actor.js
CHANGED
|
@@ -1015,8 +1015,8 @@ class Actor {
|
|
|
1015
1015
|
* });
|
|
1016
1016
|
* ```
|
|
1017
1017
|
*
|
|
1018
|
-
*
|
|
1019
|
-
* `undefined` when `{
|
|
1018
|
+
* Pass the proxy input field (`editor: "proxy"`) as is: this function returns
|
|
1019
|
+
* `undefined` when `{ useScrapelyProxy: false }` is passed without custom proxies.
|
|
1020
1020
|
*
|
|
1021
1021
|
* @param options - Proxy configuration options
|
|
1022
1022
|
* @returns Promise resolving to ProxyConfiguration instance, or undefined
|
|
@@ -53,14 +53,17 @@ export interface ProxyConfigurationOptions extends CoreProxyConfigurationOptions
|
|
|
53
53
|
/**
|
|
54
54
|
* Input-schema alias for groups. Normalized to groups.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
scrapelyProxyGroups?: string[];
|
|
57
57
|
/**
|
|
58
58
|
* Input-schema alias for subdivisionCode. Normalized to subdivisionCode.
|
|
59
59
|
*/
|
|
60
|
-
|
|
60
|
+
scrapelyProxySubdivision?: string;
|
|
61
61
|
/**
|
|
62
62
|
* Input-schema alias for countryCode. Normalized to countryCode.
|
|
63
63
|
*/
|
|
64
|
+
scrapelyProxyCountry?: string;
|
|
65
|
+
apifyProxyGroups?: string[];
|
|
66
|
+
apifyProxySubdivision?: string;
|
|
64
67
|
apifyProxyCountry?: string;
|
|
65
68
|
/**
|
|
66
69
|
* Platform proxy password. Defaults to the password injected by the
|
|
@@ -73,9 +76,10 @@ export interface ProxyConfigurationOptions extends CoreProxyConfigurationOptions
|
|
|
73
76
|
*/
|
|
74
77
|
export interface CreateProxyConfigurationOptions extends ProxyConfigurationOptions {
|
|
75
78
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
79
|
+
* The proxy input field's switch (input schema `editor: "proxy"`). When
|
|
80
|
+
* `false` and no custom proxies are given, this method returns `undefined`.
|
|
78
81
|
*/
|
|
82
|
+
useScrapelyProxy?: boolean;
|
|
79
83
|
useApifyProxy?: boolean;
|
|
80
84
|
/**
|
|
81
85
|
* Whether to verify the proxy configuration by testing a connection.
|
|
@@ -39,14 +39,17 @@ function hasCustomProxyOptions(options) {
|
|
|
39
39
|
}
|
|
40
40
|
function normalizeProxyInput(options) {
|
|
41
41
|
const normalized = { ...options };
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const groups = normalized.scrapelyProxyGroups ?? normalized.apifyProxyGroups;
|
|
43
|
+
if (groups && !normalized.groups) {
|
|
44
|
+
normalized.groups = groups;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const countryCode = normalized.scrapelyProxyCountry ?? normalized.apifyProxyCountry;
|
|
47
|
+
if (countryCode && !normalized.countryCode) {
|
|
48
|
+
normalized.countryCode = countryCode;
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const subdivisionCode = normalized.scrapelyProxySubdivision ?? normalized.apifyProxySubdivision;
|
|
51
|
+
if (subdivisionCode && !normalized.subdivisionCode) {
|
|
52
|
+
normalized.subdivisionCode = subdivisionCode;
|
|
50
53
|
}
|
|
51
54
|
return normalized;
|
|
52
55
|
}
|
|
@@ -60,17 +63,19 @@ function normalizeProxyInput(options) {
|
|
|
60
63
|
*/
|
|
61
64
|
async function createProxyConfiguration(options = {}) {
|
|
62
65
|
const normalized = normalizeProxyInput(options);
|
|
63
|
-
const { useApifyProxy, checkAccess = true, ...proxyOptions } = normalized;
|
|
64
|
-
//
|
|
66
|
+
const { useScrapelyProxy, useApifyProxy, checkAccess = true, ...proxyOptions } = normalized;
|
|
67
|
+
// `useScrapelyProxy: false` (or the legacy `useApifyProxy: false`; a boolean
|
|
68
|
+
// useScrapelyProxy wins) explicitly disables the PLATFORM proxy
|
|
65
69
|
// only — custom proxies from the same input must still be used. The standard
|
|
66
|
-
// "Custom proxy URLs" input shape is `{
|
|
70
|
+
// "Custom proxy URLs" input shape is `{ useScrapelyProxy: false, proxyUrls: [...] }`;
|
|
67
71
|
// returning undefined for it would silently drop the user's own proxies and
|
|
68
72
|
// crawl on their real IPs. We only bail when NO custom proxy mode is given —
|
|
69
73
|
// proxyUrls, tieredProxyUrls, tieredProxyConfig and newUrlFunction all count
|
|
70
74
|
// (Python SDK Actor.create_proxy_configuration parity).
|
|
71
|
-
const
|
|
75
|
+
const usePlatformProxy = typeof useScrapelyProxy === 'boolean' ? useScrapelyProxy : useApifyProxy;
|
|
76
|
+
const dontUsePlatformProxy = usePlatformProxy === false;
|
|
72
77
|
const dontUseCustomProxies = !hasCustomProxyOptions(proxyOptions);
|
|
73
|
-
if (
|
|
78
|
+
if (dontUsePlatformProxy && dontUseCustomProxies) {
|
|
74
79
|
return undefined;
|
|
75
80
|
}
|
|
76
81
|
if (!hasCustomProxyOptions(proxyOptions)) {
|
|
@@ -89,7 +89,10 @@ export declare class ProxyConfiguration extends CrawleeProxyConfiguration {
|
|
|
89
89
|
/**
|
|
90
90
|
* Verifies the proxy configuration. For platform proxy mode with an
|
|
91
91
|
* injected status URL, performs the Apify-format access check through the
|
|
92
|
-
* gateway (`GET {statusUrl}/?format=json
|
|
92
|
+
* gateway (`GET {statusUrl}/?format=json`). When the gateway answers
|
|
93
|
+
* `connected: false` (e.g. an unknown proxy group), it throws with the
|
|
94
|
+
* gateway's `connectionError` on the platform and warns locally, like
|
|
95
|
+
* Apify's `_checkAccess`; timeouts and HTTP errors only warn.
|
|
93
96
|
* BYOP mode only verifies that a test URL can be generated.
|
|
94
97
|
*/
|
|
95
98
|
initialize(options?: {
|
|
@@ -73,6 +73,12 @@ function validateProxySessionId(sessionId) {
|
|
|
73
73
|
}
|
|
74
74
|
return sessionId;
|
|
75
75
|
}
|
|
76
|
+
/** `groups`, else the input-schema alias `scrapelyProxyGroups`, else the legacy `apifyProxyGroups`. */
|
|
77
|
+
function platformGroups(options) {
|
|
78
|
+
if (options.groups?.length)
|
|
79
|
+
return options.groups;
|
|
80
|
+
return options.scrapelyProxyGroups ?? options.apifyProxyGroups;
|
|
81
|
+
}
|
|
76
82
|
/**
|
|
77
83
|
* Resolves and validates the country/subdivision options (Apify parity).
|
|
78
84
|
* Country must be a two-letter ISO code; subdivision an ISO 3166-2 code
|
|
@@ -80,8 +86,8 @@ function validateProxySessionId(sessionId) {
|
|
|
80
86
|
* the normalized values used by the username grammar.
|
|
81
87
|
*/
|
|
82
88
|
function resolveCountryOptions(options) {
|
|
83
|
-
const countryCode = options.countryCode ?? options.apifyProxyCountry;
|
|
84
|
-
const subdivisionCode = options.subdivisionCode ?? options.apifyProxySubdivision;
|
|
89
|
+
const countryCode = options.countryCode ?? options.scrapelyProxyCountry ?? options.apifyProxyCountry;
|
|
90
|
+
const subdivisionCode = options.subdivisionCode ?? options.scrapelyProxySubdivision ?? options.apifyProxySubdivision;
|
|
85
91
|
if (countryCode != null && !COUNTRY_CODE_REGEX.test(countryCode)) {
|
|
86
92
|
throw new Error(`ProxyConfiguration: "countryCode" must be a two-letter ISO country code (got "${countryCode}").`);
|
|
87
93
|
}
|
|
@@ -225,7 +231,7 @@ async function requestThroughProxy(targetUrl, proxyUrl, timeoutMs) {
|
|
|
225
231
|
* server.
|
|
226
232
|
*/
|
|
227
233
|
function buildPlatformProxyUrl(options, env, sessionId) {
|
|
228
|
-
const groups = options
|
|
234
|
+
const groups = platformGroups(options);
|
|
229
235
|
const { countryCode, subdivisionCode } = resolveCountryOptions(options);
|
|
230
236
|
const password = options.password ?? env.password;
|
|
231
237
|
// Apify username grammar: groups, then session, then country — with the
|
|
@@ -334,7 +340,7 @@ function toCrawleeOptions(options, env, placeholderOptions) {
|
|
|
334
340
|
return crawleeOptions;
|
|
335
341
|
}
|
|
336
342
|
function hasApifyOnlyOptions(options) {
|
|
337
|
-
const groups = options
|
|
343
|
+
const groups = platformGroups(options);
|
|
338
344
|
const { countryCode, subdivisionCode } = resolveCountryOptions(options);
|
|
339
345
|
return !!(groups?.length || countryCode || subdivisionCode || options.password);
|
|
340
346
|
}
|
|
@@ -382,10 +388,10 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
382
388
|
// values throw at construction, not on the first newUrl() call).
|
|
383
389
|
resolveCountryOptions(options);
|
|
384
390
|
if (hasRealCustomProxyOptions(options) && hasApifyOnlyOptions(options)) {
|
|
385
|
-
throw new Error('Cannot combine custom proxies with
|
|
391
|
+
throw new Error('Cannot combine custom proxies with Scrapely Proxy! ' +
|
|
386
392
|
'It is not allowed to set "options.proxyUrls" or "options.newUrlFunction" combined with ' +
|
|
387
|
-
'"options.groups" or "options.
|
|
388
|
-
'"options.
|
|
393
|
+
'"options.groups" or "options.scrapelyProxyGroups" and "options.countryCode" or ' +
|
|
394
|
+
'"options.scrapelyProxyCountry", "options.subdivisionCode" or "options.scrapelyProxySubdivision".');
|
|
389
395
|
}
|
|
390
396
|
if (needsPlatformProxyUrls(options) && !platformEnv) {
|
|
391
397
|
throw new Error('[Scrapely] The platform proxy was requested (via the "auto" sentinel, tieredProxyConfig tiers without ' +
|
|
@@ -445,9 +451,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
445
451
|
const sid = sessionId != null ? validateProxySessionId(String(sessionId)) : undefined;
|
|
446
452
|
const info = await super.newProxyInfo(sid, options);
|
|
447
453
|
if (info && this.platformEnv && info.hostname === this.platformEnv.hostname) {
|
|
448
|
-
const groups = this.platformOptions
|
|
449
|
-
? this.platformOptions.groups
|
|
450
|
-
: this.platformOptions.apifyProxyGroups;
|
|
454
|
+
const groups = platformGroups(this.platformOptions);
|
|
451
455
|
const { countryCode, subdivisionCode } = resolveCountryOptions(this.platformOptions);
|
|
452
456
|
return {
|
|
453
457
|
...info,
|
|
@@ -483,6 +487,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
483
487
|
connectionError: 'Platform proxy authentication failed (HTTP 407). Check the injected ' +
|
|
484
488
|
'SCRAPELY_PROXY_*/APIFY_PROXY_* credentials.',
|
|
485
489
|
isManInTheMiddle: false,
|
|
490
|
+
fromPayload: false,
|
|
486
491
|
};
|
|
487
492
|
}
|
|
488
493
|
if (statusCode >= 590 && statusCode <= 599) {
|
|
@@ -491,6 +496,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
491
496
|
connected: false,
|
|
492
497
|
connectionError: `Platform proxy upstream error (HTTP ${statusCode}: ${description}).`,
|
|
493
498
|
isManInTheMiddle: false,
|
|
499
|
+
fromPayload: false,
|
|
494
500
|
};
|
|
495
501
|
}
|
|
496
502
|
if (statusCode >= 200 && statusCode < 300) {
|
|
@@ -500,6 +506,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
500
506
|
connected: parsed.connected === true,
|
|
501
507
|
connectionError: parsed.connectionError ?? '',
|
|
502
508
|
isManInTheMiddle: parsed.isManInTheMiddle === true,
|
|
509
|
+
fromPayload: true,
|
|
503
510
|
};
|
|
504
511
|
}
|
|
505
512
|
catch {
|
|
@@ -516,7 +523,10 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
516
523
|
/**
|
|
517
524
|
* Verifies the proxy configuration. For platform proxy mode with an
|
|
518
525
|
* injected status URL, performs the Apify-format access check through the
|
|
519
|
-
* gateway (`GET {statusUrl}/?format=json
|
|
526
|
+
* gateway (`GET {statusUrl}/?format=json`). When the gateway answers
|
|
527
|
+
* `connected: false` (e.g. an unknown proxy group), it throws with the
|
|
528
|
+
* gateway's `connectionError` on the platform and warns locally, like
|
|
529
|
+
* Apify's `_checkAccess`; timeouts and HTTP errors only warn.
|
|
520
530
|
* BYOP mode only verifies that a test URL can be generated.
|
|
521
531
|
*/
|
|
522
532
|
async initialize(options = {}) {
|
|
@@ -526,7 +536,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
526
536
|
}
|
|
527
537
|
// Apify-parity access check: GET {statusUrl}/?format=json *through the
|
|
528
538
|
// platform gateway* so the proxy handshake itself authenticates the
|
|
529
|
-
// run's credentials.
|
|
539
|
+
// run's credentials.
|
|
530
540
|
if (checkAccess && this.platformEnv?.statusUrl) {
|
|
531
541
|
const status = await this.fetchProxyStatus();
|
|
532
542
|
if (!status) {
|
|
@@ -535,6 +545,9 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
|
|
|
535
545
|
}
|
|
536
546
|
else {
|
|
537
547
|
this.isManInTheMiddle = status.isManInTheMiddle;
|
|
548
|
+
if (!status.connected && status.fromPayload && this.config.get('isAtHome')) {
|
|
549
|
+
throw new Error(status.connectionError);
|
|
550
|
+
}
|
|
538
551
|
if (!status.connected) {
|
|
539
552
|
console.warn(`[Scrapely] Platform proxy access check failed: ${status.connectionError}`);
|
|
540
553
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scrapely",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.8",
|
|
4
|
+
"description": "Scrapely SDK for building JavaScript and TypeScript Actors",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|