diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index 48b5642cb8..e6f94f3a4b 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -1,5 +1,5 @@ -import * as http from 'http'; -import * as https from 'https'; +import * as http_ from 'http'; +import * as https_ from 'https'; import {RequestHeaders, RequestData} from '../Types.js'; import { HttpClient, @@ -7,6 +7,16 @@ import { HttpClientResponseInterface, } from './HttpClient.js'; +// `import * as http_ from 'http'` creates a "Module Namespace Exotic Object" +// which is immune to monkey-patching, whereas http_.default (in an ES Module context) +// will resolve to the same thing as require('http'), which is +// monkey-patchable. We care about this because users in their test +// suites might be using a library like "nock" which relies on the ability +// to monkey-patch and intercept calls to http.request. +const http = ((http_ as unknown) as {default: typeof http_}).default || http_; +const https = + ((https_ as unknown) as {default: typeof https_}).default || https_; + const defaultHttpAgent = new http.Agent({keepAlive: true}); const defaultHttpsAgent = new https.Agent({keepAlive: true}); @@ -15,9 +25,9 @@ const defaultHttpsAgent = new https.Agent({keepAlive: true}); * requests.` */ export class NodeHttpClient extends HttpClient { - _agent?: http.Agent | https.Agent | undefined; + _agent?: http_.Agent | https_.Agent | undefined; - constructor(agent?: http.Agent | https.Agent) { + constructor(agent?: http_.Agent | https_.Agent) { super(); this._agent = agent; } @@ -93,19 +103,19 @@ export class NodeHttpClient extends HttpClient { export class NodeHttpClientResponse extends HttpClientResponse implements HttpClientResponseInterface { - _res: http.IncomingMessage; + _res: http_.IncomingMessage; - constructor(res: http.IncomingMessage) { + constructor(res: http_.IncomingMessage) { // @ts-ignore super(res.statusCode, res.headers || {}); this._res = res; } - getRawResponse(): http.IncomingMessage { + getRawResponse(): http_.IncomingMessage { return this._res; } - toStream(streamCompleteCallback: () => void): http.IncomingMessage { + 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/testProjects/mjs/index.js b/testProjects/mjs/index.js index a944ba7fd1..d5bc505f23 100644 --- a/testProjects/mjs/index.js +++ b/testProjects/mjs/index.js @@ -1,5 +1,6 @@ import DefaultStripe, {Stripe} from 'stripe'; import assert from 'assert'; +import * as http from 'http'; assert(Stripe.PACKAGE_VERSION); assert(Stripe.USER_AGENT); @@ -90,9 +91,43 @@ async function exampleFunction(args) { } } + +// Test that http is monkey-patchable (motivation: https://github.com/stripe/stripe-node/issues/1844) +async function exampleMonkeyPatchFunction() { + http.default.request = () => { + throw new Error('foo'); + }; + + try { + await stripe.paymentIntents.create({ + currency: 'usd', + amount: 2000, + confirm: true, + payment_method: 'pm_card_visa', + }); + } catch (e) { + assert (e instanceof stripe.errors.StripeConnectionError); + if (e.detail.message === 'foo') { + return; + } else { + throw e; + } + } + + throw new Error('Expected an error'); +} + exampleFunction({ // The required parameter currency is missing amount: 2000, confirm: true, payment_method: 'pm_card_visa', -}); +}).then(() => { + return exampleMonkeyPatchFunction() +}).then(() => { + process.exit(0); +}).catch((e) => { + console.error(e); + process.exit(1); +} +); diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 521c16b4d9..3347244c68 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -9,7 +9,8 @@ "noImplicitThis": true, "strict": true, "strictFunctionTypes": true, - "types": [ "node" ] + "types": [ "node" ], + "esModuleInterop": false // This is a viral option, do not enable https://www.semver-ts.org/#module-interop }, "include": ["./src/**/*"], "exclude": ["./src/stripe.esm.node.ts", "./src/stripe.esm.worker.ts"],