rittenhop-ghost/versions/5.94.2/node_modules/stripe/lib/utils.js

452 lines
12 KiB
JavaScript

'use strict';
const EventEmitter = require('events').EventEmitter;
const qs = require('qs');
const crypto = require('crypto');
const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// Certain sandboxed environments (our known example right now are CloudFlare
// Workers) may make `child_process` unavailable. Because `exec` isn't critical
// to the operation of stripe-node, we handle this unavailability gracefully.
let exec = null;
try {
exec = require('child_process').exec;
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
}
const OPTIONS_KEYS = [
'apiKey',
'idempotencyKey',
'stripeAccount',
'apiVersion',
'maxNetworkRetries',
'timeout',
'host',
];
const DEPRECATED_OPTIONS = {
api_key: 'apiKey',
idempotency_key: 'idempotencyKey',
stripe_account: 'stripeAccount',
stripe_version: 'apiVersion',
stripeVersion: 'apiVersion',
};
const DEPRECATED_OPTIONS_KEYS = Object.keys(DEPRECATED_OPTIONS);
const utils = (module.exports = {
isOptionsHash(o) {
return (
o &&
typeof o === 'object' &&
(OPTIONS_KEYS.some((prop) => hasOwn(o, prop)) ||
DEPRECATED_OPTIONS_KEYS.some((prop) => hasOwn(o, prop)))
);
},
/**
* Stringifies an Object, accommodating nested objects
* (forming the conventional key 'parent[child]=value')
*/
stringifyRequestData: (data) => {
return (
qs
.stringify(data, {
serializeDate: (d) => Math.floor(d.getTime() / 1000),
})
// Don't use strict form encoding by changing the square bracket control
// characters back to their literals. This is fine by the server, and
// makes these parameter strings easier to read.
.replace(/%5B/g, '[')
.replace(/%5D/g, ']')
);
},
/**
* Outputs a new function with interpolated object property values.
* Use like so:
* const fn = makeURLInterpolator('some/url/{param1}/{param2}');
* fn({ param1: 123, param2: 456 }); // => 'some/url/123/456'
*/
makeURLInterpolator: (() => {
const rc = {
'\n': '\\n',
'"': '\\"',
'\u2028': '\\u2028',
'\u2029': '\\u2029',
};
return (str) => {
const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]);
return (outputs) => {
return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) =>
encodeURIComponent(outputs[$1] || '')
);
};
};
})(),
extractUrlParams: (path) => {
const params = path.match(/\{\w+\}/g);
if (!params) {
return [];
}
return params.map((param) => param.replace(/[{}]/g, ''));
},
/**
* Return the data argument from a list of arguments
*
* @param {object[]} args
* @returns {object}
*/
getDataFromArgs(args) {
if (!Array.isArray(args) || !args[0] || typeof args[0] !== 'object') {
return {};
}
if (!utils.isOptionsHash(args[0])) {
return args.shift();
}
const argKeys = Object.keys(args[0]);
const optionKeysInArgs = argKeys.filter((key) =>
OPTIONS_KEYS.includes(key)
);
// In some cases options may be the provided as the first argument.
// Here we're detecting a case where there are two distinct arguments
// (the first being args and the second options) and with known
// option keys in the first so that we can warn the user about it.
if (
optionKeysInArgs.length > 0 &&
optionKeysInArgs.length !== argKeys.length
) {
emitWarning(
`Options found in arguments (${optionKeysInArgs.join(
', '
)}). Did you mean to pass an options object? See https://github.com/stripe/stripe-node/wiki/Passing-Options.`
);
}
return {};
},
/**
* Return the options hash from a list of arguments
*/
getOptionsFromArgs: (args) => {
const opts = {
auth: null,
headers: {},
settings: {},
};
if (args.length > 0) {
const arg = args[args.length - 1];
if (typeof arg === 'string') {
opts.auth = args.pop();
} else if (utils.isOptionsHash(arg)) {
const params = {...args.pop()};
const extraKeys = Object.keys(params).filter(
(key) => !OPTIONS_KEYS.includes(key)
);
if (extraKeys.length) {
const nonDeprecated = extraKeys.filter((key) => {
if (!DEPRECATED_OPTIONS[key]) {
return true;
}
const newParam = DEPRECATED_OPTIONS[key];
if (params[newParam]) {
throw Error(
`Both '${newParam}' and '${key}' were provided; please remove '${key}', which is deprecated.`
);
}
/**
* TODO turn this into a hard error in a future major version (once we have fixed our docs).
*/
emitWarning(`'${key}' is deprecated; use '${newParam}' instead.`);
params[newParam] = params[key];
});
if (nonDeprecated.length) {
emitWarning(
`Invalid options found (${extraKeys.join(', ')}); ignoring.`
);
}
}
if (params.apiKey) {
opts.auth = params.apiKey;
}
if (params.idempotencyKey) {
opts.headers['Idempotency-Key'] = params.idempotencyKey;
}
if (params.stripeAccount) {
opts.headers['Stripe-Account'] = params.stripeAccount;
}
if (params.apiVersion) {
opts.headers['Stripe-Version'] = params.apiVersion;
}
if (Number.isInteger(params.maxNetworkRetries)) {
opts.settings.maxNetworkRetries = params.maxNetworkRetries;
}
if (Number.isInteger(params.timeout)) {
opts.settings.timeout = params.timeout;
}
if (params.host) {
opts.host = params.host;
}
}
}
return opts;
},
/**
* Provide simple "Class" extension mechanism
*/
protoExtend(sub) {
const Super = this;
const Constructor = hasOwn(sub, 'constructor')
? sub.constructor
: function(...args) {
Super.apply(this, args);
};
// This initialization logic is somewhat sensitive to be compatible with
// divergent JS implementations like the one found in Qt. See here for more
// context:
//
// https://github.com/stripe/stripe-node/pull/334
Object.assign(Constructor, Super);
Constructor.prototype = Object.create(Super.prototype);
Object.assign(Constructor.prototype, sub);
return Constructor;
},
/**
* Secure compare, from https://github.com/freewil/scmp
*/
secureCompare: (a, b) => {
a = Buffer.from(a);
b = Buffer.from(b);
// return early here if buffer lengths are not equal since timingSafeEqual
// will throw if buffer lengths are not equal
if (a.length !== b.length) {
return false;
}
// use crypto.timingSafeEqual if available (since Node.js v6.6.0),
// otherwise use our own scmp-internal function.
if (crypto.timingSafeEqual) {
return crypto.timingSafeEqual(a, b);
}
const len = a.length;
let result = 0;
for (let i = 0; i < len; ++i) {
result |= a[i] ^ b[i];
}
return result === 0;
},
/**
* Remove empty values from an object
*/
removeNullish: (obj) => {
if (typeof obj !== 'object') {
throw new Error('Argument must be an object');
}
return Object.keys(obj).reduce((result, key) => {
if (obj[key] != null) {
result[key] = obj[key];
}
return result;
}, {});
},
/**
* Normalize standard HTTP Headers:
* {'foo-bar': 'hi'}
* becomes
* {'Foo-Bar': 'hi'}
*/
normalizeHeaders: (obj) => {
if (!(obj && typeof obj === 'object')) {
return obj;
}
return Object.keys(obj).reduce((result, header) => {
result[utils.normalizeHeader(header)] = obj[header];
return result;
}, {});
},
/**
* Stolen from https://github.com/marten-de-vries/header-case-normalizer/blob/master/index.js#L36-L41
* without the exceptions which are irrelevant to us.
*/
normalizeHeader: (header) => {
return header
.split('-')
.map(
(text) => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase()
)
.join('-');
},
/**
* Determine if file data is a derivative of EventEmitter class.
* https://nodejs.org/api/events.html#events_events
*/
checkForStream: (obj) => {
if (obj.file && obj.file.data) {
return obj.file.data instanceof EventEmitter;
}
return false;
},
callbackifyPromiseWithTimeout: (promise, callback) => {
if (callback) {
// Ensure callback is called outside of promise stack.
return promise.then(
(res) => {
setTimeout(() => {
callback(null, res);
}, 0);
},
(err) => {
setTimeout(() => {
callback(err, null);
}, 0);
}
);
}
return promise;
},
/**
* Allow for special capitalization cases (such as OAuth)
*/
pascalToCamelCase: (name) => {
if (name === 'OAuth') {
return 'oauth';
} else {
return name[0].toLowerCase() + name.substring(1);
}
},
emitWarning,
/**
* Node's built in `exec` function sometimes throws outright,
* and sometimes has a callback with an error,
* depending on the type of error.
*
* This unifies that interface.
*/
safeExec: (cmd, cb) => {
// Occurs if we couldn't load the `child_process` module, which might
// happen in certain sandboxed environments like a CloudFlare Worker.
if (utils._exec === null) {
cb(new Error('exec not available'), null);
return;
}
try {
utils._exec(cmd, cb);
} catch (e) {
cb(e, null);
}
},
// For mocking in tests.
_exec: exec,
isObject: (obj) => {
const type = typeof obj;
return (type === 'function' || type === 'object') && !!obj;
},
// For use in multipart requests
flattenAndStringify: (data) => {
const result = {};
const step = (obj, prevKey) => {
Object.keys(obj).forEach((key) => {
const value = obj[key];
const newKey = prevKey ? `${prevKey}[${key}]` : key;
if (utils.isObject(value)) {
if (!Buffer.isBuffer(value) && !value.hasOwnProperty('data')) {
// Non-buffer non-file Objects are recursively flattened
return step(value, newKey);
} else {
// Buffers and file objects are stored without modification
result[newKey] = value;
}
} else {
// Primitives are converted to strings
result[newKey] = String(value);
}
});
};
step(data);
return result;
},
/**
* https://stackoverflow.com/a/2117523
*/
uuid4: () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
},
validateInteger: (name, n, defaultVal) => {
if (!Number.isInteger(n)) {
if (defaultVal !== undefined) {
return defaultVal;
} else {
throw new Error(`${name} must be an integer`);
}
}
return n;
},
determineProcessUserAgentProperties: () => {
return typeof process === 'undefined'
? {}
: {
lang_version: process.version,
platform: process.platform,
};
},
});
function emitWarning(warning) {
if (typeof process.emitWarning !== 'function') {
return console.warn(
`Stripe: ${warning}`
); /* eslint-disable-line no-console */
}
return process.emitWarning(warning, 'Stripe');
}