'use strict'; var protocolHttp = require('@smithy/protocol-http'); var endpointCache = require('@aws-sdk/endpoint-cache'); const ENV_ENDPOINT_DISCOVERY = ["AWS_ENABLE_ENDPOINT_DISCOVERY", "AWS_ENDPOINT_DISCOVERY_ENABLED"]; const CONFIG_ENDPOINT_DISCOVERY = "endpoint_discovery_enabled"; const isFalsy = (value) => ["false", "0"].indexOf(value) >= 0; const NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS = { environmentVariableSelector: (env) => { for (let i = 0; i < ENV_ENDPOINT_DISCOVERY.length; i++) { const envKey = ENV_ENDPOINT_DISCOVERY[i]; if (envKey in env) { const value = env[envKey]; if (value === "") { throw Error(`Environment variable ${envKey} can't be empty of undefined, got "${value}"`); } return !isFalsy(value); } } }, configFileSelector: (profile) => { if (CONFIG_ENDPOINT_DISCOVERY in profile) { const value = profile[CONFIG_ENDPOINT_DISCOVERY]; if (value === undefined) { throw Error(`Shared config entry ${CONFIG_ENDPOINT_DISCOVERY} can't be undefined, got "${value}"`); } return !isFalsy(value); } }, default: undefined, }; const getCacheKey = async (commandName, config, options) => { const { accessKeyId } = await config.credentials(); const { identifiers } = options; return JSON.stringify({ ...(accessKeyId && { accessKeyId }), ...(identifiers && { commandName, identifiers: Object.entries(identifiers) .sort() .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), }), }); }; const requestQueue = {}; const updateDiscoveredEndpointInCache = async (config, options) => new Promise((resolve, reject) => { const { endpointCache } = config; const { cacheKey, commandName, identifiers } = options; const endpoints = endpointCache.get(cacheKey); if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { if (options.isDiscoveredEndpointRequired) { if (!requestQueue[cacheKey]) requestQueue[cacheKey] = []; requestQueue[cacheKey].push({ resolve, reject }); } else { resolve(); } } else if (endpoints && endpoints.length > 0) { resolve(); } else { const placeholderEndpoints = [{ Address: "", CachePeriodInMinutes: 1 }]; endpointCache.set(cacheKey, placeholderEndpoints); const command = new options.endpointDiscoveryCommandCtor({ Operation: commandName.slice(0, -7), Identifiers: identifiers, }); const handler = command.resolveMiddleware(options.clientStack, config, options.options); handler(command) .then((result) => { endpointCache.set(cacheKey, result.output.Endpoints); if (requestQueue[cacheKey]) { requestQueue[cacheKey].forEach(({ resolve }) => { resolve(); }); delete requestQueue[cacheKey]; } resolve(); }) .catch((error) => { endpointCache.delete(cacheKey); const errorToThrow = Object.assign(new Error(`The operation to discover endpoint failed.` + ` Please retry, or provide a custom endpoint and disable endpoint discovery to proceed.`), { reason: error }); if (requestQueue[cacheKey]) { requestQueue[cacheKey].forEach(({ reject }) => { reject(errorToThrow); }); delete requestQueue[cacheKey]; } if (options.isDiscoveredEndpointRequired) { reject(errorToThrow); } else { endpointCache.set(cacheKey, placeholderEndpoints); resolve(); } }); } }); const endpointDiscoveryMiddleware = (config, middlewareConfig) => (next, context) => async (args) => { if (config.isCustomEndpoint) { if (config.isClientEndpointDiscoveryEnabled) { throw new Error(`Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.`); } return next(args); } const { endpointDiscoveryCommandCtor } = config; const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; const clientName = context.clientName; const commandName = context.commandName; const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); const cacheKey = await getCacheKey(commandName, config, { identifiers }); if (isDiscoveredEndpointRequired) { if (isEndpointDiscoveryEnabled === false) { throw new Error(`Endpoint Discovery is disabled but ${commandName} on ${clientName} requires it.` + ` Please check your configurations.`); } await updateDiscoveredEndpointInCache(config, { ...middlewareConfig, commandName, cacheKey, endpointDiscoveryCommandCtor, }); } else if (isEndpointDiscoveryEnabled) { updateDiscoveredEndpointInCache(config, { ...middlewareConfig, commandName, cacheKey, endpointDiscoveryCommandCtor, }); } const { request } = args; if (cacheKey && protocolHttp.HttpRequest.isInstance(request)) { const endpoint = config.endpointCache.getEndpoint(cacheKey); if (endpoint) { request.hostname = endpoint; } } return next(args); }; const endpointDiscoveryMiddlewareOptions = { name: "endpointDiscoveryMiddleware", step: "build", tags: ["ENDPOINT_DISCOVERY"], override: true, }; const getEndpointDiscoveryPlugin = (pluginConfig, middlewareConfig) => ({ applyToStack: (commandStack) => { commandStack.add(endpointDiscoveryMiddleware(pluginConfig, middlewareConfig), endpointDiscoveryMiddlewareOptions); }, }); const getEndpointDiscoveryRequiredPlugin = (pluginConfig, middlewareConfig) => ({ applyToStack: (commandStack) => { commandStack.add(endpointDiscoveryMiddleware(pluginConfig, { ...middlewareConfig, isDiscoveredEndpointRequired: true }), endpointDiscoveryMiddlewareOptions); }, }); const getEndpointDiscoveryOptionalPlugin = (pluginConfig, middlewareConfig) => ({ applyToStack: (commandStack) => { commandStack.add(endpointDiscoveryMiddleware(pluginConfig, { ...middlewareConfig, isDiscoveredEndpointRequired: false }), endpointDiscoveryMiddlewareOptions); }, }); const resolveEndpointDiscoveryConfig = (input, { endpointDiscoveryCommandCtor }) => { const { endpointCacheSize, endpointDiscoveryEnabled, endpointDiscoveryEnabledProvider } = input; return Object.assign(input, { endpointDiscoveryCommandCtor, endpointCache: new endpointCache.EndpointCache(endpointCacheSize ?? 1000), endpointDiscoveryEnabled: endpointDiscoveryEnabled !== undefined ? () => Promise.resolve(endpointDiscoveryEnabled) : endpointDiscoveryEnabledProvider, isClientEndpointDiscoveryEnabled: endpointDiscoveryEnabled !== undefined, }); }; exports.NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS = NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS; exports.endpointDiscoveryMiddlewareOptions = endpointDiscoveryMiddlewareOptions; exports.getEndpointDiscoveryOptionalPlugin = getEndpointDiscoveryOptionalPlugin; exports.getEndpointDiscoveryPlugin = getEndpointDiscoveryPlugin; exports.getEndpointDiscoveryRequiredPlugin = getEndpointDiscoveryRequiredPlugin; exports.resolveEndpointDiscoveryConfig = resolveEndpointDiscoveryConfig;