/* * Copyright Elasticsearch B.V. and contributors * SPDX-License-Identifier: Apache-2.0 */ import process from 'node:process'; import { URL } from 'node:url'; import buffer from 'node:buffer'; import os from 'node:os'; import { Transport, UndiciConnection, WeightedConnectionPool, CloudConnectionPool, Serializer, Diagnostic, errors } from '@elastic/transport'; import { prepareHeaders } from '@elastic/transport/lib/connection/BaseConnection.js'; import SniffingTransport from './sniffingTransport.js'; import Helpers from './helpers.js'; import API from './api/index.js'; import { kAcceptedParams } from './symbols.js'; import { clientVersion as rawClientVersion, transportVersion as rawTransportVersion } from './version.generated.js'; const kChild = Symbol('elasticsearchjs-child'); const kInitialOptions = Symbol('elasticsearchjs-initial-options'); export { kAcceptedParams }; let clientVersion = rawClientVersion; /* istanbul ignore next */ if (clientVersion.includes('-')) { // clean prerelease clientVersion = clientVersion.slice(0, clientVersion.indexOf('-')) + 'p'; } let transportVersion = rawTransportVersion; /* istanbul ignore next */ if (transportVersion.includes('-')) { // clean prerelease transportVersion = transportVersion.slice(0, transportVersion.indexOf('-')) + 'p'; } const nodeVersion = process.versions.node; const serverlessApiVersion = '2023-10-31'; export default class Client extends API { constructor(opts) { var _a, _b, _c, _d, _e, _f; super(); Object.defineProperty(this, "diagnostic", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "connectionPool", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "transport", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "serializer", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "helpers", { enumerable: true, configurable: true, writable: true, value: void 0 }); // @ts-expect-error kChild symbol is for internal use only if ((opts.cloud != null || opts.serverMode === 'serverless') && opts[kChild] === undefined) { if (opts.cloud != null) { const { id } = opts.cloud; if (typeof id !== 'string') { throw new errors.ConfigurationError('Cloud ID must be a string.'); } const parts = id.split(':'); if (parts.length !== 2 || parts[1] === '') { throw new errors.ConfigurationError('Cloud ID must be in the format "name:base64string".'); } // the cloud id is `cluster-name:base64encodedurl` // the url is a string divided by two '$', the first is the cloud url // the second the elasticsearch instance, the third the kibana instance let cloudUrls; try { cloudUrls = Buffer.from(parts[1], 'base64').toString().split('$'); } catch (err) { throw new errors.ConfigurationError('Cloud ID base64 decoding failed.'); } if (cloudUrls.length < 2 || cloudUrls[0] === '' || cloudUrls[1] === '') { throw new errors.ConfigurationError('Cloud ID base64 must contain at least two "$" separated parts: "$[$]".'); } opts.node = `https://${cloudUrls[1]}.${cloudUrls[0]}`; } // Cloud has better performance with compression enabled // see https://github.com/elastic/elasticsearch-py/pull/704. // So unless the user specifies otherwise, we enable compression. if (opts.compression == null) opts.compression = true; if (opts.tls == null || (opts.tls != null && opts.tls.secureProtocol == null)) { opts.tls = (_a = opts.tls) !== null && _a !== void 0 ? _a : {}; opts.tls.secureProtocol = 'TLSv1_2_method'; } } if (opts.node == null && opts.nodes == null) { throw new errors.ConfigurationError('Missing node(s) option'); } // @ts-expect-error kChild symbol is for internal use only if (opts[kChild] === undefined) { const checkAuth = getAuth((_b = opts.node) !== null && _b !== void 0 ? _b : opts.nodes); if ((checkAuth != null) && checkAuth.username !== '' && checkAuth.password !== '') { opts.auth = Object.assign({}, opts.auth, { username: checkAuth.username, password: checkAuth.password }); } } const headers = Object.assign({}, { 'user-agent': `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${nodeVersion}; Transport ${transportVersion})` }, (_c = opts.headers) !== null && _c !== void 0 ? _c : {}); if (opts.serverMode === 'serverless') headers['elastic-api-version'] = serverlessApiVersion; const redaction = Object.assign({}, { type: 'replace', additionalKeys: [] }, (_d = opts.redaction) !== null && _d !== void 0 ? _d : {}); const options = Object.assign({}, { Connection: UndiciConnection, Transport: opts.serverMode === 'serverless' ? Transport : SniffingTransport, Serializer, ConnectionPool: (opts.cloud != null || opts.serverMode === 'serverless') ? CloudConnectionPool : WeightedConnectionPool, maxRetries: 3, pingTimeout: 3000, sniffInterval: false, sniffOnStart: false, sniffEndpoint: '_nodes/_all/http', sniffOnConnectionFault: false, resurrectStrategy: 'ping', compression: false, tls: null, caFingerprint: null, agent: null, nodeFilter: null, generateRequestId: null, name: 'elasticsearch-js', auth: null, opaqueIdPrefix: null, context: null, proxy: null, enableMetaHeader: true, maxResponseSize: null, maxCompressedResponseSize: null, maxPathLength: null, serverMode: 'stack' }, opts, { headers, redaction }); if (options.caFingerprint != null && isHttpConnection((_e = opts.node) !== null && _e !== void 0 ? _e : opts.nodes)) { throw new errors.ConfigurationError('You can\'t configure the caFingerprint with a http connection'); } if (options.maxResponseSize != null && options.maxResponseSize > buffer.constants.MAX_STRING_LENGTH) { throw new errors.ConfigurationError(`The maxResponseSize cannot be bigger than ${buffer.constants.MAX_STRING_LENGTH}`); } if (options.maxCompressedResponseSize != null && options.maxCompressedResponseSize > buffer.constants.MAX_LENGTH) { throw new errors.ConfigurationError(`The maxCompressedResponseSize cannot be bigger than ${buffer.constants.MAX_LENGTH}`); } if (options.enableMetaHeader) { let clientMeta = `es=${clientVersion},js=${nodeVersion},t=${transportVersion}`; if (options.Connection === UndiciConnection) { clientMeta += `,un=${nodeVersion}`; } else { // assumes HttpConnection clientMeta += `,hc=${nodeVersion}`; } // detect alternative runtimes if (process.versions.bun != null) clientMeta += `,bn=${process.versions.bun}`; if (process.versions.deno != null) clientMeta += `,dn=${process.versions.deno}`; options.headers['x-elastic-client-meta'] = clientMeta; } this.name = options.name; // @ts-expect-error kInitialOptions symbol is for internal use only this[kInitialOptions] = options; // @ts-expect-error kChild symbol is for internal use only if (opts[kChild] !== undefined) { // @ts-expect-error kChild symbol is for internal use only this.serializer = opts[kChild].serializer; // @ts-expect-error kChild symbol is for internal use only this.connectionPool = opts[kChild].connectionPool; // @ts-expect-error kChild symbol is for internal use only this.diagnostic = opts[kChild].diagnostic; } else { this.diagnostic = new Diagnostic(); let serializerOptions; if (opts.disablePrototypePoisoningProtection != null) { if (typeof opts.disablePrototypePoisoningProtection === 'boolean') { serializerOptions = { enablePrototypePoisoningProtection: !opts.disablePrototypePoisoningProtection }; } else { serializerOptions = { enablePrototypePoisoningProtection: opts.disablePrototypePoisoningProtection }; } } this.serializer = new options.Serializer(serializerOptions); this.connectionPool = new options.ConnectionPool({ pingTimeout: options.pingTimeout, resurrectStrategy: options.resurrectStrategy, tls: options.tls, agent: options.agent, proxy: options.proxy, Connection: options.Connection, auth: options.auth, diagnostic: this.diagnostic, caFingerprint: options.caFingerprint }); // ensure default connection values are inherited when creating new connections // see https://github.com/elastic/elasticsearch-js/issues/1791 let nodes = (_f = options.node) !== null && _f !== void 0 ? _f : options.nodes; // serverless only supports one node, so pick the first one if (options.serverMode === 'serverless' && Array.isArray(nodes)) { nodes = nodes[0]; } let nodeOptions = Array.isArray(nodes) ? nodes : [nodes]; nodeOptions = nodeOptions.map(opt => { const { tls, headers, auth, requestTimeout: timeout, agent, proxy, caFingerprint } = options; let defaults = { tls, headers, auth, timeout, agent, proxy, caFingerprint }; // strip undefined values from defaults defaults = Object.keys(defaults).reduce((acc, key) => { const val = defaults[key]; if (val !== undefined) acc[key] = val; return acc; }, {}); let newOpts; if (typeof opt === 'string') { newOpts = { url: new URL(opt) }; } else { newOpts = opt; } return { ...defaults, ...newOpts }; }); this.connectionPool.addConnection(nodeOptions); } let transportOptions = { diagnostic: this.diagnostic, connectionPool: this.connectionPool, serializer: this.serializer, maxRetries: options.maxRetries, requestTimeout: options.requestTimeout, compression: options.compression, headers: options.headers, generateRequestId: options.generateRequestId, name: options.name, opaqueIdPrefix: options.opaqueIdPrefix, context: options.context, productCheck: 'Elasticsearch', maxResponseSize: options.maxResponseSize, maxCompressedResponseSize: options.maxCompressedResponseSize, maxPathLength: options.maxPathLength, redaction: options.redaction, // @ts-ignore enableMetaHeader will be available in transport v9.1.1 enableMetaHeader: options.enableMetaHeader }; if (options.serverMode !== 'serverless') { transportOptions = Object.assign({}, transportOptions, { sniffInterval: options.sniffInterval, sniffOnStart: options.sniffOnStart, sniffOnConnectionFault: options.sniffOnConnectionFault, sniffEndpoint: options.sniffEndpoint, nodeFilter: options.nodeFilter, nodeSelector: options.nodeSelector, vendoredHeaders: { jsonContentType: 'application/vnd.elasticsearch+json; compatible-with=9', ndjsonContentType: 'application/vnd.elasticsearch+x-ndjson; compatible-with=9', accept: 'application/vnd.elasticsearch+json; compatible-with=9,text/plain' } }); } this.transport = new options.Transport(transportOptions); this.helpers = new Helpers({ client: this, metaHeader: options.enableMetaHeader ? `es=${clientVersion},js=${nodeVersion},t=${transportVersion},hc=${nodeVersion}` : null, maxRetries: options.maxRetries }); } /** * Creates a child client instance that shared its connection pool with the parent client * @see {@link https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/child} */ child(opts) { // Merge the new options with the initial ones // @ts-expect-error kChild symbol is for internal use only const options = Object.assign({}, this[kInitialOptions], opts); // Clone headers to avoid mutating the parent's headers objectwhen prepareHeaders adds authorization if (options.headers != null) { options.headers = { ...options.headers }; } // Pass to the child client the parent instances that cannot be overridden // @ts-expect-error kInitialOptions symbol is for internal use only options[kChild] = { connectionPool: this.connectionPool, serializer: this.serializer, diagnostic: this.diagnostic, initialOptions: options }; /* istanbul ignore else */ if (options.auth !== undefined) { options.headers = prepareHeaders(options.headers, options.auth); } return new Client(options); } /** * Closes all connections in the connection pool. Connections shared with any parent or child instances will also be closed. */ async close() { return await this.connectionPool.empty(); } } function isHttpConnection(node) { if (Array.isArray(node)) { return node.some((n) => (typeof n === 'string' ? new URL(n).protocol : n.url.protocol) === 'http:'); } else { if (node == null) return false; return (typeof node === 'string' ? new URL(node).protocol : node.url.protocol) === 'http:'; } } function getAuth(node) { if (Array.isArray(node)) { for (const url of node) { const auth = getUsernameAndPassword(url); if (auth != null && auth.username !== '' && auth.password !== '') { return auth; } } return null; } else { const auth = getUsernameAndPassword(node); if (auth != null && auth.username !== '' && auth.password !== '') { return auth; } return null; } function getUsernameAndPassword(node) { /* istanbul ignore else */ if (typeof node === 'string') { const { username, password } = new URL(node); return { username: decodeURIComponent(username), password: decodeURIComponent(password) }; } else if (node != null && node.url instanceof URL) { return { username: decodeURIComponent(node.url.username), password: decodeURIComponent(node.url.password) }; } else { return null; } } } //# sourceMappingURL=client.js.map