scrapely 1.2.3 → 1.2.4

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.
Files changed (86) hide show
  1. package/README.md +119 -1
  2. package/dist/actor.d.ts +703 -0
  3. package/dist/actor.js +1441 -0
  4. package/dist/browser-configuration.d.ts +58 -0
  5. package/dist/browser-configuration.js +128 -0
  6. package/dist/browser-locations.d.ts +5 -0
  7. package/dist/browser-locations.js +5469 -0
  8. package/dist/charging.d.ts +153 -0
  9. package/dist/charging.js +782 -0
  10. package/dist/charging_client.d.ts +18 -0
  11. package/dist/charging_client.js +49 -0
  12. package/dist/configuration.d.ts +121 -0
  13. package/dist/configuration.js +128 -0
  14. package/dist/crypto/secrets.d.ts +44 -0
  15. package/dist/crypto/secrets.js +164 -0
  16. package/dist/events/events-client.d.ts +59 -0
  17. package/dist/events/events-client.js +120 -0
  18. package/dist/events/scrapely-event-manager.d.ts +13 -0
  19. package/dist/events/scrapely-event-manager.js +61 -0
  20. package/dist/index.d.ts +29 -0
  21. package/dist/index.js +95 -0
  22. package/dist/log-env.d.ts +1 -0
  23. package/dist/log-env.js +12 -0
  24. package/dist/manifest.d.ts +30 -0
  25. package/dist/manifest.js +47 -0
  26. package/dist/patch-browser-pool.d.ts +66 -0
  27. package/dist/patch-browser-pool.js +827 -0
  28. package/dist/proxy-configuration.d.ts +118 -0
  29. package/dist/proxy-configuration.js +88 -0
  30. package/dist/request-list.d.ts +57 -0
  31. package/dist/request-list.js +79 -0
  32. package/dist/runner/docker-manager.d.ts +88 -0
  33. package/dist/runner/docker-manager.js +283 -0
  34. package/dist/runner/index.d.ts +2 -0
  35. package/dist/runner/index.js +7 -0
  36. package/dist/runner/timeout-manager.d.ts +43 -0
  37. package/dist/runner/timeout-manager.js +90 -0
  38. package/dist/scrapely-proxy-configuration.d.ts +99 -0
  39. package/dist/scrapely-proxy-configuration.js +559 -0
  40. package/dist/storage/api-storage-client.d.ts +199 -0
  41. package/dist/storage/api-storage-client.js +910 -0
  42. package/dist/storage/billing-tracker.d.ts +163 -0
  43. package/dist/storage/billing-tracker.js +260 -0
  44. package/dist/storage/dataset-client.d.ts +43 -0
  45. package/dist/storage/dataset-client.js +232 -0
  46. package/dist/storage/dataset-collection-client.d.ts +8 -0
  47. package/dist/storage/dataset-collection-client.js +75 -0
  48. package/dist/storage/dataset-push-script.d.ts +18 -0
  49. package/dist/storage/dataset-push-script.js +38 -0
  50. package/dist/storage/dragonfly-client.d.ts +94 -0
  51. package/dist/storage/dragonfly-client.js +167 -0
  52. package/dist/storage/index.d.ts +11 -0
  53. package/dist/storage/index.js +25 -0
  54. package/dist/storage/key-value-store-client.d.ts +61 -0
  55. package/dist/storage/key-value-store-client.js +306 -0
  56. package/dist/storage/kv-store-collection-client.d.ts +8 -0
  57. package/dist/storage/kv-store-collection-client.js +76 -0
  58. package/dist/storage/kvs-iteration.d.ts +17 -0
  59. package/dist/storage/kvs-iteration.js +126 -0
  60. package/dist/storage/log-manager.d.ts +114 -0
  61. package/dist/storage/log-manager.js +244 -0
  62. package/dist/storage/patch-storage.d.ts +11 -0
  63. package/dist/storage/patch-storage.js +177 -0
  64. package/dist/storage/request-queue-client.d.ts +93 -0
  65. package/dist/storage/request-queue-client.js +522 -0
  66. package/dist/storage/rq-collection-client.d.ts +8 -0
  67. package/dist/storage/rq-collection-client.js +89 -0
  68. package/dist/storage/rq-list-requests.d.ts +59 -0
  69. package/dist/storage/rq-list-requests.js +114 -0
  70. package/dist/storage/rq-lock-script.d.ts +56 -0
  71. package/dist/storage/rq-lock-script.js +127 -0
  72. package/dist/storage/run-metadata.d.ts +98 -0
  73. package/dist/storage/run-metadata.js +256 -0
  74. package/dist/storage/s3-client.d.ts +103 -0
  75. package/dist/storage/s3-client.js +302 -0
  76. package/dist/storage/ttl-manager.d.ts +70 -0
  77. package/dist/storage/ttl-manager.js +115 -0
  78. package/dist/storage/utils.d.ts +3 -0
  79. package/dist/storage/utils.js +19 -0
  80. package/dist/storage-identifiers.d.ts +20 -0
  81. package/dist/storage-identifiers.js +6 -0
  82. package/dist/utils.d.ts +54 -0
  83. package/dist/utils.js +63 -0
  84. package/dist/validator.d.ts +24 -0
  85. package/dist/validator.js +158 -0
  86. package/package.json +90 -9
@@ -0,0 +1,18 @@
1
+ import type { ApifyClientOptions, PpeAwarePushDataContext } from 'scrapely-client';
2
+ import { ApifyClient, USES_PUSH_DATA_INTERCEPTION, pushDataChargingContext } from 'scrapely-client';
3
+ import { type ChargingManager } from './charging.js';
4
+ import type { Configuration } from './configuration.js';
5
+ export { USES_PUSH_DATA_INTERCEPTION, pushDataChargingContext };
6
+ export type { PpeAwarePushDataContext };
7
+ /**
8
+ * Returns a `scrapely-client` `ApifyClient` configured with the SDK's charging
9
+ * hooks, so pushes to the run's default dataset are intercepted and charged
10
+ * pay-per-event by the client itself (no subclassing).
11
+ *
12
+ * `defaultDatasetId` is captured from the configuration at construction time;
13
+ * the pricing check happens lazily on every `dataset()` call.
14
+ */
15
+ export declare function createChargingClient(options: ApifyClientOptions, actor: {
16
+ config: Configuration;
17
+ getChargingManager: () => ChargingManager;
18
+ }): ApifyClient;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pushDataChargingContext = exports.USES_PUSH_DATA_INTERCEPTION = void 0;
4
+ exports.createChargingClient = createChargingClient;
5
+ const scrapely_client_1 = require("scrapely-client");
6
+ Object.defineProperty(exports, "USES_PUSH_DATA_INTERCEPTION", { enumerable: true, get: function () { return scrapely_client_1.USES_PUSH_DATA_INTERCEPTION; } });
7
+ Object.defineProperty(exports, "pushDataChargingContext", { enumerable: true, get: function () { return scrapely_client_1.pushDataChargingContext; } });
8
+ const charging_js_1 = require("./charging.js");
9
+ /**
10
+ * Adapts the SDK's {@link ChargingManager} to `scrapely-client`'s
11
+ * {@link ChargingHooks} protocol. The client only routes `pushItems()`
12
+ * through these hooks for the run's default dataset; all pricing/budget/ledger
13
+ * decisions stay in the SDK.
14
+ */
15
+ function createChargingHooks(actor) {
16
+ return {
17
+ // Called by the client at `dataset()` time, so pricing is consulted
18
+ // lazily — same timing as the former subclass override.
19
+ shouldChargeDatasetPushes() {
20
+ return (0, charging_js_1.hasDefaultDatasetItemEventInPricing)(actor.getChargingManager().getPricingInfo().perEventPrices);
21
+ },
22
+ async pushAndCharge({ items, eventName, pushFn }) {
23
+ return (0, charging_js_1.pushDataAndCharge)({
24
+ chargingManager: actor.getChargingManager(),
25
+ items,
26
+ eventName,
27
+ // The client only intercepts the default dataset.
28
+ isDefaultDataset: true,
29
+ pushFn: async (limitedItems) => pushFn(limitedItems),
30
+ });
31
+ },
32
+ mergeChargeResults: charging_js_1.mergeChargeResults,
33
+ };
34
+ }
35
+ /**
36
+ * Returns a `scrapely-client` `ApifyClient` configured with the SDK's charging
37
+ * hooks, so pushes to the run's default dataset are intercepted and charged
38
+ * pay-per-event by the client itself (no subclassing).
39
+ *
40
+ * `defaultDatasetId` is captured from the configuration at construction time;
41
+ * the pricing check happens lazily on every `dataset()` call.
42
+ */
43
+ function createChargingClient(options, actor) {
44
+ return new scrapely_client_1.ApifyClient({
45
+ ...options,
46
+ defaultDatasetId: actor.config.get('defaultDatasetId'),
47
+ charging: createChargingHooks(actor),
48
+ });
49
+ }
@@ -0,0 +1,121 @@
1
+ import type { ConfigurationOptions as CoreConfigurationOptions } from '@crawlee/core';
2
+ import { Configuration as CoreConfiguration } from '@crawlee/core';
3
+ export interface ConfigurationOptions extends CoreConfigurationOptions {
4
+ metamorphAfterSleepMillis?: number;
5
+ actorEventsWsUrl?: string;
6
+ token?: string;
7
+ actorId?: string;
8
+ actorRunId?: string;
9
+ actorTaskId?: string;
10
+ apiBaseUrl?: string;
11
+ apiPublicBaseUrl?: string;
12
+ containerPort?: number;
13
+ containerUrl?: string;
14
+ proxyHostname?: string;
15
+ proxyPassword?: string;
16
+ proxyPort?: number;
17
+ proxyStatusUrl?: string;
18
+ /** @deprecated use `containerPort` instead */
19
+ standbyPort?: number;
20
+ standbyUrl?: string;
21
+ isAtHome?: boolean;
22
+ userId?: string;
23
+ inputSecretsPrivateKeyPassphrase?: string;
24
+ inputSecretsPrivateKeyFile?: string;
25
+ maxTotalChargeUsd?: number;
26
+ metaOrigin?: string;
27
+ testPayPerEvent?: boolean;
28
+ useChargingLogDataset?: boolean;
29
+ actorPricingInfo?: string;
30
+ chargedEventCounts?: string;
31
+ actorStoragesJson?: string;
32
+ actorPermissionLevel?: string;
33
+ userIsPaying?: boolean;
34
+ }
35
+ export declare class Configuration extends CoreConfiguration {
36
+ static globalConfig?: Configuration;
37
+ protected static ENV_MAP: {
38
+ APIFY_AVAILABLE_MEMORY_RATIO: string;
39
+ APIFY_PURGE_ON_START: string;
40
+ APIFY_MEMORY_MBYTES: string;
41
+ APIFY_DEFAULT_DATASET_ID: string;
42
+ APIFY_DEFAULT_KEY_VALUE_STORE_ID: string;
43
+ APIFY_DEFAULT_REQUEST_QUEUE_ID: string;
44
+ APIFY_INPUT_KEY: string;
45
+ APIFY_PERSIST_STATE_INTERVAL_MILLIS: string;
46
+ APIFY_HEADLESS: string;
47
+ APIFY_XVFB: string;
48
+ APIFY_CHROME_EXECUTABLE_PATH: string;
49
+ APIFY_DEFAULT_BROWSER_PATH: string;
50
+ APIFY_DISABLE_BROWSER_SANDBOX: string;
51
+ APIFY_TOKEN: string;
52
+ APIFY_METAMORPH_AFTER_SLEEP_MILLIS: string;
53
+ APIFY_TEST_PERSIST_INTERVAL_MILLIS: string;
54
+ APIFY_ACTOR_EVENTS_WS_URL: string;
55
+ APIFY_ACTOR_ID: string;
56
+ APIFY_API_BASE_URL: string;
57
+ APIFY_API_PUBLIC_BASE_URL: string;
58
+ APIFY_IS_AT_HOME: string;
59
+ APIFY_ACTOR_RUN_ID: string;
60
+ APIFY_ACTOR_TASK_ID: string;
61
+ APIFY_CONTAINER_PORT: string;
62
+ APIFY_CONTAINER_URL: string;
63
+ APIFY_USER_ID: string;
64
+ APIFY_USER_IS_PAYING: string;
65
+ APIFY_PROXY_HOSTNAME: string;
66
+ APIFY_PROXY_PASSWORD: string;
67
+ APIFY_PROXY_STATUS_URL: string;
68
+ APIFY_PROXY_PORT: string;
69
+ APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE: string;
70
+ APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE: string;
71
+ APIFY_META_ORIGIN: string;
72
+ SCRAPELY_TOKEN: string;
73
+ SCRAPELY_API_URL: string;
74
+ SCRAPELY_IS_AT_HOME: string;
75
+ ACTOR_DEFAULT_DATASET_ID: string;
76
+ ACTOR_DEFAULT_KEY_VALUE_STORE_ID: string;
77
+ ACTOR_DEFAULT_REQUEST_QUEUE_ID: string;
78
+ ACTOR_EVENTS_WEBSOCKET_URL: string;
79
+ ACTOR_ID: string;
80
+ ACTOR_INPUT_KEY: string;
81
+ ACTOR_MEMORY_MBYTES: string;
82
+ ACTOR_PERMISSION_LEVEL: string;
83
+ ACTOR_RUN_ID: string;
84
+ ACTOR_STANDBY_PORT: string;
85
+ ACTOR_STANDBY_URL: string;
86
+ ACTOR_TASK_ID: string;
87
+ ACTOR_WEB_SERVER_PORT: string;
88
+ ACTOR_WEB_SERVER_URL: string;
89
+ ACTOR_MAX_TOTAL_CHARGE_USD: string;
90
+ ACTOR_TEST_PAY_PER_EVENT: string;
91
+ ACTOR_USE_CHARGING_LOG_DATASET: string;
92
+ APIFY_ACTOR_PRICING_INFO: string;
93
+ APIFY_CHARGED_ACTOR_EVENT_COUNTS: string;
94
+ ACTOR_STORAGES_JSON: string;
95
+ ACTOR_META_ORIGIN: string;
96
+ };
97
+ protected static INTEGER_VARS: string[];
98
+ protected static BOOLEAN_VARS: string[];
99
+ protected static DEFAULTS: {
100
+ defaultKeyValueStoreId: string;
101
+ defaultDatasetId: string;
102
+ defaultRequestQueueId: string;
103
+ inputKey: string;
104
+ apiBaseUrl: string;
105
+ apiPublicBaseUrl: string;
106
+ proxyStatusUrl: string;
107
+ proxyHostname: string;
108
+ proxyPort: number;
109
+ containerPort: number;
110
+ containerUrl: string;
111
+ standbyPort: number;
112
+ metamorphAfterSleepMillis: number;
113
+ persistStateIntervalMillis: number;
114
+ testPayPerEvent: boolean;
115
+ useChargingLogDataset: boolean;
116
+ };
117
+ get<T extends keyof ConfigurationOptions, U extends ConfigurationOptions[T]>(key: T, defaultValue?: U): U;
118
+ set(key: keyof ConfigurationOptions, value?: any): void;
119
+ static getGlobalConfig(): Configuration;
120
+ static resetGlobalState(): void;
121
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Configuration = void 0;
4
+ const core_1 = require("@crawlee/core");
5
+ class Configuration extends core_1.Configuration {
6
+ static globalConfig;
7
+ static ENV_MAP = {
8
+ ...core_1.Configuration.ENV_MAP,
9
+ APIFY_AVAILABLE_MEMORY_RATIO: 'availableMemoryRatio',
10
+ APIFY_PURGE_ON_START: 'purgeOnStart',
11
+ APIFY_MEMORY_MBYTES: 'memoryMbytes',
12
+ APIFY_DEFAULT_DATASET_ID: 'defaultDatasetId',
13
+ APIFY_DEFAULT_KEY_VALUE_STORE_ID: 'defaultKeyValueStoreId',
14
+ APIFY_DEFAULT_REQUEST_QUEUE_ID: 'defaultRequestQueueId',
15
+ APIFY_INPUT_KEY: 'inputKey',
16
+ APIFY_PERSIST_STATE_INTERVAL_MILLIS: 'persistStateIntervalMillis',
17
+ APIFY_HEADLESS: 'headless',
18
+ APIFY_XVFB: 'xvfb',
19
+ APIFY_CHROME_EXECUTABLE_PATH: 'chromeExecutablePath',
20
+ APIFY_DEFAULT_BROWSER_PATH: 'defaultBrowserPath',
21
+ APIFY_DISABLE_BROWSER_SANDBOX: 'disableBrowserSandbox',
22
+ APIFY_TOKEN: 'token',
23
+ APIFY_METAMORPH_AFTER_SLEEP_MILLIS: 'metamorphAfterSleepMillis',
24
+ APIFY_TEST_PERSIST_INTERVAL_MILLIS: 'persistStateIntervalMillis',
25
+ APIFY_ACTOR_EVENTS_WS_URL: 'actorEventsWsUrl',
26
+ APIFY_ACTOR_ID: 'actorId',
27
+ APIFY_API_BASE_URL: 'apiBaseUrl',
28
+ APIFY_API_PUBLIC_BASE_URL: 'apiPublicBaseUrl',
29
+ APIFY_IS_AT_HOME: 'isAtHome',
30
+ APIFY_ACTOR_RUN_ID: 'actorRunId',
31
+ APIFY_ACTOR_TASK_ID: 'actorTaskId',
32
+ APIFY_CONTAINER_PORT: 'containerPort',
33
+ APIFY_CONTAINER_URL: 'containerUrl',
34
+ APIFY_USER_ID: 'userId',
35
+ APIFY_USER_IS_PAYING: 'userIsPaying',
36
+ APIFY_PROXY_HOSTNAME: 'proxyHostname',
37
+ APIFY_PROXY_PASSWORD: 'proxyPassword',
38
+ APIFY_PROXY_STATUS_URL: 'proxyStatusUrl',
39
+ APIFY_PROXY_PORT: 'proxyPort',
40
+ APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE: 'inputSecretsPrivateKeyFile',
41
+ APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE: 'inputSecretsPrivateKeyPassphrase',
42
+ APIFY_META_ORIGIN: 'metaOrigin',
43
+ SCRAPELY_TOKEN: 'token',
44
+ SCRAPELY_API_URL: 'apiBaseUrl',
45
+ SCRAPELY_IS_AT_HOME: 'isAtHome',
46
+ ACTOR_DEFAULT_DATASET_ID: 'defaultDatasetId',
47
+ ACTOR_DEFAULT_KEY_VALUE_STORE_ID: 'defaultKeyValueStoreId',
48
+ ACTOR_DEFAULT_REQUEST_QUEUE_ID: 'defaultRequestQueueId',
49
+ ACTOR_EVENTS_WEBSOCKET_URL: 'actorEventsWsUrl',
50
+ ACTOR_ID: 'actorId',
51
+ ACTOR_INPUT_KEY: 'inputKey',
52
+ ACTOR_MEMORY_MBYTES: 'memoryMbytes',
53
+ ACTOR_PERMISSION_LEVEL: 'actorPermissionLevel',
54
+ ACTOR_RUN_ID: 'actorRunId',
55
+ ACTOR_STANDBY_PORT: 'standbyPort',
56
+ ACTOR_STANDBY_URL: 'standbyUrl',
57
+ ACTOR_TASK_ID: 'actorTaskId',
58
+ ACTOR_WEB_SERVER_PORT: 'containerPort',
59
+ ACTOR_WEB_SERVER_URL: 'containerUrl',
60
+ ACTOR_MAX_TOTAL_CHARGE_USD: 'maxTotalChargeUsd',
61
+ ACTOR_TEST_PAY_PER_EVENT: 'testPayPerEvent',
62
+ ACTOR_USE_CHARGING_LOG_DATASET: 'useChargingLogDataset',
63
+ APIFY_ACTOR_PRICING_INFO: 'actorPricingInfo',
64
+ APIFY_CHARGED_ACTOR_EVENT_COUNTS: 'chargedEventCounts',
65
+ ACTOR_STORAGES_JSON: 'actorStoragesJson',
66
+ ACTOR_META_ORIGIN: 'metaOrigin',
67
+ };
68
+ static INTEGER_VARS = [
69
+ ...core_1.Configuration.INTEGER_VARS,
70
+ 'proxyPort',
71
+ 'containerPort',
72
+ 'standbyPort',
73
+ 'metamorphAfterSleepMillis',
74
+ 'maxTotalChargeUsd',
75
+ ];
76
+ static BOOLEAN_VARS = [
77
+ ...core_1.Configuration.BOOLEAN_VARS,
78
+ 'isAtHome',
79
+ 'testPayPerEvent',
80
+ 'useChargingLogDataset',
81
+ 'userIsPaying',
82
+ ];
83
+ static DEFAULTS = {
84
+ ...core_1.Configuration.DEFAULTS,
85
+ defaultKeyValueStoreId: 'default',
86
+ defaultDatasetId: 'default',
87
+ defaultRequestQueueId: 'default',
88
+ inputKey: 'INPUT',
89
+ apiBaseUrl: 'https://api-staging.scrape.ly',
90
+ apiPublicBaseUrl: 'https://api-staging.scrape.ly',
91
+ proxyStatusUrl: 'http://proxy.scrape.ly',
92
+ proxyHostname: 'proxy.scrape.ly',
93
+ proxyPort: 8000,
94
+ containerPort: 4321,
95
+ containerUrl: 'http://localhost:4321',
96
+ standbyPort: 4321,
97
+ metamorphAfterSleepMillis: 300e3,
98
+ persistStateIntervalMillis: 60e3,
99
+ testPayPerEvent: false,
100
+ useChargingLogDataset: false,
101
+ };
102
+ get(key, defaultValue) {
103
+ return super.get(key, defaultValue);
104
+ }
105
+ set(key, value) {
106
+ super.set(key, value);
107
+ }
108
+ static getGlobalConfig() {
109
+ if (Configuration.storage.getStore()) {
110
+ return Configuration.storage.getStore();
111
+ }
112
+ Configuration.globalConfig ??= new Configuration();
113
+ return Configuration.globalConfig;
114
+ }
115
+ static resetGlobalState() {
116
+ delete this.globalConfig;
117
+ }
118
+ }
119
+ exports.Configuration = Configuration;
120
+ core_1.Configuration.getGlobalConfig = Configuration.getGlobalConfig;
121
+ // @ts-expect-error protected property
122
+ core_1.Configuration.ENV_MAP = Configuration.ENV_MAP;
123
+ // @ts-expect-error protected property
124
+ core_1.Configuration.INTEGER_VARS = Configuration.INTEGER_VARS;
125
+ // @ts-expect-error protected property
126
+ core_1.Configuration.BOOLEAN_VARS = Configuration.BOOLEAN_VARS;
127
+ // @ts-expect-error protected property
128
+ core_1.Configuration.DEFAULTS = Configuration.DEFAULTS;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Read the private key file from disk and decrypt it with the run passphrase.
3
+ * The private key file is encrypted with AES-256-GCM using a key derived from the passphrase.
4
+ *
5
+ * @param privateKeyFilePath - Path to the encrypted private key file (e.g., /run/secrets/private_key.pem)
6
+ * @param passphrase - The run passphrase from APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE
7
+ * @returns The decrypted private key as a PEM string
8
+ */
9
+ export declare function decryptPrivateKeyFile(privateKeyFilePath: string, passphrase: string): string;
10
+ /**
11
+ * Decrypt a single secret value encrypted with RSA-OAEP.
12
+ *
13
+ * @param encryptedBase64 - Base64-encoded encrypted data
14
+ * @param privateKeyPem - The decrypted private key PEM
15
+ * @returns The decrypted plaintext value
16
+ */
17
+ export declare function decryptSecretValue(encryptedBase64: string, privateKeyPem: string): any;
18
+ /**
19
+ * Decrypt all secret values from the encrypted input payload.
20
+ *
21
+ * @param encryptedSecrets - Map of field name → base64 encrypted value
22
+ * @param privateKeyPem - The decrypted private key PEM
23
+ * @returns Map of field name → decrypted value
24
+ */
25
+ export declare function decryptAllSecrets(encryptedSecrets: Record<string, string>, privateKeyPem: string): Record<string, any>;
26
+ /**
27
+ * Merge decrypted secret values into the original input object.
28
+ * Supports nested paths like "credentials.apiKey" or "database.password".
29
+ *
30
+ * @param input - The original input object (with secrets stripped/masked)
31
+ * @param decryptedSecrets - Map of field name → decrypted value
32
+ * @returns A new input object with secrets populated
33
+ */
34
+ export declare function mergeDecryptedInput(input: Record<string, any>, decryptedSecrets: Record<string, any>): Record<string, any>;
35
+ /**
36
+ * Full decryption flow: read private key from file, decrypt it with passphrase,
37
+ * then decrypt all secrets and merge into input.
38
+ *
39
+ * This is called from Actor.init() when APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE is set.
40
+ *
41
+ * @param input - Current actor input object
42
+ * @returns The input object with decrypted secret values merged in
43
+ */
44
+ export declare function decryptSecretInputs(input: Record<string, any>): Record<string, any>;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.decryptPrivateKeyFile = decryptPrivateKeyFile;
7
+ exports.decryptSecretValue = decryptSecretValue;
8
+ exports.decryptAllSecrets = decryptAllSecrets;
9
+ exports.mergeDecryptedInput = mergeDecryptedInput;
10
+ exports.decryptSecretInputs = decryptSecretInputs;
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ const fs_1 = require("fs");
13
+ // ─── Constants ────────────────────────────────────────────────────────────────
14
+ const RSA_PADDING = crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING;
15
+ const RSA_OAEP_HASH = 'sha256';
16
+ const AES_ALGORITHM = 'aes-256-gcm';
17
+ const IV_LENGTH = 12;
18
+ const AUTH_TAG_LENGTH = 16;
19
+ const PBKDF2_ITERATIONS = 100_000;
20
+ const PBKDF2_HASH = 'sha256';
21
+ const SALT_LENGTH = 32;
22
+ const KEY_LENGTH = 32;
23
+ // ─── Private Key Decryption ───────────────────────────────────────────────────
24
+ /**
25
+ * Read the private key file from disk and decrypt it with the run passphrase.
26
+ * The private key file is encrypted with AES-256-GCM using a key derived from the passphrase.
27
+ *
28
+ * @param privateKeyFilePath - Path to the encrypted private key file (e.g., /run/secrets/private_key.pem)
29
+ * @param passphrase - The run passphrase from APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE
30
+ * @returns The decrypted private key as a PEM string
31
+ */
32
+ function decryptPrivateKeyFile(privateKeyFilePath, passphrase) {
33
+ const encryptedData = (0, fs_1.readFileSync)(privateKeyFilePath);
34
+ const salt = encryptedData.subarray(0, SALT_LENGTH);
35
+ const iv = encryptedData.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
36
+ const authTag = encryptedData.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
37
+ const encrypted = encryptedData.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
38
+ // Derive key from passphrase
39
+ const key = crypto_1.default.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_HASH);
40
+ // Decrypt
41
+ const decipher = crypto_1.default.createDecipheriv(AES_ALGORITHM, key, iv);
42
+ decipher.setAuthTag(authTag);
43
+ const decrypted = Buffer.concat([
44
+ decipher.update(encrypted),
45
+ decipher.final(),
46
+ ]);
47
+ return decrypted.toString('utf8');
48
+ }
49
+ // ─── Secret Value Decryption ──────────────────────────────────────────────────
50
+ /**
51
+ * Decrypt a single secret value encrypted with RSA-OAEP.
52
+ *
53
+ * @param encryptedBase64 - Base64-encoded encrypted data
54
+ * @param privateKeyPem - The decrypted private key PEM
55
+ * @returns The decrypted plaintext value
56
+ */
57
+ function decryptSecretValue(encryptedBase64, privateKeyPem) {
58
+ const encrypted = Buffer.from(encryptedBase64, 'base64');
59
+ const decryptedData = crypto_1.default.privateDecrypt({
60
+ key: privateKeyPem,
61
+ padding: RSA_PADDING,
62
+ oaepHash: RSA_OAEP_HASH,
63
+ }, encrypted);
64
+ const plaintext = decryptedData.toString('utf8');
65
+ // Try to parse as JSON (for objects, arrays, numbers, booleans)
66
+ try {
67
+ return JSON.parse(plaintext);
68
+ }
69
+ catch {
70
+ // Return as plain string
71
+ return plaintext;
72
+ }
73
+ }
74
+ /**
75
+ * Decrypt all secret values from the encrypted input payload.
76
+ *
77
+ * @param encryptedSecrets - Map of field name → base64 encrypted value
78
+ * @param privateKeyPem - The decrypted private key PEM
79
+ * @returns Map of field name → decrypted value
80
+ */
81
+ function decryptAllSecrets(encryptedSecrets, privateKeyPem) {
82
+ const decrypted = {};
83
+ for (const [fieldName, encryptedBase64] of Object.entries(encryptedSecrets)) {
84
+ try {
85
+ decrypted[fieldName] = decryptSecretValue(encryptedBase64, privateKeyPem);
86
+ }
87
+ catch (err) {
88
+ console.warn(`[Scrapely] Failed to decrypt secret field "${fieldName}": ${err.message}`);
89
+ }
90
+ }
91
+ return decrypted;
92
+ }
93
+ /**
94
+ * Merge decrypted secret values into the original input object.
95
+ * Supports nested paths like "credentials.apiKey" or "database.password".
96
+ *
97
+ * @param input - The original input object (with secrets stripped/masked)
98
+ * @param decryptedSecrets - Map of field name → decrypted value
99
+ * @returns A new input object with secrets populated
100
+ */
101
+ function mergeDecryptedInput(input, decryptedSecrets) {
102
+ const merged = { ...input };
103
+ for (const [fieldName, value] of Object.entries(decryptedSecrets)) {
104
+ if (fieldName.includes('.')) {
105
+ // Handle nested paths
106
+ const parts = fieldName.split('.');
107
+ let current = merged;
108
+ for (let i = 0; i < parts.length - 1; i++) {
109
+ if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
110
+ current[parts[i]] = {};
111
+ }
112
+ current = current[parts[i]];
113
+ }
114
+ current[parts[parts.length - 1]] = value;
115
+ }
116
+ else {
117
+ merged[fieldName] = value;
118
+ }
119
+ }
120
+ return merged;
121
+ }
122
+ // ─── Full Decryption Flow ─────────────────────────────────────────────────────
123
+ /**
124
+ * Full decryption flow: read private key from file, decrypt it with passphrase,
125
+ * then decrypt all secrets and merge into input.
126
+ *
127
+ * This is called from Actor.init() when APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE is set.
128
+ *
129
+ * @param input - Current actor input object
130
+ * @returns The input object with decrypted secret values merged in
131
+ */
132
+ function decryptSecretInputs(input) {
133
+ const privateKeyFile = process.env.APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE;
134
+ const passphrase = process.env.APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE;
135
+ if (!privateKeyFile || !passphrase) {
136
+ return input; // No secrets to decrypt
137
+ }
138
+ console.log('[Scrapely] Decrypting secret inputs...');
139
+ try {
140
+ // 1. Read and decrypt the private key file
141
+ const privateKeyPem = decryptPrivateKeyFile(privateKeyFile, passphrase);
142
+ console.log('[Scrapely] Private key decrypted successfully');
143
+ // 2. Get the encrypted secrets from the input
144
+ // The encrypted secrets are stored in a special __encrypted property
145
+ const encryptedSecrets = input.__encrypted_secrets;
146
+ if (!encryptedSecrets || Object.keys(encryptedSecrets).length === 0) {
147
+ console.log('[Scrapely] No encrypted secrets found in input');
148
+ return input;
149
+ }
150
+ // 3. Decrypt all secret values
151
+ const decryptedSecrets = decryptAllSecrets(encryptedSecrets, privateKeyPem);
152
+ console.log(`[Scrapely] Decrypted ${Object.keys(decryptedSecrets).length} secret field(s)`);
153
+ // 4. Merge decrypted values into input and remove the __encrypted_secrets key
154
+ const merged = mergeDecryptedInput(input, decryptedSecrets);
155
+ delete merged.__encrypted_secrets;
156
+ delete merged.__encrypted_secret_fields;
157
+ return merged;
158
+ }
159
+ catch (err) {
160
+ console.error(`[Scrapely] Failed to decrypt secret inputs: ${err.message}`);
161
+ // Return input as-is - secrets won't be available but actor can still run
162
+ return input;
163
+ }
164
+ }
@@ -0,0 +1,59 @@
1
+ import { EventEmitter } from 'node:events';
2
+ /**
3
+ * System event received from the Scrapely platform via WebSocket.
4
+ * Matches the format documented for ACTOR_EVENTS_WEBSOCKET_URL.
5
+ */
6
+ export interface SystemEvent {
7
+ name: 'systemInfo' | 'cpuInfo' | 'aborting' | 'persistState' | 'migrating';
8
+ createdAt: string;
9
+ data: Record<string, unknown>;
10
+ }
11
+ export interface SystemInfoData {
12
+ createdAt: string;
13
+ isCpuOverloaded: boolean;
14
+ cpuCurrentUsage: number;
15
+ cpuAvgUsage: number;
16
+ cpuMaxUsage: number;
17
+ memCurrentBytes: number;
18
+ memAvgBytes: number;
19
+ memMaxBytes: number;
20
+ }
21
+ export interface CpuInfoData {
22
+ isCpuOverloaded: boolean;
23
+ }
24
+ export interface PersistStateData {
25
+ isMigrating: boolean;
26
+ }
27
+ declare const events: EventEmitter<any>;
28
+ /**
29
+ * Connect to the Scrapely events WebSocket server.
30
+ * Called during Actor.init().
31
+ *
32
+ * @param eventsWsUrl - The ACTOR_EVENTS_WEBSOCKET_URL env var value
33
+ * @param token - The SCRAPELY_TOKEN for authentication
34
+ * @param runId - The ACTOR_RUN_ID
35
+ */
36
+ export declare function connectEvents(eventsWsUrl: string, token: string, runId: string): void;
37
+ /**
38
+ * Disconnect from the events WebSocket server.
39
+ * Called during Actor.exit().
40
+ */
41
+ export declare function disconnectEvents(): void;
42
+ /**
43
+ * Check if the events WebSocket is connected.
44
+ */
45
+ export declare function isEventsConnected(): boolean;
46
+ /**
47
+ * Subscribe to system events.
48
+ * Use in conjunction with Actor.on() to receive typed events.
49
+ */
50
+ export declare function onSystemEvent(listener: (event: SystemEvent) => void): void;
51
+ /**
52
+ * Subscribe to specific system event types.
53
+ */
54
+ export declare function onEvent(name: SystemEvent['name'], listener: (data: Record<string, unknown>) => void): void;
55
+ /**
56
+ * Remove a specific event listener.
57
+ */
58
+ export declare function offEvent(name: SystemEvent['name'], listener: (...args: any[]) => void): void;
59
+ export { events };