From 2fd714787b321aead9cc8934039914bfb542d45e Mon Sep 17 00:00:00 2001 From: David Brownman <1231935+xavdid@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:09:10 -0700 Subject: [PATCH 1/7] Add `stripe.major_api_version` constant (#2799) * add user-facing way to access API release * add docstring to new property * kick CI --- .claude/CLAUDE.md | 4 +++- src/stripe.core.ts | 7 ++++++- src/stripe.esm.node.ts | 7 ++++++- testProjects/types-cjs-node16/typescriptTest.ts | 3 +++ testProjects/types-cjs/typescriptTest.ts | 1 + testProjects/types/typescriptTest.ts | 9 ++++++--- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 00182919c0..7855403900 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -4,8 +4,10 @@ - Run all tests: `just test` (builds first) - Run a specific test: `just test --grep "test name pattern"` +- Run type tests: `just types-test` (builds first, then type-checks `testProjects/types/typescriptTest.ts`) - Tests use mocha - Must build TypeScript before testing (handled automatically by `just` commands) +- Most changes that add or modify public API surface should include a corresponding type test in `testProjects/types/typescriptTest.ts` ## Formatting & Linting @@ -19,7 +21,7 @@ - Node.js HTTP implementation: `src/net/NodeHttpClient.ts` - Fetch-based HTTP implementation: `src/net/FetchHttpClient.ts` - Request orchestration (headers, auth, retries): `src/RequestSender.ts` -- Core client setup: `src/stripe.core.ts` +- Core client setup: `src/stripe.core.ts` (CJS) and `src/stripe.esm.node.ts` (ESM) — changes to one usually need mirroring in the other - API version: `src/apiVersion.ts` ## Generated Code diff --git a/src/stripe.core.ts b/src/stripe.core.ts index bc45c86014..fa7c325e9c 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -11,7 +11,7 @@ import { DEFAULT_BASE_ADDRESSES, } from './Types.js'; import {createWebhooks} from './Webhooks.js'; -import {ApiVersion} from './apiVersion.js'; +import {ApiVersion, ApiMajorVersion} from './apiVersion.js'; import {CryptoProvider} from './crypto/CryptoProvider.js'; import {HttpClient, HttpClientResponse} from './net/HttpClient.js'; import {PlatformFunctions} from './platform/PlatformFunctions.js'; @@ -957,6 +957,11 @@ const defaultRequestSenderFactory: RequestSenderFactory = (stripe) => export class Stripe { static PACKAGE_VERSION = '22.4.0'; static API_VERSION: typeof ApiVersion = ApiVersion; + /** + * The major API version that this SDK uses. Objects retrieved using the same + * major version are compatible. Is an empty string in preview versions of the SDK. + */ + static MAJOR_API_VERSION = ApiMajorVersion; static aiAgent = ''; static AI_AGENT = ''; static USER_AGENT: Record = { diff --git a/src/stripe.esm.node.ts b/src/stripe.esm.node.ts index b3384637aa..7c0b195527 100644 --- a/src/stripe.esm.node.ts +++ b/src/stripe.esm.node.ts @@ -12,7 +12,7 @@ import { DEFAULT_BASE_ADDRESSES, } from './Types.js'; import {createWebhooks} from './Webhooks.js'; -import {ApiVersion} from './apiVersion.js'; +import {ApiVersion, ApiMajorVersion} from './apiVersion.js'; import {CryptoProvider} from './crypto/CryptoProvider.js'; import {HttpClient, HttpClientResponse} from './net/HttpClient.js'; import {PlatformFunctions} from './platform/PlatformFunctions.js'; @@ -959,6 +959,11 @@ const defaultRequestSenderFactory: RequestSenderFactory = (stripe) => export class Stripe { static PACKAGE_VERSION = '22.4.0'; static API_VERSION: typeof ApiVersion = ApiVersion; + /** + * The major API version that this SDK uses. Objects retrieved using the same + * major version are compatible. Is an empty string in preview versions of the SDK. + */ + static MAJOR_API_VERSION = ApiMajorVersion; static aiAgent = ''; static AI_AGENT = ''; static USER_AGENT: Record = { diff --git a/testProjects/types-cjs-node16/typescriptTest.ts b/testProjects/types-cjs-node16/typescriptTest.ts index 674bde78d4..19978761ff 100644 --- a/testProjects/types-cjs-node16/typescriptTest.ts +++ b/testProjects/types-cjs-node16/typescriptTest.ts @@ -10,6 +10,9 @@ import Stripe from 'stripe'; // Construction const stripe = new Stripe('sk_test_123'); +// Static members +const majorApiVersion: string = Stripe.MAJOR_API_VERSION; + // Top-level resource types let customer: Stripe.Customer; let charge: Stripe.Charge; diff --git a/testProjects/types-cjs/typescriptTest.ts b/testProjects/types-cjs/typescriptTest.ts index 43516dcf88..56c4355006 100644 --- a/testProjects/types-cjs/typescriptTest.ts +++ b/testProjects/types-cjs/typescriptTest.ts @@ -33,6 +33,7 @@ let opts: Stripe.RequestOptions; // Static members const version: typeof Stripe.API_VERSION = Stripe.API_VERSION; +const majorApiVersion: string = Stripe.MAJOR_API_VERSION; Stripe.errors; Stripe.errors.StripeError; diff --git a/testProjects/types/typescriptTest.ts b/testProjects/types/typescriptTest.ts index f6f7c84167..69dea3df1c 100644 --- a/testProjects/types/typescriptTest.ts +++ b/testProjects/types/typescriptTest.ts @@ -7,6 +7,8 @@ import Stripe from 'stripe'; +const majorApiVersion: string = Stripe.MAJOR_API_VERSION; + let stripe = new Stripe('sk_test_123', { apiVersion: Stripe.API_VERSION, }); @@ -282,7 +284,9 @@ const errorTypeInterchangeable = ( ): Stripe.ErrorType.StripeError => e; // instanceof narrows to the correct type -const instanceofNarrowing = (e: unknown): Stripe.ErrorType.StripeError | null => { +const instanceofNarrowing = ( + e: unknown +): Stripe.ErrorType.StripeError | null => { if (e instanceof Stripe.errors.StripeError) { return e; } @@ -505,5 +509,4 @@ const _signatureType: Stripe.Signature = null as any; // Factory function return types must be assignable to their interface types. const _nodeHttpClient: Stripe.HttpClient = Stripe.createNodeHttpClient(); -const _nodeCryptoProvider: Stripe.CryptoProvider = - Stripe.createNodeCryptoProvider(); +const _nodeCryptoProvider: Stripe.CryptoProvider = Stripe.createNodeCryptoProvider(); From 09f0f06132ef94571cee14ae13d237b0b4713e35 Mon Sep 17 00:00:00 2001 From: David Brownman <1231935+xavdid@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:36:25 -0700 Subject: [PATCH 2/7] add/adjust event parsing helpers (#2794) * Add cloud provider event parsing methods to StripeClient * add missing methods & tests * move docstrings around * add more descriptive error message * PR feedback * kick CI --- src/Webhooks.ts | 113 ++++-- src/stripe.core.ts | 177 +++++---- src/stripe.esm.node.ts | 162 +++++---- src/utils.ts | 38 ++ test/CloudProviderEvent.spec.ts | 344 ++++++++++++++++++ test/Webhook.spec.ts | 16 +- test/stripe.spec.ts | 4 +- .../types-cjs-node16/typescriptTest.ts | 6 + testProjects/types-cjs/typescriptTest.ts | 8 + testProjects/types/typescriptTest.ts | 8 + 10 files changed, 660 insertions(+), 216 deletions(-) create mode 100644 test/CloudProviderEvent.spec.ts diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 17b8dde243..d57d0e7f8c 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -5,6 +5,7 @@ import { } from './crypto/CryptoProvider.js'; import {PlatformFunctions} from './platform/PlatformFunctions.js'; import {Event} from './resources/Events.js'; +import {maybeExtractFromCloudProviderEnvelope, parsePayload} from './utils.js'; /** * Value of the `stripe-signature` header from Stripe. @@ -36,9 +37,13 @@ export type WebhookTestHeaderOptions = { cryptoProvider?: CryptoProvider; }; -// export type WebhookEvent = Record; type WebhookPayload = string | Uint8Array; export type WebhookSignatureObject = { + /** + * Verifies the authenticity (and recency) of a webhook, throwing a `SignatureVerificationError` + * if there's a mismatch. Useful for quickly validating incoming webhooks before storing them for + * later processing (at which time you can use the `*WithoutVerification` methods for parsing). + */ verifyHeader: ( encodedPayload: WebhookPayload, encodedHeader: WebhookHeader, @@ -47,6 +52,10 @@ export type WebhookSignatureObject = { cryptoProvider?: CryptoProvider, receivedAt?: number ) => boolean; + /** + * Verifies the authenticity (and recency) of a webhook (async version), throwing a + * `SignatureVerificationError` if there's a mismatch. + */ verifyHeaderAsync: ( encodedPayload: WebhookPayload, encodedHeader: WebhookHeader, @@ -59,6 +68,12 @@ export type WebhookSignatureObject = { export type WebhookObject = { DEFAULT_TOLERANCE: number; signature: WebhookSignatureObject | null; + /** + * Constructs a [snapshot event](https://docs.stripe.com/event-destinations#snapshot-payload) from an + * incoming webhook after verifying its authenticity. To work with a webhook that has already been + * verified (i.e. one from a cloud provider, an asynchronous queue, or during testing), see + * `constructEventWithoutVerification`. + */ constructEvent: ( payload: WebhookPayload, header: WebhookHeader, @@ -67,6 +82,12 @@ export type WebhookObject = { cryptoProvider?: CryptoProvider, receivedAt?: number ) => Event; + /** + * Constructs a [snapshot event](https://docs.stripe.com/event-destinations#snapshot-payload) from an + * incoming webhook after verifying its authenticity (async version). To work with a webhook that + * has already been verified (i.e. one from a cloud provider, an asynchronous queue, or during + * testing), see `constructEventWithoutVerification`. + */ constructEventAsync: ( payload: WebhookPayload, header: WebhookHeader, @@ -75,6 +96,26 @@ export type WebhookObject = { cryptoProvider?: CryptoProvider, receivedAt?: number ) => Promise; + /** + * Constructs a [snapshot event](https://docs.stripe.com/event-destinations#snapshot-payload) from an + * incoming webhook without first verifying its authenticity. Should be used after calling + * `webhooks.verifySignatureHeader(...)` or with input from a trusted source (such as + * [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge), or + * [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) payload). Or, to verify & + * construct in a single call, use `webhooks.constructEvent(...)` instead. + */ + constructEventWithoutVerification: (payload: string) => Event; + /** + * Compute the `Stripe-Signature` header for a given webhook body & secret. Useful for signing + * payloads in unit tests. + * + * @property {number} timestamp - Timestamp of the header. Defaults to Date.now() + * @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters + * @property {string} secret - Stripe webhook secret 'whsec_...' + * @property {string} scheme - Version of API to hit. Defaults to 'v1'. + * @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: (opts: WebhookTestHeaderOptions) => string; generateTestHeaderStringAsync: ( opts: WebhookTestHeaderOptions @@ -84,6 +125,15 @@ export type WebhookObject = { export function createWebhooks( platformFunctions: PlatformFunctions ): WebhookObject { + function buildEvent(jsonPayload: Record): Event { + if (jsonPayload && jsonPayload.object === 'v2.core.event') { + throw new Error( + 'You passed a thin event notification to a function that expects a webhook. Use the corresponding EventNotification method instead.' + ); + } + return (jsonPayload as unknown) as Event; + } + const Webhook: WebhookObject = { DEFAULT_TOLERANCE: 300, // 5 minutes signature: null, @@ -118,16 +168,7 @@ export function createWebhooks( throw e; } - const jsonPayload = - payload instanceof Uint8Array - ? JSON.parse(new TextDecoder('utf8').decode(payload)) - : JSON.parse(payload); - if (jsonPayload && jsonPayload.object === 'v2.core.event') { - throw new Error( - 'You passed an event notification to stripe.webhooks.constructEvent, which expects a webhook payload. Use stripe.parseEventNotification instead.' - ); - } - return jsonPayload; + return buildEvent(parsePayload(payload)); }, async constructEventAsync( @@ -153,40 +194,32 @@ export function createWebhooks( receivedAt ); - const jsonPayload = - payload instanceof Uint8Array - ? JSON.parse(new TextDecoder('utf8').decode(payload)) - : JSON.parse(payload); - if (jsonPayload && jsonPayload.object === 'v2.core.event') { - throw new Error( - 'You passed an event notification to stripe.webhooks.constructEvent, which expects a webhook payload. Use stripe.parseEventNotificationAsync instead.' - ); - } - return jsonPayload; + return buildEvent(parsePayload(payload)); + }, + + constructEventWithoutVerification(payload: string): Event { + return buildEvent(maybeExtractFromCloudProviderEnvelope(payload)); }, - /** - * Generates a header to be used for webhook mocking - * - * @typedef {object} opts - * @property {number} timestamp - Timestamp of the header. Defaults to Date.now() - * @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters - * @property {string} secret - Stripe webhook secret 'whsec_...' - * @property {string} scheme - Version of API to hit. Defaults to 'v1'. - * @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): string { - const preparedOpts = prepareOptions(opts); + try { + const preparedOpts = prepareOptions(opts); - const signature = - preparedOpts.signature || - preparedOpts.cryptoProvider.computeHMACSignature( - preparedOpts.payloadString, - preparedOpts.secret - ); + const signature = + preparedOpts.signature || + preparedOpts.cryptoProvider.computeHMACSignature( + preparedOpts.payloadString, + preparedOpts.secret + ); - return preparedOpts.generateHeaderString(signature); + return preparedOpts.generateHeaderString(signature); + } catch (e) { + if (e instanceof CryptoProviderOnlySupportsAsyncError) { + e.message += + '\nUse `await generateTestHeaderStringAsync(...)` instead of `generateTestHeaderString(...)`'; + } + throw e; + } }, generateTestHeaderStringAsync: async function( opts: WebhookTestHeaderOptions diff --git a/src/stripe.core.ts b/src/stripe.core.ts index fa7c325e9c..9d7774fbf4 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -19,6 +19,8 @@ import * as resources from './resources.js'; import { createApiKeyAuthenticator, detectAIAgent, + maybeExtractFromCloudProviderEnvelope, + parsePayload, pascalToCamelCase, validateInteger, } from './utils.js'; @@ -1583,86 +1585,68 @@ export class Stripe { return this._api[key]; } - parseEventNotification( - payload: string | Uint8Array, - header: string | Uint8Array, - secret: string, - tolerance?: number, - cryptoProvider?: CryptoProvider, - receivedAt?: number - // this return type is ignored?? picks up types from `types/index.d.ts` instead + _buildEventNotification( + parsed: Record ): V2.Core.EventNotification { - // Verify the signature using the internal signature helper directly, - // bypassing constructEvent's v2 payload check (since v2 payloads are - // expected here). - if (!this.webhooks.signature) { - throw new Error('ERR: missing signature helper, unable to verify'); + if (parsed.object === 'event') { + throw new Error( + 'You passed a v1 Event to a method that expects a thin event notification. Use the corresponding constructEvent method instead.' + ); } - this.webhooks.signature.verifyHeader( - payload, - header, - secret, - tolerance || this.webhooks.DEFAULT_TOLERANCE, - cryptoProvider || this._platformFunctions.createDefaultCryptoProvider(), - receivedAt - ); - - const eventNotification = - payload instanceof Uint8Array - ? JSON.parse(new TextDecoder('utf8').decode(payload)) - : JSON.parse(payload as string); - - if (eventNotification && eventNotification.object === 'event') { + if (parsed.object != null && parsed.object !== 'v2.core.event') { throw new Error( - 'You passed a webhook payload to stripe.parseEventNotification, which expects an event notification. Use stripe.webhooks.constructEvent instead.' + `Unexpected object type '${parsed.object}'. Expected 'v2.core.event' for an event notification.` ); } - // Parse string context into StripeContext object if present - if (eventNotification.context) { - eventNotification.context = StripeContext.parse( - eventNotification.context - ); + if (parsed.context) { + parsed.context = StripeContext.parse(parsed.context as string); } - eventNotification.fetchEvent = (): Promise => { + parsed.fetchEvent = (): Promise => { return this._requestSender._rawRequest( 'GET', - `/v2/core/events/${eventNotification.id}`, + `/v2/core/events/${parsed.id}`, undefined, { - stripeContext: eventNotification.context, + stripeContext: parsed.context as any, headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, + 'Stripe-Request-Trigger': `event=${parsed.id}`, }, }, ['fetch_event'] ); }; - eventNotification.fetchRelatedObject = (): Promise => { - if (!eventNotification.related_object) { + parsed.fetchRelatedObject = (): Promise => { + if (!parsed.related_object) { return Promise.resolve(null); } return this._requestSender._rawRequest( 'GET', - eventNotification.related_object.url, + (parsed.related_object as any).url, undefined, { - stripeContext: eventNotification.context, + stripeContext: parsed.context as any, headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, + 'Stripe-Request-Trigger': `event=${parsed.id}`, }, }, ['fetch_related_object'] ); }; - return eventNotification; + return (parsed as unknown) as V2.Core.EventNotification; } - async parseEventNotificationAsync( + /** + * Constructs a [thin event notification](https://docs.stripe.com/event-destinations#thin-payload) from an + * incoming webhook after verifying its authenticity. To work with a webhook that has already been + * verified (i.e. one from a cloud provider, an asynchronous queue, or during testing), see + * `parseEventNotificationWithoutVerification`. + */ + parseEventNotification( payload: string | Uint8Array, header: string | Uint8Array, secret: string, @@ -1670,14 +1654,14 @@ export class Stripe { cryptoProvider?: CryptoProvider, receivedAt?: number // this return type is ignored?? picks up types from `types/index.d.ts` instead - ): Promise { + ): V2.Core.EventNotification { // Verify the signature using the internal signature helper directly, // bypassing constructEvent's v2 payload check (since v2 payloads are // expected here). if (!this.webhooks.signature) { throw new Error('ERR: missing signature helper, unable to verify'); } - await this.webhooks.signature.verifyHeaderAsync( + this.webhooks.signature.verifyHeader( payload, header, secret, @@ -1686,59 +1670,62 @@ export class Stripe { receivedAt ); - const eventNotification = - payload instanceof Uint8Array - ? JSON.parse(new TextDecoder('utf8').decode(payload)) - : JSON.parse(payload as string); - - if (eventNotification && eventNotification.object === 'event') { - throw new Error( - 'You passed a webhook payload to stripe.parseEventNotificationAsync, which expects an event notification. Use stripe.webhooks.constructEventAsync instead.' - ); - } + return this._buildEventNotification(parsePayload(payload)); + } - // Parse string context into StripeContext object if present - if (eventNotification.context) { - eventNotification.context = StripeContext.parse( - eventNotification.context - ); + async parseEventNotificationAsync( + payload: string | Uint8Array, + header: string | Uint8Array, + secret: string, + tolerance?: number, + cryptoProvider?: CryptoProvider, + receivedAt?: number + // this return type is ignored?? picks up types from `types/index.d.ts` instead + ): Promise { + // Verify the signature using the internal signature helper directly, + // bypassing constructEvent's v2 payload check (since v2 payloads are + // expected here). + if (!this.webhooks.signature) { + throw new Error('ERR: missing signature helper, unable to verify'); } + await this.webhooks.signature.verifyHeaderAsync( + payload, + header, + secret, + tolerance || this.webhooks.DEFAULT_TOLERANCE, + cryptoProvider || this._platformFunctions.createDefaultCryptoProvider(), + receivedAt + ); - eventNotification.fetchEvent = (): Promise => { - return this._requestSender._rawRequest( - 'GET', - `/v2/core/events/${eventNotification.id}`, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_event'] - ); - }; - - eventNotification.fetchRelatedObject = (): Promise => { - if (!eventNotification.related_object) { - return Promise.resolve(null); - } + return this._buildEventNotification(parsePayload(payload)); + } - return this._requestSender._rawRequest( - 'GET', - eventNotification.related_object.url, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_related_object'] - ); - }; + /** + * Constructs a [snapshot event](https://docs.stripe.com/event-destinations#snapshot-payload) from an + * incoming webhook without first verifying its authenticity. Should be used after calling + * `webhooks.verifySignatureHeader(...)` or with input from a trusted source (such as + * [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge), or + * [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) payload). Or, to verify & + * construct in a single call, use `webhooks.constructEvent(...)` instead. + */ + constructEventWithoutVerification(payload: string): Event { + return this.webhooks.constructEventWithoutVerification(payload); + } - return eventNotification; + /** + * Constructs a [thin event notification](https://docs.stripe.com/event-destinations#thin-payload) from an + * incoming webhook without first verifying its authenticity. Should be used after calling + * `webhooks.verifySignatureHeader(...)` or with input from a trusted source (such as + * [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge), or + * [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) payload). Or, to verify & + * parse in a single call, use `parseEventNotification(...)` instead. + */ + parseEventNotificationWithoutVerification( + payload: string + ): V2.Core.EventNotification { + return this._buildEventNotification( + maybeExtractFromCloudProviderEnvelope(payload) + ); } } diff --git a/src/stripe.esm.node.ts b/src/stripe.esm.node.ts index 7c0b195527..cb91fafa10 100644 --- a/src/stripe.esm.node.ts +++ b/src/stripe.esm.node.ts @@ -20,6 +20,7 @@ import * as resources from './resources.js'; import { createApiKeyAuthenticator, detectAIAgent, + maybeExtractFromCloudProviderEnvelope, pascalToCamelCase, validateInteger, } from './utils.js'; @@ -1577,6 +1578,50 @@ export class Stripe { return this._api[key]; } + _buildEventNotification( + parsed: Record + ): V2.Core.EventNotification { + if (parsed.context) { + parsed.context = StripeContext.parse(parsed.context as string); + } + + parsed.fetchEvent = (): Promise => { + return this._requestSender._rawRequest( + 'GET', + `/v2/core/events/${parsed.id}`, + undefined, + { + stripeContext: parsed.context as any, + headers: { + 'Stripe-Request-Trigger': `event=${parsed.id}`, + }, + }, + ['fetch_event'] + ); + }; + + parsed.fetchRelatedObject = (): Promise => { + if (!parsed.related_object) { + return Promise.resolve(null); + } + + return this._requestSender._rawRequest( + 'GET', + (parsed.related_object as any).url, + undefined, + { + stripeContext: parsed.context as any, + headers: { + 'Stripe-Request-Trigger': `event=${parsed.id}`, + }, + }, + ['fetch_related_object'] + ); + }; + + return (parsed as unknown) as V2.Core.EventNotification; + } + parseEventNotification( payload: string | Uint8Array, header: string | Uint8Array, @@ -1606,54 +1651,18 @@ export class Stripe { ? JSON.parse(new TextDecoder('utf8').decode(payload)) : JSON.parse(payload as string); - if (eventNotification && eventNotification.object === 'event') { + if (eventNotification.object === 'event') { throw new Error( 'You passed a webhook payload to stripe.parseEventNotification, which expects an event notification. Use stripe.webhooks.constructEvent instead.' ); } - - // Parse string context into StripeContext object if present - if (eventNotification.context) { - eventNotification.context = StripeContext.parse( - eventNotification.context + if (eventNotification.object !== 'v2.core.event') { + throw new Error( + `Unexpected object type '${eventNotification.object}'. Expected 'v2.core.event' for an event notification.` ); } - eventNotification.fetchEvent = (): Promise => { - return this._requestSender._rawRequest( - 'GET', - `/v2/core/events/${eventNotification.id}`, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_event'] - ); - }; - - eventNotification.fetchRelatedObject = (): Promise => { - if (!eventNotification.related_object) { - return Promise.resolve(null); - } - - return this._requestSender._rawRequest( - 'GET', - eventNotification.related_object.url, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_related_object'] - ); - }; - - return eventNotification; + return this._buildEventNotification(eventNotification); } async parseEventNotificationAsync( @@ -1690,49 +1699,46 @@ export class Stripe { 'You passed a webhook payload to stripe.parseEventNotificationAsync, which expects an event notification. Use stripe.webhooks.constructEventAsync instead.' ); } - - // Parse string context into StripeContext object if present - if (eventNotification.context) { - eventNotification.context = StripeContext.parse( - eventNotification.context + if (eventNotification.object !== 'v2.core.event') { + throw new Error( + `Unexpected object type '${eventNotification.object}'. Expected 'v2.core.event' for an event notification.` ); } - eventNotification.fetchEvent = (): Promise => { - return this._requestSender._rawRequest( - 'GET', - `/v2/core/events/${eventNotification.id}`, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_event'] - ); - }; + return this._buildEventNotification(eventNotification); + } - eventNotification.fetchRelatedObject = (): Promise => { - if (!eventNotification.related_object) { - return Promise.resolve(null); - } + /** + * Constructs an Event from a payload string, with no signature verification. + * Accepts raw Stripe Event JSON as well as payloads wrapped in an + * [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge) + * or [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) envelope. + */ + constructEventWithoutVerification(payload: string): Event { + return this.webhooks.constructEventWithoutVerification(payload); + } - return this._requestSender._rawRequest( - 'GET', - eventNotification.related_object.url, - undefined, - { - stripeContext: eventNotification.context, - headers: { - 'Stripe-Request-Trigger': `event=${eventNotification.id}`, - }, - }, - ['fetch_related_object'] + /** + * Parses an EventNotification from a payload string, with no signature verification. + * Accepts raw Stripe Event Notification JSON as well as payloads wrapped in an + * [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge) + * or [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) envelope. + */ + parseEventNotificationWithoutVerification( + payload: string + ): V2.Core.EventNotification { + const inner = maybeExtractFromCloudProviderEnvelope(payload); + if (inner.object === 'event') { + throw new Error( + 'It looks like this cloud event contains a webhook body instead of a thin event notification. Use constructEventWithoutVerification instead.' ); - }; - - return eventNotification; + } + if (inner.object !== 'v2.core.event') { + throw new Error( + `Unexpected object type '${inner.object}'. Expected 'v2.core.event' for an event notification.` + ); + } + return this._buildEventNotification(inner); } } diff --git a/src/utils.ts b/src/utils.ts index 4140e6b408..772b42933b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -465,6 +465,44 @@ export function parseHeadersForFetch( }); } +/** + * Parses a webhook payload (string or Uint8Array) into a plain object. + */ +export function parsePayload( + payload: string | Uint8Array +): Record { + const raw = + payload instanceof Uint8Array + ? new TextDecoder('utf8').decode(payload) + : payload; + return JSON.parse(raw) as Record; +} + +export function maybeExtractFromCloudProviderEnvelope( + payload: string +): Record { + const parsed = parsePayload(payload); + + // Could add as many checks as we want here, but we'll start simple + if ('detail' in parsed) { + // AWS + // https://docs.stripe.com/event-destinations/eventbridge#event-structure + return parsed.detail as Record; + } + if ('specversion' in parsed && 'data' in parsed) { + // Azure + // https://docs.stripe.com/event-destinations/eventgrid#event-structure + return parsed.data as Record; + } + if (parsed.object === 'event' || parsed.object === 'v2.core.event') { + // Raw Stripe event passed directly: return it as-is (pass-through) + return parsed; + } + throw new Error( + 'Unrecognized event format. The payload must be an AWS EventBridge/Azure Event Grid event envelope or a Stripe webhook (thin event notification or snapshot).' + ); +} + const CALL_SITE_MARKER = '\nOriginating from:'; /** diff --git a/test/CloudProviderEvent.spec.ts b/test/CloudProviderEvent.spec.ts new file mode 100644 index 0000000000..053ef0c8cc --- /dev/null +++ b/test/CloudProviderEvent.spec.ts @@ -0,0 +1,344 @@ +// @ts-nocheck + +import {expect} from 'chai'; + +const stripe = require('../src/stripe.cjs.node.js')('sk_test_fake'); + +const EVENTBRIDGE_PAYLOAD = JSON.stringify({ + version: '0', + id: '17e8dff5-d6cd-3770-ace9-aeac02b6ac3f', + 'detail-type': 'customer.created', + source: 'aws.partner/stripe.com/ed_123', + account: '506417113029', + time: '2024-03-07T18:27:56Z', + region: 'us-west-2', + resources: [], + detail: { + id: 'evt_test_123', + object: 'event', + api_version: '2023-10-16', + created: 1709836076, + data: {object: {id: 'cus_123', object: 'customer'}}, + livemode: true, + pending_webhooks: 0, + request: {id: 'req_123', idempotency_key: null}, + type: 'customer.created', + }, +}); + +const EVENTGRID_PAYLOAD = JSON.stringify({ + specversion: '1.0', + type: 'customer.created', + source: '/providers/stripe/ed_test_123', + id: '9aeb0fdf-c01e-0131-0922-9eb54906e209', + time: '2025-07-11T14:30:00Z', + subject: null, + dataContentType: 'application/cloudevents+json', + data: { + id: 'evt_test_456', + object: 'event', + api_version: '2023-10-16', + created: 1709836076, + data: {object: {id: 'cus_456', object: 'customer'}}, + livemode: false, + pending_webhooks: 0, + request: {id: 'req_456', idempotency_key: null}, + type: 'customer.created', + }, +}); + +const EVENTBRIDGE_V2_PAYLOAD = JSON.stringify({ + version: '0', + id: '17e8dff5-d6cd-3770-ace9-aeac02b6ac3f', + 'detail-type': 'v2.billing.meter.error_report_triggered', + source: 'aws.partner/stripe.com/ed_123', + account: '506417113029', + time: '2024-03-07T18:27:56Z', + region: 'us-west-2', + resources: [], + detail: { + id: 'evt_notif_test_789', + object: 'v2.core.event', + type: 'v2.billing.meter.error_report_triggered', + created: '2024-03-07T18:27:56.000Z', + context: null, + livemode: false, + related_object: null, + }, +}); + +const EVENTGRID_V2_PAYLOAD = JSON.stringify({ + specversion: '1.0', + type: 'v2.billing.meter.error_report_triggered', + source: '/providers/stripe/ed_test_123', + id: 'abc123-eventgrid-v2', + time: '2025-07-11T14:30:00Z', + subject: null, + dataContentType: 'application/cloudevents+json', + data: { + id: 'evt_notif_test_012', + object: 'v2.core.event', + type: 'v2.billing.meter.error_report_triggered', + created: '2025-07-11T14:30:00.000Z', + context: null, + livemode: false, + related_object: null, + }, +}); + +describe('constructEventWithoutVerification', () => { + it('is accessible via the webhooks entry point and parses EventBridge payload', () => { + const event = stripe.webhooks.constructEventWithoutVerification( + EVENTBRIDGE_PAYLOAD + ); + expect(event.id).to.equal('evt_test_123'); + expect(event.type).to.equal('customer.created'); + }); + + it('parses EventBridge payload', () => { + const event = stripe.constructEventWithoutVerification(EVENTBRIDGE_PAYLOAD); + expect(event.id).to.equal('evt_test_123'); + expect(event.type).to.equal('customer.created'); + }); + + it('parses Event Grid payload', () => { + const event = stripe.constructEventWithoutVerification(EVENTGRID_PAYLOAD); + expect(event.id).to.equal('evt_test_456'); + expect(event.type).to.equal('customer.created'); + }); + + it('parses raw Stripe Event JSON directly', () => { + const rawEvent = JSON.stringify({ + id: 'evt_test_123', + object: 'event', + type: 'customer.created', + }); + const event = stripe.constructEventWithoutVerification(rawEvent); + expect(event.id).to.equal('evt_test_123'); + expect(event.type).to.equal('customer.created'); + }); + + it('throws on invalid JSON', () => { + expect(() => + stripe.constructEventWithoutVerification('not valid json') + ).to.throw(); + }); + + it('throws on unrecognized format', () => { + expect(() => + stripe.constructEventWithoutVerification('{"foo":"bar"}') + ).to.throw(/Unrecognized event format/); + }); + + it('throws when cloud envelope contains a v2 event notification', () => { + expect(() => + stripe.constructEventWithoutVerification(EVENTBRIDGE_V2_PAYLOAD) + ).to.throw(/EventNotification/); + }); + + it('throws on Azure envelope missing data field', () => { + const payload = JSON.stringify({ + specversion: '1.0', + type: 'customer.created', + source: '/providers/stripe/ed_test_123', + id: 'test-missing-data', + }); + expect(() => stripe.constructEventWithoutVerification(payload)).to.throw( + /Unrecognized event format/ + ); + }); +}); + +const SECRET = 'whsec_test_secret'; + +const RAW_V2_NOTIFICATION = JSON.stringify({ + id: 'evt_notif_signed_001', + object: 'v2.core.event', + type: 'v2.billing.meter.error_report_triggered', + created: '2024-03-07T18:27:56.000Z', + context: null, + livemode: false, + related_object: null, +}); + +const RAW_V1_EVENT = JSON.stringify({ + id: 'evt_v1_001', + object: 'event', + type: 'customer.created', + api_version: '2023-10-16', + created: 1709836076, + data: {object: {id: 'cus_001', object: 'customer'}}, + livemode: false, + pending_webhooks: 0, + request: {id: 'req_001', idempotency_key: null}, +}); + +describe('parseEventNotification', () => { + it('returns an event notification for a valid v2 payload with a valid signature', () => { + const header = stripe.webhooks.generateTestHeaderString({ + payload: RAW_V2_NOTIFICATION, + secret: SECRET, + }); + + const notification = stripe.parseEventNotification( + RAW_V2_NOTIFICATION, + header, + SECRET + ); + + expect(notification.id).to.equal('evt_notif_signed_001'); + expect(notification.object).to.equal('v2.core.event'); + expect(notification.type).to.equal( + 'v2.billing.meter.error_report_triggered' + ); + }); + + it('attaches fetchEvent and fetchRelatedObject functions to the returned notification', () => { + const header = stripe.webhooks.generateTestHeaderString({ + payload: RAW_V2_NOTIFICATION, + secret: SECRET, + }); + + const notification = stripe.parseEventNotification( + RAW_V2_NOTIFICATION, + header, + SECRET + ); + + expect(notification.fetchEvent).to.be.a('function'); + expect(notification.fetchRelatedObject).to.be.a('function'); + }); + + it('throws when a v1 Event payload is passed (suggests constructEvent)', () => { + const header = stripe.webhooks.generateTestHeaderString({ + payload: RAW_V1_EVENT, + secret: SECRET, + }); + + expect(() => + stripe.parseEventNotification(RAW_V1_EVENT, header, SECRET) + ).to.throw(/constructEvent/); + }); + + it('throws when the signature does not match the payload', () => { + const header = stripe.webhooks.generateTestHeaderString({ + payload: RAW_V2_NOTIFICATION, + secret: SECRET, + signature: 'bad_signature', + }); + + expect(() => + stripe.parseEventNotification(RAW_V2_NOTIFICATION, header, SECRET) + ).to.throw(/No signatures found matching/); + }); + + it('throws when the header is missing', () => { + expect(() => + stripe.parseEventNotification(RAW_V2_NOTIFICATION, '', SECRET) + ).to.throw(); + }); +}); + +describe('parseEventNotificationWithoutVerification', () => { + it('parses raw v2 event notification JSON directly (no cloud envelope)', () => { + const rawNotification = JSON.stringify({ + id: 'evt_234', + object: 'v2.core.event', + type: 'v2.billing.meter.error_report_triggered', + created: '2024-03-07T18:27:56.000Z', + context: null, + livemode: false, + related_object: null, + }); + const notification = stripe.parseEventNotificationWithoutVerification( + rawNotification + ); + expect(notification.id).to.equal('evt_234'); + expect(notification.object).to.equal('v2.core.event'); + }); + + it('parses EventBridge payload with v2 notification', () => { + const notification = stripe.parseEventNotificationWithoutVerification( + EVENTBRIDGE_V2_PAYLOAD + ); + expect(notification.id).to.equal('evt_notif_test_789'); + expect(notification.type).to.equal( + 'v2.billing.meter.error_report_triggered' + ); + }); + + it('parses Event Grid payload with v2 notification', () => { + const notification = stripe.parseEventNotificationWithoutVerification( + EVENTGRID_V2_PAYLOAD + ); + expect(notification.id).to.equal('evt_notif_test_012'); + expect(notification.type).to.equal( + 'v2.billing.meter.error_report_triggered' + ); + }); + + it('throws when cloud envelope contains a v1 Event', () => { + expect(() => + stripe.parseEventNotificationWithoutVerification(EVENTBRIDGE_PAYLOAD) + ).to.throw(/constructEvent/); + }); + + it('attaches a fetchEvent function to the returned notification', () => { + const notification = stripe.parseEventNotificationWithoutVerification( + EVENTBRIDGE_V2_PAYLOAD + ); + expect(notification.fetchEvent).to.be.a('function'); + }); + + it('throws on invalid JSON', () => { + expect(() => + stripe.parseEventNotificationWithoutVerification('not valid json') + ).to.throw(); + }); + + it('throws on unrecognized format', () => { + expect(() => + stripe.parseEventNotificationWithoutVerification('{"foo":"bar"}') + ).to.throw(/Unrecognized event format/); + }); + + it('throws on Azure envelope missing data field', () => { + const payload = JSON.stringify({ + specversion: '1.0', + type: 'v2.billing.meter.error_report_triggered', + source: '/providers/stripe/ed_test_123', + id: 'test-missing-data', + }); + expect(() => + stripe.parseEventNotificationWithoutVerification(payload) + ).to.throw(/Unrecognized event format/); + }); + + it('throws on unrecognized raw payload with non-event object', () => { + const payload = JSON.stringify({ + object: 'customer', + type: 'customer.created', + id: 'cus_123', + }); + expect(() => + stripe.parseEventNotificationWithoutVerification(payload) + ).to.throw(/Unrecognized event format/); + }); + + it('throws on unexpected object type in event notification', () => { + const payload = JSON.stringify({ + version: '0', + id: 'test', + 'detail-type': 'customer.created', + source: 'aws.partner/stripe.com/ed_123', + detail: { + object: 'customer', + type: 'customer.created', + id: 'cus_123', + }, + }); + expect(() => + stripe.parseEventNotificationWithoutVerification(payload) + ).to.throw(/Unexpected object type/); + }); +}); diff --git a/test/Webhook.spec.ts b/test/Webhook.spec.ts index 321527cf8b..5a5891e8e2 100644 --- a/test/Webhook.spec.ts +++ b/test/Webhook.spec.ts @@ -46,6 +46,20 @@ function createWebhooksTestSuite(stripe) { expect(header).to.not.be.undefined; expect(header.split(',')).to.have.lengthOf(2); }); + + it('should provide helpful information when CryptoProviderOnlySupportsAsyncError is thrown', () => { + expect(() => { + stripe.webhooks.generateTestHeaderString({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, + cryptoProvider: { + computeHMACSignature() { + throw new CryptoProviderOnlySupportsAsyncError('foobar'); + }, + }, + }); + }).to.throw(/foobar\nUse `await generateTestHeaderStringAsync/); + }); }); describe('.generateTestHeaderStringAsync', () => { @@ -203,7 +217,7 @@ function createWebhooksTestSuite(stripe) { SECRET ).catch((e: Error) => e); expect(err).to.be.instanceOf(Error); - expect(err.message).to.contain('stripe.parseEventNotification'); + expect(err.message).to.contain('corresponding EventNotification'); }); }; }; diff --git a/test/stripe.spec.ts b/test/stripe.spec.ts index a590e7fad0..f5c46df4e4 100644 --- a/test/stripe.spec.ts +++ b/test/stripe.spec.ts @@ -831,7 +831,7 @@ describe('Stripe Module', function() { expect.fail('Expected an error to be thrown'); } catch (e) { expect(e).to.be.instanceOf(Error); - expect(e.message).to.contain('stripe.webhooks.constructEvent'); + expect(e.message).to.contain('constructEvent'); } }); @@ -1153,7 +1153,7 @@ describe('Stripe Module', function() { expect.fail('Expected an error to be thrown'); } catch (e) { expect(e).to.be.instanceOf(Error); - expect(e.message).to.contain('stripe.webhooks.constructEventAsync'); + expect(e.message).to.contain('constructEvent'); } }); diff --git a/testProjects/types-cjs-node16/typescriptTest.ts b/testProjects/types-cjs-node16/typescriptTest.ts index 19978761ff..1d75f9d5aa 100644 --- a/testProjects/types-cjs-node16/typescriptTest.ts +++ b/testProjects/types-cjs-node16/typescriptTest.ts @@ -63,6 +63,12 @@ let accountBizRevenue: Stripe.AccountCreateParams.BusinessProfile.AnnualRevenue; // @ts-expect-error - unknown config properties should be rejected const bad = new Stripe('sk_test_123', {unknownProperty: true}); +// Webhook methods: constructEventWithoutVerification and parseEventNotificationWithoutVerification +event = stripe.webhooks.constructEventWithoutVerification('payload'); +event = stripe.constructEventWithoutVerification('payload'); +const _notificationWV: Stripe.V2.Core.EventNotification = + stripe.parseEventNotificationWithoutVerification('payload'); + // Namespace type exports that must remain accessible (v21 parity). const _stripeConfig: Stripe.StripeConfig = {maxNetworkRetries: 3}; const _latestApiVersion: Stripe.LatestApiVersion = '' as any; diff --git a/testProjects/types-cjs/typescriptTest.ts b/testProjects/types-cjs/typescriptTest.ts index 56c4355006..2feb9ab2dd 100644 --- a/testProjects/types-cjs/typescriptTest.ts +++ b/testProjects/types-cjs/typescriptTest.ts @@ -420,6 +420,14 @@ event = stripe.webhooks.constructEvent( 'secret' ); +// constructEventWithoutVerification on webhooks object and client +event = stripe.webhooks.constructEventWithoutVerification('payload'); +event = stripe.constructEventWithoutVerification('payload'); + +// parseEventNotificationWithoutVerification on client +const _notificationWV: Stripe.V2.Core.EventNotification = + stripe.parseEventNotificationWithoutVerification('payload'); + const taxExempt: Stripe.CustomerUpdateParams.TaxExempt = 'exempt'; let subscription: Stripe.Subscription; let invoice: Stripe.Invoice; diff --git a/testProjects/types/typescriptTest.ts b/testProjects/types/typescriptTest.ts index 69dea3df1c..7f3c77729b 100644 --- a/testProjects/types/typescriptTest.ts +++ b/testProjects/types/typescriptTest.ts @@ -421,6 +421,14 @@ event = stripe.webhooks.constructEvent( 'secret' ); +// constructEventWithoutVerification on webhooks object and client +event = stripe.webhooks.constructEventWithoutVerification('payload'); +event = stripe.constructEventWithoutVerification('payload'); + +// parseEventNotificationWithoutVerification on client +const _notificationWV: Stripe.V2.Core.EventNotification = + stripe.parseEventNotificationWithoutVerification('payload'); + // Verify that nested types with names matching imported types resolve correctly. // e.g. Checkout.Session.TotalDetails.Breakdown.Discount.discount should be // Stripe.Discount, not a self-referential Breakdown.Discount. (DEVSDK-3139) From 2ca69d827d344320890a1ecbed089b0881af0652 Mon Sep 17 00:00:00 2001 From: John O'Sullivan <85578318+johno-stripe@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:17:55 -0400 Subject: [PATCH 3/7] Emit Claude Code plugin hint at module load time (#2805) * set up claude code plugin hint on initialize * added to esm init, preferring anon error catch --- src/platform/NodePlatformFunctions.ts | 5 +++ src/platform/PlatformFunctions.ts | 5 +++ src/stripe.core.ts | 10 ++++++ src/stripe.esm.node.ts | 10 ++++++ test/stripe.spec.ts | 45 +++++++++++++++++++++++++++ 5 files changed, 75 insertions(+) diff --git a/src/platform/NodePlatformFunctions.ts b/src/platform/NodePlatformFunctions.ts index a90cada7a7..268487cacf 100644 --- a/src/platform/NodePlatformFunctions.ts +++ b/src/platform/NodePlatformFunctions.ts @@ -33,6 +33,11 @@ export class NodePlatformFunctions extends PlatformFunctions { return `${process.platform} ${os.release()} ${os.arch()}`; } + /** @override */ + writeStderr(msg: string): void { + process.stderr.write(msg); + } + /** @override */ emitWarning(warning: string): void { if (typeof process.emitWarning === 'function') { diff --git a/src/platform/PlatformFunctions.ts b/src/platform/PlatformFunctions.ts index 2dcbe45cb0..4b09960ce9 100644 --- a/src/platform/PlatformFunctions.ts +++ b/src/platform/PlatformFunctions.ts @@ -39,6 +39,11 @@ export class PlatformFunctions { return null; } + /** + * Writes a message to stderr, or does nothing if unavailable. + */ + writeStderr(_msg: string): void {} + /** * Emits a warning. Node.js uses process.emitWarning; other runtimes * fall back to console.warn. diff --git a/src/stripe.core.ts b/src/stripe.core.ts index 9d7774fbf4..397c03141e 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -1105,6 +1105,16 @@ export class Stripe { const env = platformFunctions.getEnv(); const runtimeVersion = platformFunctions.getRuntimeVersion(); + if (env?.CLAUDECODE || env?.CLAUDE_CODE_CHILD_SESSION) { + try { + platformFunctions.writeStderr( + '\n' + ); + } catch { + // never let hint emission crash the SDK + } + } + Stripe.aiAgent = env ? detectAIAgent(env) : ''; Stripe.AI_AGENT = Stripe.aiAgent; Stripe.USER_AGENT = { diff --git a/src/stripe.esm.node.ts b/src/stripe.esm.node.ts index cb91fafa10..c97b1146a0 100644 --- a/src/stripe.esm.node.ts +++ b/src/stripe.esm.node.ts @@ -1105,6 +1105,16 @@ export class Stripe { const env = platformFunctions.getEnv(); const runtimeVersion = platformFunctions.getRuntimeVersion(); + if (env?.CLAUDECODE || env?.CLAUDE_CODE_CHILD_SESSION) { + try { + platformFunctions.writeStderr( + '\n' + ); + } catch { + // never let hint emission crash the SDK + } + } + Stripe.aiAgent = env ? detectAIAgent(env) : ''; Stripe.AI_AGENT = Stripe.aiAgent; Stripe.USER_AGENT = { diff --git a/test/stripe.spec.ts b/test/stripe.spec.ts index f5c46df4e4..f7dbb94f7b 100644 --- a/test/stripe.spec.ts +++ b/test/stripe.spec.ts @@ -412,6 +412,51 @@ describe('Stripe Module', function() { expect(StripeCore.USER_AGENT).to.not.have.property('ai_agent'); expect(StripeCore.USER_AGENT).to.not.have.property('lang_version'); }); + + it('emits claude-code-hint to stderr when CLAUDECODE is set', () => { + const mockPlatform = new NodePlatformFunctions(); + mockPlatform.getEnv = () => ({CLAUDECODE: '1'}); + const written: string[] = []; + mockPlatform.writeStderr = (msg: string) => { + written.push(msg); + }; + + StripeCore.initialize(mockPlatform); + + expect(written).to.have.length(1); + expect(written[0]).to.equal( + '\n' + ); + }); + + it('emits claude-code-hint to stderr when CLAUDE_CODE_CHILD_SESSION is set', () => { + const mockPlatform = new NodePlatformFunctions(); + mockPlatform.getEnv = () => ({CLAUDE_CODE_CHILD_SESSION: 'session-id'}); + const written: string[] = []; + mockPlatform.writeStderr = (msg: string) => { + written.push(msg); + }; + + StripeCore.initialize(mockPlatform); + + expect(written).to.have.length(1); + expect(written[0]).to.equal( + '\n' + ); + }); + + it('does not emit claude-code-hint when no Claude env vars are set', () => { + const mockPlatform = new NodePlatformFunctions(); + mockPlatform.getEnv = () => ({}); + const written: string[] = []; + mockPlatform.writeStderr = (msg: string) => { + written.push(msg); + }; + + StripeCore.initialize(mockPlatform); + + expect(written).to.have.length(0); + }); }); describe('timeout config', () => { From 85e59b8cc381e50914cc2ea277645852ed3eba1d Mon Sep 17 00:00:00 2001 From: akalinin-stripe <86627939+akalinin-stripe@users.noreply.github.com> Date: Fri, 7 Aug 2026 09:25:56 -0700 Subject: [PATCH 4/7] Implement extensibility platform (#2785) * Implement extensibility platform * Extract _parseResponseBody helper * Update comments based on the feedback * Fix integration test for windows --- package.json | 4 + src/RequestSender.ts | 72 +++-- src/StripeEventEmitter.ts | 78 +++++ src/net/EndpointFetchHttpClient.ts | 148 ++++++++++ src/net/FetchHttpClient.ts | 11 +- src/net/HttpClient.ts | 13 + src/net/NodeHttpClient.ts | 5 +- .../ExtensibilityPlatformFunctions.ts | 98 ++++++ src/platform/PlatformFunctions.ts | 36 ++- src/stripe.cjs.extensibility.ts | 44 +++ src/stripe.core.ts | 6 +- src/stripe.esm.extensibility.ts | 15 + test/ExtensibilityPlatformFunctions.spec.ts | 223 ++++++++++++++ test/PackageExports.spec.ts | 24 ++ test/StripeEventEmitter.spec.ts | 109 +++++++ test/net/EndpointFetchHttpClient.spec.ts | 278 ++++++++++++++++++ test/net/HttpClient.spec.ts | 33 +++ tsconfig.cjs.json | 2 +- tsconfig.esm.json | 2 +- 19 files changed, 1147 insertions(+), 54 deletions(-) create mode 100644 src/StripeEventEmitter.ts create mode 100644 src/net/EndpointFetchHttpClient.ts create mode 100644 src/platform/ExtensibilityPlatformFunctions.ts create mode 100644 src/stripe.cjs.extensibility.ts create mode 100644 src/stripe.esm.extensibility.ts create mode 100644 test/ExtensibilityPlatformFunctions.spec.ts create mode 100644 test/PackageExports.spec.ts create mode 100644 test/StripeEventEmitter.spec.ts create mode 100644 test/net/EndpointFetchHttpClient.spec.ts create mode 100644 test/net/HttpClient.spec.ts diff --git a/package.json b/package.json index e99d93b77b..d2920fc42a 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,10 @@ "prepack": "just install && just build" }, "exports": { + "extensibility": { + "import": "./esm/stripe.esm.extensibility.js", + "require": "./cjs/stripe.cjs.extensibility.js" + }, "browser": { "import": "./esm/stripe.esm.worker.js", "require": "./cjs/stripe.cjs.worker.js" diff --git a/src/RequestSender.ts b/src/RequestSender.ts index 5658256f2d..499ee567ea 100644 --- a/src/RequestSender.ts +++ b/src/RequestSender.ts @@ -23,7 +23,11 @@ import { ApiMode, } from './Types.js'; import {RawRequestOptions, RequestOptions} from './lib.js'; -import {HttpClient, HttpClientResponseInterface} from './net/HttpClient.js'; +import { + HttpClient, + HttpClientResponseInterface, + HttpClientRuntimeError, +} from './net/HttpClient.js'; import {Stripe} from './stripe.core.js'; import { jsonStringifyRequestData, @@ -679,37 +683,43 @@ export class RequestSender { )(res); } }) - .catch((error: HttpClientResponseError) => { - if ( - RequestSender._shouldRetry( - null, - requestRetries, - maxRetries, - error - ) - ) { - return retryRequest( - makeRequest, - apiVersion, - headers, - requestRetries - ); - } else { - const isTimeoutError = - error.code && error.code === HttpClient.TIMEOUT_ERROR_CODE; - - return callback( - new StripeConnectionError({ - message: isTimeoutError - ? `Request aborted due to timeout being reached (${timeout}ms)` - : RequestSender._generateConnectionErrorMessage( - requestRetries - ), - detail: error, - }) - ); + .catch( + (error: HttpClientResponseError | HttpClientRuntimeError) => { + if (error instanceof HttpClientRuntimeError) { + return callback(error); + } + + if ( + RequestSender._shouldRetry( + null, + requestRetries, + maxRetries, + error + ) + ) { + return retryRequest( + makeRequest, + apiVersion, + headers, + requestRetries + ); + } else { + const isTimeoutError = + error.code && error.code === HttpClient.TIMEOUT_ERROR_CODE; + + return callback( + new StripeConnectionError({ + message: isTimeoutError + ? `Request aborted due to timeout being reached (${timeout}ms)` + : RequestSender._generateConnectionErrorMessage( + requestRetries + ), + detail: error, + }) + ); + } } - }); + ); }) .catch((e: any) => { throw new StripeError({ diff --git a/src/StripeEventEmitter.ts b/src/StripeEventEmitter.ts new file mode 100644 index 0000000000..0268da13ce --- /dev/null +++ b/src/StripeEventEmitter.ts @@ -0,0 +1,78 @@ +import {RequestEvent, ResponseEvent} from './Types.js'; + +type Listener = (...args: any[]) => any; +type ListenerRegistration = { + listener: Listener; + once: boolean; +}; + +/** + * @private + * (For internal use in stripe-node.) + * Minimal EventEmitter for runtimes without Event or EventTarget. + */ +export class StripeEventEmitter { + private _listeners: Map>; + + constructor() { + this._listeners = new Map(); + } + + on(eventName: string, listener: Listener): void { + this._getListeners(eventName).push({listener, once: false}); + } + + once(eventName: string, listener: Listener): void { + this._getListeners(eventName).push({listener, once: true}); + } + + removeListener(eventName: string, listener: Listener): void { + const listeners = this._listeners.get(eventName); + if (!listeners) { + return; + } + + const index = listeners.findIndex((entry) => entry.listener === listener); + if (index === -1) { + return; + } + + this._removeAt(eventName, index); + } + + emit(eventName: string, data?: RequestEvent | ResponseEvent): boolean { + const listeners = this._listeners.get(eventName); + if (!listeners || listeners.length === 0) { + return false; + } + + for (const entry of [...listeners]) { + if (entry.once) { + this._removeAt(eventName, listeners.indexOf(entry)); + } + entry.listener(data); + } + return true; + } + + private _getListeners(eventName: string): Array { + let listeners = this._listeners.get(eventName); + if (!listeners) { + listeners = []; + this._listeners.set(eventName, listeners); + } + return listeners; + } + + private _removeAt(eventName: string, index: number): void { + const listeners = this._listeners.get(eventName); + if (!listeners || index === -1) { + return; + } + + listeners.splice(index, 1); + if (listeners.length === 0) { + this._listeners.delete(eventName); + } + } +} diff --git a/src/net/EndpointFetchHttpClient.ts b/src/net/EndpointFetchHttpClient.ts new file mode 100644 index 0000000000..2b40d18727 --- /dev/null +++ b/src/net/EndpointFetchHttpClient.ts @@ -0,0 +1,148 @@ +import {RequestHeaders} from '../Types.js'; +import {parseHttpHeaderAsString} from '../utils.js'; +import { + HttpClient, + HttpClientResponse, + HttpClientResponseInterface, + HttpClientRuntimeError, +} from './HttpClient.js'; + +type EndpointFetchRequest = { + endpoint: 'stripe_api'; + path: string; + method: string; + body?: string; + headers?: Record; +}; + +type EndpointFetchResponse = { + status: number; + body?: string | null; +}; + +type EndpointFetchSuccessResponse = EndpointFetchResponse & { + ok: true; +}; + +type EndpointFetchError = Error & { + status?: number; + body?: string | null; +}; + +type EndpointFetch = ( + request: EndpointFetchRequest +) => Promise; + +declare const endpointFetch: EndpointFetch | undefined; + +const STRIPE_API_HOST = 'api.stripe.com'; + +export class EndpointFetchHttpClient extends HttpClient { + /** @override */ + getClientName(): string { + return 'endpointFetch'; + } + + async makeRequest( + host: string, + port: string, + path: string, + method: string, + headers: RequestHeaders, + requestData: string, + protocol: string, + timeout: number + ): Promise { + if (!path.startsWith('/')) { + throw new Error(`Only relative paths are supported, got: "${path}"`); + } + + if (host !== STRIPE_API_HOST) { + throw new HttpClientRuntimeError( + `Stripe: This entrypoint only supports Stripe API requests to ${STRIPE_API_HOST}. Received request for ${host}.` + ); + } + + if (typeof endpointFetch !== 'function') { + throw new HttpClientRuntimeError( + 'Stripe: EndpointFetchHttpClient requires `endpointFetch()` from a Stripe Script runtime or a test mock.' + ); + } + + const methodHasPayload = + method == 'POST' || method == 'PUT' || method == 'PATCH'; + const body = requestData || (methodHasPayload ? '' : undefined); + + const endpointFetchRequest: EndpointFetchRequest = { + endpoint: 'stripe_api', + path, + method, + headers: this._getHeaders(headers), + }; + if (body !== undefined) { + endpointFetchRequest.body = body; + } + + try { + const response = await endpointFetch(endpointFetchRequest); + return new EndpointFetchHttpClientResponse(response); + } catch (e) { + const response = EndpointFetchHttpClient._responseFromError(e); + if (response) { + return new EndpointFetchHttpClientResponse(response); + } + throw e; + } + } + + private _getHeaders(headers: RequestHeaders): Record { + return Object.fromEntries( + Object.entries(headers).map(([key, value]) => [ + key, + parseHttpHeaderAsString(value), + ]) + ); + } + + private static _responseFromError( + error: unknown + ): EndpointFetchResponse | null { + if (!error || typeof error !== 'object') { + return null; + } + + const endpointFetchError = error as EndpointFetchError; + if (typeof endpointFetchError.status !== 'number') { + return null; + } + + return { + status: endpointFetchError.status, + body: endpointFetchError.body ?? '', + }; + } +} + +export class EndpointFetchHttpClientResponse extends HttpClientResponse { + private _res: EndpointFetchResponse; + + constructor(res: EndpointFetchResponse) { + super(res.status, {}); + this._res = res; + } + + getRawResponse(): EndpointFetchResponse { + return this._res; + } + + toStream(streamCompleteCallback: () => void): never { + throw new HttpClientRuntimeError( + 'Stripe: EndpointFetchHttpClient does not support streaming responses.' + ); + } + + toJSON(): Promise { + const body = this._res.body || ''; + return Promise.resolve().then(() => this._parseResponseBody(body)); + } +} diff --git a/src/net/FetchHttpClient.ts b/src/net/FetchHttpClient.ts index a950ef4e38..147b9f9dbf 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -183,16 +183,7 @@ export class FetchHttpClientResponse extends HttpClientResponse } toJSON(): Promise { - return this._res.text().then((text) => { - try { - return JSON.parse(text); - } catch (e) { - if (e instanceof Error) { - (e as any).rawBody = text; - } - throw e; - } - }); + return this._res.text().then((text) => this._parseResponseBody(text)); } static _transformHeadersToObject(headers: Headers): ResponseHeaders { diff --git a/src/net/HttpClient.ts b/src/net/HttpClient.ts index 07a511d00e..78147041fe 100644 --- a/src/net/HttpClient.ts +++ b/src/net/HttpClient.ts @@ -143,4 +143,17 @@ export class HttpClientResponse implements HttpClientResponseInterface { toJSON(): any { throw new Error('toJSON not implemented.'); } + + protected _parseResponseBody(body: string): any { + try { + return JSON.parse(body); + } catch (e) { + if (e instanceof Error) { + (e as any).rawBody = body; + } + throw e; + } + } } + +export class HttpClientRuntimeError extends Error {} diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index 22f5809467..482e4845dc 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -135,11 +135,8 @@ export class NodeHttpClientResponse extends HttpClientResponse }); this._res.once('end', () => { try { - resolve(JSON.parse(response)); + resolve(this._parseResponseBody(response)); } catch (e) { - if (e instanceof Error) { - (e as any).rawBody = response; - } reject(e); } }); diff --git a/src/platform/ExtensibilityPlatformFunctions.ts b/src/platform/ExtensibilityPlatformFunctions.ts new file mode 100644 index 0000000000..c5585941da --- /dev/null +++ b/src/platform/ExtensibilityPlatformFunctions.ts @@ -0,0 +1,98 @@ +import {CryptoProvider} from '../crypto/CryptoProvider.js'; +import {EndpointFetchHttpClient} from '../net/EndpointFetchHttpClient.js'; +import { + FetchHttpClientInterface, + HttpClient, + NodeHttpClientInterface, +} from '../net/HttpClient.js'; +import {StripeEventEmitter} from '../StripeEventEmitter.js'; +import { + BufferedFile, + MultipartRequestData, + RequestAuthenticator, + RequestData, +} from '../Types.js'; +import {PlatformFunctions} from './PlatformFunctions.js'; + +const unsupportedRuntimeError = (method: string, alternative?: string): Error => + new Error( + `Stripe: \`${method}()\` is not available in the extensibility runtime.` + + (alternative ? ` ${alternative}` : '') + ); + +/** + * Platform functions for extensibility scripts. + */ +export class ExtensibilityPlatformFunctions extends PlatformFunctions { + /** @override */ + emitWarning(_warning: string): void { + // Extensibility runtime has no user-visible warning target. + } + + /** @override */ + getEnv(): null { + return null; + } + + /** @override */ + getRuntimeVersion(): null { + return null; + } + + /** @override */ + getDefaultMaxNetworkRetries(): number { + return 0; + } + + /** @override */ + createDefaultAuthenticator(): RequestAuthenticator { + // Authentication is injected automatically. + return (): Promise => Promise.resolve(); + } + + /** @override */ + createEmitter(): StripeEventEmitter { + return new StripeEventEmitter(); + } + + /** @override */ + tryBufferData( + data: MultipartRequestData + ): Promise { + return Promise.resolve(data); + } + + /** @override */ + createDefaultHttpClient(): HttpClient { + return new EndpointFetchHttpClient(); + } + + /** @override */ + createFetchHttpClient(): FetchHttpClientInterface { + throw unsupportedRuntimeError( + 'createFetchHttpClient', + 'Use the default `endpointFetch` HTTP client instead.' + ); + } + + /** @override */ + createNodeHttpClient(): NodeHttpClientInterface { + throw unsupportedRuntimeError( + 'createNodeHttpClient', + 'Use the default `endpointFetch` HTTP client instead.' + ); + } + + /** @override */ + createNodeCryptoProvider(): CryptoProvider { + throw unsupportedRuntimeError('createNodeCryptoProvider'); + } + + /** @override */ + createDefaultCryptoProvider(): CryptoProvider { + throw unsupportedRuntimeError( + 'createDefaultCryptoProvider', + 'Pass an explicit `CryptoProvider` for crypto-dependent helpers.' + ); + } +} diff --git a/src/platform/PlatformFunctions.ts b/src/platform/PlatformFunctions.ts index 4b09960ce9..c6979f9fe0 100644 --- a/src/platform/PlatformFunctions.ts +++ b/src/platform/PlatformFunctions.ts @@ -2,18 +2,28 @@ // eslint-disable-next-line wintertc-compat import * as http from 'http'; import {CryptoProvider} from '../crypto/CryptoProvider.js'; -// TODO(DEVSDK-3113): Remove EventEmitter from shared base class in next major version. -// eslint-disable-next-line wintertc-compat -import {EventEmitter} from 'events'; import {FetchHttpClient} from '../net/FetchHttpClient.js'; import { HttpClient, NodeHttpClientInterface, FetchHttpClientInterface, } from '../net/HttpClient.js'; -import {StripeEmitter} from '../StripeEmitter.js'; import {SubtleCryptoProvider} from '../crypto/SubtleCryptoProvider.js'; -import {MultipartRequestData, RequestData, BufferedFile} from '../Types.js'; +import { + MultipartRequestData, + RequestData, + BufferedFile, + RequestEvent, + ResponseEvent, + RequestAuthenticator, +} from '../Types.js'; + +export interface StripeEmitterInterface { + on(eventName: string, listener: (...args: any[]) => any): void; + once(eventName: string, listener: (...args: any[]) => any): void; + removeListener(eventName: string, listener: (...args: any[]) => any): void; + emit(eventName: string, data?: RequestEvent | ResponseEvent): boolean; +} /** * Interface encapsulating various utility functions whose @@ -68,6 +78,20 @@ export class PlatformFunctions { return null; } + /** + * Returns the default number of network retries for this platform. + */ + getDefaultMaxNetworkRetries(): number { + return 2; + } + + /** + * Returns the default request authenticator for this platform. + */ + createDefaultAuthenticator(): RequestAuthenticator | null { + return null; + } + /** * Generates a v4 UUID. See https://stackoverflow.com/a/2117523 */ @@ -98,7 +122,7 @@ export class PlatformFunctions { /** * Creates an event emitter. */ - createEmitter(): StripeEmitter | EventEmitter { + createEmitter(): StripeEmitterInterface { throw new Error('createEmitter not implemented.'); } diff --git a/src/stripe.cjs.extensibility.ts b/src/stripe.cjs.extensibility.ts new file mode 100644 index 0000000000..9d7d88246e --- /dev/null +++ b/src/stripe.cjs.extensibility.ts @@ -0,0 +1,44 @@ +import {ExtensibilityPlatformFunctions} from './platform/ExtensibilityPlatformFunctions.js'; +import {Stripe} from './stripe.core.js'; +import {StripeConfig} from './lib.js'; + +// Initialize the Stripe class with Extensibility platform functions +Stripe.initialize(new ExtensibilityPlatformFunctions()); + +type StripeCallableConstructor = typeof Stripe & { + (key?: string, config?: StripeConfig): Stripe; + new (key?: string, config?: StripeConfig): Stripe; +}; + +// Callable constructor: supports both `new Stripe()` and `Stripe()` for CJS consumers. +// typeof Stripe provides the construct signature and static members; the intersection +// adds a call signature for backward compatibility. +const StripeConstructor: StripeCallableConstructor = (function( + this: any, + key?: string, + config?: StripeConfig +): Stripe { + // Support calling without `new` + if (!(this instanceof StripeConstructor)) { + return new Stripe(key || '', config); + } + return new Stripe(key || '', config); +} as unknown) as StripeCallableConstructor; + +// Copy all static properties from Stripe to the wrapper +Object.setPrototypeOf(StripeConstructor, Stripe); +Object.setPrototypeOf(StripeConstructor.prototype, Stripe.prototype); + +// Copy static properties explicitly +for (const key of Object.getOwnPropertyNames(Stripe)) { + if (key !== 'length' && key !== 'prototype' && key !== 'name') { + Object.defineProperty(StripeConstructor, key, { + value: (Stripe as any)[key], + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +export = StripeConstructor; diff --git a/src/stripe.core.ts b/src/stripe.core.ts index 397c03141e..91d822ab8a 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -1156,7 +1156,7 @@ export class Stripe { maxNetworkRetries: validateInteger( 'maxNetworkRetries', props.maxNetworkRetries, - 2 + this._platformFunctions.getDefaultMaxNetworkRetries() ), agent: agent, httpClient: @@ -1307,6 +1307,10 @@ export class Stripe { key: string, authenticator: RequestAuthenticator | null ): void { + if (!key && !authenticator) { + authenticator = this._platformFunctions.createDefaultAuthenticator(); + } + if (key && authenticator) { throw new Error("Can't specify both apiKey and authenticator"); } diff --git a/src/stripe.esm.extensibility.ts b/src/stripe.esm.extensibility.ts new file mode 100644 index 0000000000..2c9da87c7f --- /dev/null +++ b/src/stripe.esm.extensibility.ts @@ -0,0 +1,15 @@ +import {ExtensibilityPlatformFunctions} from './platform/ExtensibilityPlatformFunctions.js'; +export {Decimal} from './Decimal.js'; +import {Stripe as StripeCore} from './stripe.core.js'; +import {StripeConfig} from './lib.js'; + +// Initialize the Stripe class with Extensibility platform functions +StripeCore.initialize(new ExtensibilityPlatformFunctions()); + +type ExtensibilityStripeConstructor = typeof StripeCore & { + new (key?: string, config?: StripeConfig): StripeCore; +}; + +const Stripe = StripeCore as ExtensibilityStripeConstructor; +export {Stripe}; +export default Stripe; diff --git a/test/ExtensibilityPlatformFunctions.spec.ts b/test/ExtensibilityPlatformFunctions.spec.ts new file mode 100644 index 0000000000..dd7ca936a2 --- /dev/null +++ b/test/ExtensibilityPlatformFunctions.spec.ts @@ -0,0 +1,223 @@ +import {expect} from 'chai'; +import {RequestEvent, ResponseEvent} from '../src/lib.js'; +import {EndpointFetchHttpClient} from '../src/net/EndpointFetchHttpClient.js'; +import {ExtensibilityPlatformFunctions} from '../src/platform/ExtensibilityPlatformFunctions.js'; +import {NodePlatformFunctions} from '../src/platform/NodePlatformFunctions.js'; +import {StripeEventEmitter} from '../src/StripeEventEmitter.js'; +import {Stripe} from '../src/stripe.core.js'; + +type TestGlobal = typeof globalThis & { + endpointFetch?: (request: { + endpoint: 'stripe_api'; + path: string; + method: string; + body?: string; + headers?: Record; + }) => Promise<{ok: true; status: number; body?: string}>; +}; +type EndpointFetch = NonNullable; +type EndpointFetchRequest = Parameters[0]; +type EndpointFetchError = Error & { + status?: number; + body?: string | null; +}; +type StripeError = Error & { + type?: string; + requestId?: string; +}; +type HookEvent = + | ['request', RequestEvent['method'], RequestEvent['path']] + | ['response', ResponseEvent['status'], ResponseEvent['request_id']]; + +describe('ExtensibilityPlatformFunctions', () => { + const testGlobal = globalThis as TestGlobal; + let platformFunctions: ExtensibilityPlatformFunctions; + let endpointFetch$: EndpointFetch | undefined; + let setTimeout$: typeof globalThis.setTimeout; + + beforeEach(() => { + platformFunctions = new ExtensibilityPlatformFunctions(); + endpointFetch$ = testGlobal.endpointFetch; + setTimeout$ = globalThis.setTimeout; + }); + + afterEach(() => { + Stripe.initialize(new NodePlatformFunctions()); + + if (endpointFetch$ === undefined) { + delete testGlobal.endpointFetch; + } else { + testGlobal.endpointFetch = endpointFetch$; + } + + globalThis.setTimeout = setTimeout$; + }); + + it('uses extensibility runtime defaults', () => { + expect(platformFunctions.getEnv()).to.be.null; + expect(platformFunctions.getRuntimeVersion()).to.be.null; + expect(platformFunctions.getDefaultMaxNetworkRetries()).to.equal(0); + expect(platformFunctions.createDefaultAuthenticator()).to.be.a('function'); + expect(() => platformFunctions.emitWarning('test')).to.not.throw(); + }); + + it('creates a pure-JS emitter', () => { + expect(platformFunctions.createEmitter()).to.be.an.instanceOf( + StripeEventEmitter + ); + }); + + it('creates an EndpointFetchHttpClient by default', () => { + expect(platformFunctions.createDefaultHttpClient()).to.be.an.instanceOf( + EndpointFetchHttpClient + ); + }); + + it('throws clear unsupported-runtime errors for unavailable helpers', () => { + expect(() => platformFunctions.createFetchHttpClient()).to.throw( + /extensibility runtime/ + ); + expect(() => platformFunctions.createNodeHttpClient()).to.throw( + /extensibility runtime/ + ); + expect(() => platformFunctions.createNodeCryptoProvider()).to.throw( + /extensibility runtime/ + ); + expect(() => platformFunctions.createDefaultCryptoProvider()).to.throw( + /explicit `CryptoProvider`/ + ); + }); + + it('constructs, emits hooks, and sends requests with platform auth defaults', async () => { + Stripe.initialize(platformFunctions); + (globalThis as {setTimeout: unknown}).setTimeout = () => { + throw new Error('setTimeout should not be called'); + }; + + const requests: EndpointFetchRequest[] = []; + testGlobal.endpointFetch = (request: EndpointFetchRequest) => { + requests.push(request); + return Promise.resolve({ + ok: true, + status: 200, + body: JSON.stringify({id: 'cus_123', object: 'customer'}), + }); + }; + + const stripe = new Stripe(''); + expect(stripe.getMaxNetworkRetries()).to.equal(0); + expect(() => + stripe.webhooks.generateTestHeaderString({ + payload: '{}', + secret: 'whsec_test', + }) + ).to.throw(/createDefaultCryptoProvider/); + expect(() => + stripe.parseEventNotification('{}', 'bad_header', 'whsec_test') + ).to.throw(/createDefaultCryptoProvider/); + + const events: HookEvent[] = []; + stripe.on('request', (event: RequestEvent) => + events.push(['request', event.method, event.path]) + ); + stripe.once('response', (event: ResponseEvent) => + events.push(['response', event.status, event.request_id]) + ); + + const customer = await stripe.customers.retrieve('cus_123'); + const request = requests[0]; + + expect(customer.id).to.equal('cus_123'); + expect(requests).to.have.length(1); + expect(request.endpoint).to.equal('stripe_api'); + expect(request.method).to.equal('GET'); + expect(request.path).to.equal('/v1/customers/cus_123'); + expect(request.headers?.Authorization).to.be.undefined; + expect(events).to.deep.equal([ + ['request', 'GET', '/v1/customers/cus_123'], + ['response', 200, undefined], + ]); + }); + + it('preserves typed Stripe API errors from endpointFetch rejections', async () => { + Stripe.initialize(platformFunctions); + testGlobal.endpointFetch = () => { + const error: EndpointFetchError = new Error('Stripe API error'); + error.status = 401; + error.body = JSON.stringify({ + error: { + message: 'No API key provided', + type: 'authentication_error', + }, + }); + return Promise.reject(error); + }; + + try { + await new Stripe('').customers.retrieve('cus_123'); + throw new Error('Expected request to fail'); + } catch (err) { + const error = err as StripeError; + expect(error.type).to.equal('StripeAuthenticationError'); + expect(error.message).to.equal('No API key provided'); + expect(error.requestId).to.be.undefined; + } + }); + + it('rejects streaming requests clearly through public SDK requests', async () => { + Stripe.initialize(platformFunctions); + testGlobal.endpointFetch = () => + Promise.resolve({ + ok: true, + status: 200, + body: '{}', + }); + + try { + await new Stripe('').rawRequest('GET', '/v1/customers', undefined, { + streaming: true, + }); + throw new Error('Expected streaming request to fail'); + } catch (err) { + const error = err as StripeError; + expect(error.message).to.equal( + 'Stripe: EndpointFetchHttpClient does not support streaming responses.' + ); + expect(error.type).to.be.undefined; + } + }); + + it('preserves clear runtime errors through public SDK requests', async () => { + Stripe.initialize(platformFunctions); + delete testGlobal.endpointFetch; + + try { + await new Stripe('').customers.retrieve('cus_123'); + throw new Error('Expected missing endpointFetch request to fail'); + } catch (err) { + const error = err as StripeError; + expect(error.message).to.equal( + 'Stripe: EndpointFetchHttpClient requires `endpointFetch()` from a Stripe Script runtime or a test mock.' + ); + expect(error.type).to.be.undefined; + } + + testGlobal.endpointFetch = () => + Promise.reject( + new Error('Should not call endpointFetch for unsupported host') + ); + + try { + await new Stripe('', {host: 'files.stripe.com'}).customers.retrieve( + 'cus_123' + ); + throw new Error('Expected unsupported host request to fail'); + } catch (err) { + const error = err as StripeError; + expect(error.message).to.match( + /only supports Stripe API requests to api\.stripe\.com/ + ); + expect(error.type).to.be.undefined; + } + }); +}); diff --git a/test/PackageExports.spec.ts b/test/PackageExports.spec.ts new file mode 100644 index 0000000000..898e610a76 --- /dev/null +++ b/test/PackageExports.spec.ts @@ -0,0 +1,24 @@ +import * as childProcess from 'child_process'; +import * as path from 'path'; +import {expect} from 'chai'; + +describe('package exports', () => { + it('prefers extensibility over browser when both conditions are present', () => { + const resolved = childProcess + .execFileSync( + process.execPath, + [ + '--conditions=browser', + '--conditions=extensibility', + '-p', + "require.resolve('stripe')", + ], + {cwd: process.cwd()} + ) + .toString() + .trim(); + + expect(path.basename(path.dirname(resolved))).to.equal('cjs'); + expect(path.basename(resolved)).to.equal('stripe.cjs.extensibility.js'); + }); +}); diff --git a/test/StripeEventEmitter.spec.ts b/test/StripeEventEmitter.spec.ts new file mode 100644 index 0000000000..80a3d302c2 --- /dev/null +++ b/test/StripeEventEmitter.spec.ts @@ -0,0 +1,109 @@ +import {expect} from 'chai'; +import {RequestEvent, ResponseEvent} from '../src/lib.js'; +import {StripeEventEmitter} from '../src/StripeEventEmitter.js'; + +const requestEvent: RequestEvent = { + api_version: '2025-01-01', + method: 'GET', + path: '/v1/customers', + request_start_time: 123, +}; +const responseEvent: ResponseEvent = { + ...requestEvent, + status: 200, + request_id: 'req_123', + elapsed: 10, + request_end_time: 133, +}; + +describe('StripeEventEmitter', () => { + it('emits data to listeners registered with on', () => { + const emitter = new StripeEventEmitter(); + const calls: Array = []; + + emitter.on('request', (data) => calls.push(data)); + + expect(emitter.emit('request', requestEvent)).to.equal(true); + expect(calls).to.deep.equal([requestEvent]); + }); + + it('listeners registered via once only fire once', () => { + const emitter = new StripeEventEmitter(); + let calls = 0; + + emitter.once('response', () => { + calls += 1; + }); + + expect(emitter.emit('response', responseEvent)).to.equal(true); + expect(emitter.emit('response', responseEvent)).to.equal(false); + expect(calls).to.equal(1); + }); + + it('removes a once listener before invoking it', () => { + const emitter = new StripeEventEmitter(); + let calls = 0; + + emitter.once('response', () => { + calls += 1; + throw new Error('listener failed'); + }); + + expect(() => emitter.emit('response', responseEvent)).to.throw( + 'listener failed' + ); + expect(emitter.emit('response', responseEvent)).to.equal(false); + expect(calls).to.equal(1); + }); + + it('removes a listener from only the requested event name', () => { + const emitter = new StripeEventEmitter(); + const calls: Array = []; + const listener = (data?: RequestEvent | ResponseEvent): void => { + calls.push(data); + }; + + emitter.on('request', listener); + emitter.on('response', listener); + emitter.removeListener('request', listener); + + expect(emitter.emit('request', requestEvent)).to.equal(false); + expect(emitter.emit('response', responseEvent)).to.equal(true); + expect(calls).to.deep.equal([responseEvent]); + }); + + it('ignores removing listeners for never-registered event names', () => { + const emitter = new StripeEventEmitter(); + + expect(() => emitter.removeListener('request', () => {})).to.not.throw(); + }); + + it('ignores removing listeners that were not registered', () => { + const emitter = new StripeEventEmitter(); + let calls = 0; + + emitter.on('request', () => { + calls += 1; + }); + emitter.removeListener('request', () => {}); + + expect(emitter.emit('request', requestEvent)).to.equal(true); + expect(calls).to.equal(1); + }); + + it('supports the same listener registered more than once', () => { + const emitter = new StripeEventEmitter(); + let calls = 0; + const listener = () => { + calls += 1; + }; + + emitter.on('request', listener); + emitter.once('request', listener); + + expect(emitter.emit('request')).to.equal(true); + expect(calls).to.equal(2); + expect(emitter.emit('request')).to.equal(true); + expect(calls).to.equal(3); + }); +}); diff --git a/test/net/EndpointFetchHttpClient.spec.ts b/test/net/EndpointFetchHttpClient.spec.ts new file mode 100644 index 0000000000..3869aedef3 --- /dev/null +++ b/test/net/EndpointFetchHttpClient.spec.ts @@ -0,0 +1,278 @@ +import {fail} from 'assert'; +import {expect} from 'chai'; +import {EndpointFetchHttpClient} from '../../src/net/EndpointFetchHttpClient.js'; +import {HttpClientRuntimeError} from '../../src/net/HttpClient.js'; +import {RequestHeaders} from '../../src/Types.js'; + +type TestGlobal = typeof globalThis & { + endpointFetch?: (request: { + endpoint: 'stripe_api'; + path: string; + method: string; + body?: string; + headers?: Record; + }) => Promise<{ + ok: true; + status: number; + body?: string; + }>; +}; +type EndpointFetch = NonNullable; +type EndpointFetchRequest = Parameters[0]; +type EndpointFetchError = Error & { + status?: number; + body?: string | null; +}; +type JsonParseError = Error & { + rawBody?: string; +}; +type MakeRequestOptions = { + host?: string; + port?: string; + path?: string; + method?: string; + headers?: RequestHeaders; + requestData?: string; + protocol?: string; + timeout?: number; +}; + +describe('EndpointFetchHttpClient', () => { + const testGlobal = globalThis as TestGlobal; + let endpointFetch$: EndpointFetch | undefined; + let capturedRequest: EndpointFetchRequest | null; + + const makeRequest = (options: MakeRequestOptions = {}) => { + const client = new EndpointFetchHttpClient(); + return client.makeRequest( + options.host ?? 'api.stripe.com', + options.port ?? '443', + options.path ?? '/v1/customers', + options.method ?? 'GET', + options.headers ?? {}, + options.requestData ?? '', + options.protocol ?? 'https', + options.timeout ?? 1000 + ); + }; + + beforeEach(() => { + endpointFetch$ = testGlobal.endpointFetch; + capturedRequest = null; + testGlobal.endpointFetch = (request: EndpointFetchRequest) => { + capturedRequest = request; + return Promise.resolve({ + ok: true, + status: 200, + body: '{"ok":true}', + }); + }; + }); + + afterEach(() => { + if (endpointFetch$ === undefined) { + delete testGlobal.endpointFetch; + } else { + testGlobal.endpointFetch = endpointFetch$; + } + }); + + it('sends method, path, headers, and body to endpointFetch', async () => { + const response = await makeRequest({ + method: 'POST', + headers: {'Stripe-Version': '2025-01-01'}, + requestData: 'description=test', + }); + + expect(capturedRequest).to.deep.equal({ + endpoint: 'stripe_api', + path: '/v1/customers', + method: 'POST', + body: 'description=test', + headers: {'Stripe-Version': '2025-01-01'}, + }); + expect(response.getStatusCode()).to.equal(200); + expect(response.getHeaders()).to.deep.equal({}); + expect(await response.toJSON()).to.deep.equal({ok: true}); + }); + + it('uses an empty string body for payload methods without request data', async () => { + await makeRequest({method: 'POST'}); + + expect(capturedRequest?.body).to.equal(''); + }); + + it('stringifies request header values for endpointFetch', async () => { + await makeRequest({ + headers: { + 'Content-Length': 12, + 'X-Stripe-Client-User-Agent': ['a', 'b'], + }, + }); + + expect(capturedRequest?.headers).to.deep.equal({ + 'Content-Length': '12', + 'X-Stripe-Client-User-Agent': 'a, b', + }); + }); + + it('does not send a body for GET requests without request data', async () => { + await makeRequest(); + + expect(capturedRequest).to.not.have.property('body'); + }); + + it('throws clearly for streams', async () => { + const response = await makeRequest(); + + expect(() => response.toStream(() => {})).to.throw( + HttpClientRuntimeError, + 'Stripe: EndpointFetchHttpClient does not support streaming responses.' + ); + }); + + it('throws with rawBody when JSON parsing fails', async () => { + testGlobal.endpointFetch = () => + Promise.resolve({ + ok: true, + status: 500, + body: '{"a"', + }); + const response = await makeRequest(); + + try { + await response.toJSON(); + fail(); + } catch (e) { + const error = e as JsonParseError; + expect(error.rawBody).to.equal('{"a"'); + } + }); + + it('preserves response-shaped endpointFetch rejections', async () => { + testGlobal.endpointFetch = () => { + const error: EndpointFetchError = new Error('Stripe API error'); + error.status = 401; + error.body = + '{"error":{"message":"No API key provided","type":"authentication_error"}}'; + return Promise.reject(error); + }; + + const response = await makeRequest(); + + expect(response.getStatusCode()).to.equal(401); + expect(response.getHeaders()).to.deep.equal({}); + expect(await response.toJSON()).to.deep.equal({ + error: { + message: 'No API key provided', + type: 'authentication_error', + }, + }); + }); + + it('handles endpointFetch HTTP errors with empty response bodies', async () => { + testGlobal.endpointFetch = () => { + const error: EndpointFetchError = new Error('Stripe API error'); + error.status = 400; + error.body = null; + return Promise.reject(error); + }; + + const response = await makeRequest(); + + expect(response.getStatusCode()).to.equal(400); + try { + await response.toJSON(); + fail(); + } catch (e) { + const error = e as JsonParseError; + expect(error.rawBody).to.equal(''); + } + }); + + it('handles endpointFetch HTTP errors without response bodies', async () => { + testGlobal.endpointFetch = () => { + const error: EndpointFetchError = new Error('Stripe API error'); + error.status = 400; + return Promise.reject(error); + }; + + const response = await makeRequest(); + + expect(response.getStatusCode()).to.equal(400); + try { + await response.toJSON(); + fail(); + } catch (e) { + const error = e as JsonParseError; + expect(error.rawBody).to.equal(''); + } + }); + + it('rethrows endpointFetch transport failures', async () => { + testGlobal.endpointFetch = () => + Promise.reject(new Error('egress unavailable')); + + try { + await makeRequest(); + fail(); + } catch (e) { + const error = e as Error; + expect(error.message).to.equal('egress unavailable'); + } + }); + + it('rethrows non-object endpointFetch failures', async () => { + testGlobal.endpointFetch = () => + new Promise((resolve, reject) => { + // eslint-disable-next-line prefer-promise-reject-errors -- endpointFetch is external, so defensive pass-through covers non-Error rejections. + reject(null); + }); + + try { + await makeRequest(); + fail(); + } catch (e) { + expect(e).to.be.null; + } + }); + + it('throws clearly when endpointFetch is unavailable', async () => { + delete testGlobal.endpointFetch; + + try { + await makeRequest(); + fail(); + } catch (e) { + const error = e as Error; + expect(error.message).to.equal( + 'Stripe: EndpointFetchHttpClient requires `endpointFetch()` from a Stripe Script runtime or a test mock.' + ); + expect(error).to.be.an.instanceOf(HttpClientRuntimeError); + } + }); + + it('throws clearly for unsupported Stripe API hosts', async () => { + try { + await makeRequest({host: 'files.stripe.com', path: '/v1/files'}); + fail(); + } catch (e) { + const error = e as Error; + expect(error.message).to.contain( + 'only supports Stripe API requests to api.stripe.com' + ); + expect(error).to.be.an.instanceOf(HttpClientRuntimeError); + } + expect(capturedRequest).to.be.null; + }); + + it('throws when path is an absolute URL', async () => { + try { + await makeRequest({path: 'https://example.com/steal'}); + fail(); + } catch (e) { + const error = e as Error; + expect(error.message).to.match(/Only relative paths are supported/); + } + }); +}); diff --git a/test/net/HttpClient.spec.ts b/test/net/HttpClient.spec.ts new file mode 100644 index 0000000000..562ace8dec --- /dev/null +++ b/test/net/HttpClient.spec.ts @@ -0,0 +1,33 @@ +import {expect} from 'chai'; +import {HttpClientResponse} from '../../src/net/HttpClient.js'; + +type JsonParseError = SyntaxError & { + rawBody?: string; +}; + +class TestHttpClientResponse extends HttpClientResponse { + parseResponseBody(body: string): any { + return this._parseResponseBody(body); + } +} + +describe('HttpClientResponse', () => { + const response = new TestHttpClientResponse(200, {}); + + it('parses JSON response bodies', () => { + expect(response.parseResponseBody('{"ok":true}')).to.deep.equal({ok: true}); + }); + + it('attaches the raw body to JSON parsing errors', () => { + let error: JsonParseError | undefined; + + try { + response.parseResponseBody('{"ok"'); + } catch (e) { + error = e as JsonParseError; + } + + expect(error).to.be.an.instanceOf(SyntaxError); + expect(error?.rawBody).to.equal('{"ok"'); + }); +}); diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index dc69dc03ac..0bcc04961a 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -17,5 +17,5 @@ "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"], + "exclude": ["./src/stripe.esm.extensibility.ts", "./src/stripe.esm.node.ts", "./src/stripe.esm.worker.ts"], } diff --git a/tsconfig.esm.json b/tsconfig.esm.json index fa4d6abf55..91c55ebeca 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -4,5 +4,5 @@ "outDir": "./esm", "module": "es2022", }, - "exclude": ["./src/stripe.cjs.node.ts", "./src/stripe.cjs.worker.ts"], + "exclude": ["./src/stripe.cjs.extensibility.ts", "./src/stripe.cjs.node.ts", "./src/stripe.cjs.worker.ts"], } From 8c2584b4de566adb870f642eeb180c5920fe27c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:34:17 +0000 Subject: [PATCH 5/7] Bump @babel/core from 7.19.6 to 7.29.7 (#2759) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.19.6 to 7.29.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.7/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-version: 7.29.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Brownman <1231935+xavdid@users.noreply.github.com> --- yarn.lock | 529 ++++++++++++++++++++++-------------------------------- 1 file changed, 216 insertions(+), 313 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1db9e2365e..4b02ed3fe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -17,178 +9,114 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" + integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" + "@babel/helper-validator-identifier" "^7.29.7" + js-tokens "^4.0.0" + picocolors "^1.1.1" -"@babel/compat-data@^7.19.3": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" - integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== +"@babel/compat-data@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" + integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== "@babel/core@^7.7.5": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" - integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.6" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helpers" "^7.19.4" - "@babel/parser" "^7.19.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" - convert-source-map "^1.7.0" + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" + integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-compilation-targets" "^7.29.7" + "@babel/helper-module-transforms" "^7.29.7" + "@babel/helpers" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" - integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== - dependencies: - "@babel/types" "^7.19.4" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" - integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== - dependencies: - "@babel/compat-data" "^7.19.3" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" - integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.19.4" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" - -"@babel/helper-simple-access@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" - integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== - dependencies: - "@babel/types" "^7.19.4" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3" + integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ== + dependencies: + "@babel/parser" "^7.29.7" + "@babel/types" "^7.29.7" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042" + integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g== + dependencies: + "@babel/compat-data" "^7.29.7" + "@babel/helper-validator-option" "^7.29.7" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b" + integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA== + +"@babel/helper-module-imports@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396" + integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g== + dependencies: + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" + +"@babel/helper-module-transforms@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae" + integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== + dependencies: + "@babel/helper-module-imports" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + "@babel/traverse" "^7.29.7" + +"@babel/helper-string-parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" + integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== + +"@babel/helper-validator-identifier@^7.18.6": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a" + integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw== -"@babel/helpers@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" - integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== +"@babel/helpers@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607" + integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.4" - "@babel/types" "^7.19.4" + "@babel/template" "^7.29.7" + "@babel/types" "^7.29.7" -"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": +"@babel/highlight@^7.10.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== @@ -197,76 +125,42 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.18.10", "@babel/parser@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" - integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/template@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" - integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" +"@babel/parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" + integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== + dependencies: + "@babel/types" "^7.29.7" + +"@babel/template@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" + integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/types" "^7.29.7" + +"@babel/traverse@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d" + integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-globals" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/types" "^7.29.7" + debug "^4.3.1" -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== +"@babel/types@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" + integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -320,24 +214,23 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -347,12 +240,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== @@ -362,6 +250,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -370,22 +263,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.17": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -719,6 +604,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +baseline-browser-mapping@^2.10.12: + version "2.10.38" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz#c84d093c4bf7325c5053c279d90f153c66526042" + integrity sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -744,15 +634,16 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.21.3: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.24.0: + version "4.28.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" caching-transform@^4.0.0: version "4.0.0" @@ -787,10 +678,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001400: - version "1.0.30001423" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz#57176d460aa8cd85ee1a72016b961eb9aca55d91" - integrity sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ== +caniuse-lite@^1.0.30001782: + version "1.0.30001799" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55" + integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw== chai-as-promised@~7.1.1: version "7.1.1" @@ -812,7 +703,7 @@ chai@^4.3.6: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -916,6 +807,11 @@ convert-source-map@^1.7.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -1031,10 +927,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +electron-to-chromium@^1.5.328: + version "1.5.375" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz#54a9a616dc2b3765e7263d98d14c2135408954d9" + integrity sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q== emoji-regex@^8.0.0: version "8.0.0" @@ -1123,6 +1019,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1535,11 +1436,6 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^13.6.0, globals@^13.9.0: version "13.17.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" @@ -1943,10 +1839,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-schema-traverse@^0.4.1: version "0.4.1" @@ -1975,7 +1871,7 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -json5@^2.2.1: +json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -2036,6 +1932,13 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2194,10 +2097,10 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.36: + version "2.0.48" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.48.tgz#4da73d040ada751fc9959d993f27de48792e3b7d" + integrity sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -2377,10 +2280,10 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -2540,7 +2443,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -semver@^6.0.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -2750,11 +2653,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2871,13 +2769,13 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -update-browserslist-db@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1" @@ -3014,6 +2912,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 3616934621a509e66221b40f3468e3a399bb627d Mon Sep 17 00:00:00 2001 From: David Brownman <1231935+xavdid@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:19:52 -0700 Subject: [PATCH 6/7] add event notif object tests (#2806) --- test/stripe.spec.ts | 2 ++ testProjects/types/typescriptTest.ts | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/stripe.spec.ts b/test/stripe.spec.ts index f7dbb94f7b..c707c8bf0a 100644 --- a/test/stripe.spec.ts +++ b/test/stripe.spec.ts @@ -835,6 +835,7 @@ describe('Stripe Module', function() { it('can parse event from JSON payload', () => { const jsonPayload = { + object: 'v2.core.event', type: 'account.created', data: 'hello', related_object: {id: '123', url: 'hello_again'}, @@ -846,6 +847,7 @@ describe('Stripe Module', function() { }); const event = stripe.parseEventNotification(payload, header, secret); + expect(event.object).to.equal('v2.core.event'); expect(event.type).to.equal(jsonPayload.type); expect(event.data).to.equal(jsonPayload.data); expect(event.related_object.id).to.equal(jsonPayload.related_object.id); diff --git a/testProjects/types/typescriptTest.ts b/testProjects/types/typescriptTest.ts index 7f3c77729b..604677ca18 100644 --- a/testProjects/types/typescriptTest.ts +++ b/testProjects/types/typescriptTest.ts @@ -359,6 +359,8 @@ const v2ContextObj: Stripe.StripeContextType | undefined = v2EventNotif.context; async (): Promise => { // parsing event notifications const eventNotification = stripe.parseEventNotification('', '', ''); + // literal type, so this is really checking the (purported) value + eventNotification.object === 'v2.core.event'; if (eventNotification.type === 'v1.billing.meter.error_report_triggered') { eventNotification.related_object; @@ -426,8 +428,9 @@ event = stripe.webhooks.constructEventWithoutVerification('payload'); event = stripe.constructEventWithoutVerification('payload'); // parseEventNotificationWithoutVerification on client -const _notificationWV: Stripe.V2.Core.EventNotification = - stripe.parseEventNotificationWithoutVerification('payload'); +const _notificationWV: Stripe.V2.Core.EventNotification = stripe.parseEventNotificationWithoutVerification( + 'payload' +); // Verify that nested types with names matching imported types resolve correctly. // e.g. Checkout.Session.TotalDetails.Breakdown.Discount.discount should be From 65d99a2b76d0786d7cec8544920affadccc8b670 Mon Sep 17 00:00:00 2001 From: David Brownman Date: Mon, 10 Aug 2026 15:12:26 -0700 Subject: [PATCH 7/7] Bump version to 22.5.0 --- CHANGELOG.md | 11 +++++++++++ VERSION | 2 +- package.json | 2 +- src/stripe.core.ts | 2 +- src/stripe.esm.node.ts | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f00c292f0..31a12dab20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 22.5.0 - 2026-08-10 +* [#2805](https://github.com/stripe/stripe-node/pull/2805) Emit Claude Code plugin hint at module load time + - Emits new Claude Code plugin hint when `CLAUDECODE` or `CLAUDE_CODE_CHILD_SESSION` environment variables are detected. +* [#2794](https://github.com/stripe/stripe-node/pull/2794) add/adjust event parsing helpers + + - Added methods that return their respective `Event`/`EventNotification` objects without verifying authenticity. Use them when you've previously verified an event (e.g. you verified, put the event in a queue, and are now processing). Supports events from [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge) and [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) natively. + - `stripe.webhooks.constructEventWithoutVerification(payload)` + - `stripe.constructEventWithoutVerification(payload)` + - `stripe.parseEventNotificationWithoutVerification(payload)` +* [#2799](https://github.com/stripe/stripe-node/pull/2799) Add `stripe.major_api_version` constant + ## 22.4.0 - 2026-07-29 This release changes the pinned API version to 2026-07-29.dahlia. diff --git a/VERSION b/VERSION index 58a1f0907f..d1c5363feb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -22.4.0 +22.5.0 diff --git a/package.json b/package.json index d2920fc42a..3d98da8bda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stripe", - "version": "22.4.0", + "version": "22.5.0", "description": "Stripe API wrapper", "keywords": [ "stripe", diff --git a/src/stripe.core.ts b/src/stripe.core.ts index 91d822ab8a..9a119d35f9 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -957,7 +957,7 @@ const defaultRequestSenderFactory: RequestSenderFactory = (stripe) => new RequestSender(stripe, StripeResource.MAX_BUFFERED_REQUEST_METRICS); export class Stripe { - static PACKAGE_VERSION = '22.4.0'; + static PACKAGE_VERSION = '22.5.0'; static API_VERSION: typeof ApiVersion = ApiVersion; /** * The major API version that this SDK uses. Objects retrieved using the same diff --git a/src/stripe.esm.node.ts b/src/stripe.esm.node.ts index c97b1146a0..2aea13c930 100644 --- a/src/stripe.esm.node.ts +++ b/src/stripe.esm.node.ts @@ -958,7 +958,7 @@ const defaultRequestSenderFactory: RequestSenderFactory = (stripe) => new RequestSender(stripe, StripeResource.MAX_BUFFERED_REQUEST_METRICS); export class Stripe { - static PACKAGE_VERSION = '22.4.0'; + static PACKAGE_VERSION = '22.5.0'; static API_VERSION: typeof ApiVersion = ApiVersion; /** * The major API version that this SDK uses. Objects retrieved using the same