UNPKG

@apify/utilities

Version:

Tools and constants shared across Apify projects.

569 lines (554 loc) 23.5 kB
import { Log } from '@apify/log'; import { Transform, TransformCallback, Readable, PassThrough } from 'node:stream'; import { KeyObject } from 'node:crypto'; /*! * This module contains various server utility and helper functions. * Note that it automatically exports functions from utilities.client.js * * Author: Jan Curn (jan@apify.com) * Copyright(c) 2015 Apify. All rights reserved. * */ /** * Generates a random cryptographically strong string consisting of 17 alphanumeric characters. * This string is similar to MongoDB ObjectIds generated by Meteor. */ declare function cryptoRandomObjectId(length?: number): string; /** * Generates unique, deterministic record ID from the provided key with given length (defaults to 17). */ declare function deterministicUniqueId(key: string, length?: number): string; /** * Returns a random integer between 0 and max (excluded, unless it is also 0). * @param {number} maxExcluded * @returns {number} */ declare function getRandomInt(maxExcluded: number): number; /** * If 'date' is a String, this function converts and returns it as a Date object. * Otherwise, the function returns the original 'date' argument. * This function is useful to convert dates transfered via JSON which doesn't natively support dates. */ declare function parseDateFromJson(date: string | Date): Date; /** * Returns a Promise object that will wait a specific number of milliseconds. * @param {number} millis Time to wait. If the value is not larger than zero, the promise resolves immediately. */ declare function delayPromise(millis: number): Promise<void>; /** * Removes an element from an array. */ declare function removeFromArray<T>(array: T[], element: T): boolean; interface RequestLike { url: string; } interface ResponseLike { status: (code: number) => void; send: (payload: string) => void; headersSent?: boolean; } /** * A default route for HTTP 404 error page for API endpoints. */ declare function http404Route(req: RequestLike, res: ResponseLike): void; /** * Default error handler of Express API endpoints. */ declare function expressErrorHandler(err: Error, req: RequestLike, res: ResponseLike, next: (...a: unknown[]) => unknown): void; type BetterIntervalID = { _betterClearInterval: () => void; }; /** * Similar to setInterval() but with two important differences: * First, it assumes the function is asynchronous and only schedules its next invocation AFTER the asynchronous function finished. * Second, it invokes the function immediately. * @param func Function to be periodically executed. * For backwards compatibility reasons, it is passed a callback as its first argument during invocation, however that callback has no effect. * @param delay The number of milliseconds to wait to next invocation of the function after the current invocation finishes. * @returns Object that can be passed to betterClearInterval() */ declare function betterSetInterval(func: ((a: (...args: unknown[]) => unknown) => void) | ((...args: unknown[]) => unknown), delay: number): BetterIntervalID; declare function betterClearInterval(intervalID: BetterIntervalID): void; /** * Escapes a string so that it can be used in regular expression (e.g. converts "myfile.*" to "myfile\\.\\*"). */ declare function escapeRegExp(str: string): string; /** * String left pad */ declare function leftpad(str: string, len: number, ch?: string | number): string; /** * Computes weighted average of 2 values. */ declare function weightedAverage(val1: number, weight1: number, val2: number, weight2: number): number; /** * Checks whether username is listed in FORBIDDEN_USERNAMES * or matches any root route path. */ declare function isForbiddenUsername(username: string): boolean; /** * Executes array of promises in sequence and then returns array where Nth item is result of Nth promise. */ declare function sequentializePromises<T>(promises: (Promise<T> | (() => Promise<T>))[]): Promise<T[]>; /** * Helper function for validation if parameter is an instance of given prototype or multiple prototypes. */ declare function checkParamPrototypeOrThrow(paramVal: any, paramName: string, prototypes: any, prototypeName: string, isOptional?: boolean): void; interface Server { removeListener(event: string, cb: (...params: any[]) => any): unknown; on(event: string, cb: (...params: any[]) => any): unknown; listen(port: number): unknown; } /** * Starts listening at a port specified in the constructor. * Unfortunately server.listen() is not a normal function that fails on error, so we need this trickery. * Returns a function that calls `server.listen(port)` and resolves once server starts listening. * * Usage: `promisifyServerListen(server)(1234)`; */ declare function promisifyServerListen<T extends Server>(server: T): (port: number) => Promise<void>; declare function configureLogger(givenLog: Log, isProduction?: boolean): void; /** * Wraps given promise with timeout. */ declare function timeoutPromise<T>(promise: Promise<T>, timeoutMillis: number, errorMessage?: string): Promise<unknown>; /** * Removes the leading ^ and trailing $ from regex */ declare function createInjectableRegExp(regex: RegExp): RegExp; /*! * This module contains various client-side utility and helper functions. * * Author: Jan Curn (jan@apify.com) * Copyright(c) 2016 Apify. All rights reserved. * */ /** * Returns true if object equals null or undefined, otherwise returns false. */ declare function isNullOrUndefined(obj: unknown): boolean; declare function isBuffer(obj: any): boolean; /** * Converts Date object to ISO string. */ declare function dateToString(date: Date, middleT: boolean): string; /** * Ensures a string is shorter than a specified number of character, and truncates it if not, * appending a specific suffix to it. * @param str * @param maxLength * @param [suffix] Suffix to be appended to truncated string. Defaults to "...[truncated]". */ declare function truncate(str: string, maxLength: number, suffix?: string): string; /** * Gets ordinal suffix for a number (e.g. "nd" for 2). */ declare function getOrdinalSuffix(num: number): string; interface Uri { protocol?: string; host?: string; path?: string; query?: string; fragment?: string; fragmentKey?: Record<string, unknown>; } /** * @deprecated use `new URL()` instead */ declare function parseUrl(str: string): Uri; declare function normalizeUrl(url: string, keepFragment?: boolean): string | null; declare function markedSetNofollowLinks(href: string, title: string, text: string, referrerHostname?: string): string; declare function markedDecreaseHeadsLevel(text: string, level: number): string; /** * Converts integer version number previously generated by buildNumberToInt() or versionNumberToInt() * to string in a form 'MAJOR.MINOR' or 'MAJOR.MINOR.BUILD' in case build number is non-zero. */ declare function buildOrVersionNumberIntToStr(int: number): string | null; /** * If a property name is invalid for MongoDB or BSON, the function transforms * it to a valid form, which can be (most of the time) reversed back using unescapePropertyName(). * For a detailed list of transformations, see escapeForBson(). * @private */ declare function escapePropertyName(name: string): string; /** * Reverses a string transformed using escapePropertyName() back to its original form. * Note that the reverse transformation might not be 100% correct for certain unlikely-to-occur strings * (e.g. string contain null chars). * @private */ declare function unescapePropertyName(name: string): string; /** * Traverses an object, creates a deep clone if requested and transforms object keys and values using a provided function. * The `traverseObject` is recursive, hence if the input object has circular references, the function will run into * and infinite recursion and crash the Node.js process. * @param obj Object to traverse, it must not contain circular references! * @param clone If true, object is not modified but cloned. * @param transformFunc Function used to transform the property names na value. * It has the following signature: `(key, value) => [key, value]`. * Beware that the transformed value is only set if it !== old value. * @returns {*} * @private */ declare function traverseObject(obj: Record<string, any>, clone: boolean, transformFunc: (key: string, value: unknown) => [string, unknown]): any[] | Record<string, any>; /** * Transforms an object so that it can be stored to MongoDB or serialized to BSON. * It does so by transforming prohibited property names (e.g. names starting with "$", * containing "." or null char, equal to "toBSON" or "_bsontype") to equivalent full-width Unicode chars * which are normally allowed. To revert this transformation, use unescapeFromBson(). * @param obj Object to be transformed. It must not contain circular references or any complex types (e.g. Maps, Promises etc.)! * @param clone If true, the function transforms a deep clone of the object rather than the original object. * @returns {*} Transformed object */ declare function escapeForBson(obj: Record<string, any>, clone?: boolean): any[] | Record<string, any>; /** * Reverts a transformation of object property names performed by escapeForBson(). * Note that the reverse transformation might not be 100% equal to the original object * for certain unlikely-to-occur property name (e.g. one contain null chars or full-width Unicode chars). * @param obj Object to be transformed. It must not contain circular references or any complex types (e.g. Maps, Promises etc.)! * @param clone If true, the function transforms a deep clone of the object rather than the original object. * @returns {*} Transformed object. */ declare function unescapeFromBson(obj: Record<string, any>, clone?: boolean): Record<string, any>; /** * Determines whether an object contains property names that cannot be stored to MongoDB. * See escapeForBson() for more details. * Note that this function only works with objects that are serializable to JSON! * @param obj Object to be checked. It must not contain circular references or any complex types (e.g. Maps, Promises etc.)! * @returns {boolean} Returns true if object is invalid, otherwise it returns false. */ declare function isBadForMongo(obj: Record<string, any>): boolean; declare class JsonVariable { readonly name: string; constructor(name: string); getToken(): string; } /** * Stringifies provided value to JSON with a difference that supports functions that * are stringified using .toString() method. * * In addition to that supports instances of JsonVariable('my.token') that are replaced * with a {{my.token}}. */ declare function jsonStringifyExtended(value: Record<string, any>, replacer?: ((k: string, val: unknown) => unknown) | null, space?: number): string; /** * Splits a full name into the first name and last name, trimming all internal and external spaces. * Returns an array with two elements or null if splitting is not possible. */ declare function splitFullName(fullName: string): (string | null)[]; /** * Perform a Regex test on a given URL to see if it is relative. */ declare function isUrlRelative(url: string): boolean; declare class RetryableError extends Error { readonly error: Error; constructor(error: Error, ...args: unknown[]); } interface RetryableError extends Error { } declare function retryWithExpBackoff<T>(params?: { func?: (...args: unknown[]) => T | Promise<T>; expBackoffMillis?: number; expBackoffMaxRepeats?: number; }): Promise<T>; declare enum CHECK_TYPES { MONGODB_PING = "MONGODB_PING", MONGODB_READ = "MONGODB_READ", MONGODB_WRITE = "MONGODB_WRITE", REDIS = "REDIS",// Old alias for 'REDIS_WRITE', deprecated REDIS_PING = "REDIS_PING", REDIS_WRITE = "REDIS_WRITE" } type CheckType<T extends Record<string, any> = Record<string, any>> = { client: T; type: CHECK_TYPES; }; interface HealthCheckerOptions { checks: CheckType[]; redisPrefix?: string; redisTtlSecs?: number; checkTimeoutMillis?: number; mongoDbWriteTestCollection?: string; mongoDbWriteTestRemoveOlderThanSecs?: number; } /** * Provides health-checking functionality to ensure that connection to Redis and MongoDB is working. * * Example use: * * ```javascript * const redis = new Redis(); * const mongo = await MongoClient.connect('mongodb://127.0.0.1:3001/my-db'); * * const checks = [{ * client: redis, * type: HealthChecker.CHECK_TYPES.REDIS, * }, { * client: mongo.db('my-db'), * type: HealthChecker.CHECK_TYPES.MONGODB_READ, * }]; * * const checker = new HealthChecker({ checks }); * setInterval(() => checker.ensureIsHealthy().then(() => console.log('ok'), err => console.log(err)), 5000); * ``` */ declare class HealthChecker { private readonly options; static readonly CHECK_TYPES: typeof CHECK_TYPES; checks: CheckType[]; redisPrefix: string; redisTtlSecs: number; checkTimeoutMillis: number; mongoDbWriteTestCollection: string; mongoDbWriteTestRemoveOlderThanSecs: number; constructor(options: HealthCheckerOptions); ensureIsHealthy(): Promise<void>; _validateCheck(check: CheckType): void; _performCheck(check: CheckType): Promise<void>; _testMongoDbPing({ client }: CheckType): Promise<void>; _testMongoDbRead({ client }: CheckType): Promise<void>; _testMongoDbWrite({ client }: CheckType): Promise<void>; _testRedisPing({ client }: CheckType): Promise<void>; _testRedisWrite({ client }: CheckType): Promise<void>; } /** * A transforming stream which accepts string/Buffer data with JSON Lines objects on input * and emits 'object' event for every parsed JavaScript objects. * The stream passes through the original data. * Each JSON object is expected to be on a separate line, some lines might be empty or contain whitespace. * After each JSON object there needs to be '\n' or end of stream. * This stream is especially useful for processing stream from Docker engine, such as: * * <pre> * {"status":"Preparing","progressDetail":{},"id":"e0380bb6c0bb"} * {"status":"Preparing","progressDetail":{},"id":"9f8566ee5135"} * {"errorDetail":{"message":"no basic auth credentials"},"error":"no basic auth credentials"} * </pre> * * **WARNING**: You still need to consume the `data` event from the transformed stream, * otherwise the internal buffers will get full and the stream might be corked. */ declare class ParseJsonlStream extends Transform { private pendingChunk; parseLineAndEmitObject(line: string): void; _transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback): void; _flush(callback: TransformCallback): void; } /** * Concat data from stream to Buffer */ declare function concatStreamToBuffer(stream: Readable | PassThrough): Promise<Buffer>; /** * Flushes the provided stream into a Buffer and transforms * it to a String using the provided encoding or utf-8 as default. */ declare function readStreamToString(stream: Readable | PassThrough, encoding?: BufferEncoding): Promise<string>; declare class WebhookPayloadTemplateError extends Error { constructor(message?: string); } declare class InvalidJsonError extends WebhookPayloadTemplateError { constructor(originalError: Error); } declare class InvalidVariableError extends Error { constructor(variable?: string); } /** * WebhookPayloadTemplate enables creation and parsing of webhook payload template strings. * Template strings are JSON that may include template variables enclosed in double * curly brackets: `{{variable}}`. When the template is parsed, variables are replaced * with values from a provided context. * * This is useful to create dynamic webhook payloads where a template is saved on webhook * creation and then variables are dynamically added on webhook dispatch. * * **Example:** * ```js * const payloadTemplate = `{ * "id": "some-id", * "createdAt": "2019-05-08T15:22:21.095Z", * "dataToSend": {{data}} * }` * * const data = { * status: 200, * body: 'hello world' * } * * const payloadObject = WebhookPayloadTemplate.parse(payloadTemplate, null, { data }) * ``` * * **Produces:** * ```js * { * id: "some-id", * createdAt: '2019-05-08T15:22:21.095Z', * dataToSend: { * status: 200, * body: 'hello world' * } * } * ``` * @hideconstructor */ declare class WebhookPayloadTemplate { private readonly template; private readonly allowedVariables; private readonly context; private payload; readonly replacedVariables: { variableName: string; replacement: string; }[]; constructor(template: string, allowedVariables?: Set<string> | null, context?: Record<string, any>); /** * Parse existing webhook payload template string into an object, replacing * template variables using the provided context. * * Parse also validates the template structure, so it can be used * to check validity of the template JSON and usage of allowedVariables. */ static parse(payloadTemplate: string, allowedVariables?: Set<string> | null, context?: Record<string, any>, options?: { interpolateStrings?: boolean; }): Record<string, any>; /** * Stringify an object into a webhook payload template. * Values created using `getTemplateVariable('foo.bar')` * will be stringified to `{{foo.bar}}` template variable. */ static stringify(objectTemplate: Record<string, any>, replacer?: ((_: any) => string) | null, indent?: number): string; /** * Produces an instance of a template variable that can be used * in objects and will be stringified into `{{variableName}}` syntax. * * **Example:** * ```js * const resourceVariable = WebhookPayloadTemplate.getVariable('resource'); * const objectTemplate = { * foo: 'foo', * bar: ['bar'], * res: resourceVariable, * } * * const payloadTemplate = WebhookPayloadTemplate.stringify(objectTemplate); * ``` * * **Produces:** * ```json * { * "foo": "foo", * "bar": ["bar"], * "res": {{resource}}, * } * ``` */ static getVariable(variableName: string): JsonVariable; private _parse; private _interpolate; private _interpolateString; private _interpolateObject; private _interpolateArray; private _findPositionOfNextVariable; private _isVariableInsideString; private _countUnescapedDoubleQuotesUpToIndex; private _replaceVariable; private _validateVariableName; private _getVariableValue; private _getVariableReplacement; } type DecryptOptions = { privateKey: KeyObject; encryptedPassword: string; encryptedValue: string; }; type EncryptOptions = { publicKey: KeyObject; value: string; }; /** * It encrypts the given value using AES cipher and the password for encryption using the public key. * NOTE: The encryption password is a string of encryption key and initial vector used for cipher. * It returns the encrypted password and encrypted value in BASE64 format. * * @param publicKey {KeyObject} Public key used for encryption * @param value {string} Value to be encrypted * @returns {Object<encryptedPassword, encryptedValue>} */ declare function publicEncrypt({ publicKey, value }: EncryptOptions): { encryptedPassword: string; encryptedValue: string; }; /** * It decrypts encrypted password using private key * and uses the password(consists of encrypted key and initial vector) * to decrypt the encrypted value. * * @param privateKey {KeyObject} Private key used for decryption * @param encryptedPassword {string} Password in Base64 encrypted using private key * @param encryptedValue {string} Content in Base64 encrypted using AES cipher * @returns {string} */ declare function privateDecrypt({ privateKey, encryptedPassword, encryptedValue, }: DecryptOptions): string; /** * Extract import statements from the code. */ declare function separateImports(code: string): { code: string; imports: string; }; declare enum CodeHashMetaKey { VERSION = "v", USER = "u" } /** * Allows hashing of an Actor input together with some metadata into a shareable link for the "Run on Apify" button. * Uses a common secret for checking the signatures. * * The hash consists of 3 parts separated by a dot, as in `ABC.DEF.GHI`, each being a base64url encoded string: * - `meta` object with the `version` and `user` properties. * - `data` data object (the one that gets encoded) * - `signature` used for verification of the URL hash, computed from the `meta` and `data` objects */ declare class CodeHashManager { private readonly secret; static readonly SECTION_SEPARATOR = "."; static readonly VERSION = 1; constructor(secret: string); /** * Encodes object (e.g. input for actor) to a string hash and uses the `secret` to sign the hash. */ encode<T extends object>(data: T, userId: string): string; decode(urlHash: string): { data: any; meta: { userId: any; version: any; isSignatureValid: boolean; }; }; private toBase64; private fromBase64; private generateSignature; } /** * Generates an HMAC signature and encodes it using Base62. * Base62 encoding reduces the signature length. * * @param secretKey {string} Secret key used for signing signatures * @param message {string} Message to be signed * @returns string */ declare function createHmacSignature(secretKey: string, message: string): string; /** * Creates a secure signature for a resource like a dataset or key-value store. * This signature is used to generate a signed URL for authenticated access, which can be expiring or permanent. * The signature is created using HMAC with the provided secret key and includes the resource ID, expiration time, and version. * * Note: expirationMillis is optional. If not provided, the signature will not expire. */ declare function createStorageContentSignature({ resourceId, urlSigningSecretKey, expiresInMillis, version, }: { resourceId: string; urlSigningSecretKey: string; expiresInMillis?: number; version?: number; }): string; export { type BetterIntervalID, CHECK_TYPES, CodeHashManager, CodeHashMetaKey, HealthChecker, InvalidJsonError, InvalidVariableError, JsonVariable, ParseJsonlStream, RetryableError, WebhookPayloadTemplate, betterClearInterval, betterSetInterval, buildOrVersionNumberIntToStr, checkParamPrototypeOrThrow, concatStreamToBuffer, configureLogger, createHmacSignature, createInjectableRegExp, createStorageContentSignature, cryptoRandomObjectId, dateToString, delayPromise, deterministicUniqueId, escapeForBson, escapePropertyName, escapeRegExp, expressErrorHandler, getOrdinalSuffix, getRandomInt, http404Route, isBadForMongo, isBuffer, isForbiddenUsername, isNullOrUndefined, isUrlRelative, jsonStringifyExtended, leftpad, markedDecreaseHeadsLevel, markedSetNofollowLinks, normalizeUrl, parseDateFromJson, parseUrl, privateDecrypt, promisifyServerListen, publicEncrypt, readStreamToString, removeFromArray, retryWithExpBackoff, separateImports, sequentializePromises, splitFullName, timeoutPromise, traverseObject, truncate, unescapeFromBson, unescapePropertyName, weightedAverage };