UNPKG

snowflake-sdk

Version:
692 lines 24.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.url = exports.string = exports.number = exports.userAgent = exports.driverVersion = exports.driverName = void 0; exports.inherits = inherits; exports.format = format; exports.isFunction = isFunction; exports.isObject = isObject; exports.isDate = isDate; exports.isArray = isArray; exports.isString = isString; exports.isBoolean = isBoolean; exports.isNumber = isNumber; exports.isPrivateKey = isPrivateKey; exports.exists = exists; exports.apply = apply; exports.isBrowser = isBrowser; exports.isNode = isNode; exports.nextSleepTime = nextSleepTime; exports.getJitteredSleepTime = getJitteredSleepTime; exports.chooseRandom = chooseRandom; exports.getNextSleepTime = getNextSleepTime; exports.getJitter = getJitter; exports.isLoginRequest = isLoginRequest; exports.isRetryableHttpError = isRetryableHttpError; exports.validateClientSessionKeepAliveHeartbeatFrequency = validateClientSessionKeepAliveHeartbeatFrequency; exports.constructHostname = constructHostname; exports.isPrivateLink = isPrivateLink; exports.createOcspResponseCacheServerUrl = createOcspResponseCacheServerUrl; exports.isPutCommand = isPutCommand; exports.isGetCommand = isGetCommand; exports.convertSmkIdToString = convertSmkIdToString; exports.getCircularReplacer = getCircularReplacer; exports.isCorrectSubdomain = isCorrectSubdomain; exports.buildCredentialCacheKey = buildCredentialCacheKey; exports.checkValidCustomCredentialManager = checkValidCustomCredentialManager; exports.checkParametersDefined = checkParametersDefined; exports.isFileModeCorrect = isFileModeCorrect; exports.isFileNotWritableByGroupOrOthers = isFileNotWritableByGroupOrOthers; exports.shouldRetryOktaAuth = shouldRetryOktaAuth; exports.getDriverDirectory = getDriverDirectory; exports.validatePath = validatePath; exports.getEnvVar = getEnvVar; exports.validateEmptyString = validateEmptyString; exports.isNotEmptyAsString = isNotEmptyAsString; exports.isNotEmptyString = isNotEmptyString; exports.isEmptyObject = isEmptyObject; exports.isWindows = isWindows; exports.getFreePort = getFreePort; exports.isPortOpen = isPortOpen; exports.lstrip = lstrip; exports.escapeHTML = escapeHTML; exports.dynamicImportESMInTypescriptWithCommonJS = dynamicImportESMInTypescriptWithCommonJS; const util_1 = __importDefault(require("util")); const url_1 = __importDefault(require("url")); const os_1 = __importDefault(require("os")); const Logger = __importStar(require("./logger")); const fs_1 = __importDefault(require("fs")); // NOTE: keeping require as it's a circular dependency so * as Errors doesn't work const Errors = require('./errors'); const net_1 = __importDefault(require("net")); const package_json_1 = require("../package.json"); Object.defineProperty(exports, "driverName", { enumerable: true, get: function () { return package_json_1.name; } }); Object.defineProperty(exports, "driverVersion", { enumerable: true, get: function () { return package_json_1.version; } }); const nodeJSVersion = process.version?.startsWith('v') ? process.version.substring(1) : process.version; exports.userAgent = `JavaScript/${package_json_1.version} (${process.platform}-${process.arch}) NodeJS/${nodeJSVersion}`; /** * Note: A simple wrapper around util.inherits() for now, but this might change * in the future. * * Inherits the prototype methods from one constructor into another. The * prototype of constructor will be set to a new object created from * superConstructor. * * @param constructor * @param superConstructor * * @returns {Object} */ function inherits(constructor, superConstructor) { return util_1.default.inherits.apply(util_1.default, [constructor, superConstructor]); } /** * Note: A simple wrapper around util.format() for now, but this will likely * change in the future. * * Returns a formatted string using the first argument as a printf-like format. * * The first argument is a string that contains zero or more placeholders. * Each placeholder is replaced with the converted value from its corresponding * argument. Supported placeholders are: * %s - String. * %d - Number (both integer and float). * %j - JSON. Replaced with the string '[Circular]' if the argument contains * circular references. * %% - single percent sign ('%'). This does not consume an argument. * * If the placeholder does not have a corresponding argument, the placeholder is * not replaced. * * If there are more arguments than placeholders, the extra arguments are * coerced to strings (for objects and symbols, util.inspect() is used) and then * concatenated, delimited by a space. * * If the first argument is not a format string then util.format() returns a * string that is the concatenation of all its arguments separated by spaces. * Each argument is converted to a string with util.inspect(). */ function format(format, ...params) { return util_1.default.format.apply(util_1.default, [format, ...params]); } /** * Determines if a given value is a function. */ function isFunction(value) { return !!value && typeof value === 'function'; } const toString = Object.prototype.toString; /** * Determines if a given value is an object. */ function isObject(value) { return toString.call(value) === '[object Object]'; } /** * Determines if a given value is a Date. */ function isDate(value) { return toString.call(value) === '[object Date]'; } /** * Determines if a given value is an array. */ function isArray(value) { return toString.call(value) === '[object Array]'; } /** * Determines if a given value is a string. */ function isString(value) { return typeof value === 'string'; } /** * Determines if a given value is a boolean. */ function isBoolean(value) { return typeof value === 'boolean'; } /** * Determines if a given value is a number. */ function isNumber(value) { return typeof value === 'number' && isFinite(value); } /** * Determines if a given value is a private key string in pem format of type pkcs8. */ function isPrivateKey(value) { const trimmedValue = value.trim(); // The private key is expected to be decrypted when set in the connection string // secret scanner complains about first check since it looks like private key, but it's only check // pragma: allowlist nextline secret return (trimmedValue.startsWith('-----BEGIN PRIVATE KEY-----') && trimmedValue.endsWith('\n-----END PRIVATE KEY-----')); } /** * A collection of number-related utility functions. */ exports.number = { /** * Determines if a given value is a positive number. */ isPositive: function (value) { return isNumber(value) && value > 0; }, /** * Determines if a given value is a non-negative number. */ isNonNegative: function (value) { return isNumber(value) && value >= 0; }, /** * Determines if a given value is an integer. */ isInteger: function (value) { return isNumber(value) && Math.floor(value) === value; }, /** * Determines if a given value is a positive integer. */ isPositiveInteger: function (value) { return this.isInteger(value) && value > 0; }, /** * Determines if a given value is a non-negative integer. */ isNonNegativeInteger: function (value) { return this.isInteger(value) && value >= 0; }, }; /** * A collection of string-related utility functions. */ exports.string = { /** * Determines if a given string is not null or empty. */ isNotNullOrEmpty: function (value) { return isString(value) && value; }, /** * Compares two version numbers of the form 'a.b.c' where a, b and c are * numbers (e.g. '1.0.12'). If one or both inputs are invalid versions, the * function will return NaN, otherwise, it will return -1 if the first * version is smaller, 1 if the first version is bigger, and 0 if the two * versions are equal. */ compareVersions: function (version1, version2) { // if one or both inputs are valid, return NaN if (!isString(version1) || !isString(version2)) { return NaN; } // split on dot const version1Parts = version1.split('.'); const version2Parts = version2.split('.'); // add trailing zeros to make the parts arrays the same length while (version1Parts.length < version2Parts.length) { version1Parts.push('0'); } while (version2Parts.length < version1Parts.length) { version2Parts.push('0'); } // compare elements in the two arrays one by one let result = 0; let version1Part, version2Part; for (let index = 0, length = version1Parts.length; index < length; index++) { // convert to number before doing any arithmetic version1Part = Number(version1Parts[index]); version2Part = Number(version2Parts[index]); // if one or both values are not numerical, consider the input invalid if (!isNumber(version1Part) || !isNumber(version2Part)) { result = NaN; break; } // if the two values are different, pick the // correct result based on which value is smaller if (version1Part !== version2Part) { result = version1Part < version2Part ? -1 : 1; break; } } return result; }, }; /** * Determines if a given value is not null or undefined. * * @deprecated Just use if (!value) instead */ function exists(value) { return value !== null && value !== undefined; } /** * A collection of url-related utility functions. */ exports.url = { /** * Appends a query parameter to a url. If an invalid url is specified, an * exception is thrown. * * @param url * @param paramName the name of the query parameter. * @param paramValue the value of the query parameter. */ appendParam: function (url, paramName, paramValue) { // if the specified url is valid const urlAsObject = url_1.default.parse(url); if (urlAsObject) { // if the url already has query parameters, use '&' as the separator // when appending the additional query parameter, otherwise use '?' url += (urlAsObject.search ? '&' : '?') + paramName + '=' + paramValue; } return url; }, appendRetryParam: function (option) { let retryUrl = this.appendParam(option.url, 'retryCount', option.retryCount); if (option.includeRetryReason) { retryUrl = this.appendParam(retryUrl, 'retryReason', option.retryReason); } return retryUrl; }, }; /** * Shallow-copies everything from a source object into a destination object. * * @param {Object} dst the object to copy properties to. * @param {Object} src the object to copy properties from. */ function apply(dst, src) { // if both dst and src are objects, copy everything from src to dst if (isObject(dst) && isObject(src)) { for (const key in src) { if (Object.prototype.hasOwnProperty.call(src, key)) { dst[key] = src[key]; } } } return dst; } /** * Returns true if the code is currently being run in the browser, false * otherwise. */ function isBrowser() { // @ts-ignore TS2339: Property 'browser' does not exist on type 'Process' return !!(process && process.browser); } /** * Returns true if the code is currently being run in node, false otherwise. */ function isNode() { return !isBrowser(); } /** * Returns the next sleep time calculated by exponential backoff with * decorrelated jitter. * sleep = min(cap, random_between(base, sleep * 3)) * for more details, check out: * http://www.awsarchitectureblog.com/2015/03/backoff.html * @param base minimum seconds * @param cap maximum seconds * @param previousSleep previous sleep time */ function nextSleepTime(base, cap, previousSleep) { return Math.min(cap, Math.abs(previousSleep * 3 - base) * Math.random() + Math.min(base, previousSleep * 3)); } /** * Return next sleep time calculated by the jitter rule. */ function getJitteredSleepTime(numofRetries, currentSleepTime, totalElapsedTime, maxRetryTimeout) { const nextsleep = getNextSleepTime(numofRetries, currentSleepTime); const sleep = maxRetryTimeout !== 0 ? Math.min(maxRetryTimeout - totalElapsedTime, nextsleep) : nextsleep; totalElapsedTime += sleep; return { sleep, totalElapsedTime }; } /** * Choose one of the number between two numbers. */ function chooseRandom(firstNumber, secondNumber) { return Math.random() * (firstNumber - secondNumber) + secondNumber; } /** * return the next sleep Time. */ function getNextSleepTime(numofRetries, currentSleepTime) { const nextSleep = 2 ** numofRetries; return chooseRandom(currentSleepTime + getJitter(currentSleepTime), nextSleep + getJitter(currentSleepTime)); } /** * return the jitter value. */ function getJitter(currentSleepTime) { const multiplicationFactor = chooseRandom(1, -1); return 0.5 * currentSleepTime * multiplicationFactor; } /** * Check whether the request is the login-request or not. */ function isLoginRequest(loginUrl) { const endPoints = ['/v1/login-request', '/authenticator-request']; return endPoints.some((endPoint) => loginUrl.includes(endPoint)); } /** * Checks if the HTTP response code is retryable * * @param response HTTP response object * @param retry403 will retry HTTP 403? */ function isRetryableHttpError(response, retry403) { return (response && ((response.statusCode >= 500 && response.statusCode < 600) || (retry403 && response.statusCode === 403) || response.statusCode === 408 || response.statusCode === 429)); } function validateClientSessionKeepAliveHeartbeatFrequency(input, masterValidity) { let heartbeatFrequency = input; const realMax = Math.floor(masterValidity / 4); const realMin = Math.floor(realMax / 4); if (input > realMax) { heartbeatFrequency = realMax; } else if (input < realMin) { heartbeatFrequency = realMin; } heartbeatFrequency = Math.floor(heartbeatFrequency); return heartbeatFrequency; } /** * Constructs host name using region and account * * @param region where the account is located * @param account which account to connect to */ function constructHostname(region, account) { let host; if (region === 'us-west-2') { host = account + '.snowflakecomputing.com'; } else if (region != null) { if (account.indexOf('.') > 0) { account = account.substring(0, account.indexOf('.')); } if (region.startsWith('cn-') || region.startsWith('CN-')) { host = account + '.' + region + '.snowflakecomputing.cn'; } else { host = account + '.' + region + '.snowflakecomputing.com'; } } else { host = account + '.snowflakecomputing.com'; } return host; } /** * Returns true if host indicates private link */ function isPrivateLink(host) { Errors.checkArgumentExists(exists(host), Errors.codes.ERR_CONN_CREATE_MISSING_HOST); return host.toLowerCase().includes('privatelink.snowflakecomputing.'); } function createOcspResponseCacheServerUrl(host) { return `http://ocsp.${host}/ocsp_response_cache.json`; } /** * Returns if command is a PUT command */ function isPutCommand(sqlText) { return sqlText.trim().substring(0, 3).toUpperCase() === 'PUT'; } /** * Returns if command is a GET command */ function isGetCommand(sqlText) { return sqlText.trim().substring(0, 3).toUpperCase() === 'GET'; } /** * Add double quotes to smkId's value to parse it as a string instead of integer * to preserve precision of numbers exceeding JavaScript's max safe integer * e.g (inputting 32621973126123526 outputs 32621973126123530) * * @param body the data in JSON */ function convertSmkIdToString(body) { return body.replace(/"smkId"(\s*):(\s*)([0-9]+)/g, '"smkId"$1:$2"$3"'); } /** * Under some circumstances the object passed to JSON.stringify in exception handling * can contain circular reference, on which JSON.stringify bails out * MDN way of handling such error */ function getCircularReplacer() { const ancestors = []; return function (key, value) { if (typeof value !== 'object' || value === null) { return value; } // `this` is the object that value is contained in, // i.e., its direct parent. // @ts-ignore TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { ancestors.pop(); } if (ancestors.includes(value)) { return '[Circular]'; } ancestors.push(value); return value; }; } /** * Returns if the provided string is a valid subdomain. */ function isCorrectSubdomain(value) { const subdomainRegex = RegExp(/^\w+([.-]\w+)*$/i); return subdomainRegex.test(value); } function buildCredentialCacheKey(host, username, credType) { if (!host || !username || !credType) { Logger.getInstance().debug('Cannot build the credential cache key because one of host, username, and credType is null'); return null; } return `{${host.toUpperCase()}}:{${username.toUpperCase()}}:{${credType.toUpperCase()}}`; } function checkValidCustomCredentialManager(customCredentialManager) { if (typeof customCredentialManager !== 'object') { return false; } const requireMethods = ['write', 'read', 'remove']; for (const method of requireMethods) { if (!Object.hasOwnProperty.call(customCredentialManager, method) || typeof customCredentialManager[method] !== 'function') { return false; } } return true; } function checkParametersDefined(...parameters) { return parameters.every((element) => element !== undefined && element !== null); } /** * Checks if the provided file or directory permissions are correct. * @param filePath * @param expectedMode * @param fsPromises * @returns {Promise<boolean>} resolves always to true for Windows */ async function isFileModeCorrect(filePath, expectedMode, fsPromises) { if (os_1.default.platform() === 'win32') { return true; } return await fsPromises.stat(filePath).then((stats) => { // we have to limit the number of LSB bits to 9 with the mask, as the stats.mode starts with the file type, // e.g. the directory with permissions 755 will have stats.mask of 40755. const mask = (1 << 9) - 1; return (stats.mode & mask) === expectedMode; }); } /** * Checks if the provided file or directory is writable only by the user. * @returns {Promise<boolean>} resolves always to true for Windows */ async function isFileNotWritableByGroupOrOthers(configFilePath, fsPromises) { if (os_1.default.platform() === 'win32') { return true; } const stats = await fsPromises.stat(configFilePath); return (stats.mode & (1 << 4)) === 0 && (stats.mode & (1 << 1)) === 0; } function shouldRetryOktaAuth({ maxRetryTimeout, maxRetryCount, numRetries, startTime, remainingTimeout, }) { return ((maxRetryTimeout === 0 || Date.now() < startTime + remainingTimeout) && numRetries <= maxRetryCount); } function getDriverDirectory() { return __dirname; } function validatePath(dir) { try { const stat = fs_1.default.statSync(dir); return stat.isDirectory(); } catch { Logger.getInstance().error('The location is invalid. Please check this location is accessible or existing'); return false; } } function getEnvVar(variable) { return process.env[variable.toLowerCase()] || process.env[variable.toUpperCase()]; } function validateEmptyString(value) { return value !== '' ? value : undefined; } function isNotEmptyAsString(variable) { if (typeof variable === 'string') { return variable; } return exists(variable); } function isNotEmptyString(variable) { return exists(variable) && variable !== ''; } /** * Checks Whether the object is empty (can be null or undefined) or not. */ function isEmptyObject(object) { if (!exists(object)) { return true; } if (typeof object !== 'object') { return false; } return Object.keys(object).length === 0; } function isWindows() { return os_1.default.platform() === 'win32'; } async function getFreePort() { return new Promise((res) => { const srv = net_1.default.createServer(); srv.listen(0, () => { // @ts-ignore TS2339: Property 'port' does not exist on type 'string | AddressInfo' const port = srv.address().port; srv.close(() => res(port)); }); }); } async function isPortOpen(port) { return new Promise((resolve, reject) => { const s = net_1.default.createServer(); s.once('error', (err) => { s.close(); if (err['code'] === 'EADDRINUSE') { Logger.getInstance().trace(`Port: ${port} is not available. Verification failed`); reject('Port not available.'); } else { Logger.getInstance().trace(`There is unexpected error during verification of port availability. Port: ${port}. Error: ${JSON.stringify(err)}`); } }); s.once('listening', () => { s.close(); Logger.getInstance().trace(`Closing server run for verification whether the port is available. Port: ${port}`); resolve('Listening'); }); s.listen(port); }); } /** * Left strip the specified character from a string. */ function lstrip(str, remove) { while (str.length > 0 && remove.indexOf(str.charAt(0)) !== -1) { str = str.substr(1); } return str; } /** * This method transforms HTML special characters into their corresponding entity representations. */ function escapeHTML(value) { if (!exists(value)) { return value; } return value .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } /** * Typescript with "module": "commonjs" will transform every import() to a require() statement. * * This will break ESM dynamic imports resulting in a runtime error: * -require() of ES Module... from ... not supported. * * A hacky solution - https://github.com/microsoft/TypeScript/issues/43329 * * This could be removed once we drop node 18 support as Node 20+ support esm in require() */ async function dynamicImportESMInTypescriptWithCommonJS(moduleName) { return Function(`return import("${moduleName}")`)(); } //# sourceMappingURL=util.js.map