diff --git a/.eslintrc.js b/.eslintrc.js index 4eb5fcc6be..05679266e1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -265,11 +265,11 @@ module.exports = { '@typescript-eslint/no-empty-interface': 0, '@typescript-eslint/no-unused-vars': 0, '@typescript-eslint/triple-slash-reference': 0, - '@typescript-eslint/ban-ts-ignore': 0, + '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-empty-function': 0, '@typescript-eslint/camelcase': 0, '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/explicit-function-return-type': 0, + '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/no-var-requires': 0, 'prefer-rest-params': 'off', }, diff --git a/lib/Error.js b/lib/Error.js index a50ccab948..0b5c8ad8db 100644 --- a/lib/Error.js +++ b/lib/Error.js @@ -1,4 +1,5 @@ 'use strict'; +/* eslint-disable camelcase */ /** * StripeError is the base error from which all other more specific Stripe errors derive. * Specifically for errors returned from Stripe's REST API. @@ -16,6 +17,7 @@ class StripeError extends Error { this.headers = raw.headers; this.requestId = raw.requestId; this.statusCode = raw.statusCode; + // @ts-ignore this.message = raw.message; this.charge = raw.charge; this.decline_code = raw.decline_code; diff --git a/lib/StripeResource.js b/lib/StripeResource.js index bfced38996..9a11b7d1c3 100644 --- a/lib/StripeResource.js +++ b/lib/StripeResource.js @@ -28,20 +28,25 @@ function StripeResource(stripe, deprecatedUrlData) { ); } this.basePath = utils.makeURLInterpolator( + // @ts-ignore changing type of basePath this.basePath || stripe.getApiField('basePath') ); + // @ts-ignore changing type of path this.resourcePath = this.path; + // @ts-ignore changing type of path this.path = utils.makeURLInterpolator(this.path); // DEPRECATED: This was kept for backwards compatibility in case users were // using this, but basic methods are now explicitly defined on a resource. if (this.includeBasic) { this.includeBasic.forEach(function(methodName) { + // @ts-ignore this[methodName] = StripeResource.BASIC_METHODS[methodName]; }, this); } this.initialize(...arguments); } StripeResource.prototype = { + _stripe: null, path: '', // Methods that don't use the API's default '/v1' path can override it with this setting. basePath: null, @@ -90,6 +95,8 @@ StripeResource.prototype = { }, // DEPRECATED: Here for backcompat in case users relied on this. wrapTimeout: utils.callbackifyPromiseWithTimeout, + // eslint-disable-next-line no-warning-comments + // TODO: Unused? _timeoutHandler(timeout, req, callback) { return () => { const timeoutErr = new TypeError('ETIMEDOUT'); @@ -234,7 +241,7 @@ StripeResource.prototype = { requestRetries > 0 ? ` Request was retried ${requestRetries} times.` : '' }`; }, - _errorHandler(req, requestRetries, callback) { + _errorHandler(res, requestRetries, callback) { return (message, detail) => { callback.call( this, @@ -451,6 +458,7 @@ StripeResource.prototype = { timeout ); const requestStartTime = Date.now(); + // @ts-ignore const requestEvent = utils.removeNullish({ api_version: apiVersion, account: headers['Stripe-Account'], @@ -470,6 +478,7 @@ StripeResource.prototype = { apiVersion, headers, requestRetries, + // @ts-ignore res.getHeaders()['retry-after'] ); } else if (options.streaming && res.getStatusCode() < 400) { @@ -496,6 +505,7 @@ StripeResource.prototype = { message: isTimeoutError ? `Request aborted due to timeout being reached (${timeout}ms)` : this._generateConnectionErrorMessage(requestRetries), + // @ts-ignore detail: error, }) ); diff --git a/lib/Webhooks.js b/lib/Webhooks.js index f7570b3946..ce6322201e 100644 --- a/lib/Webhooks.js +++ b/lib/Webhooks.js @@ -4,6 +4,7 @@ const _Error = require('./Error'); const {StripeError, StripeSignatureVerificationError} = _Error; const Webhook = { DEFAULT_TOLERANCE: 300, + // @ts-ignore signature: null, constructEvent(payload, header, secret, tolerance, cryptoProvider) { this.signature.verifyHeader( @@ -13,6 +14,7 @@ const Webhook = { tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, @@ -30,6 +32,7 @@ const Webhook = { tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, @@ -175,6 +178,7 @@ function validateComputedSignature( tolerance ) { const signatureFound = !!details.signatures.filter( + // @ts-ignore utils.secureCompare.bind(utils, expectedSignature) ).length; if (!signatureFound) { diff --git a/lib/autoPagination.js b/lib/autoPagination.js index 141afdeb49..3626ffeae3 100644 --- a/lib/autoPagination.js +++ b/lib/autoPagination.js @@ -182,6 +182,7 @@ function makeAutoPagingEach(asyncIteratorNext) { } const autoPagePromise = wrapAsyncIteratorWithCallback( asyncIteratorNext, + // @ts-ignore we might need a null check onItem ); return utils.callbackifyPromiseWithTimeout(autoPagePromise, onDone); diff --git a/lib/multipart.js b/lib/multipart.js index d88d468b1f..29e53c8363 100644 --- a/lib/multipart.js +++ b/lib/multipart.js @@ -28,14 +28,15 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { + const typedEntry = v; push( `Content-Disposition: form-data; name=${q(k)}; filename=${q( - v.name || 'blob' + typedEntry.name || 'blob' )}` ); - push(`Content-Type: ${v.type || 'application/octet-stream'}`); + push(`Content-Type: ${typedEntry.type || 'application/octet-stream'}`); push(''); - push(v.data); + push(typedEntry.data); } else { push(`Content-Disposition: form-data; name=${q(k)}`); push(''); @@ -52,6 +53,7 @@ const streamProcessor = (method, data, headers, callback) => { bufferArray.push(line); }) .once('end', () => { + // @ts-ignore const bufferData = Object.assign({}, data); bufferData.file.data = Buffer.concat(bufferArray); const buffer = multipartDataGenerator(method, bufferData, headers); diff --git a/lib/net/FetchHttpClient.js b/lib/net/FetchHttpClient.js index 195b1bb0c6..355824001a 100644 --- a/lib/net/FetchHttpClient.js +++ b/lib/net/FetchHttpClient.js @@ -44,7 +44,9 @@ class FetchHttpClient extends HttpClient { const fetchFn = this._fetchFn || fetch; const fetchPromise = fetchFn(url.toString(), { method, + // @ts-ignore headers, + // @ts-ignore body, }); // The Fetch API does not support passing in a timeout natively, so a diff --git a/lib/net/NodeHttpClient.js b/lib/net/NodeHttpClient.js index c5712dfe2f..cf580fd63d 100644 --- a/lib/net/NodeHttpClient.js +++ b/lib/net/NodeHttpClient.js @@ -74,6 +74,7 @@ class NodeHttpClient extends HttpClient { } class NodeHttpClientResponse extends HttpClientResponse { constructor(res) { + // @ts-ignore super(res.statusCode, res.headers || {}); this._res = res; } diff --git a/lib/stripe.js b/lib/stripe.js index 60b82e7312..97a78644cb 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -108,6 +108,7 @@ function Stripe(key, config = {}) { this._prevRequestMetrics = []; this._enableTelemetry = props.telemetry !== false; // Expose StripeResource on the instance too + // @ts-ignore this.StripeResource = Stripe.StripeResource; } Stripe.errors = _Error; @@ -286,14 +287,17 @@ Stripe.prototype = { throw new Error('AppInfo.name is required'); } info = info || {}; - const appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => { - if (typeof info[prop] == 'string') { - accum = accum || {}; - accum[prop] = info[prop]; - } - return accum; - }, undefined); - this._appInfo = appInfo; + this._appInfo = APP_INFO_PROPERTIES.reduce( + (accum, prop) => { + if (typeof info[prop] == 'string') { + accum = accum || {}; + accum[prop] = info[prop]; + } + return accum; + }, + // @ts-ignore + undefined + ); }, /** * @deprecated will be removed in a future major version. Use the config object instead: diff --git a/lib/utils.js b/lib/utils.js index 2ed6fb43cf..5031266832 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -9,6 +9,7 @@ let exec = null; try { exec = require('child_process').exec; } catch (e) { + // @ts-ignore if (e.code !== 'MODULE_NOT_FOUND') { throw e; } @@ -77,6 +78,7 @@ const utils = { const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); return (outputs) => { return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => + // @ts-ignore encodeURIComponent(outputs[$1] || '') ); }; diff --git a/src/Error.ts b/src/Error.ts index fbf88cc5d6..b5489fe001 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -1,35 +1,4 @@ /* eslint-disable camelcase */ -type RawErrorType = - | 'card_error' - | 'invalid_request_error' - | 'api_error' - | 'idempotency_error' - | 'rate_limit_error' - | 'authentication_error' - | 'invalid_grant'; - -type StripeRawError = { - message?: string; - type?: RawErrorType; - - headers?: {[header: string]: string}; - statusCode?: number; - requestId?: string; - code?: string; - doc_url?: string; - decline_code?: string; - param?: string; - detail?: string; - charge?: string; - payment_method_type?: string; - - payment_intent?: any; - payment_method?: any; - setup_intent?: any; - source?: any; - exception?: any; -}; - /** * StripeError is the base error from which all other more specific Stripe errors derive. * Specifically for errors returned from Stripe's REST API. @@ -38,9 +7,9 @@ class StripeError extends Error { readonly message: string; readonly type: string; readonly raw: unknown; - readonly rawType: RawErrorType; - readonly headers: {[header: string]: string}; - readonly requestId: string; + readonly rawType?: RawErrorType; + readonly headers?: {[header: string]: string}; + readonly requestId?: string; readonly code?: string; readonly doc_url?: string; @@ -69,6 +38,7 @@ class StripeError extends Error { this.headers = raw.headers; this.requestId = raw.requestId; this.statusCode = raw.statusCode; + // @ts-ignore this.message = raw.message; this.charge = raw.charge; @@ -83,7 +53,7 @@ class StripeError extends Error { /** * Helper factory which takes raw stripe errors and outputs wrapping instances */ - static generate(rawStripeError) { + static generate(rawStripeError: StripeRawError): StripeError { switch (rawStripeError.type) { case 'card_error': return new StripeCardError(rawStripeError); diff --git a/src/ResourceNamespace.ts b/src/ResourceNamespace.ts index c4b6c5566d..b4d5238c7a 100644 --- a/src/ResourceNamespace.ts +++ b/src/ResourceNamespace.ts @@ -1,19 +1,26 @@ // ResourceNamespace allows you to create nested resources, i.e. `stripe.issuing.cards`. // It also works recursively, so you could do i.e. `stripe.billing.invoicing.pay`. -function ResourceNamespace(stripe, resources) { +function ResourceNamespace( + this: StripeResourceNamespaceObject, + stripe: StripeObject, + resources: Array +): void { for (const name in resources) { const camelCaseName = name[0].toLowerCase() + name.substring(1); - const resource = new resources[name](stripe); + const resource = new (resources[name] as any)(stripe); this[camelCaseName] = resource; } } -module.exports = function(namespace, resources) { - return function(stripe) { - return new ResourceNamespace(stripe, resources); +module.exports = function( + namespace: string, + resources: Array +): (stripe: StripeObject) => StripeResourceNamespaceObject { + return function(stripe: StripeObject): StripeResourceNamespaceObject { + return new (ResourceNamespace as any)(stripe, resources); }; }; diff --git a/src/StripeMethod.ts b/src/StripeMethod.ts index f2c0595988..3a99651f4e 100644 --- a/src/StripeMethod.ts +++ b/src/StripeMethod.ts @@ -19,13 +19,13 @@ const makeAutoPaginationMethods = autoPagination.makeAutoPaginationMethods; * Usefully for applying transforms to data on a per-method basis. * @param [spec.host] Hostname for the request. */ -function stripeMethod(spec) { +function stripeMethod(spec: MethodSpec): (...args: any[]) => Promise { if (spec.path !== undefined && spec.fullPath !== undefined) { throw new Error( `Method spec specified both a 'path' (${spec.path}) and a 'fullPath' (${spec.fullPath}).` ); } - return function(...args) { + return function(this: StripeResourceObject, ...args: any[]): Promise { const callback = typeof args[args.length - 1] == 'function' && args.pop(); spec.urlParams = utils.extractUrlParams( diff --git a/src/StripeResource.ts b/src/StripeResource.ts index e8f85599fe..b4392c0d57 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -12,16 +12,6 @@ const { const {HttpClient} = require('./net/HttpClient'); -type Settings = { - timeout?: number; -}; - -type Options = { - settings?: Settings; - streaming?: boolean; - headers?: Record; -}; - // Provide extension mechanism for Stripe Resource Sub-Classes StripeResource.extend = utils.protoExtend; @@ -35,7 +25,11 @@ const MAX_RETRY_AFTER_WAIT = 60; /** * Encapsulates request logic for a Stripe Resource */ -function StripeResource(stripe, deprecatedUrlData) { +function StripeResource( + this: StripeResourceObject, + stripe: StripeObject, + deprecatedUrlData?: never +): void { this._stripe = stripe; if (deprecatedUrlData) { throw new Error( @@ -44,29 +38,32 @@ function StripeResource(stripe, deprecatedUrlData) { } this.basePath = utils.makeURLInterpolator( + // @ts-ignore changing type of basePath this.basePath || stripe.getApiField('basePath') ); + // @ts-ignore changing type of path this.resourcePath = this.path; + // @ts-ignore changing type of path this.path = utils.makeURLInterpolator(this.path); - // DEPRECATED: This was kept for backwards compatibility in case users were // using this, but basic methods are now explicitly defined on a resource. if (this.includeBasic) { this.includeBasic.forEach(function(methodName) { + // @ts-ignore this[methodName] = StripeResource.BASIC_METHODS[methodName]; }, this); } - this.initialize(...arguments); } StripeResource.prototype = { + _stripe: null as StripeObject | null, path: '', // Methods that don't use the API's default '/v1' path can override it with this setting. basePath: null, - initialize() {}, + initialize(): void {}, // Function to override the default data processor. This allows full control // over how a StripeResource's request data will get converted into an HTTP @@ -78,7 +75,10 @@ StripeResource.prototype = { // be thrown, and they will be passed to the callback/promise. validateRequest: null, - createFullPath(commandPath, urlData) { + createFullPath( + commandPath: string | ((urlData: Record) => string), + urlData: Record + ): string { const urlParts = [this.basePath(urlData), this.path(urlData)]; if (typeof commandPath === 'function') { @@ -99,7 +99,7 @@ StripeResource.prototype = { // Creates a relative resource path with symbols left in (unlike // createFullPath which takes some data to replace them with). For example it // might produce: /invoices/{id} - createResourcePathWithSymbols(pathWithSymbols) { + createResourcePathWithSymbols(pathWithSymbols: string | null): string { // If there is no path beyond the resource path, we want to produce just // / rather than //. if (pathWithSymbols) { @@ -109,7 +109,7 @@ StripeResource.prototype = { } }, - _joinUrlParts(parts) { + _joinUrlParts(parts: Array): string { // Replace any accidentally doubled up slashes. This previously used // path.join, which would do this as well. Unfortunately we need to do this // as the functions for creating paths are technically part of the public @@ -120,8 +120,14 @@ StripeResource.prototype = { // DEPRECATED: Here for backcompat in case users relied on this. wrapTimeout: utils.callbackifyPromiseWithTimeout, - _timeoutHandler(timeout, req, callback) { - return () => { + // eslint-disable-next-line no-warning-comments + // TODO: Unused? + _timeoutHandler( + timeout: number, + req: any, + callback: RequestCallback + ): () => void { + return (): void => { const timeoutErr = new TypeError('ETIMEDOUT'); (timeoutErr as any).code = 'ETIMEDOUT'; @@ -129,7 +135,10 @@ StripeResource.prototype = { }; }, - _addHeadersDirectlyToObject(obj, headers) { + _addHeadersDirectlyToObject( + obj: Record, + headers: RequestHeaders + ): void { // For convenience, make some headers easily accessible on // lastResponse. @@ -140,7 +149,11 @@ StripeResource.prototype = { obj.idempotencyKey = obj.idempotencyKey || headers['idempotency-key']; }, - _makeResponseEvent(requestEvent, statusCode, headers) { + _makeResponseEvent( + requestEvent: RequestEvent, + statusCode: number, + headers: RequestHeaders + ): ResponseEvent { const requestEndTime = Date.now(); const requestDurationMs = requestEndTime - requestEvent.request_start_time; @@ -158,7 +171,7 @@ StripeResource.prototype = { }); }, - _getRequestId(headers) { + _getRequestId(headers: RequestHeaders): HttpHeaderValue { return headers['request-id']; }, @@ -172,11 +185,14 @@ StripeResource.prototype = { * still be buffered/parsed and handled by _jsonResponseHandler -- see * makeRequest) */ - _streamingResponseHandler(requestEvent, callback) { - return (res) => { + _streamingResponseHandler( + requestEvent: RequestEvent, + callback: RequestCallback + ): (res: HttpClientResponseInterface) => RequestCallbackReturn { + return (res: HttpClientResponseInterface): RequestCallbackReturn => { const headers = res.getHeaders(); - const streamCompleteCallback = () => { + const streamCompleteCallback = (): void => { const responseEvent = this._makeResponseEvent( requestEvent, res.getStatusCode(), @@ -205,8 +221,8 @@ StripeResource.prototype = { * parses the JSON and returns it (i.e. passes it to the callback) if there * is no "error" field. Otherwise constructs/passes an appropriate Error. */ - _jsonResponseHandler(requestEvent, callback) { - return (res) => { + _jsonResponseHandler(requestEvent: RequestEvent, callback: RequestCallback) { + return (res: HttpClientResponseInterface): void => { const headers = res.getHeaders(); const requestId = this._getRequestId(headers); const statusCode = res.getStatusCode(); @@ -253,11 +269,11 @@ StripeResource.prototype = { return jsonResponse; }, - (e) => { + (e: Error) => { throw new StripeAPIError({ message: 'Invalid JSON received from the Stripe API', exception: e, - requestId: headers['request-id'], + requestId: headers['request-id'] as string, }); } ) @@ -281,14 +297,18 @@ StripeResource.prototype = { }; }, - _generateConnectionErrorMessage(requestRetries) { + _generateConnectionErrorMessage(requestRetries: number): string { return `An error occurred with our connection to Stripe.${ requestRetries > 0 ? ` Request was retried ${requestRetries} times.` : '' }`; }, - _errorHandler(req, requestRetries, callback) { - return (message, detail) => { + _errorHandler( + res: never, + requestRetries: number, + callback: RequestCallback + ): (message: string, detail: string) => void { + return (message: string, detail: string): void => { callback.call( this, new StripeConnectionError({ @@ -301,7 +321,12 @@ StripeResource.prototype = { }, // For more on when and how to retry API requests, see https://stripe.com/docs/error-handling#safely-retrying-requests-with-idempotency - _shouldRetry(res, numRetries, maxRetries, error) { + _shouldRetry( + res: HttpClientResponseInterface, + numRetries: number, + maxRetries: number, + error?: {code: number} + ): boolean { if ( error && numRetries === 0 && @@ -346,7 +371,10 @@ StripeResource.prototype = { return false; }, - _getSleepTimeInMS(numRetries, retryAfter = null) { + _getSleepTimeInMS( + numRetries: number, + retryAfter: number | null = null + ): number { const initialNetworkRetryDelay = this._stripe.getInitialNetworkRetryDelay(); const maxNetworkRetryDelay = this._stripe.getMaxNetworkRetryDelay(); @@ -366,22 +394,25 @@ StripeResource.prototype = { sleepSeconds = Math.max(initialNetworkRetryDelay, sleepSeconds); // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask. - if (Number.isInteger(retryAfter) && retryAfter <= MAX_RETRY_AFTER_WAIT) { - sleepSeconds = Math.max(sleepSeconds, retryAfter); + if (Number.isInteger(retryAfter) && retryAfter! <= MAX_RETRY_AFTER_WAIT) { + sleepSeconds = Math.max(sleepSeconds, retryAfter!); } return sleepSeconds * 1000; }, // Max retries can be set on a per request basis. Favor those over the global setting - _getMaxNetworkRetries(settings: {maxNetworkRetries?: number} = {}) { + _getMaxNetworkRetries(settings: RequestSettings = {}): number { return settings.maxNetworkRetries && Number.isInteger(settings.maxNetworkRetries) ? settings.maxNetworkRetries : this._stripe.getMaxNetworkRetries(); }, - _defaultIdempotencyKey(method, settings) { + _defaultIdempotencyKey( + method: string, + settings: RequestSettings + ): string | null { // If this is a POST and we allow multiple retries, ensure an idempotency key. const maxRetries = this._getMaxNetworkRetries(settings); @@ -392,14 +423,14 @@ StripeResource.prototype = { }, _makeHeaders( - auth, - contentLength, - apiVersion, - clientUserAgent, - method, - userSuppliedHeaders, - userSuppliedSettings - ) { + auth: string, + contentLength: number, + apiVersion: string, + clientUserAgent: string, + method: string, + userSuppliedHeaders: RequestHeaders, + userSuppliedSettings: RequestSettings + ): Record { const defaultHeaders = { // Use specified auth token or use default from this stripe instance: Authorization: auth ? `Bearer ${auth}` : this._stripe.getApiField('auth'), @@ -414,7 +445,7 @@ StripeResource.prototype = { method, userSuppliedSettings ), - }; + } as RequestHeaders; // As per https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2: // A user agent SHOULD send a Content-Length in a request message when @@ -451,7 +482,7 @@ StripeResource.prototype = { ); }, - _getUserAgentString() { + _getUserAgentString(): string { const packageVersion = this._stripe.getConstant('PACKAGE_VERSION'); const appInfo = this._stripe._appInfo ? this._stripe.getAppInfoAsString() @@ -460,7 +491,7 @@ StripeResource.prototype = { return `Stripe/v1 NodeBindings/${packageVersion} ${appInfo}`.trim(); }, - _getTelemetryHeader() { + _getTelemetryHeader(): string | undefined { if ( this._stripe.getTelemetryEnabled() && this._stripe._prevRequestMetrics.length > 0 @@ -472,7 +503,7 @@ StripeResource.prototype = { } }, - _recordRequestMetrics(requestId, requestDurationMs) { + _recordRequestMetrics(requestId: string, requestDurationMs: number): void { if (this._stripe.getTelemetryEnabled() && requestId) { if ( this._stripe._prevRequestMetrics.length > @@ -490,16 +521,24 @@ StripeResource.prototype = { } }, - _request(method, host, path, data, auth, options: Options = {}, callback) { - let requestData; + _request( + method: string, + host: string, + path: string, + data: string, + auth: string, + options: RequestOptions = {}, + callback: RequestCallback + ): void { + let requestData: string; const retryRequest = ( - requestFn, - apiVersion, - headers, - requestRetries, - retryAfter - ) => { + requestFn: typeof makeRequest, + apiVersion: string, + headers: RequestHeaders, + requestRetries: number, + retryAfter: number | null + ): NodeJS.Timeout => { return setTimeout( requestFn, this._getSleepTimeInMS(requestRetries, retryAfter), @@ -509,7 +548,11 @@ StripeResource.prototype = { ); }; - const makeRequest = (apiVersion, headers, numRetries) => { + const makeRequest = ( + apiVersion: string, + headers: RequestHeaders, + numRetries: number + ): void => { // timeout can be set on a per-request basis. Favor that over the global setting const timeout = options.settings && @@ -534,7 +577,8 @@ StripeResource.prototype = { const requestStartTime = Date.now(); - const requestEvent = utils.removeNullish({ + // @ts-ignore + const requestEvent: RequestEvent = utils.removeNullish({ api_version: apiVersion, account: headers['Stripe-Account'], idempotency_key: headers['Idempotency-Key'], @@ -550,13 +594,14 @@ StripeResource.prototype = { this._stripe._emitter.emit('request', requestEvent); req - .then((res) => { + .then((res: HttpClientResponseInterface) => { if (this._shouldRetry(res, requestRetries, maxRetries)) { return retryRequest( makeRequest, apiVersion, headers, requestRetries, + // @ts-ignore res.getHeaders()['retry-after'] ); } else if (options.streaming && res.getStatusCode() < 400) { @@ -565,7 +610,7 @@ StripeResource.prototype = { return this._jsonResponseHandler(requestEvent, callback)(res); } }) - .catch((error) => { + .catch((error: HttpClientResponseError) => { if (this._shouldRetry(null, requestRetries, maxRetries, error)) { return retryRequest( makeRequest, @@ -584,6 +629,7 @@ StripeResource.prototype = { message: isTimeoutError ? `Request aborted due to timeout being reached (${timeout}ms)` : this._generateConnectionErrorMessage(requestRetries), + // @ts-ignore detail: error, }) ); @@ -591,14 +637,14 @@ StripeResource.prototype = { }); }; - const prepareAndMakeRequest = (error, data) => { + const prepareAndMakeRequest = (error: Error | null, data: string): void => { if (error) { return callback(error); } requestData = data; - this._stripe.getClientUserAgent((clientUserAgent) => { + this._stripe.getClientUserAgent((clientUserAgent: string) => { const apiVersion = this._stripe.getApiField('version'); const headers = this._makeHeaders( auth, diff --git a/src/Types.d.ts b/src/Types.d.ts new file mode 100644 index 0000000000..c785e6f3d0 --- /dev/null +++ b/src/Types.d.ts @@ -0,0 +1,203 @@ +/* eslint-disable camelcase */ +type AppInfo = {name?: string} & Record; +type BufferedFile = {name: string; type: string; file: {data: Buffer}}; +type HttpClientResponseError = {code: number}; +type HttpHeaderValue = string | number | string[]; +type MethodSpec = { + method: string; + methodType: string; + urlParams: Array; + path?: string; + fullPath?: string; + encode: (data: RequestData) => RequestData; + validator: (data: RequestData, headers: RequestHeaders) => void; + headers: Record; + streaming?: boolean; + host?: string; + transformResponseData?: (response: HttpClientResponseInterface) => any; +}; +type MultipartRequestData = RequestData | StreamingFile | BufferedFile; +type RawErrorType = + | 'card_error' + | 'invalid_request_error' + | 'api_error' + | 'idempotency_error' + | 'rate_limit_error' + | 'authentication_error' + | 'invalid_grant'; +type RequestArgs = Array; +type RequestCallback = ( + this: StripeResourceObject | void, + error: Error | null, + response?: any +) => RequestCallbackReturn; +type RequestCallbackReturn = any; +type RequestData = Record; +type RequestEvent = { + api_version?: string; + account?: string; + idempotency_key?: string; + method?: string; + path?: string; + request_start_time: number; +}; +type RequestHeaders = Record; +type RequestOptions = { + settings?: RequestSettings; + streaming?: boolean; + headers?: RequestHeaders; +}; +type RequestOpts = { + requestMethod: string; + requestPath: string; + bodyData: RequestData; + queryData: RequestData; + auth: string; + headers: RequestHeaders; + host: string; + streaming: boolean; + settings: RequestSettings; +}; +type RequestSettings = {timeout?: number; maxNetworkRetries?: number}; +type ResponseEvent = { + api_version?: string; + account?: string; + idempotency_key?: string; + method?: string; + path?: string; + status?: number; + request_id?: string; + elapsed?: number; + request_start_time?: number; + request_end_time?: number; +}; +type ResponseHeaderValue = string | string[]; +type ResponseHeaders = Record; +interface HttpClientResponseInterface { + getStatusCode: () => number; + getHeaders: () => ResponseHeaders; + getRawResponse: () => unknown; + toStream: (streamCompleteCallback: () => void) => unknown; + toJSON: () => Promise; +} +type StreamingFile = { + name: string; + type: string; + file: {data: import('events').EventEmitter}; +}; +type StripeConstructor = { + new (key: string, config: Record): StripeObject; +}; +declare const Stripe: StripeConstructor; +type StripeCryptoProvider = { + computeHMACSignature: (data: string, secret: string) => string; + computeHMACSignatureAsync: (data: string, secret: string) => Promise; +}; +interface HttpClientInterface { + getClientName: () => string; + makeRequest: ( + host: string, + port: string, + path: string, + method: string, + headers: RequestHeaders, + requestData: RequestData, + protocol: string, + timeout: number + ) => Promise; +} +type StripeObject = { + on: any; + off: any; + once: any; + VERSION: string; + StripeResource: typeof StripeResource; + errors: any; + webhooks: any; + getApiField: (name: string) => T; + _prepResources: () => void; + _setAppInfo: (appInfo: AppInfo) => void; + _setApiKey: (apiKey: string) => void; + _prevRequestMetrics: number[]; + _api: { + auth: string | null; + host: string; + port: string | number; + protocol: string; + basePath: string; + version: string; + timeout: string; + maxNetworkRetries: number; + agent: string; + httpClient: any; + dev: boolean; + stripeAccount: string | null; + }; + _emitter: import('events').EventEmitter; + _enableTelemetry: boolean; + _getPropsFromConfig: (config: Record) => UserProvidedConfig; +}; +type StripeRawError = { + message?: string; + type?: RawErrorType; + headers?: {[header: string]: string}; + statusCode?: number; + requestId?: string; + code?: string; + doc_url?: string; + decline_code?: string; + param?: string; + detail?: string; + charge?: string; + payment_method_type?: string; + payment_intent?: any; + payment_method?: any; + setup_intent?: any; + source?: any; + exception?: any; +}; +type StripeResourceConstructor = { + new (stripe: StripeObject, deprecatedUrlData?: never): StripeResourceObject; +}; +declare const StripeResource: StripeResourceConstructor; +type StripeResourceNamespaceObject = Record< + string, + StripeResourceObject | unknown +>; +type StripeResourceObject = { + _stripe: StripeObject; + basePath: UrlInterpolator; + path: UrlInterpolator; + resourcePath: string; + includeBasic: Array; + createResourcePathWithSymbols: (path: string | null | undefined) => string; + createFullPath: ( + interpolator: UrlInterpolator, + urlData: RequestData + ) => string; + _request: ( + method: string, + host: string, + path: string, + data: RequestData, + auth: string, + options: RequestOptions, + callback: RequestCallback + ) => void; + initialize: (...args: Array) => void; +}; +type UrlInterpolator = (params: Record) => string; +type UserProvidedConfig = { + apiVersion?: string; + protocol?: string; + host?: string; + httpAgent?: any; + timeout?: number; + port?: number; + maxNetworkRetries?: number; + httpClient?: HttpClientInterface; + stripeAccount?: string; + typescript?: boolean; + telemetry?: boolean; + appInfo?: AppInfo; +}; diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 14d5a4ed8f..955da33bb6 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -2,11 +2,74 @@ import utils = require('./utils'); import _Error = require('./Error'); const {StripeError, StripeSignatureVerificationError} = _Error; -const Webhook = { +type WebhookHeader = string | Buffer; +type WebhookParsedHeader = { + signatures: Array; + timestamp: number; +}; +type WebhookParsedEvent = { + details: WebhookParsedHeader; + decodedPayload: string; + decodedHeader: string; +}; +type WebhookTestHeaderOptions = { + timestamp: number; + payload: string; + secret: string; + scheme: string; + signature: string; + cryptoProvider: StripeCryptoProvider; +}; + +type WebhookEvent = Record; +type WebhookPayload = string | Buffer; +type WebhookSignatureObject = { + verifyHeader: ( + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ) => boolean; + verifyHeaderAsync: ( + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ) => Promise; +}; +type WebhookObject = { + DEFAULT_TOLERANCE: number; + signature: WebhookSignatureObject; + constructEvent: ( + payload: WebhookPayload, + header: WebhookHeader, + secret: string, + tolerance: null, + cryptoProvider: StripeCryptoProvider + ) => WebhookEvent; + constructEventAsync: ( + payload: WebhookPayload, + header: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ) => Promise; + generateTestHeaderString: (opts: WebhookTestHeaderOptions) => string; +}; + +const Webhook: WebhookObject = { DEFAULT_TOLERANCE: 300, // 5 minutes + // @ts-ignore signature: null, - - constructEvent(payload, header, secret, tolerance, cryptoProvider) { + constructEvent( + payload: WebhookPayload, + header: WebhookHeader, + secret: string, + tolerance: null, + cryptoProvider: StripeCryptoProvider + ): WebhookEvent { this.signature.verifyHeader( payload, header, @@ -15,17 +78,18 @@ const Webhook = { cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, async constructEventAsync( - payload, - header, - secret, - tolerance, - cryptoProvider - ) { + payload: WebhookPayload, + header: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ): Promise { await this.signature.verifyHeaderAsync( payload, header, @@ -34,6 +98,7 @@ const Webhook = { cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, @@ -49,7 +114,7 @@ const Webhook = { * @property {string} signature - Computed webhook signature * @property {CryptoProvider} cryptoProvider - Crypto provider to use for computing the signature if none was provided. Defaults to NodeCryptoProvider. */ - generateTestHeaderString: function(opts) { + generateTestHeaderString: function(opts: WebhookTestHeaderOptions): string { if (!opts) { throw new StripeError({ message: 'Options are required', @@ -82,12 +147,12 @@ const signature = { EXPECTED_SCHEME: 'v1', verifyHeader( - encodedPayload, - encodedHeader, - secret, - tolerance, - cryptoProvider - ) { + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ): boolean { const { decodedHeader: header, decodedPayload: payload, @@ -112,12 +177,12 @@ const signature = { }, async verifyHeaderAsync( - encodedPayload, - encodedHeader, - secret, - tolerance, - cryptoProvider - ) { + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider + ): Promise { const { decodedHeader: header, decodedPayload: payload, @@ -141,11 +206,18 @@ const signature = { }, }; -function makeHMACContent(payload, details) { +function makeHMACContent( + payload: WebhookPayload, + details: WebhookParsedHeader +): string { return `${details.timestamp}.${payload}`; } -function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) { +function parseEventDetails( + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + expectedScheme: string +): WebhookParsedEvent { const decodedPayload = Buffer.isBuffer(encodedPayload) ? encodedPayload.toString('utf8') : encodedPayload; @@ -196,13 +268,14 @@ function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) { } function validateComputedSignature( - payload, - header, - details, - expectedSignature, - tolerance -) { + payload: WebhookPayload, + header: string, + details: WebhookParsedHeader, + expectedSignature: string, + tolerance: number +): boolean { const signatureFound = !!details.signatures.filter( + // @ts-ignore utils.secureCompare.bind(utils, expectedSignature) ).length; @@ -236,12 +309,15 @@ function validateComputedSignature( return true; } -function parseHeader(header, scheme) { +function parseHeader( + header: string, + scheme: string +): WebhookParsedHeader | null { if (typeof header !== 'string') { return null; } - return header.split(',').reduce( + return header.split(',').reduce( (accum, item) => { const kv = item.split('='); @@ -262,18 +338,18 @@ function parseHeader(header, scheme) { ); } -let webhooksNodeCryptoProviderInstance = null; +let webhooksNodeCryptoProviderInstance: StripeCryptoProvider | null = null; /** * Lazily instantiate a NodeCryptoProvider instance. This is a stateless object * so a singleton can be used here. */ -function getNodeCryptoProvider() { +function getNodeCryptoProvider(): StripeCryptoProvider { if (!webhooksNodeCryptoProviderInstance) { const NodeCryptoProvider = require('./crypto/NodeCryptoProvider'); webhooksNodeCryptoProviderInstance = new NodeCryptoProvider(); } - return webhooksNodeCryptoProviderInstance; + return webhooksNodeCryptoProviderInstance!; } Webhook.signature = signature; diff --git a/src/autoPagination.ts b/src/autoPagination.ts index b98f44d122..81ca6369b1 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -1,8 +1,50 @@ import makeRequest = require('./makeRequest'); const utils = require('./utils'); -function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { - const promiseCache = {currentPromise: null}; +type PromiseCache = { + currentPromise: Promise | undefined | null; +}; +type IterationResult = { + done: boolean; + value?: any; +}; +type IterationDoneCallback = () => void; +type IterationItemCallback = ( + item: any, + next: any +) => void | boolean | Promise; +type ListResult = { + data: Array; + // eslint-disable-next-line camelcase + has_more: boolean; +}; +type AutoPagingEach = ( + onItem: IterationItemCallback, + onDone?: IterationDoneCallback +) => Promise; + +type AutoPagingToArrayOptions = { + limit?: number; +}; +type AutoPagingToArray = ( + opts: AutoPagingToArrayOptions, + onDone: IterationDoneCallback +) => Promise>; + +type AutoPaginationMethods = { + autoPagingEach: AutoPagingEach; + autoPagingToArray: AutoPagingToArray; + next: () => Promise; + return: () => void; +}; + +function makeAutoPaginationMethods( + self: StripeResourceObject, + requestArgs: RequestArgs, + spec: MethodSpec, + firstPagePromise: Promise +): AutoPaginationMethods { + const promiseCache: PromiseCache = {currentPromise: null}; const reverseIteration = isReverseIteration(requestArgs); let pagePromise = firstPagePromise; let i = 0; @@ -14,9 +56,9 @@ function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { // // Please note: spec.methodType === 'search' is beta functionality and is // subject to change/removal at any time. - let getNextPagePromise; + let getNextPagePromise: (pageResult: any) => Promise; if (spec.methodType === 'search') { - getNextPagePromise = (pageResult) => { + getNextPagePromise = (pageResult): Promise => { if (!pageResult.next_page) { throw Error( 'Unexpected: Stripe API response does not have a well-formed `next_page` field, but `has_more` was true.' @@ -27,7 +69,7 @@ function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { }); }; } else { - getNextPagePromise = (pageResult) => { + getNextPagePromise = (pageResult): Promise => { const lastId = getLastId(pageResult, reverseIteration); return makeRequest(self, requestArgs, spec, { [reverseIteration ? 'ending_before' : 'starting_after']: lastId, @@ -35,7 +77,9 @@ function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { }; } - function iterate(pageResult) { + function iterate( + pageResult: ListResult + ): IterationResult | Promise { if ( !( pageResult && @@ -63,7 +107,7 @@ function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { return {value: undefined, done: true}; } - function asyncIteratorNext() { + function asyncIteratorNext(): Promise { return memoizedPromise(promiseCache, (resolve, reject) => { return pagePromise .then(iterate) @@ -75,13 +119,13 @@ function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { const autoPagingEach = makeAutoPagingEach(asyncIteratorNext); const autoPagingToArray = makeAutoPagingToArray(autoPagingEach); - const autoPaginationMethods = { + const autoPaginationMethods: AutoPaginationMethods = { autoPagingEach, autoPagingToArray, // Async iterator functions: next: asyncIteratorNext, - return: () => { + return: (): any => { // This is required for `break`. return {}; }, @@ -102,7 +146,7 @@ export = { * ---------------- */ -function getAsyncIteratorSymbol() { +function getAsyncIteratorSymbol(): symbol | string { if (typeof Symbol !== 'undefined' && Symbol.asyncIterator) { return Symbol.asyncIterator; } @@ -110,7 +154,7 @@ function getAsyncIteratorSymbol() { return '@@asyncIterator'; } -function getDoneCallback(args) { +function getDoneCallback(args: Array): IterationDoneCallback | undefined { if (args.length < 2) { return undefined; } @@ -134,7 +178,7 @@ function getDoneCallback(args) { * In addition to standard validation, this helper * coalesces the former forms into the latter form. */ -function getItemCallback(args) { +function getItemCallback(args: Array): IterationItemCallback | undefined { if (args.length === 0) { return undefined; } @@ -160,13 +204,13 @@ function getItemCallback(args) { // 1. `.autoPagingEach((item) => { doSomething(item); return false; });` // 2. `.autoPagingEach(async (item) => { await doSomething(item); return false; });` // 3. `.autoPagingEach((item) => doSomething(item).then(() => false));` - return function _onItem(item, next) { + return function _onItem(item, next): void { const shouldContinue = onItem(item); next(shouldContinue); }; } -function getLastId(listResult, reverseIteration) { +function getLastId(listResult: ListResult, reverseIteration: boolean): string { const lastIdx = reverseIteration ? 0 : listResult.data.length - 1; const lastItem = listResult.data[lastIdx]; const lastId = lastItem && lastItem.id; @@ -183,7 +227,10 @@ function getLastId(listResult, reverseIteration) { * return the same result until something has resolved * to prevent page-turning race conditions. */ -function memoizedPromise(promiseCache, cb) { +function memoizedPromise( + promiseCache: PromiseCache, + cb: (resolve: (value: T) => void, reject: (reason?: any) => void) => void +): Promise { if (promiseCache.currentPromise) { return promiseCache.currentPromise; } @@ -194,8 +241,10 @@ function memoizedPromise(promiseCache, cb) { return promiseCache.currentPromise; } -function makeAutoPagingEach(asyncIteratorNext) { - return function autoPagingEach(/* onItem?, onDone? */) { +function makeAutoPagingEach( + asyncIteratorNext: () => Promise +): AutoPagingEach { + return function autoPagingEach(/* onItem?, onDone? */): Promise { const args = [].slice.call(arguments); const onItem = getItemCallback(args); const onDone = getDoneCallback(args); @@ -205,14 +254,20 @@ function makeAutoPagingEach(asyncIteratorNext) { const autoPagePromise = wrapAsyncIteratorWithCallback( asyncIteratorNext, + // @ts-ignore we might need a null check onItem ); return utils.callbackifyPromiseWithTimeout(autoPagePromise, onDone); - }; + } as AutoPagingEach; } -function makeAutoPagingToArray(autoPagingEach) { - return function autoPagingToArray(opts, onDone) { +function makeAutoPagingToArray( + autoPagingEach: AutoPagingEach +): AutoPagingToArray { + return function autoPagingToArray( + opts, + onDone: IterationDoneCallback + ): Promise> { const limit = opts && opts.limit; if (!limit) { throw Error( @@ -225,7 +280,7 @@ function makeAutoPagingToArray(autoPagingEach) { ); } const promise = new Promise((resolve, reject) => { - const items = []; + const items: Array = []; autoPagingEach((item) => { items.push(item); if (items.length >= limit) { @@ -241,9 +296,12 @@ function makeAutoPagingToArray(autoPagingEach) { }; } -function wrapAsyncIteratorWithCallback(asyncIteratorNext, onItem) { +function wrapAsyncIteratorWithCallback( + asyncIteratorNext: () => Promise, + onItem: IterationItemCallback +): Promise { return new Promise((resolve, reject) => { - function handleIteration(iterResult) { + function handleIteration(iterResult: IterationResult): Promise | void { if (iterResult.done) { resolve(); return; @@ -270,7 +328,7 @@ function wrapAsyncIteratorWithCallback(asyncIteratorNext, onItem) { }); } -function isReverseIteration(requestArgs) { +function isReverseIteration(requestArgs: RequestArgs): boolean { const args = [].slice.call(requestArgs); const dataFromArgs = utils.getDataFromArgs(args); diff --git a/src/makeRequest.ts b/src/makeRequest.ts index 57737cfd25..42dc109bfb 100644 --- a/src/makeRequest.ts +++ b/src/makeRequest.ts @@ -1,13 +1,18 @@ const utils = require('./utils'); -function getRequestOpts(self, requestArgs, spec, overrideData) { +function getRequestOpts( + self: StripeResourceObject, + requestArgs: RequestArgs, + spec: MethodSpec, + overrideData: RequestData +): RequestOpts { // Extract spec values with defaults. const requestMethod = (spec.method || 'GET').toUpperCase(); const urlParams = spec.urlParams || []; - const encode = spec.encode || ((data) => data); + const encode = spec.encode || ((data): RequestData => data); const isUsingFullPath = !!spec.fullPath; - const commandPath = utils.makeURLInterpolator( + const commandPath: UrlInterpolator = utils.makeURLInterpolator( isUsingFullPath ? spec.fullPath : spec.path || '' ); // When using fullPath, we ignore the resource path as it should already be @@ -17,10 +22,10 @@ function getRequestOpts(self, requestArgs, spec, overrideData) { : self.createResourcePathWithSymbols(spec.path); // Don't mutate args externally. - const args = [].slice.call(requestArgs); + const args: RequestArgs = [].slice.call(requestArgs); // Generate and validate url params. - const urlData = urlParams.reduce((urlData, param) => { + const urlData = urlParams.reduce((urlData, param) => { const arg = args.shift(); if (typeof arg !== 'string') { throw new Error( @@ -74,9 +79,14 @@ function getRequestOpts(self, requestArgs, spec, overrideData) { }; } -function makeRequest(self, requestArgs, spec, overrideData) { - return new Promise((resolve, reject) => { - let opts; +function makeRequest( + self: StripeResourceObject, + requestArgs: RequestArgs, + spec: MethodSpec, + overrideData: RequestData +): Promise { + return new Promise((resolve, reject) => { + let opts: RequestOpts; try { opts = getRequestOpts(self, requestArgs, spec, overrideData); } catch (err) { @@ -84,7 +94,10 @@ function makeRequest(self, requestArgs, spec, overrideData) { return; } - function requestCallback(err, response) { + function requestCallback( + err: any, + response: HttpClientResponseInterface + ): void { if (err) { reject(err); } else { diff --git a/src/multipart.ts b/src/multipart.ts index dbb59a8bcf..01e91b1a37 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -3,18 +3,26 @@ import _Error = require('./Error'); const {StripeError} = _Error; class StreamProcessingError extends StripeError {} - +type MultipartCallbackReturn = any; +type MultipartCallback = ( + error: Error | null, + data: Buffer | string | null +) => MultipartCallbackReturn; // Method for formatting HTTP body for the multipart/form-data specification // Mostly taken from Fermata.js // https://github.com/natevw/fermata/blob/5d9732a33d776ce925013a265935facd1626cc88/fermata.js#L315-L343 -const multipartDataGenerator = (method, data, headers) => { +const multipartDataGenerator = ( + method: string, + data: MultipartRequestData, + headers: RequestHeaders +): Buffer => { const segno = ( Math.round(Math.random() * 1e16) + Math.round(Math.random() * 1e16) ).toString(); headers['Content-Type'] = `multipart/form-data; boundary=${segno}`; let buffer = Buffer.alloc(0); - function push(l) { + function push(l: any): void { const prevBuffer = buffer; const newBuffer = l instanceof Buffer ? l : Buffer.from(l); buffer = Buffer.alloc(prevBuffer.length + newBuffer.length + 2); @@ -23,7 +31,7 @@ const multipartDataGenerator = (method, data, headers) => { buffer.write('\r\n', buffer.length - 2); } - function q(s) { + function q(s: string): string { return `"${s.replace(/"|"/g, '%22').replace(/\r\n|\r|\n/g, ' ')}"`; } @@ -33,14 +41,19 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { + const typedEntry: { + name: string; + data: BufferedFile; + type: string; + } = v as any; push( `Content-Disposition: form-data; name=${q(k)}; filename=${q( - v.name || 'blob' + typedEntry.name || 'blob' )}` ); - push(`Content-Type: ${v.type || 'application/octet-stream'}`); + push(`Content-Type: ${typedEntry.type || 'application/octet-stream'}`); push(''); - push(v.data); + push(typedEntry.data); } else { push(`Content-Disposition: form-data; name=${q(k)}`); push(''); @@ -52,14 +65,20 @@ const multipartDataGenerator = (method, data, headers) => { return buffer; }; -const streamProcessor = (method, data, headers, callback) => { - const bufferArray = []; +const streamProcessor = ( + method: string, + data: StreamingFile, + headers: RequestHeaders, + callback: MultipartCallback +): void => { + const bufferArray: Array = []; data.file.data - .on('data', (line) => { + .on('data', (line: Buffer) => { bufferArray.push(line); }) .once('end', () => { - const bufferData = Object.assign({}, data); + // @ts-ignore + const bufferData: BufferedFile = Object.assign({}, data); bufferData.file.data = Buffer.concat(bufferArray); const buffer = multipartDataGenerator(method, bufferData, headers); callback(null, buffer); @@ -76,7 +95,12 @@ const streamProcessor = (method, data, headers, callback) => { }); }; -const multipartRequestDataProcessor = (method, data, headers, callback) => { +const multipartRequestDataProcessor = ( + method: string, + data: MultipartRequestData, + headers: RequestHeaders, + callback: MultipartCallback +): MultipartCallbackReturn => { data = data || {}; if (method !== 'POST') { @@ -85,7 +109,7 @@ const multipartRequestDataProcessor = (method, data, headers, callback) => { const isStream = utils.checkForStream(data); if (isStream) { - return streamProcessor(method, data, headers, callback); + return streamProcessor(method, data as StreamingFile, headers, callback); } const buffer = multipartDataGenerator(method, data, headers); diff --git a/src/net/FetchHttpClient.ts b/src/net/FetchHttpClient.ts index 630bddc42d..6e4facecee 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -9,33 +9,29 @@ const {HttpClient, HttpClientResponse} = _HttpClient; * Fetch API. As an example, this could be the function provided by the * node-fetch package (https://github.com/node-fetch/node-fetch). */ -class FetchHttpClient extends HttpClient { - _fetchFn: ( - input: RequestInfo | URL, - init?: RequestInit | undefined - ) => Promise; - _res: Response; +class FetchHttpClient extends HttpClient implements HttpClientInterface { + _fetchFn: typeof fetch; - constructor(fetchFn) { + constructor(fetchFn: typeof fetch) { super(); this._fetchFn = fetchFn; } /** @override. */ - getClientName() { + getClientName(): string { return 'fetch'; } makeRequest( - host, - port, - path, - method, - headers, - requestData, - protocol, - timeout - ) { + host: string, + port: string, + path: string, + method: string, + headers: RequestHeaders, + requestData: RequestData, + protocol: string, + timeout: number + ): Promise { const isInsecureConnection = protocol === 'http'; const url = new URL( @@ -55,7 +51,9 @@ class FetchHttpClient extends HttpClient { const fetchFn = this._fetchFn || fetch; const fetchPromise = fetchFn(url.toString(), { method, + // @ts-ignore headers, + // @ts-ignore body, }); @@ -72,7 +70,7 @@ class FetchHttpClient extends HttpClient { // to be established followed by 20s for the body, Fetch would timeout but // Node would not. The more fine-grained timeout cannot be implemented with // fetch. - let pendingTimeoutId; + let pendingTimeoutId: NodeJS.Timeout | null; const timeoutPromise = new Promise((_, reject) => { pendingTimeoutId = setTimeout(() => { pendingTimeoutId = null; @@ -82,7 +80,7 @@ class FetchHttpClient extends HttpClient { return Promise.race([fetchPromise, timeoutPromise]) .then((res) => { - return new FetchHttpClientResponse(res); + return new FetchHttpClientResponse(res as Response); }) .finally(() => { if (pendingTimeoutId) { @@ -92,10 +90,11 @@ class FetchHttpClient extends HttpClient { } } -class FetchHttpClientResponse extends HttpClientResponse { +class FetchHttpClientResponse extends HttpClientResponse + implements HttpClientResponseInterface { _res: Response; - constructor(res) { + constructor(res: Response) { super( res.status, FetchHttpClientResponse._transformHeadersToObject(res.headers) @@ -103,11 +102,13 @@ class FetchHttpClientResponse extends HttpClientResponse { this._res = res; } - getRawResponse() { + getRawResponse(): Response { return this._res; } - toStream(streamCompleteCallback) { + toStream( + streamCompleteCallback: () => void + ): ReadableStream | null { // Unfortunately `fetch` does not have event handlers for when the stream is // completely read. We therefore invoke the streamCompleteCallback right // away. This callback emits a response event with metadata and completes @@ -119,15 +120,14 @@ class FetchHttpClientResponse extends HttpClientResponse { return this._res.body; } - toJSON() { + toJSON(): Promise { return this._res.json(); } - static _transformHeadersToObject(headers) { + static _transformHeadersToObject(headers: Headers): ResponseHeaders { // Fetch uses a Headers instance so this must be converted to a barebones // JS object to meet the HttpClient interface. - const headersObj = {}; - + const headersObj: ResponseHeaders = {}; for (const entry of headers) { if (!Array.isArray(entry) || entry.length != 2) { throw new Error( diff --git a/src/net/HttpClient.ts b/src/net/HttpClient.ts index 2c035d7cd7..56c67090d4 100644 --- a/src/net/HttpClient.ts +++ b/src/net/HttpClient.ts @@ -9,30 +9,30 @@ type TimeoutError = TypeError & {code?: string}; * 2. A client class which extends HttpClient and implements all methods, * returning their own response class when making requests. */ -class HttpClient { +class HttpClient implements HttpClientInterface { static CONNECTION_CLOSED_ERROR_CODES: string[]; static TIMEOUT_ERROR_CODE: string; /** The client name used for diagnostics. */ - getClientName() { + getClientName(): string { throw new Error('getClientName not implemented.'); } makeRequest( - host, - port, - path, - method, - headers, - requestData, - protocol, - timeout - ) { + host: string, + port: string, + path: string, + method: string, + headers: RequestHeaders, + requestData: RequestData, + protocol: string, + timeout: number + ): Promise { throw new Error('makeRequest not implemented.'); } /** Helper to make a consistent timeout error across implementations. */ - static makeTimeoutError() { + static makeTimeoutError(): TimeoutError { const timeoutErr: TimeoutError = new TypeError( HttpClient.TIMEOUT_ERROR_CODE ); @@ -44,11 +44,11 @@ class HttpClient { HttpClient.CONNECTION_CLOSED_ERROR_CODES = ['ECONNRESET', 'EPIPE']; HttpClient.TIMEOUT_ERROR_CODE = 'ETIMEDOUT'; -class HttpClientResponse { +class HttpClientResponse implements HttpClientResponseInterface { _statusCode: number; - _headers: Record; + _headers: ResponseHeaders; - constructor(statusCode, headers) { + constructor(statusCode: number, headers: ResponseHeaders) { this._statusCode = statusCode; this._headers = headers; } @@ -57,15 +57,15 @@ class HttpClientResponse { return this._statusCode; } - getHeaders(): Record { + getHeaders(): ResponseHeaders { return this._headers; } - getRawResponse() { + getRawResponse(): unknown { throw new Error('getRawResponse not implemented.'); } - toStream(streamCompleteCallback) { + toStream(streamCompleteCallback: () => void): unknown { throw new Error('toStream not implemented.'); } diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index 1d556a0906..93b37a26b5 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -14,26 +14,26 @@ const defaultHttpsAgent = new https.Agent({keepAlive: true}); class NodeHttpClient extends HttpClient { _agent: http.Agent | https.Agent; - constructor(agent) { + constructor(agent: http.Agent | https.Agent) { super(); this._agent = agent; } /** @override. */ - getClientName() { + getClientName(): string { return 'node'; } makeRequest( - host, - port, - path, - method, - headers, - requestData, - protocol, - timeout - ) { + host: string, + port: string, + path: string, + method: string, + headers: RequestHeaders, + requestData: RequestData, + protocol: string, + timeout: number + ): Promise { const isInsecureConnection = protocol === 'http'; let agent = this._agent; @@ -41,64 +41,68 @@ class NodeHttpClient extends HttpClient { agent = isInsecureConnection ? defaultHttpAgent : defaultHttpsAgent; } - const requestPromise = new Promise((resolve, reject) => { - const req = (isInsecureConnection ? http : https).request({ - host: host, - port: port, - path, - method, - agent, - headers, - ciphers: 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!MD5', - }); - - req.setTimeout(timeout, () => { - req.destroy(HttpClient.makeTimeoutError()); - }); - - req.on('response', (res) => { - resolve(new NodeHttpClientResponse(res)); - }); - - req.on('error', (error) => { - reject(error); - }); - - req.once('socket', (socket) => { - if (socket.connecting) { - socket.once( - isInsecureConnection ? 'connect' : 'secureConnect', - () => { - // Send payload; we're safe: - req.write(requestData); - req.end(); - } - ); - } else { - // we're already connected - req.write(requestData); - req.end(); - } - }); - }); + const requestPromise = new Promise( + (resolve, reject) => { + const req = (isInsecureConnection ? http : https).request({ + host: host, + port: port, + path, + method, + agent, + headers, + ciphers: 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!MD5', + }); + + req.setTimeout(timeout, () => { + req.destroy(HttpClient.makeTimeoutError()); + }); + + req.on('response', (res) => { + resolve(new NodeHttpClientResponse(res)); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.once('socket', (socket) => { + if (socket.connecting) { + socket.once( + isInsecureConnection ? 'connect' : 'secureConnect', + () => { + // Send payload; we're safe: + req.write(requestData); + req.end(); + } + ); + } else { + // we're already connected + req.write(requestData); + req.end(); + } + }); + } + ); return requestPromise; } } -class NodeHttpClientResponse extends HttpClientResponse { +class NodeHttpClientResponse extends HttpClientResponse + implements HttpClientResponseInterface { _res: http.IncomingMessage; constructor(res: http.IncomingMessage) { + // @ts-ignore super(res.statusCode, res.headers || {}); this._res = res; } - getRawResponse() { + getRawResponse(): http.IncomingMessage { return this._res; } - toStream(streamCompleteCallback) { + toStream(streamCompleteCallback: () => void): http.IncomingMessage { // The raw response is itself the stream, so we just return that. To be // backwards compatible, we should invoke the streamCompleteCallback only // once the stream has been fully consumed. diff --git a/src/stripe.ts b/src/stripe.ts index 5d6cb7e377..6e684e19c7 100644 --- a/src/stripe.ts +++ b/src/stripe.ts @@ -5,7 +5,7 @@ const resources = require('./resources'); const DEFAULT_HOST = 'api.stripe.com'; const DEFAULT_PORT = '443'; const DEFAULT_BASE_PATH = '/v1/'; -const DEFAULT_API_VERSION = null; +const DEFAULT_API_VERSION = (null as unknown) as string; const DEFAULT_TIMEOUT = 80000; @@ -24,7 +24,7 @@ Stripe.USER_AGENT = { }; /** @private */ -Stripe._UNAME_CACHE = null; +Stripe._UNAME_CACHE = null as Promise | null; const MAX_NETWORK_RETRY_DELAY_SEC = 2; const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; @@ -46,8 +46,8 @@ const ALLOWED_CONFIG_PROPERTIES = [ ]; const EventEmitter = require('events').EventEmitter; - import StripeResource = require('./StripeResource'); +import * as http from 'http'; Stripe.StripeResource = StripeResource; Stripe.resources = resources; @@ -58,7 +58,11 @@ Stripe.HttpClientResponse = HttpClientResponse; const CryptoProvider = require('./crypto/CryptoProvider'); Stripe.CryptoProvider = CryptoProvider; -function Stripe(key, config = {}) { +function Stripe( + this: StripeObject, + key: string, + config: Record = {} +): void { if (!(this instanceof Stripe)) { return new (Stripe as any)(key, config); } @@ -132,13 +136,14 @@ function Stripe(key, config = {}) { this._enableTelemetry = props.telemetry !== false; // Expose StripeResource on the instance too + // @ts-ignore this.StripeResource = Stripe.StripeResource; } Stripe.errors = _Error; Stripe.webhooks = require('./Webhooks'); -Stripe.createNodeHttpClient = (agent) => { +Stripe.createNodeHttpClient = (agent: http.Agent): typeof HttpClient => { const {NodeHttpClient} = require('./net/NodeHttpClient'); return new NodeHttpClient(agent); }; @@ -150,7 +155,7 @@ Stripe.createNodeHttpClient = (agent) => { * A fetch function can optionally be passed in as a parameter. If none is * passed, will default to the default `fetch` function in the global scope. */ -Stripe.createFetchHttpClient = (fetchFn) => { +Stripe.createFetchHttpClient = (fetchFn: typeof fetch): typeof HttpClient => { const {FetchHttpClient} = require('./net/FetchHttpClient'); return new FetchHttpClient(fetchFn); }; @@ -159,7 +164,7 @@ Stripe.createFetchHttpClient = (fetchFn) => { * Create a CryptoProvider which uses the built-in Node crypto libraries for * its crypto operations. */ -Stripe.createNodeCryptoProvider = () => { +Stripe.createNodeCryptoProvider = (): StripeCryptoProvider => { const NodeCryptoProvider = require('./crypto/NodeCryptoProvider'); return new NodeCryptoProvider(); }; @@ -172,7 +177,9 @@ Stripe.createNodeCryptoProvider = () => { * is passed, will default to the default `crypto.subtle` object in the global * scope. */ -Stripe.createSubtleCryptoProvider = (subtleCrypto) => { +Stripe.createSubtleCryptoProvider = ( + subtleCrypto: typeof crypto.subtle +): StripeCryptoProvider => { const SubtleCryptoProvider = require('./crypto/SubtleCryptoProvider'); return new SubtleCryptoProvider(subtleCrypto); }; @@ -188,7 +195,7 @@ Stripe.prototype = { * }); * */ - setHost(host, port, protocol) { + setHost(host: string, port: number, protocol: string): void { emitWarning( '`setHost` is deprecated. Use the `host` config option instead.' ); @@ -209,7 +216,7 @@ Stripe.prototype = { * }); * */ - setProtocol(protocol) { + setProtocol(protocol: string): void { emitWarning( '`setProtocol` is deprecated. Use the `protocol` config option instead.' ); @@ -224,7 +231,7 @@ Stripe.prototype = { * }); * */ - setPort(port) { + setPort(port: number): void { emitWarning( '`setPort` is deprecated. Use the `port` config option instead.' ); @@ -239,7 +246,7 @@ Stripe.prototype = { * }); * */ - setApiVersion(version) { + setApiVersion(version: string): void { emitWarning( '`setApiVersion` is deprecated. Use the `apiVersion` config or request option instead.' ); @@ -263,7 +270,7 @@ Stripe.prototype = { * * stripe.customers.create(params, {apiKey: 'sk_test_...'}); */ - setApiKey(key) { + setApiKey(key: string): void { emitWarning( '`setApiKey` is deprecated. Use the `apiKey` request option instead.' ); @@ -273,7 +280,7 @@ Stripe.prototype = { /** * @private */ - _setApiKey(key) { + _setApiKey(key: string): void { if (key) { this._setApiField('auth', `Bearer ${key}`); } @@ -286,7 +293,7 @@ Stripe.prototype = { * timeout: TIMEOUT_MS, * }); */ - setTimeout(timeout) { + setTimeout(timeout: number): void { emitWarning( '`setTimeout` is deprecated. Use the `timeout` config or request option instead.' ); @@ -305,7 +312,7 @@ Stripe.prototype = { * }, * }); */ - setAppInfo(info) { + setAppInfo(info: AppInfo): void { emitWarning( '`setAppInfo` is deprecated. Use the `appInfo` config option instead.' ); @@ -316,7 +323,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setAppInfo(info) { + _setAppInfo(info: AppInfo): void { if (info && typeof info !== 'object') { throw new Error('AppInfo must be an object.'); } @@ -327,7 +334,7 @@ Stripe.prototype = { info = info || {}; - const appInfo = APP_INFO_PROPERTIES.reduce( + this._appInfo = APP_INFO_PROPERTIES.reduce>( (accum: Record, prop) => { if (typeof info[prop] == 'string') { accum = accum || {}; @@ -337,10 +344,9 @@ Stripe.prototype = { return accum; }, + // @ts-ignore undefined ); - - this._appInfo = appInfo; }, /** @@ -352,7 +358,7 @@ Stripe.prototype = { * }); * */ - setHttpAgent(agent) { + setHttpAgent(agent: string): void { emitWarning( '`setHttpAgent` is deprecated. Use the `httpAgent` config option instead.' ); @@ -363,7 +369,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setApiField(key, value) { + _setApiField(key: string, value: unknown): void { this._api[key] = value; }, @@ -374,15 +380,15 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getApiField(key) { + getApiField(key: string): T { return this._api[key]; }, - setClientId(clientId) { + setClientId(clientId: string): void { this._clientId = clientId; }, - getClientId() { + getClientId(): string { return this._clientId; }, @@ -393,7 +399,7 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getConstant: (c) => { + getConstant: (c: string): unknown => { switch (c) { case 'DEFAULT_HOST': return DEFAULT_HOST; @@ -410,10 +416,10 @@ Stripe.prototype = { case 'INITIAL_NETWORK_RETRY_DELAY_SEC': return INITIAL_NETWORK_RETRY_DELAY_SEC; } - return Stripe[c]; + return ((Stripe as unknown) as Record)[c]; }, - getMaxNetworkRetries() { + getMaxNetworkRetries(): number { return this.getApiField('maxNetworkRetries'); }, @@ -425,7 +431,7 @@ Stripe.prototype = { * }); * */ - setMaxNetworkRetries(maxNetworkRetries) { + setMaxNetworkRetries(maxNetworkRetries: number): void { this._setApiNumberField('maxNetworkRetries', maxNetworkRetries); }, @@ -433,32 +439,32 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setApiNumberField(prop, n, defaultVal) { + _setApiNumberField(prop: string, n: number, defaultVal?: number): void { const val = utils.validateInteger(prop, n, defaultVal); this._setApiField(prop, val); }, - getMaxNetworkRetryDelay() { + getMaxNetworkRetryDelay(): number { return MAX_NETWORK_RETRY_DELAY_SEC; }, - getInitialNetworkRetryDelay() { + getInitialNetworkRetryDelay(): number { return INITIAL_NETWORK_RETRY_DELAY_SEC; }, /** * @private */ - getUname(cb) { + getUname(cb: (uname: string) => void): void { if (!Stripe._UNAME_CACHE) { - Stripe._UNAME_CACHE = new Promise((resolve) => { - utils.safeExec('uname -a', (err, uname) => { + Stripe._UNAME_CACHE = new Promise((resolve) => { + utils.safeExec('uname -a', (err: Error, uname: string) => { resolve(uname); }); }); } - Stripe._UNAME_CACHE.then((uname) => cb(uname)); + Stripe._UNAME_CACHE.then((uname: string) => cb(uname)); }, /** @@ -471,7 +477,7 @@ Stripe.prototype = { * Gets a JSON version of a User-Agent and uses a cached version for a slight * speed advantage. */ - getClientUserAgent(cb) { + getClientUserAgent(cb: (userAgent: string) => void): void { return this.getClientUserAgentSeeded(Stripe.USER_AGENT, cb); }, @@ -485,8 +491,11 @@ Stripe.prototype = { * Gets a JSON version of a User-Agent by encoding a seeded object and * fetching a uname from the system. */ - getClientUserAgentSeeded(seed, cb) { - this.getUname((uname) => { + getClientUserAgentSeeded( + seed: Record, + cb: (userAgent: string) => void + ): void { + this.getUname((uname: string) => { const userAgent: Record = {}; for (const field in seed) { userAgent[field] = encodeURIComponent(seed[field]); @@ -515,7 +524,7 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getAppInfoAsString() { + getAppInfoAsString(): string { if (!this._appInfo) { return ''; } @@ -541,14 +550,14 @@ Stripe.prototype = { * }); * */ - setTelemetryEnabled(enableTelemetry) { + setTelemetryEnabled(enableTelemetry: boolean): void { emitWarning( '`setTelemetryEnabled` is deprecated. Use the `telemetry` config option instead.' ); this._enableTelemetry = enableTelemetry; }, - getTelemetryEnabled() { + getTelemetryEnabled(): boolean { return this._enableTelemetry; }, @@ -556,7 +565,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _prepResources() { + _prepResources(): void { for (const name in resources) { this[utils.pascalToCamelCase(name)] = new resources[name](this); } @@ -566,7 +575,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _getPropsFromConfig(config) { + _getPropsFromConfig(config: Record): UserProvidedConfig { // If config is null or undefined, just bail early with no props if (!config) { return {}; diff --git a/src/utils.ts b/src/utils.ts index 40b12656bd..9eb3971cc9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,6 +9,7 @@ let exec = null; try { exec = require('child_process').exec; } catch (e) { + // @ts-ignore if (e.code !== 'MODULE_NOT_FOUND') { throw e; } @@ -30,7 +31,7 @@ const DEPRECATED_OPTIONS = { stripe_account: 'stripeAccount', stripe_version: 'apiVersion', stripeVersion: 'apiVersion', -}; +} as Record; const DEPRECATED_OPTIONS_KEYS = Object.keys(DEPRECATED_OPTIONS); type Settings = { @@ -39,15 +40,15 @@ type Settings = { }; type Options = { - auth?: string; + auth?: string | null; host?: string; - settings?: Settings; + settings: Settings; streaming?: boolean; - headers?: Record; + headers: Record; }; const utils = { - isOptionsHash(o) { + isOptionsHash(o: unknown): boolean | unknown { return ( o && typeof o === 'object' && @@ -64,11 +65,11 @@ const utils = { * Stringifies an Object, accommodating nested objects * (forming the conventional key 'parent[child]=value') */ - stringifyRequestData: (data) => { + stringifyRequestData: (data: RequestData | string): string => { return ( qs .stringify(data, { - serializeDate: (d) => Math.floor(d.getTime() / 1000), + serializeDate: (d: Date) => 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 @@ -84,24 +85,25 @@ const utils = { * const fn = makeURLInterpolator('some/url/{param1}/{param2}'); * fn({ param1: 123, param2: 456 }); // => 'some/url/123/456' */ - makeURLInterpolator: (() => { + makeURLInterpolator: ((): ((s: string) => UrlInterpolator) => { const rc = { '\n': '\\n', '"': '\\"', '\u2028': '\\u2028', '\u2029': '\\u2029', - }; - return (str) => { + } as Record; + return (str: string): UrlInterpolator => { const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); - return (outputs) => { + return (outputs: Record): string => { return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => + // @ts-ignore encodeURIComponent(outputs[$1] || '') ); }; }; })(), - extractUrlParams: (path) => { + extractUrlParams: (path: string): Array => { const params = path.match(/\{\w+\}/g); if (!params) { return []; @@ -116,7 +118,7 @@ const utils = { * @param {object[]} args * @returns {object} */ - getDataFromArgs(args) { + getDataFromArgs(args: RequestArgs): RequestData { if (!Array.isArray(args) || !args[0] || typeof args[0] !== 'object') { return {}; } @@ -152,7 +154,7 @@ const utils = { /** * Return the options hash from a list of arguments */ - getOptionsFromArgs: (args) => { + getOptionsFromArgs: (args: RequestArgs): Options => { const opts: Options = { auth: null, headers: {}, @@ -161,9 +163,9 @@ const utils = { if (args.length > 0) { const arg = args[args.length - 1]; if (typeof arg === 'string') { - opts.auth = args.pop(); + opts.auth = args.pop() as string; } else if (utils.isOptionsHash(arg)) { - const params = {...args.pop()}; + const params = {...(args.pop() as Record)}; const extraKeys = Object.keys(params).filter( (key) => !OPTIONS_KEYS.includes(key) @@ -194,7 +196,7 @@ const utils = { } if (params.apiKey) { - opts.auth = params.apiKey; + opts.auth = params.apiKey as string; } if (params.idempotencyKey) { opts.headers['Idempotency-Key'] = params.idempotencyKey; @@ -206,13 +208,13 @@ const utils = { opts.headers['Stripe-Version'] = params.apiVersion; } if (Number.isInteger(params.maxNetworkRetries)) { - opts.settings.maxNetworkRetries = params.maxNetworkRetries; + opts.settings.maxNetworkRetries = params.maxNetworkRetries as number; } if (Number.isInteger(params.timeout)) { - opts.settings.timeout = params.timeout; + opts.settings.timeout = params.timeout as number; } if (params.host) { - opts.host = params.host; + opts.host = params.host as string; } } } @@ -222,12 +224,12 @@ const utils = { /** * Provide simple "Class" extension mechanism */ - protoExtend(sub) { + protoExtend(this: any, sub: any): (...args: any[]) => void { // eslint-disable-next-line @typescript-eslint/no-this-alias const Super = this; const Constructor = Object.prototype.hasOwnProperty.call(sub, 'constructor') ? sub.constructor - : function(...args) { + : function(this: StripeResourceObject, ...args: any[]): void { Super.apply(this, args); }; @@ -246,7 +248,7 @@ const utils = { /** * Secure compare, from https://github.com/freewil/scmp */ - secureCompare: (a, b) => { + secureCompare: (a: Uint8Array, b: Uint8Array): boolean => { a = Buffer.from(a); b = Buffer.from(b); @@ -274,12 +276,12 @@ const utils = { /** * Remove empty values from an object */ - removeNullish: (obj) => { + removeNullish: (obj: Record): Record => { if (typeof obj !== 'object') { throw new Error('Argument must be an object'); } - return Object.keys(obj).reduce((result, key) => { + return Object.keys(obj).reduce>((result, key) => { if (obj[key] != null) { result[key] = obj[key]; } @@ -293,12 +295,12 @@ const utils = { * becomes * {'Foo-Bar': 'hi'} */ - normalizeHeaders: (obj) => { + normalizeHeaders: (obj: RequestHeaders): RequestHeaders => { if (!(obj && typeof obj === 'object')) { return obj; } - return Object.keys(obj).reduce((result, header) => { + return Object.keys(obj).reduce((result, header) => { result[utils.normalizeHeader(header)] = obj[header]; return result; }, {}); @@ -308,7 +310,7 @@ const utils = { * 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) => { + normalizeHeader: (header: string): string => { return header .split('-') .map( @@ -321,14 +323,17 @@ const utils = { * Determine if file data is a derivative of EventEmitter class. * https://nodejs.org/api/events.html#events_events */ - checkForStream: (obj) => { + checkForStream: (obj: {file?: {data: unknown}}): boolean => { if (obj.file && obj.file.data) { return obj.file.data instanceof EventEmitter; } return false; }, - callbackifyPromiseWithTimeout: (promise, callback) => { + callbackifyPromiseWithTimeout: ( + promise: Promise, + callback: (error: unknown, result: T | null) => void + ): Promise => { if (callback) { // Ensure callback is called outside of promise stack. return promise.then( @@ -351,7 +356,7 @@ const utils = { /** * Allow for special capitalization cases (such as OAuth) */ - pascalToCamelCase: (name) => { + pascalToCamelCase: (name: string): string => { if (name === 'OAuth') { return 'oauth'; } else { @@ -368,7 +373,10 @@ const utils = { * * This unifies that interface. */ - safeExec: (cmd, cb) => { + safeExec: ( + cmd: string, + cb: (error: unknown, stdout: string | null) => void + ): void => { // 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) { @@ -386,16 +394,18 @@ const utils = { // For mocking in tests. _exec: exec, - isObject: (obj) => { + isObject: (obj: unknown): boolean => { const type = typeof obj; return (type === 'function' || type === 'object') && !!obj; }, // For use in multipart requests - flattenAndStringify: (data) => { - const result = {}; + flattenAndStringify: ( + data: MultipartRequestData + ): Record => { + const result: RequestData = {}; - const step = (obj, prevKey) => { + const step = (obj: RequestData, prevKey: string | null): void => { Object.keys(obj).forEach((key) => { const value = obj[key]; @@ -407,7 +417,7 @@ const utils = { !Object.prototype.hasOwnProperty.call(value, 'data') ) { // Non-buffer non-file Objects are recursively flattened - return step(value, newKey); + return step(value as RequestData, newKey); } else { // Buffers and file objects are stored without modification result[newKey] = value; @@ -427,7 +437,7 @@ const utils = { /** * https://stackoverflow.com/a/2117523 */ - uuid4: () => { + uuid4: (): string => { // available in: v14.17.x+ if (crypto.randomUUID) { return crypto.randomUUID(); @@ -441,7 +451,7 @@ const utils = { }); }, - validateInteger: (name, n, defaultVal) => { + validateInteger: (name: string, n: unknown, defaultVal?: number): number => { if (!Number.isInteger(n)) { if (defaultVal !== undefined) { return defaultVal; @@ -450,10 +460,10 @@ const utils = { } } - return n; + return n as number; }, - determineProcessUserAgentProperties: () => { + determineProcessUserAgentProperties: (): Record => { return typeof process === 'undefined' ? {} : { @@ -463,7 +473,7 @@ const utils = { }, }; -function emitWarning(warning) { +function emitWarning(warning: string): void { if (typeof process.emitWarning !== 'function') { return console.warn( `Stripe: ${warning}` diff --git a/tsconfig.json b/tsconfig.json index 199458365d..b0e175f4d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,9 @@ "module": "commonjs", "checkJs": false, "alwaysStrict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strict": true, }, "include": ["./src/**/*"] }