From a21545a64cd61a7241ef1ffaa429750a8c6c2743 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 8 Nov 2022 09:40:49 -0800 Subject: [PATCH 01/14] Add type information here and there --- src/StripeResource.ts | 81 +++++++++++++++++++++++++++---------------- src/makeRequest.ts | 42 +++++++++++++++------- src/multipart.ts | 13 +++++-- src/stripe.ts | 18 +++++----- src/utils.ts | 58 +++++++++++++++++-------------- 5 files changed, 132 insertions(+), 80 deletions(-) diff --git a/src/StripeResource.ts b/src/StripeResource.ts index e8f85599fe..551da2b02d 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -14,6 +14,7 @@ const {HttpClient} = require('./net/HttpClient'); type Settings = { timeout?: number; + maxNetworkRetries?: number; }; type Options = { @@ -35,7 +36,7 @@ const MAX_RETRY_AFTER_WAIT = 60; /** * Encapsulates request logic for a Stripe Resource */ -function StripeResource(stripe, deprecatedUrlData) { +function StripeResource(stripe, deprecatedUrlData?: never) { this._stripe = stripe; if (deprecatedUrlData) { throw new Error( @@ -78,7 +79,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 + ) { const urlParts = [this.basePath(urlData), this.path(urlData)]; if (typeof commandPath === 'function') { @@ -99,7 +103,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) { // If there is no path beyond the resource path, we want to produce just // / rather than //. if (pathWithSymbols) { @@ -109,7 +113,7 @@ StripeResource.prototype = { } }, - _joinUrlParts(parts) { + _joinUrlParts(parts: Array) { // 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 @@ -129,7 +133,10 @@ StripeResource.prototype = { }; }, - _addHeadersDirectlyToObject(obj, headers) { + _addHeadersDirectlyToObject( + obj: Record, + headers: Record + ) { // For convenience, make some headers easily accessible on // lastResponse. @@ -140,7 +147,11 @@ StripeResource.prototype = { obj.idempotencyKey = obj.idempotencyKey || headers['idempotency-key']; }, - _makeResponseEvent(requestEvent, statusCode, headers) { + _makeResponseEvent( + requestEvent, + statusCode: number, + headers: Record + ) { const requestEndTime = Date.now(); const requestDurationMs = requestEndTime - requestEvent.request_start_time; @@ -158,7 +169,7 @@ StripeResource.prototype = { }); }, - _getRequestId(headers) { + _getRequestId(headers: Record) { return headers['request-id']; }, @@ -301,7 +312,7 @@ 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, numRetries: number, maxRetries: number, error?) { if ( error && numRetries === 0 && @@ -346,7 +357,7 @@ StripeResource.prototype = { return false; }, - _getSleepTimeInMS(numRetries, retryAfter = null) { + _getSleepTimeInMS(numRetries: number, retryAfter: number | null = null) { const initialNetworkRetryDelay = this._stripe.getInitialNetworkRetryDelay(); const maxNetworkRetryDelay = this._stripe.getMaxNetworkRetryDelay(); @@ -374,14 +385,14 @@ StripeResource.prototype = { }, // Max retries can be set on a per request basis. Favor those over the global setting - _getMaxNetworkRetries(settings: {maxNetworkRetries?: number} = {}) { + _getMaxNetworkRetries(settings: Settings = {}) { return settings.maxNetworkRetries && Number.isInteger(settings.maxNetworkRetries) ? settings.maxNetworkRetries : this._stripe.getMaxNetworkRetries(); }, - _defaultIdempotencyKey(method, settings) { + _defaultIdempotencyKey(method: string, settings: Settings) { // If this is a POST and we allow multiple retries, ensure an idempotency key. const maxRetries = this._getMaxNetworkRetries(settings); @@ -392,14 +403,14 @@ StripeResource.prototype = { }, _makeHeaders( - auth, - contentLength, - apiVersion, - clientUserAgent, - method, - userSuppliedHeaders, - userSuppliedSettings - ) { + auth: string, + contentLength: number, + apiVersion: string, + clientUserAgent: string, + method: string, + userSuppliedHeaders: Record, + userSuppliedSettings: Settings + ): Record { const defaultHeaders = { // Use specified auth token or use default from this stripe instance: Authorization: auth ? `Bearer ${auth}` : this._stripe.getApiField('auth'), @@ -451,7 +462,7 @@ StripeResource.prototype = { ); }, - _getUserAgentString() { + _getUserAgentString(): string { const packageVersion = this._stripe.getConstant('PACKAGE_VERSION'); const appInfo = this._stripe._appInfo ? this._stripe.getAppInfoAsString() @@ -460,7 +471,7 @@ StripeResource.prototype = { return `Stripe/v1 NodeBindings/${packageVersion} ${appInfo}`.trim(); }, - _getTelemetryHeader() { + _getTelemetryHeader(): string { if ( this._stripe.getTelemetryEnabled() && this._stripe._prevRequestMetrics.length > 0 @@ -472,7 +483,7 @@ StripeResource.prototype = { } }, - _recordRequestMetrics(requestId, requestDurationMs) { + _recordRequestMetrics(requestId, requestDurationMs): void { if (this._stripe.getTelemetryEnabled() && requestId) { if ( this._stripe._prevRequestMetrics.length > @@ -490,15 +501,23 @@ StripeResource.prototype = { } }, - _request(method, host, path, data, auth, options: Options = {}, callback) { + _request( + method: string, + host: string, + path: string, + data: unknown, + auth: string, + options: Options = {}, + callback + ) { let requestData; const retryRequest = ( - requestFn, - apiVersion, - headers, - requestRetries, - retryAfter + requestFn: typeof makeRequest, + apiVersion: string, + headers: Record, + requestRetries: number, + retryAfter: number | null ) => { return setTimeout( requestFn, @@ -509,7 +528,11 @@ StripeResource.prototype = { ); }; - const makeRequest = (apiVersion, headers, numRetries) => { + const makeRequest = ( + apiVersion: string, + headers: Record, + numRetries: number + ) => { // timeout can be set on a per-request basis. Favor that over the global setting const timeout = options.settings && diff --git a/src/makeRequest.ts b/src/makeRequest.ts index 57737cfd25..515f253e77 100644 --- a/src/makeRequest.ts +++ b/src/makeRequest.ts @@ -1,6 +1,21 @@ const utils = require('./utils'); -function getRequestOpts(self, requestArgs, spec, overrideData) { +type MethodSpec = { + method: string; + urlParams: Array; + path?: string; + fullPath?: string; + encode: (data: Record) => Record; + validator: ( + data: Record, + headers: Record + ) => void; + headers: Record; + streaming?: boolean; + host?: string; +}; + +function getRequestOpts(self, requestArgs, spec: MethodSpec, overrideData) { // Extract spec values with defaults. const requestMethod = (spec.method || 'GET').toUpperCase(); const urlParams = spec.urlParams || []; @@ -20,17 +35,20 @@ function getRequestOpts(self, requestArgs, spec, overrideData) { const args = [].slice.call(requestArgs); // Generate and validate url params. - const urlData = urlParams.reduce((urlData, param) => { - const arg = args.shift(); - if (typeof arg !== 'string') { - throw new Error( - `Stripe: Argument "${param}" must be a string, but got: ${arg} (on API request to \`${requestMethod} ${path}\`)` - ); - } + const urlData = urlParams.reduce>( + (urlData, param) => { + const arg = args.shift(); + if (typeof arg !== 'string') { + throw new Error( + `Stripe: Argument "${param}" must be a string, but got: ${arg} (on API request to \`${requestMethod} ${path}\`)` + ); + } - urlData[param] = arg; - return urlData; - }, {}); + urlData[param] = arg; + return urlData; + }, + {} + ); // Pull request data and options (headers, auth) from args. const dataFromArgs = utils.getDataFromArgs(args); @@ -84,7 +102,7 @@ function makeRequest(self, requestArgs, spec, overrideData) { return; } - function requestCallback(err, response) { + function requestCallback(err: any, response) { if (err) { reject(err); } else { diff --git a/src/multipart.ts b/src/multipart.ts index dbb59a8bcf..96b57868f2 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -33,14 +33,21 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { + // eslint-disable-next-line no-warning-comments + // TODO: I don't think we ever hit this branch + const typedEntry: { + name: string; + data: string; + 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(''); diff --git a/src/stripe.ts b/src/stripe.ts index 5d6cb7e377..25ff85625a 100644 --- a/src/stripe.ts +++ b/src/stripe.ts @@ -1,4 +1,5 @@ import _Error = require('./Error'); +import StripeResource = require('./StripeResource'); const resources = require('./resources'); @@ -47,7 +48,6 @@ const ALLOWED_CONFIG_PROPERTIES = [ const EventEmitter = require('events').EventEmitter; -import StripeResource = require('./StripeResource'); Stripe.StripeResource = StripeResource; Stripe.resources = resources; @@ -327,7 +327,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 || {}; @@ -339,8 +339,6 @@ Stripe.prototype = { }, undefined ); - - this._appInfo = appInfo; }, /** @@ -374,15 +372,15 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getApiField(key) { + getApiField(key: string): unknown { return this._api[key]; }, - setClientId(clientId) { + setClientId(clientId: string): void { this._clientId = clientId; }, - getClientId() { + getClientId(): string { return this._clientId; }, @@ -413,7 +411,7 @@ Stripe.prototype = { return Stripe[c]; }, - getMaxNetworkRetries() { + getMaxNetworkRetries(): number { return this.getApiField('maxNetworkRetries'); }, @@ -425,7 +423,7 @@ Stripe.prototype = { * }); * */ - setMaxNetworkRetries(maxNetworkRetries) { + setMaxNetworkRetries(maxNetworkRetries: number) { this._setApiNumberField('maxNetworkRetries', maxNetworkRetries); }, @@ -433,7 +431,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setApiNumberField(prop, n, defaultVal) { + _setApiNumberField(prop: string, n: number, defaultVal?: number) { const val = utils.validateInteger(prop, n, defaultVal); this._setApiField(prop, val); diff --git a/src/utils.ts b/src/utils.ts index b3ac274cbc..d3bcc8f997 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -43,11 +43,11 @@ type Options = { host?: string; settings?: Settings; streaming?: boolean; - headers?: Record; + headers?: Record; }; const utils = { - isOptionsHash(o) { + isOptionsHash(o: unknown): boolean { return ( o && typeof o === 'object' && @@ -64,7 +64,7 @@ const utils = { * Stringifies an Object, accommodating nested objects * (forming the conventional key 'parent[child]=value') */ - stringifyRequestData: (data) => { + stringifyRequestData: (data: unknown): string => { return ( qs .stringify(data, { @@ -101,7 +101,7 @@ const utils = { }; })(), - extractUrlParams: (path) => { + extractUrlParams: (path: string): Array => { const params = path.match(/\{\w+\}/g); if (!params) { return []; @@ -116,7 +116,7 @@ const utils = { * @param {object[]} args * @returns {object} */ - getDataFromArgs(args) { + getDataFromArgs(args: Array): Record { if (!Array.isArray(args) || !args[0] || typeof args[0] !== 'object') { return {}; } @@ -152,7 +152,7 @@ const utils = { /** * Return the options hash from a list of arguments */ - getOptionsFromArgs: (args) => { + getOptionsFromArgs: (args: Array): Options => { const opts: Options = { auth: null, headers: {}, @@ -161,9 +161,10 @@ const utils = { if (args.length > 0) { const arg = args[args.length - 1]; if (typeof arg === 'string') { - opts.auth = args.pop(); + args.pop(); + opts.auth = arg; } else if (utils.isOptionsHash(arg)) { - const params = {...args.pop()}; + const params = {...(arg as Record)}; const extraKeys = Object.keys(params).filter( (key) => !OPTIONS_KEYS.includes(key) @@ -194,7 +195,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 +207,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; } } } @@ -246,7 +247,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,7 +275,7 @@ 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'); } @@ -293,7 +294,7 @@ const utils = { * becomes * {'Foo-Bar': 'hi'} */ - normalizeHeaders: (obj) => { + normalizeHeaders: (obj: Record): Record => { if (!(obj && typeof obj === 'object')) { return obj; } @@ -308,7 +309,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( @@ -328,7 +329,10 @@ const utils = { return false; }, - callbackifyPromiseWithTimeout: (promise, callback) => { + callbackifyPromiseWithTimeout: ( + promise: Promise, + callback: (error: unknown, result: T) => void + ): Promise => { if (callback) { // Ensure callback is called outside of promise stack. return promise.then( @@ -351,7 +355,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 +372,7 @@ const utils = { * * This unifies that interface. */ - safeExec: (cmd, cb) => { + safeExec: (cmd: string, cb: (error: Error, stdout: string) => 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,13 +390,15 @@ 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) => { + flattenAndStringify: ( + data: Record + ): Record => { const result = {}; const step = (obj, prevKey) => { @@ -427,7 +433,7 @@ const utils = { /** * https://stackoverflow.com/a/2117523 */ - uuid4: () => { + uuid4: (): string => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; @@ -435,7 +441,7 @@ const utils = { }); }, - validateInteger: (name, n, defaultVal) => { + validateInteger: (name: string, n: unknown, defaultVal?: number): number => { if (!Number.isInteger(n)) { if (defaultVal !== undefined) { return defaultVal; @@ -444,10 +450,10 @@ const utils = { } } - return n; + return n as number; }, - determineProcessUserAgentProperties: () => { + determineProcessUserAgentProperties: (): Record => { return typeof process === 'undefined' ? {} : { @@ -457,7 +463,7 @@ const utils = { }, }; -function emitWarning(warning) { +function emitWarning(warning: string): void { if (typeof process.emitWarning !== 'function') { return console.warn( `Stripe: ${warning}` From e9ce7f2779c4bf3cfbbd6901990ae7aeba63e42f Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 8 Nov 2022 09:50:40 -0800 Subject: [PATCH 02/14] reduce diff --- src/utils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 0648f3a7aa..82071907e8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -161,10 +161,9 @@ const utils = { if (args.length > 0) { const arg = args[args.length - 1]; if (typeof arg === 'string') { - args.pop(); - opts.auth = arg; + opts.auth = args.pop() as string; } else if (utils.isOptionsHash(arg)) { - const params = {...(arg as Record)}; + const params = {...(args.pop() as Record)}; const extraKeys = Object.keys(params).filter( (key) => !OPTIONS_KEYS.includes(key) From 7933b16dd4436471c1dc22ad047a043338332525 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 8 Nov 2022 10:10:15 -0800 Subject: [PATCH 03/14] more --- lib/multipart.js | 9 ++++++--- lib/stripe.js | 5 ++--- src/utils.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/multipart.js b/lib/multipart.js index d88d468b1f..cf7f6d9e53 100644 --- a/lib/multipart.js +++ b/lib/multipart.js @@ -28,14 +28,17 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { + // eslint-disable-next-line no-warning-comments + // TODO: I don't think we ever hit this branch + 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(''); diff --git a/lib/stripe.js b/lib/stripe.js index 60b82e7312..b0b3f748e8 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -1,5 +1,6 @@ 'use strict'; const _Error = require('./Error'); +const StripeResource = require('./StripeResource'); const resources = require('./resources'); const DEFAULT_HOST = 'api.stripe.com'; const DEFAULT_PORT = '443'; @@ -39,7 +40,6 @@ const ALLOWED_CONFIG_PROPERTIES = [ 'stripeAccount', ]; const EventEmitter = require('events').EventEmitter; -const StripeResource = require('./StripeResource'); Stripe.StripeResource = StripeResource; Stripe.resources = resources; const {HttpClient, HttpClientResponse} = require('./net/HttpClient'); @@ -286,14 +286,13 @@ Stripe.prototype = { throw new Error('AppInfo.name is required'); } info = info || {}; - const appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => { + this._appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => { if (typeof info[prop] == 'string') { accum = accum || {}; accum[prop] = info[prop]; } return accum; }, undefined); - this._appInfo = appInfo; }, /** * @deprecated will be removed in a future major version. Use the config object instead: diff --git a/src/utils.ts b/src/utils.ts index 82071907e8..7832722810 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -321,7 +321,7 @@ const utils = { * Determine if file data is a derivative of EventEmitter class. * https://nodejs.org/api/events.html#events_events */ - checkForStream: (obj) => { + checkForStream: (obj): boolean => { if (obj.file && obj.file.data) { return obj.file.data instanceof EventEmitter; } From 4d30ef1ab246c26ef9c4e958d3f2de833102d370 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 8 Nov 2022 10:11:30 -0800 Subject: [PATCH 04/14] more --- lib/stripe.js | 2 +- src/stripe.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/stripe.js b/lib/stripe.js index b0b3f748e8..fc1e5bc95e 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -1,6 +1,5 @@ 'use strict'; const _Error = require('./Error'); -const StripeResource = require('./StripeResource'); const resources = require('./resources'); const DEFAULT_HOST = 'api.stripe.com'; const DEFAULT_PORT = '443'; @@ -40,6 +39,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ 'stripeAccount', ]; const EventEmitter = require('events').EventEmitter; +const StripeResource = require('./StripeResource'); Stripe.StripeResource = StripeResource; Stripe.resources = resources; const {HttpClient, HttpClientResponse} = require('./net/HttpClient'); diff --git a/src/stripe.ts b/src/stripe.ts index 25ff85625a..c9f8784f25 100644 --- a/src/stripe.ts +++ b/src/stripe.ts @@ -1,5 +1,4 @@ import _Error = require('./Error'); -import StripeResource = require('./StripeResource'); const resources = require('./resources'); @@ -47,7 +46,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ ]; const EventEmitter = require('events').EventEmitter; - +import StripeResource = require('./StripeResource'); Stripe.StripeResource = StripeResource; Stripe.resources = resources; From 6fb008c1dfd465f805b3958bbfcd2c3b2089e539 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 08:29:02 -0800 Subject: [PATCH 05/14] type webhooks --- src/Error.ts | 2 +- src/Types.d.ts | 4 ++ src/Webhooks.ts | 97 +++++++++++++++++++++++++++------------ src/multipart.ts | 16 +++++-- src/net/NodeHttpClient.ts | 16 +++---- src/utils.ts | 14 +++--- tsconfig.json | 1 + 7 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 src/Types.d.ts diff --git a/src/Error.ts b/src/Error.ts index fbf88cc5d6..3e5078236c 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -83,7 +83,7 @@ class StripeError extends Error { /** * Helper factory which takes raw stripe errors and outputs wrapping instances */ - static generate(rawStripeError) { + static generate(rawStripeError: StripeRawError) { switch (rawStripeError.type) { case 'card_error': return new StripeCardError(rawStripeError); diff --git a/src/Types.d.ts b/src/Types.d.ts new file mode 100644 index 0000000000..beac628435 --- /dev/null +++ b/src/Types.d.ts @@ -0,0 +1,4 @@ +type StripeResourceObject = {}; + +type HttpHeaders = Record; +type RequestData = Record; diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 14d5a4ed8f..1381f03c23 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -2,11 +2,41 @@ import utils = require('./utils'); import _Error = require('./Error'); const {StripeError, StripeSignatureVerificationError} = _Error; +type StripeCryptoProvider = { + computeHMACSignature: (data: string, secret: string) => string; + computeHMACSignatureAsync: (data: string, secret: string) => Promise; +}; + +type WebhookPayload = string | Buffer; +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; +}; const Webhook = { DEFAULT_TOLERANCE: 300, // 5 minutes - signature: null, - - constructEvent(payload, header, secret, tolerance, cryptoProvider) { + signature: null as typeof signature, + + constructEvent( + payload: WebhookPayload, + header: WebhookHeader, + secret: string, + tolerance: null, + cryptoProvider: StripeCryptoProvider + ) { this.signature.verifyHeader( payload, header, @@ -15,16 +45,17 @@ 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 ) { await this.signature.verifyHeaderAsync( payload, @@ -34,6 +65,7 @@ const Webhook = { cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, @@ -49,7 +81,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) { if (!opts) { throw new StripeError({ message: 'Options are required', @@ -82,11 +114,11 @@ const signature = { EXPECTED_SCHEME: 'v1', verifyHeader( - encodedPayload, - encodedHeader, - secret, - tolerance, - cryptoProvider + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider ) { const { decodedHeader: header, @@ -112,11 +144,11 @@ const signature = { }, async verifyHeaderAsync( - encodedPayload, - encodedHeader, - secret, - tolerance, - cryptoProvider + encodedPayload: WebhookPayload, + encodedHeader: WebhookHeader, + secret: string, + tolerance: number, + cryptoProvider: StripeCryptoProvider ) { const { decodedHeader: header, @@ -141,11 +173,18 @@ const signature = { }, }; -function makeHMACContent(payload, details) { +function makeHMACContent( + payload: WebhookPayload, + details: WebhookParsedHeader +) { 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,11 +235,11 @@ function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) { } function validateComputedSignature( - payload, - header, - details, - expectedSignature, - tolerance + payload: WebhookPayload, + header: string, + details: WebhookParsedHeader, + expectedSignature: string, + tolerance: number ) { const signatureFound = !!details.signatures.filter( utils.secureCompare.bind(utils, expectedSignature) @@ -236,7 +275,7 @@ function validateComputedSignature( return true; } -function parseHeader(header, scheme) { +function parseHeader(header: string, scheme: string): WebhookParsedHeader { if (typeof header !== 'string') { return null; } @@ -262,13 +301,13 @@ function parseHeader(header, scheme) { ); } -let webhooksNodeCryptoProviderInstance = null; +let webhooksNodeCryptoProviderInstance = null as StripeCryptoProvider; /** * 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(); diff --git a/src/multipart.ts b/src/multipart.ts index 96b57868f2..cff39bbd99 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -14,7 +14,7 @@ const multipartDataGenerator = (method, data, headers) => { headers['Content-Type'] = `multipart/form-data; boundary=${segno}`; let buffer = Buffer.alloc(0); - function push(l) { + function push(l: Buffer | string) { const prevBuffer = buffer; const newBuffer = l instanceof Buffer ? l : Buffer.from(l); buffer = Buffer.alloc(prevBuffer.length + newBuffer.length + 2); @@ -59,7 +59,12 @@ const multipartDataGenerator = (method, data, headers) => { return buffer; }; -const streamProcessor = (method, data, headers, callback) => { +const streamProcessor = ( + method: string, + data: RequestData, + headers: HttpHeaders, + callback +) => { const bufferArray = []; data.file.data .on('data', (line) => { @@ -83,7 +88,12 @@ const streamProcessor = (method, data, headers, callback) => { }); }; -const multipartRequestDataProcessor = (method, data, headers, callback) => { +const multipartRequestDataProcessor = ( + method: string, + data: RequestData, + headers: HttpHeaders, + callback +) => { data = data || {}; if (method !== 'POST') { diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index 1d556a0906..eff3e7fb9d 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -25,14 +25,14 @@ class NodeHttpClient extends HttpClient { } makeRequest( - host, - port, - path, - method, - headers, - requestData, - protocol, - timeout + host: string, + port: string, + path: string, + method: string, + headers: HttpHeaders, + requestData: Record, + protocol: string, + timeout: number ) { const isInsecureConnection = protocol === 'http'; diff --git a/src/utils.ts b/src/utils.ts index 7832722810..da5d652115 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,7 +30,7 @@ const DEPRECATED_OPTIONS = { stripe_account: 'stripeAccount', stripe_version: 'apiVersion', stripeVersion: 'apiVersion', -}; +} as Record; const DEPRECATED_OPTIONS_KEYS = Object.keys(DEPRECATED_OPTIONS); type Settings = { @@ -68,7 +68,7 @@ const utils = { 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 @@ -90,10 +90,10 @@ const utils = { '"': '\\"', '\u2028': '\\u2028', '\u2029': '\\u2029', - }; - return (str) => { + } as Record; + return (str: string) => { const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); - return (outputs) => { + return (outputs: Record) => { return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => encodeURIComponent(outputs[$1] || '') ); @@ -222,12 +222,12 @@ const utils = { /** * Provide simple "Class" extension mechanism */ - protoExtend(sub) { + protoExtend(sub: any) { // 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(...args: any[]) { Super.apply(this, args); }; diff --git a/tsconfig.json b/tsconfig.json index 199458365d..2f0736a2a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "module": "commonjs", "checkJs": false, "alwaysStrict": true, + "noImplicitAny": true, }, "include": ["./src/**/*"] } From 769c521fca38bcd5d253b95cb3bdbb782e1723a7 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 11:48:39 -0800 Subject: [PATCH 06/14] a lot more types --- .eslintrc.js | 4 +- src/Error.ts | 33 +------ src/StripeMethod.ts | 4 +- src/StripeResource.ts | 111 +++++++++++----------- src/Types.d.ts | 185 ++++++++++++++++++++++++++++++++++++- src/Webhooks.ts | 21 ++--- src/autoPagination.ts | 7 +- src/makeRequest.ts | 67 +++++++------- src/multipart.ts | 4 +- src/net/FetchHttpClient.ts | 49 +++++----- src/net/HttpClient.ts | 36 ++++---- src/net/NodeHttpClient.ts | 99 ++++++++++---------- src/stripe.ts | 80 ++++++++-------- src/utils.ts | 32 +++---- 14 files changed, 445 insertions(+), 287 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4eb5fcc6be..d10ac49cb3 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-ignore': '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/src/Error.ts b/src/Error.ts index 3e5078236c..d4fef7bb93 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. @@ -83,7 +52,7 @@ class StripeError extends Error { /** * Helper factory which takes raw stripe errors and outputs wrapping instances */ - static generate(rawStripeError: StripeRawError) { + static generate(rawStripeError: StripeRawError): StripeError { switch (rawStripeError.type) { case 'card_error': return new StripeCardError(rawStripeError); diff --git a/src/StripeMethod.ts b/src/StripeMethod.ts index f2c0595988..51c6a716ea 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): (...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(...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 551da2b02d..4f8a68a996 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -12,17 +12,6 @@ const { const {HttpClient} = require('./net/HttpClient'); -type Settings = { - timeout?: number; - maxNetworkRetries?: number; -}; - -type Options = { - settings?: Settings; - streaming?: boolean; - headers?: Record; -}; - // Provide extension mechanism for Stripe Resource Sub-Classes StripeResource.extend = utils.protoExtend; @@ -36,7 +25,7 @@ const MAX_RETRY_AFTER_WAIT = 60; /** * Encapsulates request logic for a Stripe Resource */ -function StripeResource(stripe, deprecatedUrlData?: never) { +function StripeResource(stripe: StripeObject, deprecatedUrlData?: never): void { this._stripe = stripe; if (deprecatedUrlData) { throw new Error( @@ -53,7 +42,7 @@ function StripeResource(stripe, deprecatedUrlData?: never) { // 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) { + this.includeBasic.forEach(function(methodName: string) { this[methodName] = StripeResource.BASIC_METHODS[methodName]; }, this); } @@ -62,12 +51,13 @@ function StripeResource(stripe, deprecatedUrlData?: never) { } StripeResource.prototype = { + _stripe: null as StripeObject, 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 @@ -82,7 +72,7 @@ StripeResource.prototype = { createFullPath( commandPath: string | ((urlData: Record) => string), urlData: Record - ) { + ): string { const urlParts = [this.basePath(urlData), this.path(urlData)]; if (typeof commandPath === 'function') { @@ -103,7 +93,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: string) { + createResourcePathWithSymbols(pathWithSymbols: string): string { // If there is no path beyond the resource path, we want to produce just // / rather than //. if (pathWithSymbols) { @@ -113,7 +103,7 @@ StripeResource.prototype = { } }, - _joinUrlParts(parts: Array) { + _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 @@ -124,8 +114,8 @@ StripeResource.prototype = { // DEPRECATED: Here for backcompat in case users relied on this. wrapTimeout: utils.callbackifyPromiseWithTimeout, - _timeoutHandler(timeout, req, callback) { - return () => { + _timeoutHandler(timeout: number, req, callback): () => void { + return (): void => { const timeoutErr = new TypeError('ETIMEDOUT'); (timeoutErr as any).code = 'ETIMEDOUT'; @@ -135,8 +125,8 @@ StripeResource.prototype = { _addHeadersDirectlyToObject( obj: Record, - headers: Record - ) { + headers: RequestHeaders + ): void { // For convenience, make some headers easily accessible on // lastResponse. @@ -148,10 +138,10 @@ StripeResource.prototype = { }, _makeResponseEvent( - requestEvent, + requestEvent: RequestEvent, statusCode: number, - headers: Record - ) { + headers: RequestHeaders + ): ResponseEvent { const requestEndTime = Date.now(); const requestDurationMs = requestEndTime - requestEvent.request_start_time; @@ -169,7 +159,7 @@ StripeResource.prototype = { }); }, - _getRequestId(headers: Record) { + _getRequestId(headers: RequestHeaders): HttpHeaderValue { return headers['request-id']; }, @@ -183,8 +173,11 @@ StripeResource.prototype = { * still be buffered/parsed and handled by _jsonResponseHandler -- see * makeRequest) */ - _streamingResponseHandler(requestEvent, callback) { - return (res) => { + _streamingResponseHandler( + requestEvent: RequestEvent, + callback: RequestCallback + ) { + return (res: HttpClientResponseInterface) => { const headers = res.getHeaders(); const streamCompleteCallback = () => { @@ -216,8 +209,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(); @@ -264,7 +257,7 @@ StripeResource.prototype = { return jsonResponse; }, - (e) => { + (e: Error) => { throw new StripeAPIError({ message: 'Invalid JSON received from the Stripe API', exception: e, @@ -292,14 +285,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({ @@ -312,7 +309,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: number, maxRetries: number, error?) { + _shouldRetry( + res: HttpClientResponseInterface, + numRetries: number, + maxRetries: number, + error? + ): boolean { if ( error && numRetries === 0 && @@ -357,7 +359,10 @@ StripeResource.prototype = { return false; }, - _getSleepTimeInMS(numRetries: number, retryAfter: number | null = null) { + _getSleepTimeInMS( + numRetries: number, + retryAfter: number | null = null + ): number { const initialNetworkRetryDelay = this._stripe.getInitialNetworkRetryDelay(); const maxNetworkRetryDelay = this._stripe.getMaxNetworkRetryDelay(); @@ -385,14 +390,14 @@ StripeResource.prototype = { }, // Max retries can be set on a per request basis. Favor those over the global setting - _getMaxNetworkRetries(settings: Settings = {}) { + _getMaxNetworkRetries(settings: RequestSettings = {}): number { return settings.maxNetworkRetries && Number.isInteger(settings.maxNetworkRetries) ? settings.maxNetworkRetries : this._stripe.getMaxNetworkRetries(); }, - _defaultIdempotencyKey(method: string, settings: Settings) { + _defaultIdempotencyKey(method: string, settings: RequestSettings): string { // If this is a POST and we allow multiple retries, ensure an idempotency key. const maxRetries = this._getMaxNetworkRetries(settings); @@ -408,8 +413,8 @@ StripeResource.prototype = { apiVersion: string, clientUserAgent: string, method: string, - userSuppliedHeaders: Record, - userSuppliedSettings: Settings + userSuppliedHeaders: RequestHeaders, + userSuppliedSettings: RequestSettings ): Record { const defaultHeaders = { // Use specified auth token or use default from this stripe instance: @@ -425,7 +430,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 @@ -483,7 +488,7 @@ StripeResource.prototype = { } }, - _recordRequestMetrics(requestId, requestDurationMs): void { + _recordRequestMetrics(requestId: string, requestDurationMs: number): void { if (this._stripe.getTelemetryEnabled() && requestId) { if ( this._stripe._prevRequestMetrics.length > @@ -505,20 +510,20 @@ StripeResource.prototype = { method: string, host: string, path: string, - data: unknown, + data: string, auth: string, - options: Options = {}, - callback + options: RequestOptions = {}, + callback: RequestCallback ) { - let requestData; + let requestData: string; const retryRequest = ( requestFn: typeof makeRequest, apiVersion: string, - headers: Record, + headers: RequestHeaders, requestRetries: number, retryAfter: number | null - ) => { + ): NodeJS.Timeout => { return setTimeout( requestFn, this._getSleepTimeInMS(requestRetries, retryAfter), @@ -530,9 +535,9 @@ StripeResource.prototype = { const makeRequest = ( apiVersion: string, - headers: Record, + headers: RequestHeaders, numRetries: number - ) => { + ): void => { // timeout can be set on a per-request basis. Favor that over the global setting const timeout = options.settings && @@ -557,7 +562,7 @@ StripeResource.prototype = { const requestStartTime = Date.now(); - const requestEvent = utils.removeNullish({ + const requestEvent: RequestEvent = utils.removeNullish({ api_version: apiVersion, account: headers['Stripe-Account'], idempotency_key: headers['Idempotency-Key'], @@ -573,7 +578,7 @@ StripeResource.prototype = { this._stripe._emitter.emit('request', requestEvent); req - .then((res) => { + .then((res: HttpClientResponseInterface) => { if (this._shouldRetry(res, requestRetries, maxRetries)) { return retryRequest( makeRequest, @@ -614,14 +619,14 @@ StripeResource.prototype = { }); }; - const prepareAndMakeRequest = (error, data) => { + const prepareAndMakeRequest = (error: Error, 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 index beac628435..5556ac59de 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -1,4 +1,185 @@ -type StripeResourceObject = {}; +/* eslint-disable camelcase */ +type StripeObject = { + on: any; + off: any; + once: any; + VERSION: string; + StripeResource: StripeResourceObject; + errors: any; + webhooks: any; + getApiField: (name: string) => T; + _prepResources: () => void; + _setAppInfo: (appInfo: AppInfo) => void; + _setApiKey: (apiKey: string) => void; + _prevRequestMetrics: number[]; + _api: { + auth: string; + host: string; + port: string | number; + protocol: string; + basePath: string; + version: string; + timeout: string; + maxNetworkRetries: number; + agent: string; + httpClient: any; + dev: boolean; + stripeAccount: string; + }; + _emitter: import('events').EventEmitter; + _enableTelemetry: boolean; + _getPropsFromConfig: (config: Record) => UserProvidedConfig; +}; +type StripeResourceObject = { + createResourcePathWithSymbols: (path: string) => string; + createFullPath: ( + interpolator: UrlInterpolator, + urlData: RequestData + ) => string; + _request: ( + method: string, + host: string, + path: string, + data: RequestData, + auth: string, + options: RequestOptions, + callback: RequestCallback + ) => Promise; +}; +type RequestCallback = ( + this: StripeResourceObject | void, + error: Error, + response?: any +) => void; -type HttpHeaders = Record; +type RequestEvent = { + api_version?: string; + account?: string; + idempotency_key?: string; + method?: string; + path?: string; + request_start_time?: 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 RequestOptions = { + settings?: RequestSettings; + streaming?: boolean; + headers?: RequestHeaders; +}; +type RequestSettings = { + timeout?: number; + maxNetworkRetries?: number; +}; +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; +}; +type HttpHeaderValue = string | number | string[]; +type RequestHeaders = Record; type RequestData = Record; +type RequestArgs = Array; +type UrlInterpolator = (params: Record) => string; +type AppInfo = { + name?: string; +} & Record; + +type MethodSpec = { + method: 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 RequestOpts = { + requestMethod: string; + requestPath: string; + bodyData: RequestData; + queryData: RequestData; + auth: string; + headers: RequestHeaders; + host: string; + streaming: boolean; + settings: RequestSettings; +}; +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 ResponseHeaders = Record; +interface HttpClientResponseInterface { + getStatusCode: () => number; + getHeaders: () => ResponseHeaders; + getRawResponse: () => unknown; + toStream: (streamCompleteCallback: () => void) => unknown; + toJSON: () => Promise; +} +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; +}; diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 1381f03c23..68c698d747 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -2,11 +2,6 @@ import utils = require('./utils'); import _Error = require('./Error'); const {StripeError, StripeSignatureVerificationError} = _Error; -type StripeCryptoProvider = { - computeHMACSignature: (data: string, secret: string) => string; - computeHMACSignatureAsync: (data: string, secret: string) => Promise; -}; - type WebhookPayload = string | Buffer; type WebhookHeader = string | Buffer; type WebhookParsedHeader = { @@ -26,6 +21,8 @@ type WebhookTestHeaderOptions = { signature: string; cryptoProvider: StripeCryptoProvider; }; +type WebhookEvent = Record; + const Webhook = { DEFAULT_TOLERANCE: 300, // 5 minutes signature: null as typeof signature, @@ -36,7 +33,7 @@ const Webhook = { secret: string, tolerance: null, cryptoProvider: StripeCryptoProvider - ) { + ): WebhookEvent { this.signature.verifyHeader( payload, header, @@ -56,7 +53,7 @@ const Webhook = { secret: string, tolerance: number, cryptoProvider: StripeCryptoProvider - ) { + ): Promise { await this.signature.verifyHeaderAsync( payload, header, @@ -81,7 +78,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: WebhookTestHeaderOptions) { + generateTestHeaderString: function(opts: WebhookTestHeaderOptions): string { if (!opts) { throw new StripeError({ message: 'Options are required', @@ -119,7 +116,7 @@ const signature = { secret: string, tolerance: number, cryptoProvider: StripeCryptoProvider - ) { + ): boolean { const { decodedHeader: header, decodedPayload: payload, @@ -149,7 +146,7 @@ const signature = { secret: string, tolerance: number, cryptoProvider: StripeCryptoProvider - ) { + ): Promise { const { decodedHeader: header, decodedPayload: payload, @@ -176,7 +173,7 @@ const signature = { function makeHMACContent( payload: WebhookPayload, details: WebhookParsedHeader -) { +): string { return `${details.timestamp}.${payload}`; } @@ -240,7 +237,7 @@ function validateComputedSignature( details: WebhookParsedHeader, expectedSignature: string, tolerance: number -) { +): boolean { const signatureFound = !!details.signatures.filter( utils.secureCompare.bind(utils, expectedSignature) ).length; diff --git a/src/autoPagination.ts b/src/autoPagination.ts index b98f44d122..0032ad77b9 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -1,7 +1,12 @@ import makeRequest = require('./makeRequest'); const utils = require('./utils'); -function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { +function makeAutoPaginationMethods( + self: StripeResourceObject, + requestArgs, + spec, + firstPagePromise +) { const promiseCache = {currentPromise: null}; const reverseIteration = isReverseIteration(requestArgs); let pagePromise = firstPagePromise; diff --git a/src/makeRequest.ts b/src/makeRequest.ts index 515f253e77..42dc109bfb 100644 --- a/src/makeRequest.ts +++ b/src/makeRequest.ts @@ -1,28 +1,18 @@ const utils = require('./utils'); -type MethodSpec = { - method: string; - urlParams: Array; - path?: string; - fullPath?: string; - encode: (data: Record) => Record; - validator: ( - data: Record, - headers: Record - ) => void; - headers: Record; - streaming?: boolean; - host?: string; -}; - -function getRequestOpts(self, requestArgs, spec: MethodSpec, 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 @@ -32,23 +22,20 @@ function getRequestOpts(self, requestArgs, spec: MethodSpec, 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 arg = args.shift(); - if (typeof arg !== 'string') { - throw new Error( - `Stripe: Argument "${param}" must be a string, but got: ${arg} (on API request to \`${requestMethod} ${path}\`)` - ); - } + const urlData = urlParams.reduce((urlData, param) => { + const arg = args.shift(); + if (typeof arg !== 'string') { + throw new Error( + `Stripe: Argument "${param}" must be a string, but got: ${arg} (on API request to \`${requestMethod} ${path}\`)` + ); + } - urlData[param] = arg; - return urlData; - }, - {} - ); + urlData[param] = arg; + return urlData; + }, {}); // Pull request data and options (headers, auth) from args. const dataFromArgs = utils.getDataFromArgs(args); @@ -92,9 +79,14 @@ function getRequestOpts(self, requestArgs, spec: MethodSpec, 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) { @@ -102,7 +94,10 @@ function makeRequest(self, requestArgs, spec, overrideData) { return; } - function requestCallback(err: any, response) { + function requestCallback( + err: any, + response: HttpClientResponseInterface + ): void { if (err) { reject(err); } else { diff --git a/src/multipart.ts b/src/multipart.ts index cff39bbd99..3aa6a7f91b 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -62,7 +62,7 @@ const multipartDataGenerator = (method, data, headers) => { const streamProcessor = ( method: string, data: RequestData, - headers: HttpHeaders, + headers: RequestHeaders, callback ) => { const bufferArray = []; @@ -91,7 +91,7 @@ const streamProcessor = ( const multipartRequestDataProcessor = ( method: string, data: RequestData, - headers: HttpHeaders, + headers: RequestHeaders, callback ) => { data = data || {}; diff --git a/src/net/FetchHttpClient.ts b/src/net/FetchHttpClient.ts index 630bddc42d..6a3f845986 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -9,33 +9,30 @@ 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; +class FetchHttpClient extends HttpClient implements HttpClientInterface { + _fetchFn: typeof fetch; _res: Response; - 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( @@ -72,7 +69,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; const timeoutPromise = new Promise((_, reject) => { pendingTimeoutId = setTimeout(() => { pendingTimeoutId = null; @@ -81,7 +78,7 @@ class FetchHttpClient extends HttpClient { }); return Promise.race([fetchPromise, timeoutPromise]) - .then((res) => { + .then((res: Response) => { return new FetchHttpClientResponse(res); }) .finally(() => { @@ -92,10 +89,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 +101,11 @@ class FetchHttpClientResponse extends HttpClientResponse { this._res = res; } - getRawResponse() { + getRawResponse(): Response { return this._res; } - toStream(streamCompleteCallback) { + toStream(streamCompleteCallback: () => void): ReadableStream { // 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 +117,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 eff3e7fb9d..d337b677d1 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -14,13 +14,13 @@ 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'; } @@ -29,11 +29,11 @@ class NodeHttpClient extends HttpClient { port: string, path: string, method: string, - headers: HttpHeaders, - requestData: Record, + headers: RequestHeaders, + requestData: RequestData, protocol: string, timeout: number - ) { + ): Promise { const isInsecureConnection = protocol === 'http'; let agent = this._agent; @@ -41,52 +41,55 @@ 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) { @@ -94,11 +97,11 @@ class NodeHttpClientResponse extends HttpClientResponse { 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 c9f8784f25..f40acf52df 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 string; const DEFAULT_TIMEOUT = 80000; @@ -24,7 +24,7 @@ Stripe.USER_AGENT = { }; /** @private */ -Stripe._UNAME_CACHE = null; +Stripe._UNAME_CACHE = null as Promise; const MAX_NETWORK_RETRY_DELAY_SEC = 2; const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; @@ -47,6 +47,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ const EventEmitter = require('events').EventEmitter; import StripeResource = require('./StripeResource'); +import * as http from 'http'; Stripe.StripeResource = StripeResource; Stripe.resources = resources; @@ -57,7 +58,7 @@ Stripe.HttpClientResponse = HttpClientResponse; const CryptoProvider = require('./crypto/CryptoProvider'); Stripe.CryptoProvider = CryptoProvider; -function Stripe(key, config = {}) { +function Stripe(this: StripeObject, key: string, config = {}): void { if (!(this instanceof Stripe)) { return new (Stripe as any)(key, config); } @@ -137,7 +138,7 @@ function Stripe(key, config = {}) { 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); }; @@ -149,7 +150,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); }; @@ -158,7 +159,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(); }; @@ -171,7 +172,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); }; @@ -187,7 +190,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.' ); @@ -208,7 +211,7 @@ Stripe.prototype = { * }); * */ - setProtocol(protocol) { + setProtocol(protocol: string): void { emitWarning( '`setProtocol` is deprecated. Use the `protocol` config option instead.' ); @@ -223,7 +226,7 @@ Stripe.prototype = { * }); * */ - setPort(port) { + setPort(port: number): void { emitWarning( '`setPort` is deprecated. Use the `port` config option instead.' ); @@ -238,7 +241,7 @@ Stripe.prototype = { * }); * */ - setApiVersion(version) { + setApiVersion(version: string): void { emitWarning( '`setApiVersion` is deprecated. Use the `apiVersion` config or request option instead.' ); @@ -262,7 +265,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.' ); @@ -272,7 +275,7 @@ Stripe.prototype = { /** * @private */ - _setApiKey(key) { + _setApiKey(key: string): void { if (key) { this._setApiField('auth', `Bearer ${key}`); } @@ -285,7 +288,7 @@ Stripe.prototype = { * timeout: TIMEOUT_MS, * }); */ - setTimeout(timeout) { + setTimeout(timeout: number): void { emitWarning( '`setTimeout` is deprecated. Use the `timeout` config or request option instead.' ); @@ -304,7 +307,7 @@ Stripe.prototype = { * }, * }); */ - setAppInfo(info) { + setAppInfo(info: AppInfo): void { emitWarning( '`setAppInfo` is deprecated. Use the `appInfo` config option instead.' ); @@ -315,7 +318,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.'); } @@ -349,7 +352,7 @@ Stripe.prototype = { * }); * */ - setHttpAgent(agent) { + setHttpAgent(agent: string): void { emitWarning( '`setHttpAgent` is deprecated. Use the `httpAgent` config option instead.' ); @@ -360,7 +363,7 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setApiField(key, value) { + _setApiField(key: string, value: unknown): void { this._api[key] = value; }, @@ -371,7 +374,7 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getApiField(key: string): unknown { + getApiField(key: string): T { return this._api[key]; }, @@ -390,7 +393,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; @@ -407,7 +410,7 @@ 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(): number { @@ -422,7 +425,7 @@ Stripe.prototype = { * }); * */ - setMaxNetworkRetries(maxNetworkRetries: number) { + setMaxNetworkRetries(maxNetworkRetries: number): void { this._setApiNumberField('maxNetworkRetries', maxNetworkRetries); }, @@ -430,32 +433,32 @@ Stripe.prototype = { * @private * This may be removed in the future. */ - _setApiNumberField(prop: string, n: number, defaultVal?: number) { + _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)); }, /** @@ -468,7 +471,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); }, @@ -482,8 +485,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]); @@ -512,7 +518,7 @@ Stripe.prototype = { * * It may be deprecated and removed in the future. */ - getAppInfoAsString() { + getAppInfoAsString(): string { if (!this._appInfo) { return ''; } @@ -538,14 +544,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; }, @@ -553,7 +559,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); } @@ -563,7 +569,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 da5d652115..75dc3017c8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -64,7 +64,7 @@ const utils = { * Stringifies an Object, accommodating nested objects * (forming the conventional key 'parent[child]=value') */ - stringifyRequestData: (data: unknown): string => { + stringifyRequestData: (data: RequestData | string): string => { return ( qs .stringify(data, { @@ -84,16 +84,16 @@ 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', } as Record; - return (str: string) => { + return (str: string): UrlInterpolator => { const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); - return (outputs: Record) => { + return (outputs: Record): string => { return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => encodeURIComponent(outputs[$1] || '') ); @@ -116,7 +116,7 @@ const utils = { * @param {object[]} args * @returns {object} */ - getDataFromArgs(args: Array): Record { + getDataFromArgs(args: RequestArgs): RequestData { if (!Array.isArray(args) || !args[0] || typeof args[0] !== 'object') { return {}; } @@ -152,7 +152,7 @@ const utils = { /** * Return the options hash from a list of arguments */ - getOptionsFromArgs: (args: Array): Options => { + getOptionsFromArgs: (args: RequestArgs): Options => { const opts: Options = { auth: null, headers: {}, @@ -222,12 +222,12 @@ const utils = { /** * Provide simple "Class" extension mechanism */ - protoExtend(sub: any) { + protoExtend(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: any[]) { + : function(...args: any[]): void { Super.apply(this, args); }; @@ -274,12 +274,12 @@ const utils = { /** * Remove empty values from an object */ - removeNullish: (obj: Record): Record => { + 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 +293,12 @@ const utils = { * becomes * {'Foo-Bar': 'hi'} */ - normalizeHeaders: (obj: Record): Record => { + 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; }, {}); @@ -321,7 +321,7 @@ const utils = { * Determine if file data is a derivative of EventEmitter class. * https://nodejs.org/api/events.html#events_events */ - checkForStream: (obj): boolean => { + checkForStream: (obj: {file?: {data: unknown}}): boolean => { if (obj.file && obj.file.data) { return obj.file.data instanceof EventEmitter; } @@ -398,9 +398,9 @@ const utils = { flattenAndStringify: ( data: Record ): Record => { - const result = {}; + const result: RequestData = {}; - const step = (obj, prevKey) => { + const step = (obj: RequestData, prevKey: string): void => { Object.keys(obj).forEach((key) => { const value = obj[key]; @@ -412,7 +412,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; From 263f65c808443466183d753d4c314280bf678918 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 12:01:57 -0800 Subject: [PATCH 07/14] few more types --- src/ResourceNamespace.ts | 17 ++++++++++++----- src/StripeMethod.ts | 2 +- src/Types.d.ts | 9 +++++++-- 3 files changed, 20 insertions(+), 8 deletions(-) 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 51c6a716ea..cfafee0a5a 100644 --- a/src/StripeMethod.ts +++ b/src/StripeMethod.ts @@ -19,7 +19,7 @@ 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): (...args: any[]) => Promise { +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}).` diff --git a/src/Types.d.ts b/src/Types.d.ts index 5556ac59de..13b8546a7c 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -104,9 +104,13 @@ type UrlInterpolator = (params: Record) => string; type AppInfo = { name?: string; } & Record; - +type StripeResourceNamespaceObject = Record< + string, + StripeResourceObject | unknown +>; type MethodSpec = { method: string; + methodType: string; urlParams: Array; path?: string; fullPath?: string; @@ -145,7 +149,8 @@ interface HttpClientInterface { timeout: number ) => Promise; } -type ResponseHeaders = Record; +type ResponseHeaderValue = string | string[]; +type ResponseHeaders = Record; interface HttpClientResponseInterface { getStatusCode: () => number; getHeaders: () => ResponseHeaders; From 023ff6ae6019e4ddc3ca03616dd685bb732e720f Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 15:25:47 -0800 Subject: [PATCH 08/14] autopagination --- src/Types.d.ts | 12 ++++++ src/autoPagination.ts | 93 ++++++++++++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/Types.d.ts b/src/Types.d.ts index 13b8546a7c..9df09cf523 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -188,3 +188,15 @@ type StripeRawError = { source?: any; exception?: any; }; +type IterationDoneCallback = () => void; +type IterationItemCallback = ( + item: any, + next: any +) => void | boolean | Promise; +type ListResult = { + data: Array; + has_more: boolean; +}; +type ListOptions = { + limit?: number; +}; diff --git a/src/autoPagination.ts b/src/autoPagination.ts index 0032ad77b9..43ed92f1e3 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -1,13 +1,37 @@ import makeRequest = require('./makeRequest'); const utils = require('./utils'); +type PromiseCache = { + currentPromise: Promise; +}; +type IterationResult = { + done: boolean; + value?: any; +}; +type AutoPagingEach = ( + onItem: IterationItemCallback, + onDone?: IterationDoneCallback +) => Promise; + +type AutoPagingToArray = ( + opts: ListOptions, + onDone: IterationDoneCallback +) => Promise>; + +type AutoPaginationMethods = { + autoPagingEach: AutoPagingEach; + autoPagingToArray: AutoPagingToArray; + next: () => Promise; + return: () => void; +}; + function makeAutoPaginationMethods( self: StripeResourceObject, - requestArgs, - spec, - firstPagePromise -) { - const promiseCache = {currentPromise: null}; + requestArgs: RequestArgs, + spec: MethodSpec, + firstPagePromise: Promise +): AutoPaginationMethods { + const promiseCache: PromiseCache = {currentPromise: null}; const reverseIteration = isReverseIteration(requestArgs); let pagePromise = firstPagePromise; let i = 0; @@ -19,9 +43,9 @@ function makeAutoPaginationMethods( // // 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.' @@ -32,7 +56,7 @@ function makeAutoPaginationMethods( }); }; } else { - getNextPagePromise = (pageResult) => { + getNextPagePromise = (pageResult): Promise => { const lastId = getLastId(pageResult, reverseIteration); return makeRequest(self, requestArgs, spec, { [reverseIteration ? 'ending_before' : 'starting_after']: lastId, @@ -40,7 +64,9 @@ function makeAutoPaginationMethods( }; } - function iterate(pageResult) { + function iterate( + pageResult: ListResult + ): IterationResult | Promise { if ( !( pageResult && @@ -68,7 +94,7 @@ function makeAutoPaginationMethods( return {value: undefined, done: true}; } - function asyncIteratorNext() { + function asyncIteratorNext(): Promise { return memoizedPromise(promiseCache, (resolve, reject) => { return pagePromise .then(iterate) @@ -80,13 +106,13 @@ function makeAutoPaginationMethods( 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 {}; }, @@ -107,7 +133,7 @@ export = { * ---------------- */ -function getAsyncIteratorSymbol() { +function getAsyncIteratorSymbol(): symbol | string { if (typeof Symbol !== 'undefined' && Symbol.asyncIterator) { return Symbol.asyncIterator; } @@ -115,7 +141,7 @@ function getAsyncIteratorSymbol() { return '@@asyncIterator'; } -function getDoneCallback(args) { +function getDoneCallback(args: Array): IterationDoneCallback { if (args.length < 2) { return undefined; } @@ -139,7 +165,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 { if (args.length === 0) { return undefined; } @@ -165,13 +191,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; @@ -188,7 +214,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; } @@ -199,8 +228,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); @@ -213,11 +244,16 @@ function makeAutoPagingEach(asyncIteratorNext) { 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( @@ -230,7 +266,7 @@ function makeAutoPagingToArray(autoPagingEach) { ); } const promise = new Promise((resolve, reject) => { - const items = []; + const items: Array = []; autoPagingEach((item) => { items.push(item); if (items.length >= limit) { @@ -246,9 +282,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 { if (iterResult.done) { resolve(); return; @@ -275,7 +314,7 @@ function wrapAsyncIteratorWithCallback(asyncIteratorNext, onItem) { }); } -function isReverseIteration(requestArgs) { +function isReverseIteration(requestArgs: RequestArgs): boolean { const args = [].slice.call(requestArgs); const dataFromArgs = utils.getDataFromArgs(args); From 3988b685758f55e5bebcec87fec7b0152df5a5eb Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 15:55:18 -0800 Subject: [PATCH 09/14] types everywhere --- .eslintrc.js | 2 +- src/StripeResource.ts | 24 ++++++++++++++-------- src/Types.d.ts | 26 ++++++++++++++++++++---- src/autoPagination.ts | 5 ++++- src/multipart.ts | 41 ++++++++++++++++++++++---------------- src/net/FetchHttpClient.ts | 2 ++ src/utils.ts | 2 +- 7 files changed, 70 insertions(+), 32 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d10ac49cb3..05679266e1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -265,7 +265,7 @@ 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': 'off', + '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-empty-function': 0, '@typescript-eslint/camelcase': 0, '@typescript-eslint/no-explicit-any': 0, diff --git a/src/StripeResource.ts b/src/StripeResource.ts index 4f8a68a996..2cccbd7af5 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -114,7 +114,13 @@ StripeResource.prototype = { // DEPRECATED: Here for backcompat in case users relied on this. wrapTimeout: utils.callbackifyPromiseWithTimeout, - _timeoutHandler(timeout: number, req, callback): () => void { + // 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'; @@ -176,11 +182,11 @@ StripeResource.prototype = { _streamingResponseHandler( requestEvent: RequestEvent, callback: RequestCallback - ) { - return (res: HttpClientResponseInterface) => { + ): (res: HttpClientResponseInterface) => RequestCallbackReturn { + return (res: HttpClientResponseInterface): RequestCallbackReturn => { const headers = res.getHeaders(); - const streamCompleteCallback = () => { + const streamCompleteCallback = (): void => { const responseEvent = this._makeResponseEvent( requestEvent, res.getStatusCode(), @@ -261,7 +267,7 @@ StripeResource.prototype = { throw new StripeAPIError({ message: 'Invalid JSON received from the Stripe API', exception: e, - requestId: headers['request-id'], + requestId: headers['request-id'] as string, }); } ) @@ -313,7 +319,7 @@ StripeResource.prototype = { res: HttpClientResponseInterface, numRetries: number, maxRetries: number, - error? + error?: {code: number} ): boolean { if ( error && @@ -514,7 +520,7 @@ StripeResource.prototype = { auth: string, options: RequestOptions = {}, callback: RequestCallback - ) { + ): void { let requestData: string; const retryRequest = ( @@ -585,6 +591,7 @@ StripeResource.prototype = { apiVersion, headers, requestRetries, + // @ts-ignore res.getHeaders()['retry-after'] ); } else if (options.streaming && res.getStatusCode() < 400) { @@ -593,7 +600,7 @@ StripeResource.prototype = { return this._jsonResponseHandler(requestEvent, callback)(res); } }) - .catch((error) => { + .catch((error: HttpClientResponseError) => { if (this._shouldRetry(null, requestRetries, maxRetries, error)) { return retryRequest( makeRequest, @@ -612,6 +619,7 @@ StripeResource.prototype = { message: isTimeoutError ? `Request aborted due to timeout being reached (${timeout}ms)` : this._generateConnectionErrorMessage(requestRetries), + // @ts-ignore detail: error, }) ); diff --git a/src/Types.d.ts b/src/Types.d.ts index 9df09cf523..95f2bea300 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -44,13 +44,14 @@ type StripeResourceObject = { auth: string, options: RequestOptions, callback: RequestCallback - ) => Promise; + ) => void; }; +type RequestCallbackReturn = any; type RequestCallback = ( this: StripeResourceObject | void, error: Error, response?: any -) => void; +) => RequestCallbackReturn; type RequestEvent = { api_version?: string; @@ -99,6 +100,23 @@ type UserProvidedConfig = { type HttpHeaderValue = string | number | string[]; type RequestHeaders = Record; type RequestData = Record; +type MultipartRequestData = RequestData | StreamingFile | BufferedFile; + +type BufferedFile = { + name: string; + type: string; + file: { + data: Buffer; + }; +}; + +type StreamingFile = { + name: string; + type: string; + file: { + data: import('events').EventEmitter; + }; +}; type RequestArgs = Array; type UrlInterpolator = (params: Record) => string; type AppInfo = { @@ -197,6 +215,6 @@ type ListResult = { data: Array; has_more: boolean; }; -type ListOptions = { - limit?: number; +type HttpClientResponseError = { + code: number; }; diff --git a/src/autoPagination.ts b/src/autoPagination.ts index 43ed92f1e3..083e1bedd0 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -13,8 +13,11 @@ type AutoPagingEach = ( onDone?: IterationDoneCallback ) => Promise; +type AutoPagingToArrayOptions = { + limit?: number; +}; type AutoPagingToArray = ( - opts: ListOptions, + opts: AutoPagingToArrayOptions, onDone: IterationDoneCallback ) => Promise>; diff --git a/src/multipart.ts b/src/multipart.ts index 3aa6a7f91b..e7b4af248f 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, + data: Buffer | string +) => 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: Buffer | string) { + 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,11 +41,9 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { - // eslint-disable-next-line no-warning-comments - // TODO: I don't think we ever hit this branch const typedEntry: { name: string; - data: string; + data: BufferedFile; type: string; } = v as any; push( @@ -61,17 +67,18 @@ const multipartDataGenerator = (method, data, headers) => { const streamProcessor = ( method: string, - data: RequestData, + data: StreamingFile, headers: RequestHeaders, - callback -) => { - const bufferArray = []; + 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); @@ -90,10 +97,10 @@ const streamProcessor = ( const multipartRequestDataProcessor = ( method: string, - data: RequestData, + data: MultipartRequestData, headers: RequestHeaders, - callback -) => { + callback: MultipartCallback +): MultipartCallbackReturn => { data = data || {}; if (method !== 'POST') { @@ -102,7 +109,7 @@ const multipartRequestDataProcessor = ( 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 6a3f845986..1e3d7ca275 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -52,7 +52,9 @@ class FetchHttpClient extends HttpClient implements HttpClientInterface { const fetchFn = this._fetchFn || fetch; const fetchPromise = fetchFn(url.toString(), { method, + // @ts-ignore headers, + // @ts-ignore body, }); diff --git a/src/utils.ts b/src/utils.ts index 75dc3017c8..325849d2c5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -396,7 +396,7 @@ const utils = { // For use in multipart requests flattenAndStringify: ( - data: Record + data: MultipartRequestData ): Record => { const result: RequestData = {}; From 827b3dba948ef8fceaff79abcefd2b20976368cb Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 15:56:14 -0800 Subject: [PATCH 10/14] more --- lib/Error.js | 1 + lib/StripeResource.js | 7 ++++++- lib/Webhooks.js | 2 ++ lib/multipart.js | 3 +-- lib/net/FetchHttpClient.js | 2 ++ lib/stripe.js | 1 + src/stripe.ts | 1 + 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/Error.js b/lib/Error.js index a50ccab948..27a73f84ca 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. diff --git a/lib/StripeResource.js b/lib/StripeResource.js index bfced38996..322c1389e2 100644 --- a/lib/StripeResource.js +++ b/lib/StripeResource.js @@ -42,6 +42,7 @@ function StripeResource(stripe, deprecatedUrlData) { 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 +91,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 +237,7 @@ StripeResource.prototype = { requestRetries > 0 ? ` Request was retried ${requestRetries} times.` : '' }`; }, - _errorHandler(req, requestRetries, callback) { + _errorHandler(res, requestRetries, callback) { return (message, detail) => { callback.call( this, @@ -470,6 +473,7 @@ StripeResource.prototype = { apiVersion, headers, requestRetries, + // @ts-ignore res.getHeaders()['retry-after'] ); } else if (options.streaming && res.getStatusCode() < 400) { @@ -496,6 +500,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..36acd5bbe2 100644 --- a/lib/Webhooks.js +++ b/lib/Webhooks.js @@ -13,6 +13,7 @@ const Webhook = { tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, @@ -30,6 +31,7 @@ const Webhook = { tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider ); + // @ts-ignore const jsonPayload = JSON.parse(payload); return jsonPayload; }, diff --git a/lib/multipart.js b/lib/multipart.js index cf7f6d9e53..29e53c8363 100644 --- a/lib/multipart.js +++ b/lib/multipart.js @@ -28,8 +28,6 @@ const multipartDataGenerator = (method, data, headers) => { const v = flattenedData[k]; push(`--${segno}`); if (Object.prototype.hasOwnProperty.call(v, 'data')) { - // eslint-disable-next-line no-warning-comments - // TODO: I don't think we ever hit this branch const typedEntry = v; push( `Content-Disposition: form-data; name=${q(k)}; filename=${q( @@ -55,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/stripe.js b/lib/stripe.js index fc1e5bc95e..4de52b6ad1 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; diff --git a/src/stripe.ts b/src/stripe.ts index f40acf52df..721d6d451e 100644 --- a/src/stripe.ts +++ b/src/stripe.ts @@ -132,6 +132,7 @@ function Stripe(this: StripeObject, key: string, config = {}): void { this._enableTelemetry = props.telemetry !== false; // Expose StripeResource on the instance too + // @ts-ignore this.StripeResource = Stripe.StripeResource; } From 5ecdff2217b30afaed12e5ef3b24a770dca94610 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 9 Nov 2022 16:01:54 -0800 Subject: [PATCH 11/14] even more types --- src/StripeMethod.ts | 2 +- src/StripeResource.ts | 17 ++++++++--------- src/Types.d.ts | 6 ++++++ src/utils.ts | 5 +++-- tsconfig.json | 1 + 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/StripeMethod.ts b/src/StripeMethod.ts index cfafee0a5a..3a99651f4e 100644 --- a/src/StripeMethod.ts +++ b/src/StripeMethod.ts @@ -25,7 +25,7 @@ function stripeMethod(spec: MethodSpec): (...args: any[]) => Promise { `Method spec specified both a 'path' (${spec.path}) and a 'fullPath' (${spec.fullPath}).` ); } - return function(...args: any[]): Promise { + 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 2cccbd7af5..221abe0a02 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -25,7 +25,11 @@ const MAX_RETRY_AFTER_WAIT = 60; /** * Encapsulates request logic for a Stripe Resource */ -function StripeResource(stripe: StripeObject, deprecatedUrlData?: never): void { +function StripeResource( + this: StripeResourceObject, + stripe: StripeObject, + deprecatedUrlData?: never +): void { this._stripe = stripe; if (deprecatedUrlData) { throw new Error( @@ -34,19 +38,14 @@ function StripeResource(stripe: StripeObject, deprecatedUrlData?: never): void { } 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: string) { - this[methodName] = StripeResource.BASIC_METHODS[methodName]; - }, this); - } - this.initialize(...arguments); } diff --git a/src/Types.d.ts b/src/Types.d.ts index 95f2bea300..e9033e9ac3 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -31,6 +31,10 @@ type StripeObject = { _getPropsFromConfig: (config: Record) => UserProvidedConfig; }; type StripeResourceObject = { + _stripe: StripeObject; + basePath: UrlInterpolator; + path: UrlInterpolator; + resourcePath: string; createResourcePathWithSymbols: (path: string) => string; createFullPath: ( interpolator: UrlInterpolator, @@ -45,6 +49,8 @@ type StripeResourceObject = { options: RequestOptions, callback: RequestCallback ) => void; + initialize: (...args: Array) => void; + prototype: StripeResourceObject; }; type RequestCallbackReturn = any; type RequestCallback = ( diff --git a/src/utils.ts b/src/utils.ts index 325849d2c5..ad933b3646 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -222,12 +222,13 @@ const utils = { /** * Provide simple "Class" extension mechanism */ - protoExtend(sub: any): (...args: any[]) => void { + protoExtend(this: StripeResourceObject, 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: any[]): void { + : function(this: StripeResourceObject, ...args: any[]): void { + // @ts-ignore Super.apply(this, args); }; diff --git a/tsconfig.json b/tsconfig.json index 2f0736a2a1..8c17887f26 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "checkJs": false, "alwaysStrict": true, "noImplicitAny": true, + "noImplicitThis": true }, "include": ["./src/**/*"] } From bf9b8561769c7fb689e9f92ef2c97d9371a55343 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 10 Nov 2022 07:34:23 -0800 Subject: [PATCH 12/14] strict --- src/Error.ts | 7 +++-- src/StripeResource.ts | 37 +++++++++++++------------ src/Types.d.ts | 34 +++++++++++------------ src/Webhooks.ts | 56 ++++++++++++++++++++++++++++++++------ src/autoPagination.ts | 19 ++++++++++--- src/multipart.ts | 4 +-- src/net/FetchHttpClient.ts | 11 ++++---- src/net/NodeHttpClient.ts | 1 + src/stripe.ts | 13 ++++++--- src/utils.ts | 24 +++++++++------- tsconfig.json | 3 +- 11 files changed, 138 insertions(+), 71 deletions(-) diff --git a/src/Error.ts b/src/Error.ts index d4fef7bb93..b5489fe001 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -7,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; @@ -38,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; diff --git a/src/StripeResource.ts b/src/StripeResource.ts index 221abe0a02..9f89af8fab 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -12,16 +12,6 @@ const { const {HttpClient} = require('./net/HttpClient'); -// Provide extension mechanism for Stripe Resource Sub-Classes -StripeResource.extend = utils.protoExtend; - -// Expose method-creator & prepared (basic) methods -StripeResource.method = require('./StripeMethod'); -StripeResource.BASIC_METHODS = require('./StripeMethod.basic'); - -StripeResource.MAX_BUFFERED_REQUEST_METRICS = 100; -const MAX_RETRY_AFTER_WAIT = 60; - /** * Encapsulates request logic for a Stripe Resource */ @@ -48,9 +38,18 @@ function StripeResource( this.initialize(...arguments); } +// Provide extension mechanism for Stripe Resource Sub-Classes +StripeResource.extend = utils.protoExtend; + +// Expose method-creator & prepared (basic) methods +StripeResource.method = require('./StripeMethod'); +StripeResource.BASIC_METHODS = require('./StripeMethod.basic'); + +StripeResource.MAX_BUFFERED_REQUEST_METRICS = 100; +const MAX_RETRY_AFTER_WAIT = 60; StripeResource.prototype = { - _stripe: null as StripeObject, + _stripe: null as StripeObject | null, path: '', // Methods that don't use the API's default '/v1' path can override it with this setting. @@ -92,7 +91,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: string): string { + createResourcePathWithSymbols(pathWithSymbols: string | null): string { // If there is no path beyond the resource path, we want to produce just // / rather than //. if (pathWithSymbols) { @@ -387,8 +386,8 @@ 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; @@ -402,7 +401,10 @@ StripeResource.prototype = { : this._stripe.getMaxNetworkRetries(); }, - _defaultIdempotencyKey(method: string, settings: RequestSettings): string { + _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); @@ -481,7 +483,7 @@ StripeResource.prototype = { return `Stripe/v1 NodeBindings/${packageVersion} ${appInfo}`.trim(); }, - _getTelemetryHeader(): string { + _getTelemetryHeader(): string | undefined { if ( this._stripe.getTelemetryEnabled() && this._stripe._prevRequestMetrics.length > 0 @@ -567,6 +569,7 @@ StripeResource.prototype = { const requestStartTime = Date.now(); + // @ts-ignore const requestEvent: RequestEvent = utils.removeNullish({ api_version: apiVersion, account: headers['Stripe-Account'], @@ -626,7 +629,7 @@ StripeResource.prototype = { }); }; - const prepareAndMakeRequest = (error: Error, data: string): void => { + const prepareAndMakeRequest = (error: Error | null, data: string): void => { if (error) { return callback(error); } diff --git a/src/Types.d.ts b/src/Types.d.ts index e9033e9ac3..b7de25d967 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -4,7 +4,7 @@ type StripeObject = { off: any; once: any; VERSION: string; - StripeResource: StripeResourceObject; + StripeResource: typeof StripeResource; errors: any; webhooks: any; getApiField: (name: string) => T; @@ -13,7 +13,7 @@ type StripeObject = { _setApiKey: (apiKey: string) => void; _prevRequestMetrics: number[]; _api: { - auth: string; + auth: string | null; host: string; port: string | number; protocol: string; @@ -24,7 +24,7 @@ type StripeObject = { agent: string; httpClient: any; dev: boolean; - stripeAccount: string; + stripeAccount: string | null; }; _emitter: import('events').EventEmitter; _enableTelemetry: boolean; @@ -35,7 +35,7 @@ type StripeResourceObject = { basePath: UrlInterpolator; path: UrlInterpolator; resourcePath: string; - createResourcePathWithSymbols: (path: string) => string; + createResourcePathWithSymbols: (path: string | null | undefined) => string; createFullPath: ( interpolator: UrlInterpolator, urlData: RequestData @@ -50,12 +50,11 @@ type StripeResourceObject = { callback: RequestCallback ) => void; initialize: (...args: Array) => void; - prototype: StripeResourceObject; }; type RequestCallbackReturn = any; type RequestCallback = ( this: StripeResourceObject | void, - error: Error, + error: Error | null, response?: any ) => RequestCallbackReturn; @@ -65,7 +64,7 @@ type RequestEvent = { idempotency_key?: string; method?: string; path?: string; - request_start_time?: number; + request_start_time: number; }; type ResponseEvent = { @@ -173,6 +172,9 @@ interface HttpClientInterface { timeout: number ) => Promise; } +type HttpClientResponseError = { + code: number; +}; type ResponseHeaderValue = string | string[]; type ResponseHeaders = Record; interface HttpClientResponseInterface { @@ -212,15 +214,13 @@ type StripeRawError = { source?: any; exception?: any; }; -type IterationDoneCallback = () => void; -type IterationItemCallback = ( - item: any, - next: any -) => void | boolean | Promise; -type ListResult = { - data: Array; - has_more: boolean; + +type StripeResourceConstructor = { + new (stripe: StripeObject, deprecatedUrlData?: never): StripeResourceObject; }; -type HttpClientResponseError = { - code: number; +declare const StripeResource: StripeResourceConstructor; + +type StripeConstructor = { + new (key: string, config: Record): StripeObject; }; +declare const Stripe: StripeConstructor; diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 68c698d747..955da33bb6 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -2,7 +2,6 @@ import utils = require('./utils'); import _Error = require('./Error'); const {StripeError, StripeSignatureVerificationError} = _Error; -type WebhookPayload = string | Buffer; type WebhookHeader = string | Buffer; type WebhookParsedHeader = { signatures: Array; @@ -21,12 +20,49 @@ type WebhookTestHeaderOptions = { 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 = { +const Webhook: WebhookObject = { DEFAULT_TOLERANCE: 300, // 5 minutes - signature: null as typeof signature, - + // @ts-ignore + signature: null, constructEvent( payload: WebhookPayload, header: WebhookHeader, @@ -239,6 +275,7 @@ function validateComputedSignature( tolerance: number ): boolean { const signatureFound = !!details.signatures.filter( + // @ts-ignore utils.secureCompare.bind(utils, expectedSignature) ).length; @@ -272,12 +309,15 @@ function validateComputedSignature( return true; } -function parseHeader(header: string, scheme: string): WebhookParsedHeader { +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('='); @@ -298,7 +338,7 @@ function parseHeader(header: string, scheme: string): WebhookParsedHeader { ); } -let webhooksNodeCryptoProviderInstance = null as StripeCryptoProvider; +let webhooksNodeCryptoProviderInstance: StripeCryptoProvider | null = null; /** * Lazily instantiate a NodeCryptoProvider instance. This is a stateless object @@ -309,7 +349,7 @@ function getNodeCryptoProvider(): StripeCryptoProvider { 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 083e1bedd0..81ca6369b1 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -2,12 +2,22 @@ import makeRequest = require('./makeRequest'); const utils = require('./utils'); type PromiseCache = { - currentPromise: Promise; + 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 @@ -144,7 +154,7 @@ function getAsyncIteratorSymbol(): symbol | string { return '@@asyncIterator'; } -function getDoneCallback(args: Array): IterationDoneCallback { +function getDoneCallback(args: Array): IterationDoneCallback | undefined { if (args.length < 2) { return undefined; } @@ -168,7 +178,7 @@ function getDoneCallback(args: Array): IterationDoneCallback { * In addition to standard validation, this helper * coalesces the former forms into the latter form. */ -function getItemCallback(args: Array): IterationItemCallback { +function getItemCallback(args: Array): IterationItemCallback | undefined { if (args.length === 0) { return undefined; } @@ -244,6 +254,7 @@ function makeAutoPagingEach( const autoPagePromise = wrapAsyncIteratorWithCallback( asyncIteratorNext, + // @ts-ignore we might need a null check onItem ); return utils.callbackifyPromiseWithTimeout(autoPagePromise, onDone); @@ -290,7 +301,7 @@ function wrapAsyncIteratorWithCallback( onItem: IterationItemCallback ): Promise { return new Promise((resolve, reject) => { - function handleIteration(iterResult: IterationResult): Promise { + function handleIteration(iterResult: IterationResult): Promise | void { if (iterResult.done) { resolve(); return; diff --git a/src/multipart.ts b/src/multipart.ts index e7b4af248f..01e91b1a37 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -5,8 +5,8 @@ const {StripeError} = _Error; class StreamProcessingError extends StripeError {} type MultipartCallbackReturn = any; type MultipartCallback = ( - error: Error, - data: Buffer | string + error: Error | null, + data: Buffer | string | null ) => MultipartCallbackReturn; // Method for formatting HTTP body for the multipart/form-data specification // Mostly taken from Fermata.js diff --git a/src/net/FetchHttpClient.ts b/src/net/FetchHttpClient.ts index 1e3d7ca275..6e4facecee 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -11,7 +11,6 @@ const {HttpClient, HttpClientResponse} = _HttpClient; */ class FetchHttpClient extends HttpClient implements HttpClientInterface { _fetchFn: typeof fetch; - _res: Response; constructor(fetchFn: typeof fetch) { super(); @@ -71,7 +70,7 @@ class FetchHttpClient extends HttpClient implements HttpClientInterface { // 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: NodeJS.Timeout; + let pendingTimeoutId: NodeJS.Timeout | null; const timeoutPromise = new Promise((_, reject) => { pendingTimeoutId = setTimeout(() => { pendingTimeoutId = null; @@ -80,8 +79,8 @@ class FetchHttpClient extends HttpClient implements HttpClientInterface { }); return Promise.race([fetchPromise, timeoutPromise]) - .then((res: Response) => { - return new FetchHttpClientResponse(res); + .then((res) => { + return new FetchHttpClientResponse(res as Response); }) .finally(() => { if (pendingTimeoutId) { @@ -107,7 +106,9 @@ class FetchHttpClientResponse extends HttpClientResponse return this._res; } - toStream(streamCompleteCallback: () => void): ReadableStream { + 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 diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index d337b677d1..93b37a26b5 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -93,6 +93,7 @@ class NodeHttpClientResponse extends HttpClientResponse _res: http.IncomingMessage; constructor(res: http.IncomingMessage) { + // @ts-ignore super(res.statusCode, res.headers || {}); this._res = res; } diff --git a/src/stripe.ts b/src/stripe.ts index 721d6d451e..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 as string; +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 as Promise; +Stripe._UNAME_CACHE = null as Promise | null; const MAX_NETWORK_RETRY_DELAY_SEC = 2; const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; @@ -58,7 +58,11 @@ Stripe.HttpClientResponse = HttpClientResponse; const CryptoProvider = require('./crypto/CryptoProvider'); Stripe.CryptoProvider = CryptoProvider; -function Stripe(this: StripeObject, key: string, config = {}): void { +function Stripe( + this: StripeObject, + key: string, + config: Record = {} +): void { if (!(this instanceof Stripe)) { return new (Stripe as any)(key, config); } @@ -330,7 +334,7 @@ Stripe.prototype = { info = info || {}; - this._appInfo = APP_INFO_PROPERTIES.reduce( + this._appInfo = APP_INFO_PROPERTIES.reduce>( (accum: Record, prop) => { if (typeof info[prop] == 'string') { accum = accum || {}; @@ -340,6 +344,7 @@ Stripe.prototype = { return accum; }, + // @ts-ignore undefined ); }, diff --git a/src/utils.ts b/src/utils.ts index ad933b3646..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; } @@ -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: unknown): boolean { + isOptionsHash(o: unknown): boolean | unknown { return ( o && typeof o === 'object' && @@ -93,8 +94,9 @@ const utils = { } as Record; return (str: string): UrlInterpolator => { const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); - return (outputs: Record): string => { + return (outputs: Record): string => { return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => + // @ts-ignore encodeURIComponent(outputs[$1] || '') ); }; @@ -222,13 +224,12 @@ const utils = { /** * Provide simple "Class" extension mechanism */ - protoExtend(this: StripeResourceObject, sub: any): (...args: any[]) => void { + 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(this: StripeResourceObject, ...args: any[]): void { - // @ts-ignore Super.apply(this, args); }; @@ -331,7 +332,7 @@ const utils = { callbackifyPromiseWithTimeout: ( promise: Promise, - callback: (error: unknown, result: T) => void + callback: (error: unknown, result: T | null) => void ): Promise => { if (callback) { // Ensure callback is called outside of promise stack. @@ -372,7 +373,10 @@ const utils = { * * This unifies that interface. */ - safeExec: (cmd: string, cb: (error: Error, stdout: string) => void): void => { + 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) { @@ -401,7 +405,7 @@ const utils = { ): Record => { const result: RequestData = {}; - const step = (obj: RequestData, prevKey: string): void => { + const step = (obj: RequestData, prevKey: string | null): void => { Object.keys(obj).forEach((key) => { const value = obj[key]; diff --git a/tsconfig.json b/tsconfig.json index 8c17887f26..1b672e1407 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "checkJs": false, "alwaysStrict": true, "noImplicitAny": true, - "noImplicitThis": true + "noImplicitThis": true, + "strict": true }, "include": ["./src/**/*"] } From 3c3b82144b200ea7bb84c2121a745434dfb360c7 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 10 Nov 2022 07:39:15 -0800 Subject: [PATCH 13/14] more --- lib/Error.js | 1 + lib/StripeResource.js | 5 +++++ lib/Webhooks.js | 2 ++ lib/autoPagination.js | 1 + lib/net/NodeHttpClient.js | 1 + lib/stripe.js | 18 +++++++++++------- lib/utils.js | 2 ++ src/StripeResource.ts | 28 ++++++++++++++++++---------- src/Types.d.ts | 1 + tsconfig.json | 2 +- 10 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/Error.js b/lib/Error.js index 27a73f84ca..0b5c8ad8db 100644 --- a/lib/Error.js +++ b/lib/Error.js @@ -17,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 322c1389e2..9a11b7d1c3 100644 --- a/lib/StripeResource.js +++ b/lib/StripeResource.js @@ -28,14 +28,18 @@ 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); } @@ -454,6 +458,7 @@ StripeResource.prototype = { timeout ); const requestStartTime = Date.now(); + // @ts-ignore const requestEvent = utils.removeNullish({ api_version: apiVersion, account: headers['Stripe-Account'], diff --git a/lib/Webhooks.js b/lib/Webhooks.js index 36acd5bbe2..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( @@ -177,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/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 4de52b6ad1..97a78644cb 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -287,13 +287,17 @@ Stripe.prototype = { throw new Error('AppInfo.name is required'); } info = info || {}; - this._appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => { - if (typeof info[prop] == 'string') { - accum = accum || {}; - accum[prop] = info[prop]; - } - return accum; - }, undefined); + 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/StripeResource.ts b/src/StripeResource.ts index 9f89af8fab..b4392c0d57 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -12,6 +12,16 @@ const { const {HttpClient} = require('./net/HttpClient'); +// Provide extension mechanism for Stripe Resource Sub-Classes +StripeResource.extend = utils.protoExtend; + +// Expose method-creator & prepared (basic) methods +StripeResource.method = require('./StripeMethod'); +StripeResource.BASIC_METHODS = require('./StripeMethod.basic'); + +StripeResource.MAX_BUFFERED_REQUEST_METRICS = 100; +const MAX_RETRY_AFTER_WAIT = 60; + /** * Encapsulates request logic for a Stripe Resource */ @@ -35,18 +45,16 @@ function StripeResource( 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); } -// Provide extension mechanism for Stripe Resource Sub-Classes -StripeResource.extend = utils.protoExtend; - -// Expose method-creator & prepared (basic) methods -StripeResource.method = require('./StripeMethod'); -StripeResource.BASIC_METHODS = require('./StripeMethod.basic'); - -StripeResource.MAX_BUFFERED_REQUEST_METRICS = 100; -const MAX_RETRY_AFTER_WAIT = 60; StripeResource.prototype = { _stripe: null as StripeObject | null, diff --git a/src/Types.d.ts b/src/Types.d.ts index b7de25d967..bb2bd48391 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -35,6 +35,7 @@ type StripeResourceObject = { basePath: UrlInterpolator; path: UrlInterpolator; resourcePath: string; + includeBasic: Array; createResourcePathWithSymbols: (path: string | null | undefined) => string; createFullPath: ( interpolator: UrlInterpolator, diff --git a/tsconfig.json b/tsconfig.json index 1b672e1407..b0e175f4d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, - "strict": true + "strict": true, }, "include": ["./src/**/*"] } From 3f0eed52c5f7b918afdb865bd635edd80a955596 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 10 Nov 2022 07:47:32 -0800 Subject: [PATCH 14/14] sort types --- src/Types.d.ts | 282 ++++++++++++++++++++++--------------------------- 1 file changed, 129 insertions(+), 153 deletions(-) diff --git a/src/Types.d.ts b/src/Types.d.ts index bb2bd48391..c785e6f3d0 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -1,64 +1,38 @@ /* eslint-disable camelcase */ -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 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 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 RequestCallbackReturn = 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; @@ -67,7 +41,24 @@ type RequestEvent = { 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; @@ -80,82 +71,24 @@ type ResponseEvent = { request_start_time?: number; request_end_time?: number; }; -type RequestOptions = { - settings?: RequestSettings; - streaming?: boolean; - headers?: RequestHeaders; -}; -type RequestSettings = { - timeout?: number; - maxNetworkRetries?: number; -}; -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; -}; -type HttpHeaderValue = string | number | string[]; -type RequestHeaders = Record; -type RequestData = Record; -type MultipartRequestData = RequestData | StreamingFile | BufferedFile; - -type BufferedFile = { - name: string; - type: string; - file: { - data: Buffer; - }; -}; - +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 RequestArgs = Array; -type UrlInterpolator = (params: Record) => string; -type AppInfo = { - name?: string; -} & Record; -type StripeResourceNamespaceObject = Record< - string, - StripeResourceObject | unknown ->; -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; + file: {data: import('events').EventEmitter}; }; -type RequestOpts = { - requestMethod: string; - requestPath: string; - bodyData: RequestData; - queryData: RequestData; - auth: string; - headers: RequestHeaders; - host: string; - streaming: boolean; - settings: RequestSettings; +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; @@ -173,31 +106,40 @@ interface HttpClientInterface { timeout: number ) => Promise; } -type HttpClientResponseError = { - code: number; +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 ResponseHeaderValue = string | string[]; -type ResponseHeaders = Record; -interface HttpClientResponseInterface { - getStatusCode: () => number; - getHeaders: () => ResponseHeaders; - getRawResponse: () => unknown; - toStream: (streamCompleteCallback: () => void) => unknown; - toJSON: () => Promise; -} -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; @@ -208,20 +150,54 @@ type StripeRawError = { 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 StripeConstructor = { - new (key: string, config: Record): StripeObject; +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; }; -declare const Stripe: StripeConstructor;