From d5f521f729eb1345e1272310886c3f302c0cefcf Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 2 Oct 2023 20:08:53 -0500 Subject: [PATCH 01/30] feat(clerk-js): Spike out telemetry collection --- packages/clerk-js/src/core/clerk.ts | 13 ++++ packages/clerk-js/src/core/telemetry.ts | 94 +++++++++++++++++++++++++ packages/types/src/clerk.ts | 5 ++ 3 files changed, 112 insertions(+) create mode 100644 packages/clerk-js/src/core/telemetry.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 79cc54dd11f..58b28eac61f 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -40,6 +40,7 @@ import type { PublishableKey, RedirectOptions, Resources, + SDKData, SetActiveParams, SignInProps, SignInRedirectOptions, @@ -111,6 +112,7 @@ import { OrganizationMembership, } from './resources/internal'; import { SessionCookieService } from './services'; +import { TelemetryCollector } from './telemetry'; import { warnings } from './warnings'; export type ClerkCoreBroadcastChannelEvent = { type: 'signout' }; @@ -145,6 +147,7 @@ export default class Clerk implements ClerkInterface { public organization?: OrganizationResource | null; public user?: UserResource | null; public __internal_country?: string | null; + public __internal_sdk?: SDKData; public readonly frontendApi: string; public readonly publishableKey?: string; @@ -156,6 +159,7 @@ export default class Clerk implements ClerkInterface { #devBrowserHandler: DevBrowserHandler | null = null; #environment?: EnvironmentResource | null; #fapiClient: FapiClient; + #telemetry: TelemetryCollector; #instanceType: InstanceType; #isReady = false; #lastOrganizationInvitation: OrganizationInvitationResource | null = null; @@ -265,6 +269,7 @@ export default class Clerk implements ClerkInterface { this.#instanceType = instanceType; } this.#fapiClient = createFapiClient(this); + this.#telemetry = new TelemetryCollector(this); BaseResource.clerk = this; } @@ -397,6 +402,7 @@ export default class Clerk implements ClerkInterface { props, }), ); + this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignIn' }); }; public unmountSignIn = (node: HTMLDivElement): void => { @@ -1105,6 +1111,13 @@ export default class Clerk implements ClerkInterface { } }; + __internal_setSDK = (name: string, version: string) => { + this.__internal_sdk = { + name, + version, + }; + }; + updateClient = (newClient: ClientResource): void => { if (!this.client) { // This is the first time client is being diff --git a/packages/clerk-js/src/core/telemetry.ts b/packages/clerk-js/src/core/telemetry.ts new file mode 100644 index 00000000000..7fcd2606a69 --- /dev/null +++ b/packages/clerk-js/src/core/telemetry.ts @@ -0,0 +1,94 @@ +import type { InstanceType } from '@clerk/types'; + +import type Clerk from './clerk'; + +interface TelemetryEvent { + event: string; + /** + * publishableKey + */ + pk?: string; + /** + * instanceType + */ + it: InstanceType; + /** + * clerkVersion + */ + cv: string; + /** + * SDK + */ + sdk?: string; + /** + * SDK Version + */ + sdkv?: string; + payload: Record; +} + +// TODO: determine some type of debounce/dedupe heuristic to avoid sending excessive events for e.g. a component render +export class TelemetryCollector { + readonly #clerk: Clerk; + + constructor(clerk: Clerk) { + this.#clerk = clerk; + } + + get isEnabled(): boolean { + // TODO: check clerk environment, environment variable + return true; + } + + record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { + if (!this.isEnabled) { + return; + } + + const preparedPayload = this.#preparePayload(event, payload); + this.#logEvent(preparedPayload.event, preparedPayload); + + // fetch(`http://localhost:8787/v0/events`, { + // method: 'POST', + // body: JSON.stringify(preparedPayload), + // mode: 'no-cors', + // headers: { + // 'Content-Type': 'application/json', + // }, + // }).catch(() => void 0); + + // this.#fapiClient + // .request({ + // path: '/v1/events', + // method: 'POST', + // body: JSON.stringify({ + // event, + // payload, + // }), + // }) + // .catch(() => { + // void 0; + // }); + } + + #logEvent(event: TelemetryEvent['event'], payload: Record) { + console.groupCollapsed('[clerk/telemetry]', event); + console.log(payload); + console.groupEnd(); + } + + /** + * Append relevant metadata from the Clerk singleton to the event payload. + */ + #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { + return { + event, + cv: this.#clerk.version, + it: this.#clerk.instanceType, + ...(this.#clerk.publishableKey ? { pk: this.#clerk.publishableKey } : {}), + sdk: this.#clerk.__internal_sdk?.name, + sdkv: this.#clerk.__internal_sdk?.version, + payload, + }; + } +} diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 2000815da37..185017038d1 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -22,6 +22,11 @@ import type { DeepPartial, DeepSnakeToCamel } from './utils'; export type InstanceType = 'production' | 'development'; +export type SDKData = { + name: string; + version: string; +}; + export type ListenerCallback = (emission: Resources) => void; export type UnsubscribeCallback = () => void; export type BeforeEmitCallback = (session?: ActiveSessionResource | null) => void | Promise; From 986a29dd0bd9c95cb365008fbec14c4272856c59 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 2 Oct 2023 20:45:10 -0500 Subject: [PATCH 02/30] feat(clerk-js): Add sampling rate and verbose option --- packages/clerk-js/src/core/telemetry.ts | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/clerk-js/src/core/telemetry.ts b/packages/clerk-js/src/core/telemetry.ts index 7fcd2606a69..c90b7fd9262 100644 --- a/packages/clerk-js/src/core/telemetry.ts +++ b/packages/clerk-js/src/core/telemetry.ts @@ -2,7 +2,18 @@ import type { InstanceType } from '@clerk/types'; import type Clerk from './clerk'; -interface TelemetryEvent { +type TelemetryCollectorConfig = { + /** + * Sampling rate, 0-1 + */ + samplingRate: number; + /** + * Determines whether or not events will be logged to the console. + */ + verbose: boolean; +}; + +type TelemetryEvent = { event: string; /** * publishableKey @@ -25,14 +36,22 @@ interface TelemetryEvent { */ sdkv?: string; payload: Record; -} +}; -// TODO: determine some type of debounce/dedupe heuristic to avoid sending excessive events for e.g. a component render +const DEFAULT_CONFIG: TelemetryCollectorConfig = { + samplingRate: 1, + verbose: false, +}; + +// TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render export class TelemetryCollector { readonly #clerk: Clerk; - constructor(clerk: Clerk) { + #config: TelemetryCollectorConfig; + + constructor(clerk: Clerk, config: Partial = {}) { this.#clerk = clerk; + this.#config = Object.assign(DEFAULT_CONFIG, config); } get isEnabled(): boolean { @@ -48,6 +67,10 @@ export class TelemetryCollector { const preparedPayload = this.#preparePayload(event, payload); this.#logEvent(preparedPayload.event, preparedPayload); + if (!this.#shouldRecord()) { + return; + } + // fetch(`http://localhost:8787/v0/events`, { // method: 'POST', // body: JSON.stringify(preparedPayload), @@ -71,7 +94,15 @@ export class TelemetryCollector { // }); } + #shouldRecord(): boolean { + return Math.random() <= this.#config.samplingRate; + } + #logEvent(event: TelemetryEvent['event'], payload: Record) { + if (!this.#config.verbose) { + return; + } + console.groupCollapsed('[clerk/telemetry]', event); console.log(payload); console.groupEnd(); From ce862eeb9d8edd969a8bbf39ae20c6abb5adc5a9 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Oct 2023 18:07:44 -0500 Subject: [PATCH 03/30] feat(shared): Move TelemtryCollector into shared, make it isomorphic --- packages/clerk-js/src/core/clerk.ts | 13 +- packages/clerk-js/src/core/telemetry.ts | 125 ------------- packages/shared/src/hooks/contexts.tsx | 2 + .../shared/src/hooks/createContextAndHook.ts | 2 + packages/shared/src/index.ts | 5 + packages/shared/src/telemetry/index.ts | 173 ++++++++++++++++++ 6 files changed, 192 insertions(+), 128 deletions(-) delete mode 100644 packages/clerk-js/src/core/telemetry.ts create mode 100644 packages/shared/src/telemetry/index.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 58b28eac61f..a4d3cd8c73d 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -13,6 +13,7 @@ import { parsePublishableKey, proxyUrlToAbsoluteURL, stripScheme, + TelemetryCollector, } from '@clerk/shared'; import type { ActiveSessionResource, @@ -112,7 +113,6 @@ import { OrganizationMembership, } from './resources/internal'; import { SessionCookieService } from './services'; -import { TelemetryCollector } from './telemetry'; import { warnings } from './warnings'; export type ClerkCoreBroadcastChannelEvent = { type: 'signout' }; @@ -269,7 +269,14 @@ export default class Clerk implements ClerkInterface { this.#instanceType = instanceType; } this.#fapiClient = createFapiClient(this); - this.#telemetry = new TelemetryCollector(this); + this.#telemetry = new TelemetryCollector({ + clerkVersion: Clerk.version, + verbose: true, + samplingRate: 1, + publishableKey: key, + sdk: 'clerk-js', + sdkVersion: Clerk.version, + }); BaseResource.clerk = this; } @@ -402,7 +409,7 @@ export default class Clerk implements ClerkInterface { props, }), ); - this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignIn' }); + this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignIn', appearanceProp: Boolean(props?.appearance) }); }; public unmountSignIn = (node: HTMLDivElement): void => { diff --git a/packages/clerk-js/src/core/telemetry.ts b/packages/clerk-js/src/core/telemetry.ts deleted file mode 100644 index c90b7fd9262..00000000000 --- a/packages/clerk-js/src/core/telemetry.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { InstanceType } from '@clerk/types'; - -import type Clerk from './clerk'; - -type TelemetryCollectorConfig = { - /** - * Sampling rate, 0-1 - */ - samplingRate: number; - /** - * Determines whether or not events will be logged to the console. - */ - verbose: boolean; -}; - -type TelemetryEvent = { - event: string; - /** - * publishableKey - */ - pk?: string; - /** - * instanceType - */ - it: InstanceType; - /** - * clerkVersion - */ - cv: string; - /** - * SDK - */ - sdk?: string; - /** - * SDK Version - */ - sdkv?: string; - payload: Record; -}; - -const DEFAULT_CONFIG: TelemetryCollectorConfig = { - samplingRate: 1, - verbose: false, -}; - -// TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render -export class TelemetryCollector { - readonly #clerk: Clerk; - - #config: TelemetryCollectorConfig; - - constructor(clerk: Clerk, config: Partial = {}) { - this.#clerk = clerk; - this.#config = Object.assign(DEFAULT_CONFIG, config); - } - - get isEnabled(): boolean { - // TODO: check clerk environment, environment variable - return true; - } - - record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { - if (!this.isEnabled) { - return; - } - - const preparedPayload = this.#preparePayload(event, payload); - this.#logEvent(preparedPayload.event, preparedPayload); - - if (!this.#shouldRecord()) { - return; - } - - // fetch(`http://localhost:8787/v0/events`, { - // method: 'POST', - // body: JSON.stringify(preparedPayload), - // mode: 'no-cors', - // headers: { - // 'Content-Type': 'application/json', - // }, - // }).catch(() => void 0); - - // this.#fapiClient - // .request({ - // path: '/v1/events', - // method: 'POST', - // body: JSON.stringify({ - // event, - // payload, - // }), - // }) - // .catch(() => { - // void 0; - // }); - } - - #shouldRecord(): boolean { - return Math.random() <= this.#config.samplingRate; - } - - #logEvent(event: TelemetryEvent['event'], payload: Record) { - if (!this.#config.verbose) { - return; - } - - console.groupCollapsed('[clerk/telemetry]', event); - console.log(payload); - console.groupEnd(); - } - - /** - * Append relevant metadata from the Clerk singleton to the event payload. - */ - #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { - return { - event, - cv: this.#clerk.version, - it: this.#clerk.instanceType, - ...(this.#clerk.publishableKey ? { pk: this.#clerk.publishableKey } : {}), - sdk: this.#clerk.__internal_sdk?.name, - sdkv: this.#clerk.__internal_sdk?.version, - payload, - }; - } -} diff --git a/packages/shared/src/hooks/contexts.tsx b/packages/shared/src/hooks/contexts.tsx index 8c18e278b08..d70c53f3e0f 100644 --- a/packages/shared/src/hooks/contexts.tsx +++ b/packages/shared/src/hooks/contexts.tsx @@ -1,3 +1,5 @@ +'use client'; + import type { ActiveSessionResource, ClientResource, diff --git a/packages/shared/src/hooks/createContextAndHook.ts b/packages/shared/src/hooks/createContextAndHook.ts index b7df23501fc..a9ac5bf37b2 100644 --- a/packages/shared/src/hooks/createContextAndHook.ts +++ b/packages/shared/src/hooks/createContextAndHook.ts @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; export function assertContextExists(contextVal: unknown, msgOrCtx: string | React.Context): asserts contextVal { diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index e2fbe82050d..01c16ecc4a5 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -11,6 +11,11 @@ export * from './errors/Error'; */ export * from './utils'; +/** + * Telemetry + */ +export * from './telemetry'; + /** * Hooks */ diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts new file mode 100644 index 00000000000..883b88cb918 --- /dev/null +++ b/packages/shared/src/telemetry/index.ts @@ -0,0 +1,173 @@ +import type { InstanceType } from '@clerk/types'; + +import { parsePublishableKey } from '../utils/keys'; + +type TelemetryCollectorOptions = { + /** + * Sampling rate, 0-1 + */ + samplingRate?: number; + /** + * Determines whether or not events will be logged to the console. + */ + verbose?: boolean; + /** + * The publishableKey to associate with the collected events. + */ + publishableKey: string; + /** + * The current clerk-js version. + */ + clerkVersion?: string; + /** + * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. + */ + sdk: string; + /** + * The version of the SDK being used. + */ + sdkVersion: string; +}; + +type TelemetryCollectorConfig = Pick & { endpoint: string }; + +type TelemetryMetadata = Required< + Pick +> & { + /** + * The instance type, derived from the provided publishableKey. + */ + instanceType: InstanceType; +}; + +type TelemetryEvent = { + event: string; + /** + * publishableKey + */ + pk?: string; + /** + * instanceType + */ + it: InstanceType; + /** + * clerkVersion + */ + cv: string; + /** + * SDK + */ + sdk?: string; + /** + * SDK Version + */ + sdkv?: string; + payload: Record; +}; + +const DEFAULT_CONFIG: Partial> = { + samplingRate: 1, + verbose: false, +}; + +// TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render +export class TelemetryCollector { + #config: Required; + #metadata: TelemetryMetadata = {} as TelemetryMetadata; + + constructor(options: TelemetryCollectorOptions) { + this.#config = { + ...DEFAULT_CONFIG, + samplingRate: options.samplingRate, + verbose: options.verbose, + } as Required; + + if (!options.clerkVersion && typeof window === 'undefined') { + // N/A in a server environment + this.#metadata.clerkVersion = ''; + } else { + this.#metadata.clerkVersion = options.clerkVersion ?? ''; + } + + this.#metadata.sdk = options.sdk; + this.#metadata.sdkVersion = options.sdkVersion; + + this.#metadata.publishableKey = options.publishableKey; + + const parsedKey = parsePublishableKey(options.publishableKey); + if (parsedKey) { + this.#metadata.instanceType = parsedKey.instanceType; + } + + // TODO: determine FAPI endpoint from the publishable key + this.#config.endpoint = 'https://telemetry-service-staging.bryce-clerk.workers.dev'; + // this.#config.endpoint = 'http://localhost:8787'; + } + + get isEnabled(): boolean { + if (this.#metadata.instanceType !== 'development') { + return false; + } + + // TODO: check clerk environment / environment variable + return true; + } + + record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { + if (!this.isEnabled) { + return; + } + + const preparedPayload = this.#preparePayload(event, payload); + this.#logEvent(preparedPayload.event, preparedPayload); + + if (!this.#shouldRecord()) { + return; + } + + fetch(new URL('/v0/events', this.#config.endpoint), { + method: 'POST', + body: JSON.stringify(preparedPayload), + mode: 'no-cors', + headers: { + 'Content-Type': 'application/json', + }, + }).catch(() => void 0); + } + + #shouldRecord(): boolean { + return Math.random() <= this.#config.samplingRate; + } + + /** + * If running in verbose mode, log the event and its payload to the console. + */ + #logEvent(event: TelemetryEvent['event'], payload: Record) { + if (!this.#config.verbose) { + return; + } + + if (typeof console.groupCollapsed !== 'undefined') { + console.groupCollapsed('[clerk/telemetry]', event); + console.log(payload); + console.groupEnd(); + } else { + console.log('[clerk/telemetry]', event, payload); + } + } + + /** + * Append relevant metadata from the Clerk singleton to the event payload. + */ + #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { + return { + event, + cv: this.#metadata.clerkVersion, + it: this.#metadata.instanceType, + sdk: this.#metadata.sdk, + sdkv: this.#metadata.sdkVersion, + ...(this.#metadata.publishableKey ? { pk: this.#metadata.publishableKey } : {}), + payload, + }; + } +} From 5a84f430776e6bb3d67c3f004b72e3c93e8388ce Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Oct 2023 18:08:15 -0500 Subject: [PATCH 04/30] feat(nextjs): Add telemtry collection for authMiddleware --- package-lock.json | 1 + packages/nextjs/package.json | 1 + packages/nextjs/src/global.d.ts | 2 ++ packages/nextjs/src/server/authMiddleware.ts | 18 ++++++++++++++++++ 4 files changed, 22 insertions(+) diff --git a/package-lock.json b/package-lock.json index e5cbe62b0bb..157a6143720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37005,6 +37005,7 @@ "@clerk/backend": "0.30.1", "@clerk/clerk-react": "4.26.1", "@clerk/clerk-sdk-node": "4.12.10", + "@clerk/shared": "^0.24.1", "@clerk/types": "3.53.0", "path-to-regexp": "6.2.1", "tslib": "2.4.1" diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 6395f6120fd..29498ba261b 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -78,6 +78,7 @@ "@clerk/backend": "0.30.1", "@clerk/clerk-react": "4.26.1", "@clerk/clerk-sdk-node": "4.12.10", + "@clerk/shared": "0.24.1", "@clerk/types": "3.53.0", "path-to-regexp": "6.2.1", "tslib": "2.4.1" diff --git a/packages/nextjs/src/global.d.ts b/packages/nextjs/src/global.d.ts index b4321ec3756..571f09e5e7b 100644 --- a/packages/nextjs/src/global.d.ts +++ b/packages/nextjs/src/global.d.ts @@ -6,6 +6,8 @@ declare global { } declare global { + const PACKAGE_NAME: string; + const PACKAGE_VERSION: string; namespace NodeJS { interface ProcessEnv { NEXT_PUBLIC_CLERK_FRONTEND_API: string | undefined; diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index 77fee7de8f0..d3f8a31fa49 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -1,6 +1,7 @@ /* eslint-disable turbo/no-undeclared-env-vars */ import type { AuthObject, RequestState } from '@clerk/backend'; import { buildRequestUrl, constants } from '@clerk/backend'; +import { TelemetryCollector } from '@clerk/shared'; import type Link from 'next/link'; import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; @@ -9,6 +10,7 @@ import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from ' import { withLogger } from '../utils/debugLogger'; import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest'; import { SECRET_KEY } from './clerkClient'; +import { PUBLISHABLE_KEY } from './constants'; import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser'; import { clockSkewDetected, @@ -27,6 +29,14 @@ import { setRequestHeadersOnNextResponse, } from './utils'; +const telemetry = new TelemetryCollector({ + verbose: true, + samplingRate: 1, + publishableKey: PUBLISHABLE_KEY, + sdk: PACKAGE_NAME, + sdkVersion: PACKAGE_VERSION, +}); + type WithPathPatternWildcard = `${T & string}(.*)`; type NextTypedRoute['0']['href']> = T extends string ? T : never; @@ -149,6 +159,14 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { const isApiRoute = createApiRoutes(apiRoutes); const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute, params); + telemetry.record('METHOD_CALLED', { + method: 'authMiddleware', + publicRoutes: Boolean(publicRoutes), + ignoredRoutes: Boolean(ignoredRoutes), + beforeAuth: Boolean(beforeAuth), + afterAuth: Boolean(afterAuth), + }); + return withLogger('authMiddleware', logger => async (_req: NextRequest, evt: NextFetchEvent) => { if (options.debug) { logger.enable(); From 9e7ddb05717100065700d61dcd643353a54f9a07 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 5 Oct 2023 13:32:07 -0500 Subject: [PATCH 05/30] feat(shared): Support passing multiple events to the telemetry endpoint --- packages/clerk-js/src/core/clerk.ts | 1 + packages/shared/src/telemetry/index.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index a4d3cd8c73d..30f7a6401ff 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -431,6 +431,7 @@ export default class Clerk implements ClerkInterface { props, }), ); + this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignUp', appearanceProp: Boolean(props?.appearance) }); }; public unmountSignUp = (node: HTMLDivElement): void => { diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index 883b88cb918..eb904aadc81 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -100,8 +100,8 @@ export class TelemetryCollector { } // TODO: determine FAPI endpoint from the publishable key - this.#config.endpoint = 'https://telemetry-service-staging.bryce-clerk.workers.dev'; - // this.#config.endpoint = 'http://localhost:8787'; + // this.#config.endpoint = 'https://telemetry-service-staging.bryce-clerk.workers.dev'; + this.#config.endpoint = 'http://localhost:8787'; } get isEnabled(): boolean { @@ -127,7 +127,10 @@ export class TelemetryCollector { fetch(new URL('/v0/events', this.#config.endpoint), { method: 'POST', - body: JSON.stringify(preparedPayload), + // TODO: We send an array here with that idea that we can eventually send multiple events. + body: JSON.stringify({ + events: [preparedPayload], + }), mode: 'no-cors', headers: { 'Content-Type': 'application/json', From 60a020e230a428808f66021d00029ac5efdd931b Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 5 Oct 2023 15:40:22 -0500 Subject: [PATCH 06/30] feat(shared): Abstract fetch into sendEvent --- packages/shared/src/telemetry/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index eb904aadc81..a7c8becbd6c 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -119,17 +119,26 @@ export class TelemetryCollector { } const preparedPayload = this.#preparePayload(event, payload); + this.#logEvent(preparedPayload.event, preparedPayload); if (!this.#shouldRecord()) { return; } + this.#sendEvent(preparedPayload); + } + + #shouldRecord(): boolean { + return Math.random() <= this.#config.samplingRate; + } + + #sendEvent(event: TelemetryEvent): void { fetch(new URL('/v0/events', this.#config.endpoint), { method: 'POST', // TODO: We send an array here with that idea that we can eventually send multiple events. body: JSON.stringify({ - events: [preparedPayload], + events: [event], }), mode: 'no-cors', headers: { @@ -138,10 +147,6 @@ export class TelemetryCollector { }).catch(() => void 0); } - #shouldRecord(): boolean { - return Math.random() <= this.#config.samplingRate; - } - /** * If running in verbose mode, log the event and its payload to the console. */ From 5f1968fe31a16912ffa17d841ba724e638400acf Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 18 Oct 2023 22:33:34 -0500 Subject: [PATCH 07/30] feat(shared): Refactor SDK metadata handling to get accurate value --- packages/clerk-js/src/core/clerk.ts | 166 ++++++++++++++++++++----- packages/react/src/isomorphicClerk.ts | 28 ++++- packages/shared/src/telemetry/index.ts | 41 ++++-- 3 files changed, 194 insertions(+), 41 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 30f7a6401ff..271666bcf4e 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -28,6 +28,7 @@ import type { DomainOrProxyUrl, EnvironmentJSON, EnvironmentResource, + HandleEmailLinkVerificationParams, HandleMagicLinkVerificationParams, HandleOAuthCallbackParams, InstanceType, @@ -41,7 +42,7 @@ import type { PublishableKey, RedirectOptions, Resources, - SDKData, + SDKMetadata, SetActiveParams, SignInProps, SignInRedirectOptions, @@ -63,7 +64,6 @@ import type { MountComponentRenderer } from '../ui/Components'; import { completeSignUpFlow } from '../ui/components/SignUp/util'; import { appendAsQueryParams, - appendUrlsAsQueryParams, buildURL, createBeforeUnloadTracker, createCookieHandler, @@ -86,6 +86,8 @@ import { sessionExistsAndSingleSessionModeEnabled, setDevBrowserJWTInURL, stripOrigin, + stripSameOrigin, + toURL, validateFrontendApi, windowNavigate, } from '../utils'; @@ -95,6 +97,8 @@ import type { DevBrowserHandler } from './devBrowserHandler'; import createDevBrowserHandler from './devBrowserHandler'; import { clerkErrorInitFailed, + clerkInvalidSignInUrlFormat, + clerkInvalidSignInUrlOrigin, clerkMissingDevBrowserJwt, clerkMissingProxyUrlAndDomain, clerkMissingSignInUrlAsSatellite, @@ -106,6 +110,8 @@ import createFapiClient from './fapiClient'; import { BaseResource, Client, + EmailLinkError, + EmailLinkErrorCode, Environment, MagicLinkError, MagicLinkErrorCode, @@ -141,13 +147,18 @@ const defaultOptions: ClerkOptions = { export default class Clerk implements ClerkInterface { public static mountComponentRenderer?: MountComponentRenderer; + public static version: string = __PKG_VERSION__; + public static sdkMetadata: SDKMetadata = { + name: __PKG_NAME__, + version: __PKG_VERSION__, + }; + public client?: ClientResource; public session?: ActiveSessionResource | null; public organization?: OrganizationResource | null; public user?: UserResource | null; public __internal_country?: string | null; - public __internal_sdk?: SDKData; public readonly frontendApi: string; public readonly publishableKey?: string; @@ -162,7 +173,14 @@ export default class Clerk implements ClerkInterface { #telemetry: TelemetryCollector; #instanceType: InstanceType; #isReady = false; + + /** + * @deprecated Although this being a private field, this is a reminder to drop it with the next major release + */ #lastOrganizationInvitation: OrganizationInvitationResource | null = null; + /** + * @deprecated Although this being a private field, this is a reminder to drop it with the next major release + */ #lastOrganizationMember: OrganizationMembershipResource | null = null; #listeners: Array<(emission: Resources) => void> = []; #options: ClerkOptions = {}; @@ -209,7 +227,15 @@ export default class Clerk implements ClerkInterface { return this.#instanceType; } + get isStandardBrowser(): boolean { + return this.#options.standardBrowser || false; + } + + /** + * @deprecated This getter is no longer used internally and will be dropped in the next major version + */ get experimental_canUseCaptcha(): boolean | undefined { + deprecated('experimental_canUseCaptcha', 'This is will be dropped in the next major version'); if (this.#environment) { return ( this.#environment.userSettings.signUp.captcha_enabled && @@ -221,7 +247,11 @@ export default class Clerk implements ClerkInterface { return false; } + /** + * @deprecated This getter is no longer used internally and will be dropped in the next major version + */ get experimental_captchaSiteKey(): string | null { + deprecated('experimental_captchaSiteKey', 'This is will be dropped in the next major version'); if (this.#environment) { return this.#environment.displayConfig.captchaPublicKey; } @@ -229,7 +259,11 @@ export default class Clerk implements ClerkInterface { return null; } + /** + * @deprecated This getter is no longer used internally and will be dropped in the next major version + */ get experimental_captchaURL(): string | null { + deprecated('experimental_captchaURL', 'This is will be dropped in the next major version'); if (this.#fapiClient) { return this.#fapiClient .buildUrl({ @@ -249,6 +283,8 @@ export default class Clerk implements ClerkInterface { this.#proxyUrl = options?.proxyUrl; if (isLegacyFrontendApiKey(key)) { + deprecated('frontendApi', 'Use `publishableKey` instead.'); + if (!validateFrontendApi(key)) { errorThrower.throwInvalidFrontendApiError({ key }); } @@ -274,8 +310,6 @@ export default class Clerk implements ClerkInterface { verbose: true, samplingRate: 1, publishableKey: key, - sdk: 'clerk-js', - sdkVersion: Clerk.version, }); BaseResource.clerk = this; } @@ -559,9 +593,6 @@ export default class Clerk implements ClerkInterface { /** * `setActive` can be used to set the active session and/or organization. - * It will eventually replace `setSession`. - * - * @experimental */ public setActive = async ({ session, organization, beforeEmit }: SetActiveParams): Promise => { if (!this.client) { @@ -821,10 +852,16 @@ export default class Clerk implements ClerkInterface { return; }; + /** + * + * @deprecated Use `handleEmailLinkVerification` instead. + */ public handleMagicLinkVerification = async ( params: HandleMagicLinkVerificationParams, customNavigate?: (to: string) => Promise, ): Promise => { + deprecated('handleMagicLinkVerification', 'Use `handleEmailLinkVerification` instead.'); + if (!this.client) { return; } @@ -864,6 +901,49 @@ export default class Clerk implements ClerkInterface { return null; }; + public handleEmailLinkVerification = async ( + params: HandleEmailLinkVerificationParams, + customNavigate?: (to: string) => Promise, + ): Promise => { + if (!this.client) { + return; + } + + const verificationStatus = getClerkQueryParam('__clerk_status'); + if (verificationStatus === 'expired') { + throw new EmailLinkError(EmailLinkErrorCode.Expired); + } else if (verificationStatus !== 'verified') { + throw new EmailLinkError(EmailLinkErrorCode.Failed); + } + + const newSessionId = getClerkQueryParam('__clerk_created_session'); + const { signIn, signUp, sessions } = this.client; + + const shouldCompleteOnThisDevice = sessions.some(s => s.id === newSessionId); + const shouldContinueOnThisDevice = + signIn.status === 'needs_second_factor' || signUp.status === 'missing_requirements'; + + const navigate = (to: string) => + customNavigate && typeof customNavigate === 'function' ? customNavigate(to) : this.navigate(to); + + const redirectComplete = params.redirectUrlComplete ? () => navigate(params.redirectUrlComplete as string) : noop; + const redirectContinue = params.redirectUrl ? () => navigate(params.redirectUrl as string) : noop; + + if (shouldCompleteOnThisDevice) { + return this.setActive({ + session: newSessionId, + beforeEmit: redirectComplete, + }); + } else if (shouldContinueOnThisDevice) { + return redirectContinue(); + } + + if (typeof params.onVerifiedOnOtherDevice === 'function') { + params.onVerifiedOnOtherDevice(); + } + return null; + }; + public handleRedirectCallback = async ( params: HandleOAuthCallbackParams = {}, customNavigate?: (to: string) => Promise, @@ -1103,10 +1183,8 @@ export default class Clerk implements ClerkInterface { return await OrganizationMembership.retrieve(); }; - public getOrganization = async (organizationId: string): Promise => { - return (await OrganizationMembership.retrieve()).find(orgMem => orgMem.organization.id === organizationId) - ?.organization; - }; + public getOrganization = async (organizationId: string): Promise => + Organization.get(organizationId); public updateEnvironment(environment: EnvironmentResource) { this.#environment = environment; @@ -1119,13 +1197,6 @@ export default class Clerk implements ClerkInterface { } }; - __internal_setSDK = (name: string, version: string) => { - this.__internal_sdk = { - name, - version, - }; - }; - updateClient = (newClient: ClientResource): void => { if (!this.client) { // This is the first time client is being @@ -1145,12 +1216,28 @@ export default class Clerk implements ClerkInterface { this.#emit(); }; + /** + * @deprecated This method will be dropped in the next major release. + * This method is only used in another deprecated part: `invitationList` from useOrganization + */ __unstable__invitationUpdate(invitation: OrganizationInvitationResource) { + deprecated( + '__unstable__invitationUpdate', + 'We are completely dropping this method as it was introduced for internal use only', + ); this.#lastOrganizationInvitation = invitation; this.#emit(); } + /** + * @deprecated This method will be dropped in the next major release. + * This method is only used in another deprecated part: `membershipList` from useOrganization + */ __unstable__membershipUpdate(membership: OrganizationMembershipResource) { + deprecated( + '__unstable__membershipUpdate', + 'We are completely dropping this method as it was introduced for internal use only', + ); this.#lastOrganizationMember = membership; this.#emit(); } @@ -1245,10 +1332,24 @@ export default class Clerk implements ClerkInterface { } }; + #assertSignInFormatAndOrigin = (_signInUrl: string, origin: string) => { + let signInUrl: URL; + try { + signInUrl = new URL(_signInUrl); + } catch { + clerkInvalidSignInUrlFormat(); + } + + if (signInUrl.origin === origin) { + clerkInvalidSignInUrlOrigin(); + } + }; + #validateMultiDomainOptions = () => { if (!this.isSatellite) { return; } + if (this.#instanceType === 'development' && !this.#options.signInUrl) { clerkMissingSignInUrlAsSatellite(); } @@ -1256,6 +1357,10 @@ export default class Clerk implements ClerkInterface { if (!this.proxyUrl && !this.domain) { clerkMissingProxyUrlAndDomain(); } + + if (this.#options.signInUrl) { + this.#assertSignInFormatAndOrigin(this.#options.signInUrl, window.location.origin); + } }; #loadInStandardBrowser = async (): Promise => { @@ -1491,21 +1596,26 @@ export default class Clerk implements ClerkInterface { return ''; } - const opts: RedirectOptions = { - afterSignInUrl: pickRedirectionProp('afterSignInUrl', { ctx: options, options: this.#options }, false), - afterSignUpUrl: pickRedirectionProp('afterSignUpUrl', { ctx: options, options: this.#options }, false), - redirectUrl: options?.redirectUrl || window.location.href, - }; - const signInOrUpUrl = pickRedirectionProp( key, { options: this.#options, displayConfig: this.#environment.displayConfig }, false, ); - return this.buildUrlWithAuth( - appendUrlsAsQueryParams(appendAsQueryParams(signInOrUpUrl, options?.initialValues || {}), opts), - ); + const urls: RedirectOptions = { + afterSignInUrl: pickRedirectionProp('afterSignInUrl', { ctx: options, options: this.#options }, false), + afterSignUpUrl: pickRedirectionProp('afterSignUpUrl', { ctx: options, options: this.#options }, false), + redirectUrl: options?.redirectUrl || window.location.href, + }; + + (Object.keys(urls) as Array).forEach(function (key) { + const url = urls[key]; + if (url) { + urls[key] = stripSameOrigin(toURL(url), toURL(signInOrUpUrl)); + } + }); + + return this.buildUrlWithAuth(appendAsQueryParams(signInOrUpUrl, { ...urls, ...options?.initialValues })); }; assertComponentsReady(controls: unknown): asserts controls is ReturnType { diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 9fa62ef93e7..42b44c697dd 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -8,6 +8,7 @@ import type { CreateOrganizationParams, CreateOrganizationProps, DomainOrProxyUrl, + HandleEmailLinkVerificationParams, HandleMagicLinkVerificationParams, HandleOAuthCallbackParams, ListenerCallback, @@ -40,6 +41,8 @@ import type { } from './types'; import { isConstructor, loadClerkJsScript } from './utils'; +const SDK_METADATA = { name: PACKAGE_NAME, version: PACKAGE_VERSION }; + export interface Global { Clerk?: HeadlessBrowserClerk | BrowserClerk; } @@ -167,6 +170,8 @@ export default class IsomorphicClerk { proxyUrl: this.proxyUrl, domain: this.domain, } as any); + + c.sdkMetadata = this.options.sdkMetadata ?? SDK_METADATA; await c.load(this.options); } else { // Otherwise use the instantiated Clerk object @@ -194,6 +199,8 @@ export default class IsomorphicClerk { throw new Error('Failed to download latest ClerkJS. Contact support@clerk.com.'); } + global.Clerk.sdkMetadata = this.options.sdkMetadata ?? SDK_METADATA; + await global.Clerk.load(this.options); } @@ -343,9 +350,6 @@ export default class IsomorphicClerk { /** * `setActive` can be used to set the active session and/or organization. - * It will eventually replace `setSession`. - * - * @experimental */ setActive = ({ session, organization, beforeEmit }: SetActiveParams): Promise => { if (this.clerkjs) { @@ -667,8 +671,11 @@ export default class IsomorphicClerk { this.premountMethodCalls.set('handleRedirectCallback', callback); } }; - + /** + * @deprecated Use `handleEmailLinkVerification` instead. + */ handleMagicLinkVerification = async (params: HandleMagicLinkVerificationParams): Promise => { + deprecated('handleMagicLinkVerification', 'Use `handleEmailLinkVerification` instead.'); const callback = () => this.clerkjs?.handleMagicLinkVerification(params); if (this.clerkjs && this.#loaded) { return callback() as Promise; @@ -677,6 +684,15 @@ export default class IsomorphicClerk { } }; + handleEmailLinkVerification = async (params: HandleEmailLinkVerificationParams): Promise => { + const callback = () => this.clerkjs?.handleEmailLinkVerification(params); + if (this.clerkjs && this.#loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('handleEmailLinkVerification', callback); + } + }; + authenticateWithMetamask = async (params: AuthenticateWithMetamaskParams): Promise => { const callback = () => this.clerkjs?.authenticateWithMetamask(params); if (this.clerkjs && this.#loaded) { @@ -704,10 +720,10 @@ export default class IsomorphicClerk { } }; - getOrganization = async (organizationId: string): Promise => { + getOrganization = async (organizationId: string): Promise => { const callback = () => this.clerkjs?.getOrganization(organizationId); if (this.clerkjs && this.#loaded) { - return callback() as Promise; + return callback() as Promise; } else { this.premountMethodCalls.set('getOrganization', callback); } diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index a7c8becbd6c..1d146d1af60 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -1,7 +1,13 @@ -import type { InstanceType } from '@clerk/types'; +import type { Clerk, InstanceType } from '@clerk/types'; import { parsePublishableKey } from '../utils/keys'; +declare global { + interface Window { + Clerk: Clerk; + } +} + type TelemetryCollectorOptions = { /** * Sampling rate, 0-1 @@ -22,11 +28,11 @@ type TelemetryCollectorOptions = { /** * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. */ - sdk: string; + sdk?: string; /** * The version of the SDK being used. */ - sdkVersion: string; + sdkVersion?: string; }; type TelemetryCollectorConfig = Pick & { endpoint: string }; @@ -89,8 +95,9 @@ export class TelemetryCollector { this.#metadata.clerkVersion = options.clerkVersion ?? ''; } - this.#metadata.sdk = options.sdk; - this.#metadata.sdkVersion = options.sdkVersion; + // We will try to grab the SDK data lazily when an event is triggered, so it should always be defined once the event is sent. + this.#metadata.sdk = options.sdk!; + this.#metadata.sdkVersion = options.sdkVersion!; this.#metadata.publishableKey = options.publishableKey; @@ -164,16 +171,36 @@ export class TelemetryCollector { } } + /** + * If in browser, attempt to lazily grab the SDK metadata from the Clerk singleton, otherwise fallback to the initially passed in values. + * + * This is necessary because the sdkMetadata can be set by the host SDK after the TelemetryCollector is instantiated. + */ + #getSDKMetadata() { + let sdkMetadata = { + name: this.#metadata.sdk, + version: this.#metadata.sdkVersion, + }; + + if (typeof window !== 'undefined' && window.Clerk) { + sdkMetadata = { ...sdkMetadata, ...window.Clerk.sdkMetadata }; + } + + return sdkMetadata; + } + /** * Append relevant metadata from the Clerk singleton to the event payload. */ #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { + const sdkMetadata = this.#getSDKMetadata(); + return { event, cv: this.#metadata.clerkVersion, it: this.#metadata.instanceType, - sdk: this.#metadata.sdk, - sdkv: this.#metadata.sdkVersion, + sdk: sdkMetadata.name, + sdkv: sdkMetadata.version, ...(this.#metadata.publishableKey ? { pk: this.#metadata.publishableKey } : {}), payload, }; From faee737a2bbf0c211fbaf2c9bacd25e879892638 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 8 Nov 2023 12:52:05 -0600 Subject: [PATCH 08/30] feat(shared): Update endpoint, don't use no-cors --- packages/shared/src/telemetry/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index 1d146d1af60..12efb81a31e 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -141,17 +141,16 @@ export class TelemetryCollector { } #sendEvent(event: TelemetryEvent): void { - fetch(new URL('/v0/events', this.#config.endpoint), { + fetch(new URL('/v1/event', this.#config.endpoint), { method: 'POST', // TODO: We send an array here with that idea that we can eventually send multiple events. body: JSON.stringify({ events: [event], }), - mode: 'no-cors', headers: { 'Content-Type': 'application/json', }, - }).catch(() => void 0); + }).catch(err => console.error(err)); } /** From aa53dd795fa8f3ee9854afff4dbcdd97766103a2 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 8 Nov 2023 13:07:17 -0600 Subject: [PATCH 09/30] fix(nextjs): Fix merge conflict --- packages/nextjs/src/server/authMiddleware.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index 965397e5059..d59038eff74 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -1,7 +1,7 @@ import type { AuthObject, RequestState } from '@clerk/backend'; import { buildRequestUrl, constants } from '@clerk/backend'; -import { TelemetryCollector } from '@clerk/shared/telemetry'; import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; +import { TelemetryCollector } from '@clerk/shared/telemetry'; import type Link from 'next/link'; import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; @@ -9,12 +9,7 @@ import { NextResponse } from 'next/server'; import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from '../utils'; import { withLogger } from '../utils/debugLogger'; import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest'; -<<<<<<< HEAD -import { SECRET_KEY } from './clerkClient'; -import { PUBLISHABLE_KEY } from './constants'; -======= -import { SECRET_KEY } from './constants'; ->>>>>>> main +import { PUBLISHABLE_KEY, SECRET_KEY } from './constants'; import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser'; import { clockSkewDetected, From 6e7144e80b606d53d9d72f351ded0da669dfedea Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 8 Nov 2023 15:57:09 -0600 Subject: [PATCH 10/30] feat(shared): Add batching to the telemetry collector --- packages/shared/src/telemetry.ts | 70 +++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 79f0b6848a8..9fafeef0a04 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,13 +1,7 @@ -import type { Clerk, InstanceType } from '@clerk/types'; +import type { InstanceType } from '@clerk/types'; import { parsePublishableKey } from './keys'; -declare global { - interface Window { - Clerk: Clerk; - } -} - type TelemetryCollectorOptions = { /** * Sampling rate, 0-1 @@ -35,7 +29,10 @@ type TelemetryCollectorOptions = { sdkVersion?: string; }; -type TelemetryCollectorConfig = Pick & { endpoint: string }; +type TelemetryCollectorConfig = Pick & { + endpoint: string; + maxBufferSize: number; +}; type TelemetryMetadata = Required< Pick @@ -74,12 +71,15 @@ type TelemetryEvent = { const DEFAULT_CONFIG: Partial> = { samplingRate: 1, verbose: false, + maxBufferSize: 5, }; // TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render export class TelemetryCollector { #config: Required; #metadata: TelemetryMetadata = {} as TelemetryMetadata; + #buffer: TelemetryEvent[] = []; + #pendingFlush: any; constructor(options: TelemetryCollectorOptions) { this.#config = { @@ -106,7 +106,6 @@ export class TelemetryCollector { this.#metadata.instanceType = parsedKey.instanceType; } - // TODO: determine FAPI endpoint from the publishable key // this.#config.endpoint = 'https://telemetry-service-staging.bryce-clerk.workers.dev'; this.#config.endpoint = 'http://localhost:8787'; } @@ -133,24 +132,65 @@ export class TelemetryCollector { return; } - this.#sendEvent(preparedPayload); + this.#buffer.push(preparedPayload); + + this.#scheduleFlush(); } #shouldRecord(): boolean { return Math.random() <= this.#config.samplingRate; } - #sendEvent(event: TelemetryEvent): void { + #scheduleFlush(): void { + // On the server, we want to flush immediately as we have less guarantees about the lifecycle of the process + if (typeof window === 'undefined') { + this.#flush(); + return; + } + + const isBufferFull = this.#buffer.length >= this.#config.maxBufferSize; + if (isBufferFull) { + // If the buffer is full, flush immediately to make sure we minimize the chance of event loss. + // Cancel any pending flushes as we're going to flush immediately + if (this.#pendingFlush) { + const cancel = cancelIdleCallback || clearTimeout; + cancel(this.#pendingFlush); + } + this.#flush(); + return; + } + + // If we have a pending flush, do nothing + if (this.#pendingFlush) return; + + if ('requestIdleCallback' in window) { + this.#pendingFlush = requestIdleCallback(() => { + this.#flush(); + }); + } else { + // This is not an ideal solution, but it at least waits until the next tick + this.#pendingFlush = setTimeout(() => { + this.#flush(); + }, 0); + } + } + + #flush(): void { fetch(new URL('/v1/event', this.#config.endpoint), { method: 'POST', // TODO: We send an array here with that idea that we can eventually send multiple events. body: JSON.stringify({ - events: [event], + events: this.#buffer, }), headers: { 'Content-Type': 'application/json', }, - }).catch(err => console.error(err)); + }) + .catch(err => console.error(err)) + .then(() => { + this.#buffer = []; + }) + .catch(() => void 0); } /** @@ -181,8 +221,10 @@ export class TelemetryCollector { version: this.#metadata.sdkVersion, }; + // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here if (typeof window !== 'undefined' && window.Clerk) { - sdkMetadata = { ...sdkMetadata, ...window.Clerk.sdkMetadata }; + // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here + sdkMetadata = { ...sdkMetadata, ...window.Clerk.constructor.prototype.sdkMetadata }; } return sdkMetadata; From 85c5a45fe7eea72be66202a535b902abbec0bada Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 13 Nov 2023 13:40:54 -0500 Subject: [PATCH 11/30] feat(shared): Add opt-out support for SDK telemetry (#2099) --- packages/clerk-js/src/core/clerk.ts | 21 ++- .../src/GatsbyClerkProvider.tsx | 6 + packages/gatsby-plugin-clerk/src/constants.ts | 4 + .../src/utils/mergeNextClerkPropsWithEnv.ts | 4 + packages/react/src/isomorphicClerk.ts | 10 +- .../remix/src/client/RemixClerkProvider.tsx | 6 + packages/remix/src/client/types.ts | 2 + packages/remix/src/ssr/utils.ts | 3 + packages/shared/.gitignore | 2 +- packages/shared/jest.config.js | 3 + .../shared/src/__tests__/telemetry.test.ts | 149 ++++++++++++++++++ packages/shared/src/telemetry.ts | 54 +++++-- packages/types/src/clerk.ts | 10 ++ 13 files changed, 245 insertions(+), 29 deletions(-) create mode 100644 packages/shared/src/__tests__/telemetry.test.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index d4067a3597c..1dbdabd56c4 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -173,7 +173,7 @@ export default class Clerk implements ClerkInterface { #devBrowserHandler: DevBrowserHandler | null = null; #environment?: EnvironmentResource | null; #fapiClient: FapiClient; - #telemetry: TelemetryCollector; + #telemetry?: TelemetryCollector; #instanceType: InstanceType; #isReady = false; @@ -308,12 +308,6 @@ export default class Clerk implements ClerkInterface { this.#instanceType = instanceType; } this.#fapiClient = createFapiClient(this); - this.#telemetry = new TelemetryCollector({ - clerkVersion: Clerk.version, - verbose: true, - samplingRate: 1, - publishableKey: key, - }); BaseResource.clerk = this; } @@ -331,6 +325,19 @@ export default class Clerk implements ClerkInterface { ...options, }; + if (this.#options.sdkMetadata) { + Clerk.sdkMetadata = this.#options.sdkMetadata; + } + + this.#telemetry = new TelemetryCollector({ + clerkVersion: Clerk.version, + verbose: true, + samplingRate: 1, + // @ts-expect-error -- in v5 this will always be defined + publishableKey: this.publishableKey, + ...this.#options.telemetry, + }); + if (this.#options.standardBrowser) { this.#isReady = await this.#loadInStandardBrowser(); } else { diff --git a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx index 59f09296af3..74d558f0989 100644 --- a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx +++ b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx @@ -7,6 +7,8 @@ import { import { navigate } from 'gatsby'; import React from 'react'; +import { TELEMETRY_DEBUG, TELEMETRY_DISABLED } from './constants'; + const SDK_METADATA = { name: PACKAGE_NAME, version: PACKAGE_VERSION, @@ -28,6 +30,10 @@ export function ClerkProvider({ children, ...rest }: GatsbyClerkProviderProps) { navigate={to => navigate(to)} initialState={__clerk_ssr_state || {}} sdkMetadata={SDK_METADATA} + telemetry={{ + disabled: TELEMETRY_DISABLED, + debug: TELEMETRY_DEBUG, + }} {...restProps} > {__clerk_ssr_interstitial_html ? ( diff --git a/packages/gatsby-plugin-clerk/src/constants.ts b/packages/gatsby-plugin-clerk/src/constants.ts index 00d3f788c55..d3423677597 100644 --- a/packages/gatsby-plugin-clerk/src/constants.ts +++ b/packages/gatsby-plugin-clerk/src/constants.ts @@ -1,4 +1,5 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; +import { isTruthy } from '@clerk/shared/underscore'; export const PUBLISHABLE_KEY = process.env.GATSBY_CLERK_PUBLISHABLE_KEY || ''; export const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); @@ -7,3 +8,6 @@ export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; export const CLERK_JS = process.env.GATSBY_CLERK_JS; export const PROXY_URL = process.env.GATSBY_CLERK_PROXY_URL; + +export const TELEMETRY_DISABLED = isTruthy(process.env.GATSBY_CLERK_TELEMETRY_DISABLED); +export const TELEMETRY_DEBUG = isTruthy(process.env.GATSBY_CLERK_TELEMETRY_DEBUG); diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index 0cdd5894515..d2b742bd21c 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -15,6 +15,10 @@ export const mergeNextClerkPropsWithEnv = (props: Omit { @@ -77,6 +79,10 @@ export function ClerkProvider({ children, ...rest }: RemixClerkProviderProps): J afterSignUpUrl: __afterSignUpUrl, clerkJSUrl: __clerkJSUrl, clerkJSVersion: __clerkJSVersion, + telemetry: { + disabled: __telemetryDisabled, + debug: __telemetryDebug, + }, }; return ( diff --git a/packages/remix/src/client/types.ts b/packages/remix/src/client/types.ts index 4fca0c9f8d2..beb58f4e29f 100644 --- a/packages/remix/src/client/types.ts +++ b/packages/remix/src/client/types.ts @@ -18,6 +18,8 @@ export type ClerkState = { __clerk_debug: any; __clerkJSUrl: string | undefined; __clerkJSVersion: string | undefined; + __telemetryDisabled: boolean | undefined; + __telemetryDebug: boolean | undefined; }; }; diff --git a/packages/remix/src/ssr/utils.ts b/packages/remix/src/ssr/utils.ts index 5c30cbaf225..37d08f55b53 100644 --- a/packages/remix/src/ssr/utils.ts +++ b/packages/remix/src/ssr/utils.ts @@ -1,5 +1,6 @@ import type { AuthObject, RequestState } from '@clerk/backend'; import { constants, debugRequestState, loadInterstitialFromLocal } from '@clerk/backend'; +import { isTruthy } from '@clerk/shared/underscore'; import type { AppLoadContext, defer } from '@remix-run/server-runtime'; import { json } from '@remix-run/server-runtime'; import cookie from 'cookie'; @@ -165,6 +166,8 @@ export function getResponseClerkState(requestState: RequestState, context: AppLo __clerk_debug: debugRequestState(requestState), __clerkJSUrl: getEnvVariable('CLERK_JS', context), __clerkJSVersion: getEnvVariable('CLERK_JS_VERSION', context), + __telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED', context)), + __telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG', context)), }); const headers = observabilityHeadersFromRequestState(requestState); diff --git a/packages/shared/.gitignore b/packages/shared/.gitignore index 87400d96af4..a525d2ce5cb 100644 --- a/packages/shared/.gitignore +++ b/packages/shared/.gitignore @@ -1,2 +1,2 @@ -*/ +/*/ !src/ diff --git a/packages/shared/jest.config.js b/packages/shared/jest.config.js index f9da269204a..9dd261d1d2f 100644 --- a/packages/shared/jest.config.js +++ b/packages/shared/jest.config.js @@ -4,6 +4,9 @@ const { name } = require('./package.json'); const config = { displayName: name.replace('@clerk', ''), injectGlobals: true, + globals: { + fetch: global.fetch, + }, testEnvironment: 'jsdom', roots: ['/src'], diff --git a/packages/shared/src/__tests__/telemetry.test.ts b/packages/shared/src/__tests__/telemetry.test.ts new file mode 100644 index 00000000000..16e0211ff29 --- /dev/null +++ b/packages/shared/src/__tests__/telemetry.test.ts @@ -0,0 +1,149 @@ +import { TelemetryCollector } from '../telemetry'; + +jest.useFakeTimers(); + +const TEST_PK = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; + +describe('TelemetryCollector', () => { + test('does nothing when disabled', async () => { + const fetchSpy = jest.spyOn(global, 'fetch'); + + const collector = new TelemetryCollector({ + disabled: true, + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + + jest.runAllTimers(); + + expect(fetchSpy).not.toHaveBeenCalled(); + + fetchSpy.mockRestore(); + }); + + test('does nothing when CLERK_TELEMETRY_DISABLED is set', async () => { + process.env.CLERK_TELEMETRY_DISABLED = '1'; + + const fetchSpy = jest.spyOn(global, 'fetch'); + + const collector = new TelemetryCollector({ + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + + jest.runAllTimers(); + + expect(fetchSpy).not.toHaveBeenCalled(); + + fetchSpy.mockRestore(); + + process.env.CLERK_TELEMETRY_DISABLED = undefined; + }); + + test('does not send events when debug is enabled, logs them instead', async () => { + const fetchSpy = jest.spyOn(global, 'fetch'); + const consoleSpy = jest.spyOn(global.console, 'log'); + + const collector = new TelemetryCollector({ + debug: true, + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + + jest.runAllTimers(); + + expect(fetchSpy).not.toHaveBeenCalled(); + + expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "cv": "", + "event": "TEST_EVENT", + "it": "development", + "payload": {}, + "pk": "pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk", + "sdk": undefined, + "sdkv": undefined, + }, + ], + ] + `); + + consoleSpy.mockRestore(); + fetchSpy.mockRestore(); + }); + + test('enables debug via environment variable', async () => { + process.env.CLERK_TELEMETRY_DEBUG = '1'; + + const fetchSpy = jest.spyOn(global, 'fetch'); + const consoleSpy = jest.spyOn(global.console, 'log'); + + const collector = new TelemetryCollector({ + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + + jest.runAllTimers(); + + expect(fetchSpy).not.toHaveBeenCalled(); + + expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "cv": "", + "event": "TEST_EVENT", + "it": "development", + "payload": {}, + "pk": "pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk", + "sdk": undefined, + "sdkv": undefined, + }, + ], + ] + `); + + consoleSpy.mockRestore(); + fetchSpy.mockRestore(); + + process.env.CLERK_TELEMETRY_DEBUG = undefined; + }); + + test('sends events after a delay when buffer is not full', async () => { + const fetchSpy = jest.spyOn(global, 'fetch'); + + const collector = new TelemetryCollector({ + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + + jest.runAllTimers(); + + expect(fetchSpy).toHaveBeenCalled(); + + fetchSpy.mockRestore(); + }); + + test('sends events immediately when the buffer limit is reached', async () => { + const fetchSpy = jest.spyOn(global, 'fetch'); + + const collector = new TelemetryCollector({ + maxBufferSize: 2, + publishableKey: TEST_PK, + }); + + collector.record('TEST_EVENT', {}); + collector.record('TEST_EVENT', {}); + + expect(fetchSpy).toHaveBeenCalled(); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 9fafeef0a04..3be742efd88 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,12 +1,25 @@ import type { InstanceType } from '@clerk/types'; import { parsePublishableKey } from './keys'; +import { isTruthy } from './underscore'; type TelemetryCollectorOptions = { + /** + * If true, telemetry will not be collected. + */ + disabled?: boolean; + /** + * If true, telemetry will not be sent, but collected events will be logged to the console. + */ + debug?: boolean; /** * Sampling rate, 0-1 */ samplingRate?: number; + /** + * Set a custom buffer size to control how often events are sent + */ + maxBufferSize?: number; /** * Determines whether or not events will be logged to the console. */ @@ -29,9 +42,11 @@ type TelemetryCollectorOptions = { sdkVersion?: string; }; -type TelemetryCollectorConfig = Pick & { +type TelemetryCollectorConfig = Pick< + TelemetryCollectorOptions, + 'samplingRate' | 'verbose' | 'disabled' | 'debug' | 'maxBufferSize' +> & { endpoint: string; - maxBufferSize: number; }; type TelemetryMetadata = Required< @@ -83,9 +98,11 @@ export class TelemetryCollector { constructor(options: TelemetryCollectorOptions) { this.#config = { - ...DEFAULT_CONFIG, - samplingRate: options.samplingRate, - verbose: options.verbose, + maxBufferSize: options.maxBufferSize ?? DEFAULT_CONFIG.maxBufferSize, + samplingRate: options.samplingRate ?? DEFAULT_CONFIG.samplingRate, + verbose: options.verbose ?? DEFAULT_CONFIG.verbose, + disabled: options.disabled ?? false, + debug: options.debug ?? false, } as Required; if (!options.clerkVersion && typeof window === 'undefined') { @@ -115,15 +132,20 @@ export class TelemetryCollector { return false; } - // TODO: check clerk environment / environment variable + // In browser or client environments, we most likely pass the disabled option to the collector, but in environments + // where environment variables are available we also check for `CLERK_TELEMETRY_DISABLED`. + if (this.#config.disabled || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DISABLED))) { + return false; + } + return true; } - record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { - if (!this.isEnabled) { - return; - } + get isDebug(): boolean { + return this.#config.debug || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DEBUG)); + } + record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { const preparedPayload = this.#preparePayload(event, payload); this.#logEvent(preparedPayload.event, preparedPayload); @@ -138,7 +160,7 @@ export class TelemetryCollector { } #shouldRecord(): boolean { - return Math.random() <= this.#config.samplingRate; + return this.isEnabled && !this.isDebug && Math.random() <= this.#config.samplingRate; } #scheduleFlush(): void { @@ -153,7 +175,7 @@ export class TelemetryCollector { // If the buffer is full, flush immediately to make sure we minimize the chance of event loss. // Cancel any pending flushes as we're going to flush immediately if (this.#pendingFlush) { - const cancel = cancelIdleCallback || clearTimeout; + const cancel = typeof cancelIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout; cancel(this.#pendingFlush); } this.#flush(); @@ -186,7 +208,7 @@ export class TelemetryCollector { 'Content-Type': 'application/json', }, }) - .catch(err => console.error(err)) + .catch(() => void 0) .then(() => { this.#buffer = []; }) @@ -194,10 +216,10 @@ export class TelemetryCollector { } /** - * If running in verbose mode, log the event and its payload to the console. + * If running in debug mode, log the event and its payload to the console. */ #logEvent(event: TelemetryEvent['event'], payload: Record) { - if (!this.#config.verbose) { + if (!this.isDebug) { return; } @@ -224,7 +246,7 @@ export class TelemetryCollector { // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here if (typeof window !== 'undefined' && window.Clerk) { // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here - sdkMetadata = { ...sdkMetadata, ...window.Clerk.constructor.prototype.sdkMetadata }; + sdkMetadata = { ...sdkMetadata, ...window.Clerk.constructor.sdkMetadata }; } return sdkMetadata; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 044d1b936a4..bd26d270d92 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -552,6 +552,16 @@ export interface ClerkOptions { */ isInterstitial?: boolean; isSatellite?: boolean | ((url: URL) => boolean); + + /** + * Telemetry options + */ + telemetry?: { + disabled?: boolean; + debug?: boolean; + }; + + sdkMetadata?: SDKMetadata; } export interface Resources { From 01ac71698362737c571992be11473eff27ea82e0 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 11:25:43 -0600 Subject: [PATCH 12/30] feat(clerk-js): Add TelemetryCollector instance to backend client (#2134) --- packages/backend/src/index.ts | 12 ++++++- packages/fastify/src/clerkClient.ts | 3 +- packages/fastify/src/constants.ts | 4 +++ packages/fastify/src/global.d.ts | 6 ++++ packages/fastify/tsconfig.json | 2 +- .../src/GatsbyClerkProvider.tsx | 7 +--- packages/gatsby-plugin-clerk/src/constants.ts | 5 +++ .../src/ssr/clerkClient.ts | 7 +++- packages/nextjs/src/server/authMiddleware.ts | 14 ++------ packages/nextjs/src/server/clerkClient.ts | 17 ++++++++- packages/nextjs/src/server/constants.ts | 7 ++++ .../src/utils/mergeNextClerkPropsWithEnv.ts | 6 ++-- packages/sdk-node/src/globals.d.ts | 6 ++++ packages/sdk-node/src/utils.ts | 4 +++ packages/sdk-node/tsconfig.json | 2 +- packages/shared/src/telemetry.ts | 36 +++++++++++-------- playground/app-router/src/middleware.ts | 4 ++- 17 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 packages/fastify/src/global.d.ts create mode 100644 packages/sdk-node/src/globals.d.ts diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 0d1a552ca2e..45992d644de 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,4 +1,7 @@ import { deprecatedObjectProperty } from '@clerk/shared/deprecated'; +import type { TelemetryCollectorOptions } from '@clerk/shared/telemetry'; +import { TelemetryCollector } from '@clerk/shared/telemetry'; +import type { SDKMetadata } from '@clerk/types'; import type { CreateBackendApiOptions } from './api'; import { createBackendApiClient } from './api'; @@ -21,16 +24,23 @@ export type ClerkOptions = CreateBackendApiOptions & CreateAuthenticateRequestOptions['options'], 'audience' | 'jwtKey' | 'proxyUrl' | 'secretKey' | 'publishableKey' | 'domain' | 'isSatellite' > - >; + > & { sdkMetadata?: SDKMetadata; telemetry?: Pick }; export function Clerk(options: ClerkOptions) { const opts = { ...options }; const apiClient = createBackendApiClient(opts); const requestState = createAuthenticateRequest({ options: opts, apiClient }); + const telemetry = new TelemetryCollector({ + ...options.telemetry, + publishableKey: opts.publishableKey, + secretKey: opts.secretKey, + ...(opts.sdkMetadata ? { sdk: opts.sdkMetadata.name, sdkVersion: opts.sdkMetadata.version } : {}), + }); const clerkInstance = { ...apiClient, ...requestState, + telemetry, /** * @deprecated This prop has been deprecated and will be removed in the next major release. */ diff --git a/packages/fastify/src/clerkClient.ts b/packages/fastify/src/clerkClient.ts index 03141d50442..3e31939e2eb 100644 --- a/packages/fastify/src/clerkClient.ts +++ b/packages/fastify/src/clerkClient.ts @@ -1,6 +1,6 @@ import { Clerk } from '@clerk/backend'; -import { API_URL, API_VERSION, JWT_KEY, SECRET_KEY } from './constants'; +import { API_URL, API_VERSION, JWT_KEY, SDK_METADATA, SECRET_KEY } from './constants'; export const createClerkClient = Clerk; @@ -9,4 +9,5 @@ export const clerkClient = createClerkClient({ apiUrl: API_URL, apiVersion: API_VERSION, jwtKey: JWT_KEY, + sdkMetadata: SDK_METADATA, }); diff --git a/packages/fastify/src/constants.ts b/packages/fastify/src/constants.ts index 6699d20ee09..725f748d28e 100644 --- a/packages/fastify/src/constants.ts +++ b/packages/fastify/src/constants.ts @@ -6,5 +6,9 @@ export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; export const PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY || ''; export const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); export const JWT_KEY = process.env.CLERK_JWT_KEY || ''; +export const SDK_METADATA = { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, +}; export const { Cookies, Headers } = constants; diff --git a/packages/fastify/src/global.d.ts b/packages/fastify/src/global.d.ts new file mode 100644 index 00000000000..1ae75219e34 --- /dev/null +++ b/packages/fastify/src/global.d.ts @@ -0,0 +1,6 @@ +declare global { + const PACKAGE_NAME: string; + const PACKAGE_VERSION: string; +} + +export {}; diff --git a/packages/fastify/tsconfig.json b/packages/fastify/tsconfig.json index f06061d1ecf..5fbaf3766c4 100644 --- a/packages/fastify/tsconfig.json +++ b/packages/fastify/tsconfig.json @@ -15,6 +15,6 @@ "resolveJsonModule": true, "declarationDir": "dist/types" }, - "include": ["src/index.ts"], + "include": ["src/index.ts", "src/global.d.ts"], "exclude": ["node_modules"] } diff --git a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx index 74d558f0989..29ec9a41c0e 100644 --- a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx +++ b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx @@ -7,12 +7,7 @@ import { import { navigate } from 'gatsby'; import React from 'react'; -import { TELEMETRY_DEBUG, TELEMETRY_DISABLED } from './constants'; - -const SDK_METADATA = { - name: PACKAGE_NAME, - version: PACKAGE_VERSION, -}; +import { SDK_METADATA, TELEMETRY_DEBUG, TELEMETRY_DISABLED } from './constants'; __internal__setErrorThrowerOptions({ packageName: 'gatsby-plugin-clerk' }); diff --git a/packages/gatsby-plugin-clerk/src/constants.ts b/packages/gatsby-plugin-clerk/src/constants.ts index d3423677597..5fcf6a6f262 100644 --- a/packages/gatsby-plugin-clerk/src/constants.ts +++ b/packages/gatsby-plugin-clerk/src/constants.ts @@ -11,3 +11,8 @@ export const PROXY_URL = process.env.GATSBY_CLERK_PROXY_URL; export const TELEMETRY_DISABLED = isTruthy(process.env.GATSBY_CLERK_TELEMETRY_DISABLED); export const TELEMETRY_DEBUG = isTruthy(process.env.GATSBY_CLERK_TELEMETRY_DEBUG); + +export const SDK_METADATA = { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, +}; diff --git a/packages/gatsby-plugin-clerk/src/ssr/clerkClient.ts b/packages/gatsby-plugin-clerk/src/ssr/clerkClient.ts index b70a88df128..5584cd09497 100644 --- a/packages/gatsby-plugin-clerk/src/ssr/clerkClient.ts +++ b/packages/gatsby-plugin-clerk/src/ssr/clerkClient.ts @@ -1,6 +1,6 @@ import { Clerk } from '@clerk/backend'; -import { API_URL, API_VERSION, SECRET_KEY } from '../constants'; +import { API_URL, API_VERSION, SDK_METADATA, SECRET_KEY, TELEMETRY_DEBUG, TELEMETRY_DISABLED } from '../constants'; const clerkClient = Clerk({ secretKey: SECRET_KEY, @@ -8,6 +8,11 @@ const clerkClient = Clerk({ apiVersion: API_VERSION, // TODO: Fetch version from package.json userAgent: 'gatsby-plugin-clerk', + sdkMetadata: SDK_METADATA, + telemetry: { + disabled: TELEMETRY_DISABLED, + debug: TELEMETRY_DEBUG, + }, }); const createClerkClient = Clerk; diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index 2c28a77786e..91eac43086b 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -1,7 +1,6 @@ import type { AuthObject, RequestState } from '@clerk/backend'; import { buildRequestUrl, constants } from '@clerk/backend'; import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; -import { TelemetryCollector } from '@clerk/shared/telemetry'; import type { Autocomplete } from '@clerk/types'; import type Link from 'next/link'; import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; @@ -10,7 +9,8 @@ import { NextResponse } from 'next/server'; import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from '../utils'; import { withLogger } from '../utils/debugLogger'; import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest'; -import { PUBLISHABLE_KEY, SECRET_KEY } from './constants'; +import { clerkClient } from './clerkClient'; +import { SECRET_KEY } from './constants'; import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser'; import { clockSkewDetected, @@ -28,14 +28,6 @@ import { setRequestHeadersOnNextResponse, } from './utils'; -const telemetry = new TelemetryCollector({ - verbose: true, - samplingRate: 1, - publishableKey: PUBLISHABLE_KEY, - sdk: PACKAGE_NAME, - sdkVersion: PACKAGE_VERSION, -}); - type WithPathPatternWildcard = `${T & string}(.*)`; type NextTypedRoute['0']['href']> = T extends string ? T : never; @@ -154,7 +146,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { const isApiRoute = createApiRoutes(apiRoutes); const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute, params); - telemetry.record('METHOD_CALLED', { + clerkClient.telemetry.record('METHOD_CALLED', { method: 'authMiddleware', publicRoutes: Boolean(publicRoutes), ignoredRoutes: Boolean(ignoredRoutes), diff --git a/packages/nextjs/src/server/clerkClient.ts b/packages/nextjs/src/server/clerkClient.ts index 04e6413ef34..4c8fe410435 100644 --- a/packages/nextjs/src/server/clerkClient.ts +++ b/packages/nextjs/src/server/clerkClient.ts @@ -1,6 +1,16 @@ import { Clerk } from '@clerk/backend'; -import { API_URL, API_VERSION, DOMAIN, IS_SATELLITE, PROXY_URL, SECRET_KEY } from './constants'; +import { + API_URL, + API_VERSION, + DOMAIN, + IS_SATELLITE, + PROXY_URL, + SDK_METADATA, + SECRET_KEY, + TELEMETRY_DEBUG, + TELEMETRY_DISABLED, +} from './constants'; const clerkClient = Clerk({ secretKey: SECRET_KEY, @@ -11,6 +21,11 @@ const clerkClient = Clerk({ proxyUrl: PROXY_URL, domain: DOMAIN, isSatellite: IS_SATELLITE, + sdkMetadata: SDK_METADATA, + telemetry: { + disabled: TELEMETRY_DISABLED, + debug: TELEMETRY_DEBUG, + }, }); const createClerkClient = Clerk; diff --git a/packages/nextjs/src/server/constants.ts b/packages/nextjs/src/server/constants.ts index 00b7dcbe774..3b6603c107d 100644 --- a/packages/nextjs/src/server/constants.ts +++ b/packages/nextjs/src/server/constants.ts @@ -12,3 +12,10 @@ export const PROXY_URL = process.env.NEXT_PUBLIC_CLERK_PROXY_URL || ''; export const IS_SATELLITE = isTruthy(process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE) || false; export const SIGN_IN_URL = process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL || ''; export const SIGN_UP_URL = process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL || ''; +export const SDK_METADATA = { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, +}; + +export const TELEMETRY_DISABLED = isTruthy(process.env.NEXT_PUBLIC_CLERK_TELEMETRY_DISABLED); +export const TELEMETRY_DEBUG = isTruthy(process.env.NEXT_PUBLIC_CLERK_TELEMETRY_DEBUG); diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index d2b742bd21c..1c3de67aa8c 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -1,5 +1,6 @@ import { isTruthy } from '@clerk/shared/underscore'; +import { SDK_METADATA } from '../server/constants'; import type { NextClerkProviderProps } from '../types'; export const mergeNextClerkPropsWithEnv = (props: Omit) => { @@ -19,9 +20,6 @@ export const mergeNextClerkPropsWithEnv = (props: Omit { signInUrl: process.env.CLERK_SIGN_IN_URL || '', isSatellite: isTruthy(process.env.CLERK_IS_SATELLITE), jwtKey: process.env.CLERK_JWT_KEY || '', + sdkMetadata: { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, + }, }; }; diff --git a/packages/sdk-node/tsconfig.json b/packages/sdk-node/tsconfig.json index 81ebb93d55d..d30cd134410 100644 --- a/packages/sdk-node/tsconfig.json +++ b/packages/sdk-node/tsconfig.json @@ -19,5 +19,5 @@ "types": ["jest"] }, "exclude": ["node_modules"], - "include": ["src/index.ts", "src/instance.ts"] + "include": ["src/index.ts", "src/instance.ts", "src/globals.d.ts"] } diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 3be742efd88..dd1c59bbeb6 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -3,7 +3,7 @@ import type { InstanceType } from '@clerk/types'; import { parsePublishableKey } from './keys'; import { isTruthy } from './underscore'; -type TelemetryCollectorOptions = { +export type TelemetryCollectorOptions = { /** * If true, telemetry will not be collected. */ @@ -21,13 +21,13 @@ type TelemetryCollectorOptions = { */ maxBufferSize?: number; /** - * Determines whether or not events will be logged to the console. + * The publishableKey to associate with the collected events. */ - verbose?: boolean; + publishableKey?: string; /** - * The publishableKey to associate with the collected events. + * The secretKey to associate with the collected events. */ - publishableKey: string; + secretKey?: string; /** * The current clerk-js version. */ @@ -44,13 +44,13 @@ type TelemetryCollectorOptions = { type TelemetryCollectorConfig = Pick< TelemetryCollectorOptions, - 'samplingRate' | 'verbose' | 'disabled' | 'debug' | 'maxBufferSize' + 'samplingRate' | 'disabled' | 'debug' | 'maxBufferSize' > & { endpoint: string; }; type TelemetryMetadata = Required< - Pick + Pick > & { /** * The instance type, derived from the provided publishableKey. @@ -64,6 +64,10 @@ type TelemetryEvent = { * publishableKey */ pk?: string; + /** + * secretKey + */ + sk?: string; /** * instanceType */ @@ -85,8 +89,8 @@ type TelemetryEvent = { const DEFAULT_CONFIG: Partial> = { samplingRate: 1, - verbose: false, maxBufferSize: 5, + endpoint: 'https://staging.clerk-telemetry.com', }; // TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render @@ -100,7 +104,6 @@ export class TelemetryCollector { this.#config = { maxBufferSize: options.maxBufferSize ?? DEFAULT_CONFIG.maxBufferSize, samplingRate: options.samplingRate ?? DEFAULT_CONFIG.samplingRate, - verbose: options.verbose ?? DEFAULT_CONFIG.verbose, disabled: options.disabled ?? false, debug: options.debug ?? false, } as Required; @@ -116,15 +119,19 @@ export class TelemetryCollector { this.#metadata.sdk = options.sdk!; this.#metadata.sdkVersion = options.sdkVersion!; - this.#metadata.publishableKey = options.publishableKey; + this.#metadata.publishableKey = options.publishableKey ?? ''; const parsedKey = parsePublishableKey(options.publishableKey); if (parsedKey) { this.#metadata.instanceType = parsedKey.instanceType; } - // this.#config.endpoint = 'https://telemetry-service-staging.bryce-clerk.workers.dev'; - this.#config.endpoint = 'http://localhost:8787'; + if (options.secretKey) { + // Only send the first 16 characters of the secret key to to avoid sending the full key. We can still query against the partial key. + this.#metadata.secretKey = options.secretKey.substring(0, 16); + } + + // this.#config.endpoint = 'http://localhost:8787'; } get isEnabled(): boolean { @@ -260,11 +267,12 @@ export class TelemetryCollector { return { event, - cv: this.#metadata.clerkVersion, - it: this.#metadata.instanceType, + cv: this.#metadata.clerkVersion ?? '', + it: this.#metadata.instanceType ?? '', sdk: sdkMetadata.name, sdkv: sdkMetadata.version, ...(this.#metadata.publishableKey ? { pk: this.#metadata.publishableKey } : {}), + ...(this.#metadata.secretKey ? { sk: this.#metadata.secretKey } : {}), payload, }; } diff --git a/playground/app-router/src/middleware.ts b/playground/app-router/src/middleware.ts index b89c54e56d9..6010f58b825 100644 --- a/playground/app-router/src/middleware.ts +++ b/playground/app-router/src/middleware.ts @@ -1,11 +1,13 @@ import { authMiddleware } from '@clerk/nextjs'; import { NextMiddleware, NextResponse } from 'next/server'; +const clerkMiddleware = authMiddleware() + const middleware: NextMiddleware = (request, event) => { if (request.geo?.country?.toLocaleUpperCase() === 'IN') { return new NextResponse(null, { status: 403 }); } - return authMiddleware()(request, event); + return clerkMiddleware(request, event); }; export default middleware; From 7b462a2ff78b774c868a3c0eab5cf204a6130c33 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 16:58:13 -0600 Subject: [PATCH 13/30] chore(repo): Add changeset --- .changeset/wicked-spoons-confess.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/wicked-spoons-confess.md diff --git a/.changeset/wicked-spoons-confess.md b/.changeset/wicked-spoons-confess.md new file mode 100644 index 00000000000..b41036bb8be --- /dev/null +++ b/.changeset/wicked-spoons-confess.md @@ -0,0 +1,14 @@ +--- +'gatsby-plugin-clerk': patch +'@clerk/clerk-js': patch +'@clerk/clerk-sdk-node': patch +'@clerk/backend': patch +'@clerk/fastify': patch +'@clerk/nextjs': patch +'@clerk/shared': patch +'@clerk/clerk-react': patch +'@clerk/remix': patch +'@clerk/types': patch +--- + +Introduces telemetry collection from Clerk's SDKs. Collected telemetry will be used to gain insights into product usage and help drive roadmap priority. For more information, see https://clerk.com/docs/telemetry. From 3a0a23963a84c43cf8acb9539aeada874f99a3e5 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 18:16:20 -0600 Subject: [PATCH 14/30] fix(shared): Fix tests that rely on fetch --- package-lock.json | 785 ++++++++---------- packages/shared/jest.config.js | 3 - packages/shared/package.json | 1 + .../shared/src/__tests__/telemetry.test.ts | 10 +- packages/shared/src/telemetry.ts | 1 + 5 files changed, 355 insertions(+), 445 deletions(-) diff --git a/package-lock.json b/package-lock.json index 359c38b3410..c87ec565955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,6 @@ }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -721,7 +720,7 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1003,7 +1002,7 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1016,7 +1015,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1026,7 +1025,7 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" @@ -1115,7 +1114,7 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1126,7 +1125,7 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1137,7 +1136,7 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.21.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1151,7 +1150,7 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1162,7 +1161,7 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1173,7 +1172,7 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1184,7 +1183,7 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1195,7 +1194,7 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1206,7 +1205,7 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1231,7 +1230,7 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -1245,7 +1244,7 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.21.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -2345,7 +2344,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "devOptional": true }, "node_modules/@builder.io/partytown": { "version": "0.7.6", @@ -3527,7 +3526,6 @@ }, "node_modules/@eslint/eslintrc": { "version": "2.1.2", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -3549,12 +3547,10 @@ }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", - "dev": true, "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.21.0", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -3568,7 +3564,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3579,7 +3574,6 @@ }, "node_modules/@eslint/js": { "version": "8.49.0", - "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3835,7 +3829,6 @@ }, "node_modules/@fastify/ajv-compiler": { "version": "3.5.0", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.11.0", @@ -3845,7 +3838,6 @@ }, "node_modules/@fastify/ajv-compiler/node_modules/ajv": { "version": "8.12.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3860,7 +3852,6 @@ }, "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/@fastify/busboy": { @@ -3874,17 +3865,14 @@ }, "node_modules/@fastify/deepmerge": { "version": "1.3.0", - "dev": true, "license": "MIT" }, "node_modules/@fastify/error": { "version": "3.2.1", - "dev": true, "license": "MIT" }, "node_modules/@fastify/fast-json-stringify-compiler": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "fast-json-stringify": "^5.7.0" @@ -4483,7 +4471,6 @@ }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", - "dev": true, "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -4496,7 +4483,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -4508,7 +4494,6 @@ }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@iarna/toml": { @@ -4604,7 +4589,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, + "devOptional": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -4620,7 +4605,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -4629,7 +4614,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -4642,7 +4627,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -4654,7 +4639,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -4666,7 +4651,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -4675,7 +4660,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -4692,7 +4677,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4707,7 +4692,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4723,7 +4708,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4735,13 +4720,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/@jest/console/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -4750,7 +4735,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4762,7 +4747,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -4809,7 +4794,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4824,7 +4809,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4840,7 +4825,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4852,13 +4837,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/@jest/core/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -4867,7 +4852,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -4881,7 +4866,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -4893,13 +4878,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/@jest/core/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4911,7 +4896,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -4926,7 +4911,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, + "devOptional": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -4939,7 +4924,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -4951,7 +4936,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -4968,7 +4953,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -4983,7 +4968,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, + "devOptional": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -5026,7 +5011,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5041,7 +5026,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5057,7 +5042,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5069,13 +5054,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5095,7 +5080,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -5104,7 +5089,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -5119,7 +5104,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5134,7 +5119,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5144,7 +5129,7 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -5157,7 +5142,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -5171,7 +5156,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -5186,7 +5171,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -5201,7 +5186,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -5227,7 +5212,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5242,7 +5227,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5258,7 +5243,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5270,19 +5255,19 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/@jest/transform/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "devOptional": true }, "node_modules/@jest/transform/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -5291,13 +5276,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "devOptional": true }, "node_modules/@jest/transform/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5309,7 +5294,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "devOptional": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -5322,7 +5307,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -5337,7 +5322,7 @@ }, "node_modules/@jest/types/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5351,7 +5336,7 @@ }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5366,7 +5351,7 @@ }, "node_modules/@jest/types/node_modules/color-convert": { "version": "2.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5377,12 +5362,12 @@ }, "node_modules/@jest/types/node_modules/color-name": { "version": "1.1.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5390,7 +5375,7 @@ }, "node_modules/@jest/types/node_modules/supports-color": { "version": "7.2.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7371,7 +7356,7 @@ }, "node_modules/@sinclair/typebox": { "version": "0.27.8", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -7425,7 +7410,7 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -7433,7 +7418,7 @@ }, "node_modules/@sinonjs/fake-timers": { "version": "10.2.0", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -7989,7 +7974,7 @@ "version": "7.20.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -8002,7 +7987,7 @@ "version": "7.6.6", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -8011,7 +7996,7 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -8021,7 +8006,7 @@ "version": "7.20.3", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -8224,7 +8209,7 @@ "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*" } @@ -8257,12 +8242,12 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -8270,7 +8255,7 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -8385,7 +8370,7 @@ "version": "18.18.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz", "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==", - "dev": true, + "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -8543,7 +8528,7 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/testing-library__jest-dom": { @@ -8592,7 +8577,7 @@ }, "node_modules/@types/yargs": { "version": "17.0.24", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -8600,7 +8585,7 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/yoga-layout": { @@ -9485,7 +9470,6 @@ }, "node_modules/abort-controller": { "version": "3.0.0", - "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -9501,7 +9485,6 @@ }, "node_modules/abstract-logging": { "version": "2.0.1", - "dev": true, "license": "MIT" }, "node_modules/accepts": { @@ -9518,7 +9501,6 @@ }, "node_modules/acorn": { "version": "8.10.0", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -9546,7 +9528,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -9634,7 +9615,6 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -9650,7 +9630,6 @@ }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.12.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -9665,7 +9644,6 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/ajv-keywords": { @@ -9699,7 +9677,7 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -9713,7 +9691,7 @@ }, "node_modules/ansi-escapes/node_modules/type-fest": { "version": "0.21.3", - "dev": true, + "devOptional": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -9735,7 +9713,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9763,7 +9740,7 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -9818,7 +9795,6 @@ }, "node_modules/archy": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/are-we-there-yet": { @@ -9838,7 +9814,7 @@ }, "node_modules/argparse": { "version": "1.0.10", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -10059,7 +10035,6 @@ }, "node_modules/atomic-sleep": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -10124,7 +10099,6 @@ }, "node_modules/avvio": { "version": "8.2.1", - "dev": true, "license": "MIT", "dependencies": { "archy": "^1.0.0", @@ -10176,7 +10150,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -10197,7 +10171,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -10212,7 +10186,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10228,7 +10202,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10240,13 +10214,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/babel-jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -10255,7 +10229,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10320,7 +10294,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -10336,7 +10310,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -10352,7 +10326,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "devOptional": true, "bin": { "semver": "bin/semver.js" } @@ -10361,7 +10335,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -10518,7 +10492,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -10607,7 +10581,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, + "devOptional": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -11031,7 +11005,7 @@ }, "node_modules/bser": { "version": "2.1.1", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -11066,7 +11040,7 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/builtin-modules": { @@ -11324,7 +11298,7 @@ }, "node_modules/camelcase": { "version": "6.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -11487,7 +11461,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -11585,7 +11559,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "devOptional": true }, "node_modules/clean-regexp": { "version": "1.0.0", @@ -11878,7 +11852,7 @@ }, "node_modules/cliui": { "version": "8.0.1", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -11925,7 +11899,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, + "devOptional": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -11944,7 +11918,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "devOptional": true }, "node_modules/color": { "version": "4.2.3", @@ -12643,7 +12617,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -12664,7 +12638,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -12679,7 +12653,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -12695,7 +12669,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -12707,13 +12681,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/create-jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -12722,7 +12696,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -13221,7 +13195,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, + "devOptional": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -13279,12 +13253,11 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "dev": true, "license": "MIT" }, "node_modules/deepmerge": { "version": "4.2.2", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13530,7 +13503,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -13680,7 +13653,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -13713,7 +13686,6 @@ }, "node_modules/doctrine": { "version": "3.0.0", - "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -13926,7 +13898,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=12" }, @@ -14463,7 +14435,6 @@ }, "node_modules/eslint": { "version": "8.49.0", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -15108,7 +15079,6 @@ }, "node_modules/eslint-scope": { "version": "7.2.2", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -15214,7 +15184,6 @@ }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -15228,12 +15197,10 @@ }, "node_modules/eslint/node_modules/argparse": { "version": "2.0.1", - "dev": true, "license": "Python-2.0" }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -15248,7 +15215,6 @@ }, "node_modules/eslint/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -15259,12 +15225,10 @@ }, "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/globals": { "version": "13.20.0", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -15278,7 +15242,6 @@ }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15286,7 +15249,6 @@ }, "node_modules/eslint/node_modules/js-yaml": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -15297,7 +15259,6 @@ }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15308,7 +15269,6 @@ }, "node_modules/espree": { "version": "9.6.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", @@ -15324,7 +15284,7 @@ }, "node_modules/esprima": { "version": "4.0.1", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -15417,7 +15377,6 @@ }, "node_modules/event-target-shim": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15430,7 +15389,6 @@ }, "node_modules/events": { "version": "3.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -15482,7 +15440,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8.0" } @@ -15499,7 +15457,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -15743,12 +15701,10 @@ }, "node_modules/fast-content-type-parse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", - "dev": true, "license": "MIT" }, "node_modules/fast-deep-equal": { @@ -15790,7 +15746,6 @@ }, "node_modules/fast-json-stringify": { "version": "5.7.0", - "dev": true, "license": "MIT", "dependencies": { "@fastify/deepmerge": "^1.0.0", @@ -15803,7 +15758,6 @@ }, "node_modules/fast-json-stringify/node_modules/ajv": { "version": "8.12.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -15818,17 +15772,14 @@ }, "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "dev": true, "license": "MIT" }, "node_modules/fast-querystring": { "version": "1.1.2", - "dev": true, "license": "MIT", "dependencies": { "fast-decode-uri-component": "^1.0.1" @@ -15836,7 +15787,6 @@ }, "node_modules/fast-redact": { "version": "3.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15849,7 +15799,6 @@ }, "node_modules/fast-uri": { "version": "2.2.0", - "dev": true, "license": "MIT" }, "node_modules/fastest-levenshtein": { @@ -15861,7 +15810,6 @@ }, "node_modules/fastify": { "version": "4.12.0", - "dev": true, "license": "MIT", "dependencies": { "@fastify/ajv-compiler": "^3.3.1", @@ -15883,7 +15831,6 @@ }, "node_modules/fastify-plugin": { "version": "4.5.0", - "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -15906,7 +15853,7 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -16028,7 +15975,6 @@ }, "node_modules/file-entry-cache": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" @@ -16128,7 +16074,6 @@ }, "node_modules/find-my-way": { "version": "7.6.2", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -16145,7 +16090,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -16169,7 +16113,6 @@ }, "node_modules/flat-cache": { "version": "3.0.4", - "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.1.0", @@ -16181,7 +16124,6 @@ }, "node_modules/flat-cache/node_modules/glob": { "version": "7.2.3", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -16200,7 +16142,6 @@ }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -16214,7 +16155,6 @@ }, "node_modules/flatted": { "version": "3.2.7", - "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -16494,7 +16434,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -18032,7 +17971,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -18063,7 +18002,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.0.0" } @@ -18282,7 +18221,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -18815,7 +18753,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "devOptional": true }, "node_modules/html-rewriter-wasm": { "version": "0.4.1", @@ -19255,7 +19193,7 @@ }, "node_modules/import-local": { "version": "3.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", @@ -19273,7 +19211,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -19441,7 +19378,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -19647,7 +19583,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -19789,7 +19725,6 @@ }, "node_modules/is-path-inside": { "version": "3.0.3", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -20082,7 +20017,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.1.tgz", "integrity": "sha512-opCrKqbthmq3SKZ10mFMQG9dk3fTa3quaOLD35kJa5ejwZHd9xAr+kLuziiZz2cG32s4lMZxNdmdcEQnTDP4+g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20091,7 +20026,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -20107,7 +20042,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, + "devOptional": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -20121,7 +20056,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20130,7 +20065,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "devOptional": true, "dependencies": { "semver": "^7.5.3" }, @@ -20145,7 +20080,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20157,7 +20092,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -20171,7 +20106,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -20180,7 +20115,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, + "devOptional": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -20226,7 +20161,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -20252,7 +20187,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, + "devOptional": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -20266,7 +20201,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -20281,7 +20216,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -20312,7 +20247,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -20327,7 +20262,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20343,7 +20278,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -20355,13 +20290,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-circus/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20370,7 +20305,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -20385,7 +20320,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -20399,7 +20334,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -20411,13 +20346,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-circus/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20429,7 +20364,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -20462,7 +20397,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -20477,7 +20412,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20493,7 +20428,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -20505,13 +20440,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-cli/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20520,7 +20455,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20532,7 +20467,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -20577,7 +20512,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -20592,7 +20527,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20608,7 +20543,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -20620,13 +20555,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20646,7 +20581,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20655,7 +20590,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -20669,7 +20604,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -20681,13 +20616,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-config/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20699,7 +20634,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -20714,7 +20649,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -20729,7 +20664,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20745,7 +20680,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -20757,13 +20692,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-diff/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20772,7 +20707,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -20786,7 +20721,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -20798,13 +20733,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-diff/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20816,7 +20751,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, + "devOptional": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -20828,7 +20763,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -20844,7 +20779,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -20859,7 +20794,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20875,7 +20810,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -20887,13 +20822,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-each/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20902,7 +20837,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -20916,7 +20851,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -20928,13 +20863,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-each/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20972,7 +20907,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -20989,7 +20924,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -20998,7 +20933,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -21023,7 +20958,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21032,7 +20967,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -21047,7 +20982,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21062,7 +20997,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, + "devOptional": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -21075,7 +21010,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -21087,7 +21022,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -21101,13 +21036,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -21122,7 +21057,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21137,7 +21072,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21153,7 +21088,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21165,13 +21100,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-matcher-utils/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21180,7 +21115,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -21194,7 +21129,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -21206,13 +21141,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-matcher-utils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21224,7 +21159,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -21242,7 +21177,7 @@ }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -21256,7 +21191,7 @@ }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -21271,7 +21206,7 @@ }, "node_modules/jest-message-util/node_modules/color-convert": { "version": "2.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -21282,12 +21217,12 @@ }, "node_modules/jest-message-util/node_modules/color-name": { "version": "1.1.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-message-util/node_modules/has-flag": { "version": "4.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -21297,7 +21232,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -21311,7 +21246,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -21323,11 +21258,11 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-message-util/node_modules/supports-color": { "version": "7.2.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -21340,7 +21275,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -21354,7 +21289,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" }, @@ -21371,7 +21306,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -21380,7 +21315,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -21400,7 +21335,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -21413,7 +21348,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21428,7 +21363,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21444,7 +21379,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21456,13 +21391,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-resolve/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21471,7 +21406,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21483,7 +21418,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -21515,7 +21450,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21530,7 +21465,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21546,7 +21481,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21558,13 +21493,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-runner/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21573,7 +21508,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -21588,7 +21523,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21603,7 +21538,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -21618,7 +21553,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21630,7 +21565,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -21663,7 +21598,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21678,7 +21613,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21694,7 +21629,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21706,13 +21641,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -21732,7 +21667,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21741,7 +21676,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21753,7 +21688,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -21784,7 +21719,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21799,7 +21734,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21815,7 +21750,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21827,13 +21762,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-snapshot/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21842,7 +21777,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -21856,7 +21791,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -21868,13 +21803,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-snapshot/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21886,7 +21821,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -21901,7 +21836,7 @@ }, "node_modules/jest-util/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -21915,7 +21850,7 @@ }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -21930,7 +21865,7 @@ }, "node_modules/jest-util/node_modules/color-convert": { "version": "2.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -21941,12 +21876,12 @@ }, "node_modules/jest-util/node_modules/color-name": { "version": "1.1.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-util/node_modules/has-flag": { "version": "4.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -21954,7 +21889,7 @@ }, "node_modules/jest-util/node_modules/supports-color": { "version": "7.2.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -21967,7 +21902,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -21984,7 +21919,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21999,7 +21934,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -22015,7 +21950,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -22027,13 +21962,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-validate/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -22042,7 +21977,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -22056,7 +21991,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -22068,13 +22003,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/jest-validate/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22086,7 +22021,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -22105,7 +22040,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -22120,7 +22055,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -22136,7 +22071,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -22148,13 +22083,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true }, "node_modules/jest-watcher/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -22163,7 +22098,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22240,7 +22175,7 @@ }, "node_modules/js-yaml": { "version": "3.14.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -22352,7 +22287,6 @@ }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { @@ -22576,14 +22510,13 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } }, "node_modules/levn": { "version": "0.4.1", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -22595,7 +22528,6 @@ }, "node_modules/light-my-request": { "version": "5.9.2", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "cookie": "^0.5.0", @@ -22961,7 +22893,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -23097,7 +23028,6 @@ }, "node_modules/lodash.merge": { "version": "4.6.2", - "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { @@ -23400,7 +23330,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, + "devOptional": true, "dependencies": { "tmpl": "1.0.5" } @@ -24359,7 +24289,7 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-object-hash": { @@ -24405,7 +24335,7 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -24970,7 +24900,6 @@ }, "node_modules/on-exit-leak-free": { "version": "2.1.0", - "dev": true, "license": "MIT" }, "node_modules/on-finished": { @@ -25046,7 +24975,6 @@ }, "node_modules/optionator": { "version": "0.9.3", - "dev": true, "license": "MIT", "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", @@ -25160,7 +25088,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -25174,7 +25101,6 @@ }, "node_modules/p-locate/node_modules/p-limit": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -25676,7 +25602,6 @@ }, "node_modules/pino": { "version": "8.14.1", - "dev": true, "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", @@ -25697,7 +25622,6 @@ }, "node_modules/pino-abstract-transport": { "version": "1.0.0", - "dev": true, "license": "MIT", "dependencies": { "readable-stream": "^4.0.0", @@ -25706,7 +25630,6 @@ }, "node_modules/pino-abstract-transport/node_modules/buffer": { "version": "6.0.3", - "dev": true, "funding": [ { "type": "github", @@ -25729,7 +25652,6 @@ }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { "version": "4.4.0", - "dev": true, "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -25743,7 +25665,6 @@ }, "node_modules/pino-abstract-transport/node_modules/split2": { "version": "4.2.0", - "dev": true, "license": "ISC", "engines": { "node": ">= 10.x" @@ -25751,12 +25672,11 @@ }, "node_modules/pino-std-serializers": { "version": "6.2.1", - "dev": true, "license": "MIT" }, "node_modules/pirates": { "version": "4.0.5", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 6" @@ -25764,7 +25684,7 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -25775,7 +25695,7 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "4.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -25787,7 +25707,7 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "5.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -25798,7 +25718,7 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -26596,7 +26516,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -26612,7 +26531,7 @@ }, "node_modules/prettier": { "version": "2.8.8", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "prettier": "bin-prettier.js" @@ -26705,7 +26624,6 @@ }, "node_modules/process": { "version": "0.11.10", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -26718,7 +26636,6 @@ }, "node_modules/process-warning": { "version": "2.2.0", - "dev": true, "license": "MIT" }, "node_modules/progress": { @@ -26739,7 +26656,7 @@ }, "node_modules/prompts": { "version": "2.4.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -26751,7 +26668,7 @@ }, "node_modules/prompts/node_modules/kleur": { "version": "3.0.3", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -26806,7 +26723,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -26879,7 +26795,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -26967,7 +26883,6 @@ }, "node_modules/quick-format-unescaped": { "version": "4.0.4", - "dev": true, "license": "MIT" }, "node_modules/quick-lru": { @@ -27074,7 +26989,6 @@ }, "node_modules/react": { "version": "18.2.0", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -27210,7 +27124,6 @@ }, "node_modules/react-dom": { "version": "18.2.0", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -27517,7 +27430,6 @@ }, "node_modules/real-require": { "version": "0.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 12.13.0" @@ -27838,7 +27750,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -27846,7 +27758,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -27901,7 +27812,7 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" @@ -27912,7 +27823,7 @@ }, "node_modules/resolve-from": { "version": "5.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -27940,7 +27851,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -27975,7 +27886,6 @@ }, "node_modules/ret": { "version": "0.2.2", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -27999,7 +27909,6 @@ }, "node_modules/rfdc": { "version": "1.3.0", - "dev": true, "license": "MIT" }, "node_modules/rimraf": { @@ -28200,7 +28109,6 @@ }, "node_modules/safe-regex2": { "version": "2.0.0", - "dev": true, "license": "MIT", "dependencies": { "ret": "~0.2.0" @@ -28208,7 +28116,6 @@ }, "node_modules/safe-stable-stringify": { "version": "2.4.3", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -28237,7 +28144,6 @@ }, "node_modules/scheduler": { "version": "0.23.0", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -28267,7 +28173,6 @@ }, "node_modules/secure-json-parse": { "version": "2.7.0", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/select-hose": { @@ -28487,7 +28392,6 @@ }, "node_modules/set-cookie-parser": { "version": "2.6.0", - "dev": true, "license": "MIT" }, "node_modules/set-function-name": { @@ -28757,7 +28661,7 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/slash": { @@ -29070,7 +28974,6 @@ }, "node_modules/sonic-boom": { "version": "3.3.0", - "dev": true, "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -29158,7 +29061,7 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -29168,7 +29071,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -29353,7 +29256,7 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/sshpk": { @@ -29403,7 +29306,7 @@ }, "node_modules/stack-utils": { "version": "2.0.6", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -29414,7 +29317,7 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -29529,7 +29432,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, + "devOptional": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -29557,7 +29460,7 @@ }, "node_modules/string-width": { "version": "4.2.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -29597,12 +29500,12 @@ }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -29682,7 +29585,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -29707,7 +29609,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -29742,7 +29644,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -30259,7 +30160,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, + "devOptional": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -30273,7 +30174,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -30299,7 +30200,6 @@ }, "node_modules/text-table": { "version": "0.2.0", - "dev": true, "license": "MIT" }, "node_modules/thenify": { @@ -30323,7 +30223,6 @@ }, "node_modules/thread-stream": { "version": "2.3.0", - "dev": true, "license": "MIT", "dependencies": { "real-require": "^0.2.0" @@ -30394,7 +30293,6 @@ }, "node_modules/tiny-lru": { "version": "10.4.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -30433,7 +30331,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "devOptional": true }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -31183,7 +31081,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -31194,7 +31091,7 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -31299,7 +31196,6 @@ }, "node_modules/typescript": { "version": "5.2.2", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -31375,7 +31271,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "devOptional": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -31654,7 +31550,7 @@ "version": "9.1.3", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -31668,7 +31564,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "devOptional": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -31947,7 +31843,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, + "devOptional": true, "dependencies": { "makeerror": "1.0.12" } @@ -32737,7 +32633,7 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -32800,7 +32696,7 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -32814,7 +32710,7 @@ }, "node_modules/wrap-ansi/node_modules/color-convert": { "version": "2.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -32825,7 +32721,7 @@ }, "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/wrappy": { @@ -32968,7 +32864,7 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=10" @@ -33008,7 +32904,7 @@ }, "node_modules/yargs": { "version": "17.7.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -33045,7 +32941,7 @@ }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=12" @@ -33061,7 +32957,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -33916,6 +33811,7 @@ "@types/glob-to-regexp": "0.4.1", "@types/js-cookie": "3.0.2", "@types/node": "^18.18.0", + "cross-fetch": "^4.0.0", "eslint-config-custom": "*", "tsup": "*", "typescript": "*" @@ -33932,6 +33828,15 @@ } } }, + "packages/shared/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "packages/themes": { "name": "@clerk/themes", "version": "2.0.0-alpha-v5.0", diff --git a/packages/shared/jest.config.js b/packages/shared/jest.config.js index 9dd261d1d2f..f9da269204a 100644 --- a/packages/shared/jest.config.js +++ b/packages/shared/jest.config.js @@ -4,9 +4,6 @@ const { name } = require('./package.json'); const config = { displayName: name.replace('@clerk', ''), injectGlobals: true, - globals: { - fetch: global.fetch, - }, testEnvironment: 'jsdom', roots: ['/src'], diff --git a/packages/shared/package.json b/packages/shared/package.json index 17839eef8aa..fc738db6a6b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -93,6 +93,7 @@ "@types/glob-to-regexp": "0.4.1", "@types/js-cookie": "3.0.2", "@types/node": "^18.18.0", + "cross-fetch": "^4.0.0", "eslint-config-custom": "*", "tsup": "*", "typescript": "*" diff --git a/packages/shared/src/__tests__/telemetry.test.ts b/packages/shared/src/__tests__/telemetry.test.ts index 16e0211ff29..7c91bcc1701 100644 --- a/packages/shared/src/__tests__/telemetry.test.ts +++ b/packages/shared/src/__tests__/telemetry.test.ts @@ -1,3 +1,5 @@ +import 'cross-fetch/polyfill'; + import { TelemetryCollector } from '../telemetry'; jest.useFakeTimers(); @@ -44,7 +46,8 @@ describe('TelemetryCollector', () => { test('does not send events when debug is enabled, logs them instead', async () => { const fetchSpy = jest.spyOn(global, 'fetch'); - const consoleSpy = jest.spyOn(global.console, 'log'); + const consoleGroupSpy = jest.spyOn(console, 'groupCollapsed').mockImplementation(() => {}); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); const collector = new TelemetryCollector({ debug: true, @@ -73,6 +76,7 @@ describe('TelemetryCollector', () => { ] `); + consoleGroupSpy.mockRestore(); consoleSpy.mockRestore(); fetchSpy.mockRestore(); }); @@ -81,7 +85,8 @@ describe('TelemetryCollector', () => { process.env.CLERK_TELEMETRY_DEBUG = '1'; const fetchSpy = jest.spyOn(global, 'fetch'); - const consoleSpy = jest.spyOn(global.console, 'log'); + const consoleGroupSpy = jest.spyOn(console, 'groupCollapsed').mockImplementation(() => {}); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); const collector = new TelemetryCollector({ publishableKey: TEST_PK, @@ -109,6 +114,7 @@ describe('TelemetryCollector', () => { ] `); + consoleGroupSpy.mockRestore(); consoleSpy.mockRestore(); fetchSpy.mockRestore(); diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index dd1c59bbeb6..537a1855505 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -106,6 +106,7 @@ export class TelemetryCollector { samplingRate: options.samplingRate ?? DEFAULT_CONFIG.samplingRate, disabled: options.disabled ?? false, debug: options.debug ?? false, + endpoint: DEFAULT_CONFIG.endpoint, } as Required; if (!options.clerkVersion && typeof window === 'undefined') { From 2c4ba6b00c13f6b03aedd490411d46dcbea76562 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 18:17:45 -0600 Subject: [PATCH 15/30] feat(shared): Set production endpoint --- packages/shared/src/telemetry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 537a1855505..c75fa09b67d 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -90,7 +90,10 @@ type TelemetryEvent = { const DEFAULT_CONFIG: Partial> = { samplingRate: 1, maxBufferSize: 5, - endpoint: 'https://staging.clerk-telemetry.com', + // Production endpoint: https://clerk-telemetry.com + // Staging endpoint: https://staging.clerk-telemetry.com + // Local: http://localhost:8787 + endpoint: 'https://clerk-telemetry.com', }; // TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render @@ -131,8 +134,6 @@ export class TelemetryCollector { // Only send the first 16 characters of the secret key to to avoid sending the full key. We can still query against the partial key. this.#metadata.secretKey = options.secretKey.substring(0, 16); } - - // this.#config.endpoint = 'http://localhost:8787'; } get isEnabled(): boolean { From c43c32ce1287a3c6083ac9db5cd393aedf2d9237 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 19:17:27 -0600 Subject: [PATCH 16/30] feat(clerk-js): Make telemtry public, expose via isomorphicClerk --- packages/clerk-js/src/core/clerk.ts | 40 +++++++++++++++++++++++---- packages/react/src/isomorphicClerk.ts | 11 +++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8c087b3a593..d501506ef6e 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -149,6 +149,7 @@ export default class Clerk implements ClerkInterface { public organization?: OrganizationResource | null; public user?: UserResource | null; public __internal_country?: string | null; + public telemetry?: TelemetryCollector; protected internal_last_error: ClerkAPIError | null = null; @@ -162,7 +163,6 @@ export default class Clerk implements ClerkInterface { #environment?: EnvironmentResource | null; //@ts-expect-error with being undefined even though it's not possible - related to issue with ts and error thrower #fapiClient: FapiClient; - #telemetry?: TelemetryCollector; //@ts-expect-error with undefined even though it's not possible - related to issue with ts and error thrower #instanceType: InstanceType; #isReady = false; @@ -271,11 +271,9 @@ export default class Clerk implements ClerkInterface { Clerk.sdkMetadata = this.#options.sdkMetadata; } - this.#telemetry = new TelemetryCollector({ + this.telemetry = new TelemetryCollector({ clerkVersion: Clerk.version, - verbose: true, samplingRate: 1, - // @ts-expect-error -- in v5 this will always be defined publishableKey: this.publishableKey, ...this.#options.telemetry, }); @@ -395,7 +393,7 @@ export default class Clerk implements ClerkInterface { props, }), ); - this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignIn', appearanceProp: Boolean(props?.appearance) }); + this.telemetry?.record('COMPONENT_MOUNTED', { component: 'SignIn', appearanceProp: Boolean(props?.appearance) }); }; public unmountSignIn = (node: HTMLDivElement): void => { @@ -417,7 +415,7 @@ export default class Clerk implements ClerkInterface { props, }), ); - this.#telemetry.record('COMPONENT_MOUNTED', { component: 'SignUp', appearanceProp: Boolean(props?.appearance) }); + this.telemetry?.record('COMPONENT_MOUNTED', { component: 'SignUp', appearanceProp: Boolean(props?.appearance) }); }; public unmountSignUp = (node: HTMLDivElement): void => { @@ -439,6 +437,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'UserProfile', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountUserProfile = (node: HTMLDivElement): void => { @@ -460,6 +463,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'OrganizationProfile', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountOrganizationProfile = (node: HTMLDivElement) => { @@ -481,6 +489,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'CreateOrganization', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountCreateOrganization = (node: HTMLDivElement) => { @@ -502,6 +515,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'OrganizationSwitcher', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountOrganizationSwitcher = (node: HTMLDivElement): void => { @@ -519,6 +537,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'OrganizationList', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountOrganizationList = (node: HTMLDivElement): void => { @@ -536,6 +559,11 @@ export default class Clerk implements ClerkInterface { props, }), ); + + this.telemetry?.record('COMPONENT_MOUNTED', { + component: 'UserButton', + appearanceProp: Boolean(props?.appearance), + }); }; public unmountUserButton = (node: HTMLDivElement): void => { diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 39e44aed39f..679d158886e 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -1,5 +1,6 @@ import { inBrowser } from '@clerk/shared/browser'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; +import type { TelemetryCollector } from '@clerk/shared/telemetry'; import type { ActiveSessionResource, AuthenticateWithMetamaskParams, @@ -12,7 +13,6 @@ import type { HandleOAuthCallbackParams, ListenerCallback, OrganizationListProps, - OrganizationMembershipResource, OrganizationProfileProps, OrganizationResource, OrganizationSwitcherProps, @@ -320,6 +320,15 @@ export default class IsomorphicClerk { } } + get telemetry(): TelemetryCollector | undefined { + if (this.clerkjs) { + // @ts-expect-error -- We can't add the type here due to the TelemetryCollector type existing in shared, but the Clerk type existing in types + return this.clerkjs.telemetry; + } else { + return undefined; + } + } + get __unstable__environment(): any { if (this.clerkjs) { return (this.clerkjs as any).__unstable__environment; From e2ac1d6a01b6ca0ee38ee7956d005844f7004b88 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 19:17:50 -0600 Subject: [PATCH 17/30] feat(shared): Add doc on the TelemetryCollector --- packages/shared/src/telemetry.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index c75fa09b67d..d97036a1dc1 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -96,7 +96,18 @@ const DEFAULT_CONFIG: Partial> = { endpoint: 'https://clerk-telemetry.com', }; -// TODO: determine some type of throttle/dedupe heuristic to avoid sending excessive events for e.g. a component render +/** + * Handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. + * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: + * + * ```jsx + * + * ... + * + * ``` + * + * For more information, please see the telemetry documentation page: https://clerk.com/docs/telemetry + */ export class TelemetryCollector { #config: Required; #metadata: TelemetryMetadata = {} as TelemetryMetadata; From a33dc6227272b7719badb476ad20ba0a7d33a054 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 23:03:18 -0600 Subject: [PATCH 18/30] feat(clerk-js): Support telemetry: false as a way to disable telemetry --- packages/clerk-js/src/core/clerk.ts | 14 ++++++++------ .../src/GatsbyClerkProvider.tsx | 10 ++++++---- .../nextjs/src/utils/mergeNextClerkPropsWithEnv.ts | 2 +- packages/shared/src/telemetry.ts | 2 +- packages/types/src/clerk.ts | 10 ++++++---- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index d501506ef6e..896c1d0f3d1 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -271,12 +271,14 @@ export default class Clerk implements ClerkInterface { Clerk.sdkMetadata = this.#options.sdkMetadata; } - this.telemetry = new TelemetryCollector({ - clerkVersion: Clerk.version, - samplingRate: 1, - publishableKey: this.publishableKey, - ...this.#options.telemetry, - }); + if (this.#options.telemetry !== false) { + this.telemetry = new TelemetryCollector({ + clerkVersion: Clerk.version, + samplingRate: 1, + publishableKey: this.publishableKey, + ...this.#options.telemetry, + }); + } if (this.#options.standardBrowser) { this.#isReady = await this.#loadInStandardBrowser(); diff --git a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx index 29ec9a41c0e..56851d3f120 100644 --- a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx +++ b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx @@ -25,10 +25,12 @@ export function ClerkProvider({ children, ...rest }: GatsbyClerkProviderProps) { navigate={to => navigate(to)} initialState={__clerk_ssr_state || {}} sdkMetadata={SDK_METADATA} - telemetry={{ - disabled: TELEMETRY_DISABLED, - debug: TELEMETRY_DEBUG, - }} + telemetry={ + restProps?.telemetry ?? { + disabled: TELEMETRY_DISABLED, + debug: TELEMETRY_DEBUG, + } + } {...restProps} > {__clerk_ssr_interstitial_html ? ( diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index 1c3de67aa8c..391b9191cf4 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -16,7 +16,7 @@ export const mergeNextClerkPropsWithEnv = (props: Omit> = { * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: * * ```jsx - * + * * ... * * ``` diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 6e01ed3ce12..aa6a23d4e29 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -529,10 +529,12 @@ export interface ClerkOptions { /** * Telemetry options */ - telemetry?: { - disabled?: boolean; - debug?: boolean; - }; + telemetry?: + | false + | { + disabled?: boolean; + debug?: boolean; + }; sdkMetadata?: SDKMetadata; } From 2c3e8e6d297ee5e09004766854e8413eabd7d367 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 23:15:23 -0600 Subject: [PATCH 19/30] chore(shared): Tweak comment --- packages/shared/src/telemetry.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 5c61bad498c..c533faa52c0 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,3 +1,15 @@ +/** + * The TelemetryCollector class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. + * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: + * + * ```jsx + * + * ... + * + * ``` + * + * For more information, please see the telemetry documentation page: https://clerk.com/docs/telemetry + */ import type { InstanceType } from '@clerk/types'; import { parsePublishableKey } from './keys'; @@ -96,18 +108,6 @@ const DEFAULT_CONFIG: Partial> = { endpoint: 'https://clerk-telemetry.com', }; -/** - * Handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. - * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: - * - * ```jsx - * - * ... - * - * ``` - * - * For more information, please see the telemetry documentation page: https://clerk.com/docs/telemetry - */ export class TelemetryCollector { #config: Required; #metadata: TelemetryMetadata = {} as TelemetryMetadata; From 120f9745713cce82c9962b1e0aba1834e9b5dd17 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 23:15:46 -0600 Subject: [PATCH 20/30] chore(shared): Tweak comment --- packages/shared/src/telemetry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index c533faa52c0..f474579e6ee 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,5 +1,5 @@ /** - * The TelemetryCollector class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. + * The `TelemetryCollector` class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: * * ```jsx From 0c15744efa937c6c44c873e86c3edb98ee7d61f8 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 16 Nov 2023 23:31:18 -0600 Subject: [PATCH 21/30] fix(clerk-sdk-node): Add PACKAGE_ constants for tests --- packages/nextjs/jest.config.js | 1 + packages/sdk-node/jest.config.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/nextjs/jest.config.js b/packages/nextjs/jest.config.js index 8bf235426f6..46839c6617c 100644 --- a/packages/nextjs/jest.config.js +++ b/packages/nextjs/jest.config.js @@ -3,6 +3,7 @@ const { name } = require('./package.json'); /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { globals: { + PACKAGE_NAME: '@clerk/nextjs', PACKAGE_VERSION: '0.0.0-test', }, displayName: name.replace('@clerk', ''), diff --git a/packages/sdk-node/jest.config.js b/packages/sdk-node/jest.config.js index 67cdf529b55..26b66006337 100644 --- a/packages/sdk-node/jest.config.js +++ b/packages/sdk-node/jest.config.js @@ -2,6 +2,10 @@ const { name } = require('./package.json'); module.exports = { displayName: name.replace('@clerk', ''), + globals: { + PACKAGE_NAME: '@clerk/clerk-sdk-node', + PACKAGE_VERSION: '0.0.0-test', + }, roots: ['/src'], testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], transform: { From 69652ca68919a235de563fe205b65fe829d234e4 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 17 Nov 2023 10:42:11 -0600 Subject: [PATCH 22/30] fix(fastify): Define PACKAGE_ constants in test environment --- packages/fastify/jest.config.js | 4 ++++ packages/fastify/src/__snapshots__/clerkClient.test.ts.snap | 4 ++++ packages/fastify/src/__snapshots__/constants.test.ts.snap | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/packages/fastify/jest.config.js b/packages/fastify/jest.config.js index 392987e7757..591c70700d1 100644 --- a/packages/fastify/jest.config.js +++ b/packages/fastify/jest.config.js @@ -3,6 +3,10 @@ const { name } = require('./package.json'); /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { displayName: name.replace('@clerk', ''), + globals: { + PACKAGE_NAME: '@clerk/fastify', + PACKAGE_VERSION: '0.0.0-test', + }, injectGlobals: true, roots: ['/src'], testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], diff --git a/packages/fastify/src/__snapshots__/clerkClient.test.ts.snap b/packages/fastify/src/__snapshots__/clerkClient.test.ts.snap index af94ad90c75..cf50eb65430 100644 --- a/packages/fastify/src/__snapshots__/clerkClient.test.ts.snap +++ b/packages/fastify/src/__snapshots__/clerkClient.test.ts.snap @@ -7,6 +7,10 @@ exports[`clerk initializes clerk with constants 1`] = ` "apiUrl": "https://api.clerk.com", "apiVersion": "v1", "jwtKey": "", + "sdkMetadata": { + "name": "@clerk/fastify", + "version": "0.0.0-test", + }, "secretKey": "TEST_SECRET_KEY", }, ], diff --git a/packages/fastify/src/__snapshots__/constants.test.ts.snap b/packages/fastify/src/__snapshots__/constants.test.ts.snap index a8b15646fec..b653b78cd85 100644 --- a/packages/fastify/src/__snapshots__/constants.test.ts.snap +++ b/packages/fastify/src/__snapshots__/constants.test.ts.snap @@ -27,6 +27,10 @@ exports[`constants from environment variables 1`] = ` }, "JWT_KEY": "CLERK_JWT_KEY", "PUBLISHABLE_KEY": "CLERK_PUBLISHABLE_KEY", + "SDK_METADATA": { + "name": "@clerk/fastify", + "version": "0.0.0-test", + }, "SECRET_KEY": "CLERK_SECRET_KEY", } `; From 29f60d192e639bf06b90c6e1e42000dd9fec0d4d Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 17:32:45 -0600 Subject: [PATCH 23/30] feat(shared): Refactor telemetry instrumentation to rely on helpers --- packages/clerk-js/src/core/clerk.ts | 36 +-- packages/nextjs/src/server/authMiddleware.ts | 16 +- packages/shared/src/telemetry.ts | 293 +---------------- packages/shared/src/telemetry/collector.ts | 300 ++++++++++++++++++ .../src/telemetry/events/component-mounted.ts | 22 ++ packages/shared/src/telemetry/events/index.ts | 2 + .../src/telemetry/events/method-called.ts | 21 ++ 7 files changed, 365 insertions(+), 325 deletions(-) create mode 100644 packages/shared/src/telemetry/collector.ts create mode 100644 packages/shared/src/telemetry/events/component-mounted.ts create mode 100644 packages/shared/src/telemetry/events/index.ts create mode 100644 packages/shared/src/telemetry/events/method-called.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 32452e7642d..5af59e33a3d 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -13,7 +13,7 @@ import { setDevBrowserJWTInURL, stripScheme, } from '@clerk/shared'; -import { TelemetryCollector } from '@clerk/shared/telemetry'; +import { eventComponentMounted, TelemetryCollector } from '@clerk/shared/telemetry'; import type { ActiveSessionResource, AuthenticateWithMetamaskParams, @@ -401,7 +401,7 @@ export class Clerk implements ClerkInterface { props, }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { component: 'SignIn', appearanceProp: Boolean(props?.appearance) }); + this.telemetry?.record(eventComponentMounted('SignIn', props)); }; public unmountSignIn = (node: HTMLDivElement): void => { @@ -423,7 +423,7 @@ export class Clerk implements ClerkInterface { props, }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { component: 'SignUp', appearanceProp: Boolean(props?.appearance) }); + this.telemetry?.record(eventComponentMounted('SignUp', props)); }; public unmountSignUp = (node: HTMLDivElement): void => { @@ -446,10 +446,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'UserProfile', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('UserProfile', props)); }; public unmountUserProfile = (node: HTMLDivElement): void => { @@ -472,10 +469,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'OrganizationProfile', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('OrganizationProfile', props)); }; public unmountOrganizationProfile = (node: HTMLDivElement) => { @@ -498,10 +492,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'CreateOrganization', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('CreateOrganization', props)); }; public unmountCreateOrganization = (node: HTMLDivElement) => { @@ -524,10 +515,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'OrganizationSwitcher', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('OrganizationSwitcher', props)); }; public unmountOrganizationSwitcher = (node: HTMLDivElement): void => { @@ -546,10 +534,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'OrganizationList', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('OrganizationList', props)); }; public unmountOrganizationList = (node: HTMLDivElement): void => { @@ -568,10 +553,7 @@ export class Clerk implements ClerkInterface { }), ); - this.telemetry?.record('COMPONENT_MOUNTED', { - component: 'UserButton', - appearanceProp: Boolean(props?.appearance), - }); + this.telemetry?.record(eventComponentMounted('UserButton', props)); }; public unmountUserButton = (node: HTMLDivElement): void => { diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index 97b64b0302b..54b614f9f13 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -2,6 +2,7 @@ import type { AuthObject, RequestState } from '@clerk/backend'; import { buildRequestUrl, constants, TokenVerificationErrorReason } from '@clerk/backend'; import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser'; import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; +import { eventMethodCalled } from '@clerk/shared/telemetry'; import type { Autocomplete } from '@clerk/types'; import type Link from 'next/link'; import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; @@ -146,13 +147,14 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { const isApiRoute = createApiRoutes(apiRoutes); const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute, params); - clerkClient.telemetry.record('METHOD_CALLED', { - method: 'authMiddleware', - publicRoutes: Boolean(publicRoutes), - ignoredRoutes: Boolean(ignoredRoutes), - beforeAuth: Boolean(beforeAuth), - afterAuth: Boolean(afterAuth), - }); + clerkClient.telemetry.record( + eventMethodCalled('authMiddleware', { + publicRoutes: Boolean(publicRoutes), + ignoredRoutes: Boolean(ignoredRoutes), + beforeAuth: Boolean(beforeAuth), + afterAuth: Boolean(afterAuth), + }), + ); return withLogger('authMiddleware', logger => async (_req: NextRequest, evt: NextFetchEvent) => { if (options.debug) { diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index f474579e6ee..8abbde0af44 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,292 +1,3 @@ -/** - * The `TelemetryCollector` class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. - * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: - * - * ```jsx - * - * ... - * - * ``` - * - * For more information, please see the telemetry documentation page: https://clerk.com/docs/telemetry - */ -import type { InstanceType } from '@clerk/types'; +export { TelemetryCollector, type TelemetryCollectorOptions } from './telemetry/collector'; -import { parsePublishableKey } from './keys'; -import { isTruthy } from './underscore'; - -export type TelemetryCollectorOptions = { - /** - * If true, telemetry will not be collected. - */ - disabled?: boolean; - /** - * If true, telemetry will not be sent, but collected events will be logged to the console. - */ - debug?: boolean; - /** - * Sampling rate, 0-1 - */ - samplingRate?: number; - /** - * Set a custom buffer size to control how often events are sent - */ - maxBufferSize?: number; - /** - * The publishableKey to associate with the collected events. - */ - publishableKey?: string; - /** - * The secretKey to associate with the collected events. - */ - secretKey?: string; - /** - * The current clerk-js version. - */ - clerkVersion?: string; - /** - * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. - */ - sdk?: string; - /** - * The version of the SDK being used. - */ - sdkVersion?: string; -}; - -type TelemetryCollectorConfig = Pick< - TelemetryCollectorOptions, - 'samplingRate' | 'disabled' | 'debug' | 'maxBufferSize' -> & { - endpoint: string; -}; - -type TelemetryMetadata = Required< - Pick -> & { - /** - * The instance type, derived from the provided publishableKey. - */ - instanceType: InstanceType; -}; - -type TelemetryEvent = { - event: string; - /** - * publishableKey - */ - pk?: string; - /** - * secretKey - */ - sk?: string; - /** - * instanceType - */ - it: InstanceType; - /** - * clerkVersion - */ - cv: string; - /** - * SDK - */ - sdk?: string; - /** - * SDK Version - */ - sdkv?: string; - payload: Record; -}; - -const DEFAULT_CONFIG: Partial> = { - samplingRate: 1, - maxBufferSize: 5, - // Production endpoint: https://clerk-telemetry.com - // Staging endpoint: https://staging.clerk-telemetry.com - // Local: http://localhost:8787 - endpoint: 'https://clerk-telemetry.com', -}; - -export class TelemetryCollector { - #config: Required; - #metadata: TelemetryMetadata = {} as TelemetryMetadata; - #buffer: TelemetryEvent[] = []; - #pendingFlush: any; - - constructor(options: TelemetryCollectorOptions) { - this.#config = { - maxBufferSize: options.maxBufferSize ?? DEFAULT_CONFIG.maxBufferSize, - samplingRate: options.samplingRate ?? DEFAULT_CONFIG.samplingRate, - disabled: options.disabled ?? false, - debug: options.debug ?? false, - endpoint: DEFAULT_CONFIG.endpoint, - } as Required; - - if (!options.clerkVersion && typeof window === 'undefined') { - // N/A in a server environment - this.#metadata.clerkVersion = ''; - } else { - this.#metadata.clerkVersion = options.clerkVersion ?? ''; - } - - // We will try to grab the SDK data lazily when an event is triggered, so it should always be defined once the event is sent. - this.#metadata.sdk = options.sdk!; - this.#metadata.sdkVersion = options.sdkVersion!; - - this.#metadata.publishableKey = options.publishableKey ?? ''; - - const parsedKey = parsePublishableKey(options.publishableKey); - if (parsedKey) { - this.#metadata.instanceType = parsedKey.instanceType; - } - - if (options.secretKey) { - // Only send the first 16 characters of the secret key to to avoid sending the full key. We can still query against the partial key. - this.#metadata.secretKey = options.secretKey.substring(0, 16); - } - } - - get isEnabled(): boolean { - if (this.#metadata.instanceType !== 'development') { - return false; - } - - // In browser or client environments, we most likely pass the disabled option to the collector, but in environments - // where environment variables are available we also check for `CLERK_TELEMETRY_DISABLED`. - if (this.#config.disabled || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DISABLED))) { - return false; - } - - return true; - } - - get isDebug(): boolean { - return this.#config.debug || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DEBUG)); - } - - record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']) { - const preparedPayload = this.#preparePayload(event, payload); - - this.#logEvent(preparedPayload.event, preparedPayload); - - if (!this.#shouldRecord()) { - return; - } - - this.#buffer.push(preparedPayload); - - this.#scheduleFlush(); - } - - #shouldRecord(): boolean { - return this.isEnabled && !this.isDebug && Math.random() <= this.#config.samplingRate; - } - - #scheduleFlush(): void { - // On the server, we want to flush immediately as we have less guarantees about the lifecycle of the process - if (typeof window === 'undefined') { - this.#flush(); - return; - } - - const isBufferFull = this.#buffer.length >= this.#config.maxBufferSize; - if (isBufferFull) { - // If the buffer is full, flush immediately to make sure we minimize the chance of event loss. - // Cancel any pending flushes as we're going to flush immediately - if (this.#pendingFlush) { - const cancel = typeof cancelIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout; - cancel(this.#pendingFlush); - } - this.#flush(); - return; - } - - // If we have a pending flush, do nothing - if (this.#pendingFlush) return; - - if ('requestIdleCallback' in window) { - this.#pendingFlush = requestIdleCallback(() => { - this.#flush(); - }); - } else { - // This is not an ideal solution, but it at least waits until the next tick - this.#pendingFlush = setTimeout(() => { - this.#flush(); - }, 0); - } - } - - #flush(): void { - fetch(new URL('/v1/event', this.#config.endpoint), { - method: 'POST', - // TODO: We send an array here with that idea that we can eventually send multiple events. - body: JSON.stringify({ - events: this.#buffer, - }), - headers: { - 'Content-Type': 'application/json', - }, - }) - .catch(() => void 0) - .then(() => { - this.#buffer = []; - }) - .catch(() => void 0); - } - - /** - * If running in debug mode, log the event and its payload to the console. - */ - #logEvent(event: TelemetryEvent['event'], payload: Record) { - if (!this.isDebug) { - return; - } - - if (typeof console.groupCollapsed !== 'undefined') { - console.groupCollapsed('[clerk/telemetry]', event); - console.log(payload); - console.groupEnd(); - } else { - console.log('[clerk/telemetry]', event, payload); - } - } - - /** - * If in browser, attempt to lazily grab the SDK metadata from the Clerk singleton, otherwise fallback to the initially passed in values. - * - * This is necessary because the sdkMetadata can be set by the host SDK after the TelemetryCollector is instantiated. - */ - #getSDKMetadata() { - let sdkMetadata = { - name: this.#metadata.sdk, - version: this.#metadata.sdkVersion, - }; - - // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here - if (typeof window !== 'undefined' && window.Clerk) { - // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here - sdkMetadata = { ...sdkMetadata, ...window.Clerk.constructor.sdkMetadata }; - } - - return sdkMetadata; - } - - /** - * Append relevant metadata from the Clerk singleton to the event payload. - */ - #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { - const sdkMetadata = this.#getSDKMetadata(); - - return { - event, - cv: this.#metadata.clerkVersion ?? '', - it: this.#metadata.instanceType ?? '', - sdk: sdkMetadata.name, - sdkv: sdkMetadata.version, - ...(this.#metadata.publishableKey ? { pk: this.#metadata.publishableKey } : {}), - ...(this.#metadata.secretKey ? { sk: this.#metadata.secretKey } : {}), - payload, - }; - } -} +export * from './telemetry/events'; diff --git a/packages/shared/src/telemetry/collector.ts b/packages/shared/src/telemetry/collector.ts new file mode 100644 index 00000000000..14b43c09d8e --- /dev/null +++ b/packages/shared/src/telemetry/collector.ts @@ -0,0 +1,300 @@ +/** + * The `TelemetryCollector` class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. + * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: + * + * ```jsx + * + * ... + * + * ``` + * + * For more information, please see the telemetry documentation page: https://clerk.com/docs/telemetry + */ +import type { InstanceType } from '@clerk/types'; + +import { parsePublishableKey } from '../keys'; +import { isTruthy } from '../underscore'; + +export type TelemetryCollectorOptions = { + /** + * If true, telemetry will not be collected. + */ + disabled?: boolean; + /** + * If true, telemetry will not be sent, but collected events will be logged to the console. + */ + debug?: boolean; + /** + * Sampling rate, 0-1 + */ + samplingRate?: number; + /** + * Set a custom buffer size to control how often events are sent + */ + maxBufferSize?: number; + /** + * The publishableKey to associate with the collected events. + */ + publishableKey?: string; + /** + * The secretKey to associate with the collected events. + */ + secretKey?: string; + /** + * The current clerk-js version. + */ + clerkVersion?: string; + /** + * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. + */ + sdk?: string; + /** + * The version of the SDK being used. + */ + sdkVersion?: string; +}; + +type TelemetryCollectorConfig = Pick< + TelemetryCollectorOptions, + 'samplingRate' | 'disabled' | 'debug' | 'maxBufferSize' +> & { + endpoint: string; +}; + +type TelemetryMetadata = Required< + Pick +> & { + /** + * The instance type, derived from the provided publishableKey. + */ + instanceType: InstanceType; +}; + +type TelemetryEvent = { + event: string; + /** + * publishableKey + */ + pk?: string; + /** + * secretKey + */ + sk?: string; + /** + * instanceType + */ + it: InstanceType; + /** + * clerkVersion + */ + cv: string; + /** + * SDK + */ + sdk?: string; + /** + * SDK Version + */ + sdkv?: string; + payload: Record; +}; + +const DEFAULT_CONFIG: Partial> = { + samplingRate: 1, + maxBufferSize: 5, + // Production endpoint: https://clerk-telemetry.com + // Staging endpoint: https://staging.clerk-telemetry.com + // Local: http://localhost:8787 + endpoint: 'https://clerk-telemetry.com', +}; + +export class TelemetryCollector { + #config: Required; + #metadata: TelemetryMetadata = {} as TelemetryMetadata; + #buffer: TelemetryEvent[] = []; + #pendingFlush: any; + + constructor(options: TelemetryCollectorOptions) { + this.#config = { + maxBufferSize: options.maxBufferSize ?? DEFAULT_CONFIG.maxBufferSize, + samplingRate: options.samplingRate ?? DEFAULT_CONFIG.samplingRate, + disabled: options.disabled ?? false, + debug: options.debug ?? false, + endpoint: DEFAULT_CONFIG.endpoint, + } as Required; + + if (!options.clerkVersion && typeof window === 'undefined') { + // N/A in a server environment + this.#metadata.clerkVersion = ''; + } else { + this.#metadata.clerkVersion = options.clerkVersion ?? ''; + } + + // We will try to grab the SDK data lazily when an event is triggered, so it should always be defined once the event is sent. + this.#metadata.sdk = options.sdk!; + this.#metadata.sdkVersion = options.sdkVersion!; + + this.#metadata.publishableKey = options.publishableKey ?? ''; + + const parsedKey = parsePublishableKey(options.publishableKey); + if (parsedKey) { + this.#metadata.instanceType = parsedKey.instanceType; + } + + if (options.secretKey) { + // Only send the first 16 characters of the secret key to to avoid sending the full key. We can still query against the partial key. + this.#metadata.secretKey = options.secretKey.substring(0, 16); + } + } + + get isEnabled(): boolean { + if (this.#metadata.instanceType !== 'development') { + return false; + } + + // In browser or client environments, we most likely pass the disabled option to the collector, but in environments + // where environment variables are available we also check for `CLERK_TELEMETRY_DISABLED`. + if (this.#config.disabled || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DISABLED))) { + return false; + } + + return true; + } + + get isDebug(): boolean { + return this.#config.debug || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DEBUG)); + } + + record(event: Pick): void; + record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): void; + record( + event: TelemetryEvent['event'] | Pick, + payload?: TelemetryEvent['payload'], + ): void { + const preparedPayload = + typeof event === 'string' + ? this.#preparePayload(event, payload ?? {}) + : this.#preparePayload(event.event, event.payload); + + this.#logEvent(preparedPayload.event, preparedPayload); + + if (!this.#shouldRecord()) { + return; + } + + this.#buffer.push(preparedPayload); + + this.#scheduleFlush(); + } + + #shouldRecord(): boolean { + return this.isEnabled && !this.isDebug && Math.random() <= this.#config.samplingRate; + } + + #scheduleFlush(): void { + // On the server, we want to flush immediately as we have less guarantees about the lifecycle of the process + if (typeof window === 'undefined') { + this.#flush(); + return; + } + + const isBufferFull = this.#buffer.length >= this.#config.maxBufferSize; + if (isBufferFull) { + // If the buffer is full, flush immediately to make sure we minimize the chance of event loss. + // Cancel any pending flushes as we're going to flush immediately + if (this.#pendingFlush) { + const cancel = typeof cancelIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout; + cancel(this.#pendingFlush); + } + this.#flush(); + return; + } + + // If we have a pending flush, do nothing + if (this.#pendingFlush) return; + + if ('requestIdleCallback' in window) { + this.#pendingFlush = requestIdleCallback(() => { + this.#flush(); + }); + } else { + // This is not an ideal solution, but it at least waits until the next tick + this.#pendingFlush = setTimeout(() => { + this.#flush(); + }, 0); + } + } + + #flush(): void { + fetch(new URL('/v1/event', this.#config.endpoint), { + method: 'POST', + // TODO: We send an array here with that idea that we can eventually send multiple events. + body: JSON.stringify({ + events: this.#buffer, + }), + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch(() => void 0) + .then(() => { + this.#buffer = []; + }) + .catch(() => void 0); + } + + /** + * If running in debug mode, log the event and its payload to the console. + */ + #logEvent(event: TelemetryEvent['event'], payload: Record) { + if (!this.isDebug) { + return; + } + + if (typeof console.groupCollapsed !== 'undefined') { + console.groupCollapsed('[clerk/telemetry]', event); + console.log(payload); + console.groupEnd(); + } else { + console.log('[clerk/telemetry]', event, payload); + } + } + + /** + * If in browser, attempt to lazily grab the SDK metadata from the Clerk singleton, otherwise fallback to the initially passed in values. + * + * This is necessary because the sdkMetadata can be set by the host SDK after the TelemetryCollector is instantiated. + */ + #getSDKMetadata() { + let sdkMetadata = { + name: this.#metadata.sdk, + version: this.#metadata.sdkVersion, + }; + + // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here + if (typeof window !== 'undefined' && window.Clerk) { + // @ts-expect-error -- The global window.Clerk type is declared in clerk-js, but we can't rely on that here + sdkMetadata = { ...sdkMetadata, ...window.Clerk.constructor.sdkMetadata }; + } + + return sdkMetadata; + } + + /** + * Append relevant metadata from the Clerk singleton to the event payload. + */ + #preparePayload(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): TelemetryEvent { + const sdkMetadata = this.#getSDKMetadata(); + + return { + event, + cv: this.#metadata.clerkVersion ?? '', + it: this.#metadata.instanceType ?? '', + sdk: sdkMetadata.name, + sdkv: sdkMetadata.version, + ...(this.#metadata.publishableKey ? { pk: this.#metadata.publishableKey } : {}), + ...(this.#metadata.secretKey ? { sk: this.#metadata.secretKey } : {}), + payload, + }; + } +} diff --git a/packages/shared/src/telemetry/events/component-mounted.ts b/packages/shared/src/telemetry/events/component-mounted.ts new file mode 100644 index 00000000000..c294d3f0e9f --- /dev/null +++ b/packages/shared/src/telemetry/events/component-mounted.ts @@ -0,0 +1,22 @@ +const EVENT_COMPONENT_MOUNTED = 'COMPONENT_MOUNTED' as const; + +type EventComponentMounted = { + component: string; + appearanceProp: boolean; +}; + +/** + * Fired when one of the Clerk components is mounted. + */ +export function eventComponentMounted( + component: string, + props?: Record, +): { event: typeof EVENT_COMPONENT_MOUNTED; payload: EventComponentMounted } { + return { + event: EVENT_COMPONENT_MOUNTED, + payload: { + component, + appearanceProp: Boolean(props?.appearance), + }, + }; +} diff --git a/packages/shared/src/telemetry/events/index.ts b/packages/shared/src/telemetry/events/index.ts new file mode 100644 index 00000000000..1957f6d4f20 --- /dev/null +++ b/packages/shared/src/telemetry/events/index.ts @@ -0,0 +1,2 @@ +export * from './component-mounted'; +export * from './method-called'; diff --git a/packages/shared/src/telemetry/events/method-called.ts b/packages/shared/src/telemetry/events/method-called.ts new file mode 100644 index 00000000000..000e83d4d80 --- /dev/null +++ b/packages/shared/src/telemetry/events/method-called.ts @@ -0,0 +1,21 @@ +const EVENT_METHOD_CALLED = 'METHOD_CALLED' as const; + +type EventMethodCalled = { + method: string; +} & Record; + +/** + * Fired when a helper method is called from a Clerk SDK. + */ +export function eventMethodCalled( + method: string, + payload?: Record, +): { event: typeof EVENT_METHOD_CALLED; payload: EventMethodCalled } { + return { + event: EVENT_METHOD_CALLED, + payload: { + method, + ...payload, + }, + }; +} From ba5bcfd725c254e9504a0b60952af55d0ac3189b Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 22:42:31 -0600 Subject: [PATCH 24/30] feat(shared): Add telemetry notice --- packages/shared/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/shared/package.json b/packages/shared/package.json index 162eb5e388d..50ac6aa26f1 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -66,7 +66,8 @@ "url", "react", "constants", - "apiUrlFromPublishableKey" + "apiUrlFromPublishableKey", + "scripts" ], "scripts": { "build": "tsup", @@ -74,6 +75,7 @@ "clean": "rimraf ./dist", "dev": "tsup --watch", "dev:publish": "npm run dev -- --env.publish", + "postinstall": "node ./scripts/postinstall.mjs", "lint": "eslint src/", "lint:attw": "attw --pack .", "lint:publint": "publint", From 0746c64ae48887ab36b16bd318870ff18f1b1922 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 22:45:40 -0600 Subject: [PATCH 25/30] feat(shared): Add postinstall script --- packages/shared/.gitignore | 1 + packages/shared/scripts/postinstall.mjs | 69 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100755 packages/shared/scripts/postinstall.mjs diff --git a/packages/shared/.gitignore b/packages/shared/.gitignore index 5b45b89ce31..53005d45b9d 100644 --- a/packages/shared/.gitignore +++ b/packages/shared/.gitignore @@ -1,2 +1,3 @@ /*/ !/src/ +!/scripts/ diff --git a/packages/shared/scripts/postinstall.mjs b/packages/shared/scripts/postinstall.mjs new file mode 100755 index 00000000000..4a93c8818d1 --- /dev/null +++ b/packages/shared/scripts/postinstall.mjs @@ -0,0 +1,69 @@ +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +// If we make significant changes to how telemetry is collected in the future, bump this version. +const TELEMETRY_NOTICE_VERSION = '1'; + +function telemetryNotice() { + console.log(`Attention: Clerk now collects telemetry data from its SDKs connected to development instances.`); + console.log(`The data collected is used to inform Clerk's product roadmap.`); + console.log( + `To learn more, including how to opt-out from the telemetry program, visit: https://clerk.com/docs/telemetry.`, + ); + console.log(''); +} + +// Adapted from https://github.com/sindresorhus/env-paths +function getConfigDir(name) { + const homedir = os.homedir(); + const macos = () => path.join(homedir, 'Library', 'Preferences', name); + const win = () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + const { APPDATA = path.join(homedir, 'AppData', 'Roaming') } = process.env; + return path.join(APPDATA, name, 'Config'); + }; + const linux = () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + const { XDG_CONFIG_HOME = path.join(homedir, '.config') } = process.env; + return path.join(XDG_CONFIG_HOME, name); + }; + switch (process.platform) { + case 'darwin': + return macos(); + case 'win32': + return win(); + default: + return linux(); + } +} + +async function notifyAboutTelemetry() { + const configDir = getConfigDir('clerk'); + const configFile = path.join(configDir, 'config.json'); + + await fs.mkdir(configDir, { recursive: true }); + + let config = {}; + try { + config = JSON.parse(await fs.readFile(configFile, 'utf8')); + } catch (err) { + // File can't be read and parsed, continue + } + + if (parseInt(config.telemetryNoticeVersion, 10) >= TELEMETRY_NOTICE_VERSION) { + return; + } + + config.telemetryNoticeVersion = TELEMETRY_NOTICE_VERSION; + + telemetryNotice(); + + await fs.writeFile(configFile, JSON.stringify(config, null, '\t')); +} + +async function main() { + await notifyAboutTelemetry(); +} + +main(); From e17596226d541e14b7f0ad3045fa76c9d021c9b5 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 22:46:35 -0600 Subject: [PATCH 26/30] chore(shared): Add word --- packages/shared/scripts/postinstall.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/scripts/postinstall.mjs b/packages/shared/scripts/postinstall.mjs index 4a93c8818d1..cf5f0220f3a 100755 --- a/packages/shared/scripts/postinstall.mjs +++ b/packages/shared/scripts/postinstall.mjs @@ -6,7 +6,7 @@ import path from 'node:path'; const TELEMETRY_NOTICE_VERSION = '1'; function telemetryNotice() { - console.log(`Attention: Clerk now collects telemetry data from its SDKs connected to development instances.`); + console.log(`Attention: Clerk now collects telemetry data from its SDKs when connected to development instances.`); console.log(`The data collected is used to inform Clerk's product roadmap.`); console.log( `To learn more, including how to opt-out from the telemetry program, visit: https://clerk.com/docs/telemetry.`, From b2db03baf7c85ed841f60162d5a1903841079824 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 22:47:34 -0600 Subject: [PATCH 27/30] chore(shared): Swallow errors in postinstall --- packages/shared/scripts/postinstall.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/shared/scripts/postinstall.mjs b/packages/shared/scripts/postinstall.mjs index cf5f0220f3a..b1a1c7e1551 100755 --- a/packages/shared/scripts/postinstall.mjs +++ b/packages/shared/scripts/postinstall.mjs @@ -63,7 +63,11 @@ async function notifyAboutTelemetry() { } async function main() { - await notifyAboutTelemetry(); + try { + await notifyAboutTelemetry(); + } catch { + // Do nothing, we _really_ don't want to log errors during install. + } } main(); From afed654fdbb35b13039c9ccd91650dfbab15555a Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 20 Nov 2023 22:51:20 -0600 Subject: [PATCH 28/30] chore(repo): Allow scripts directory in subpaths-workaround --- scripts/subpath-workaround.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/subpath-workaround.mjs b/scripts/subpath-workaround.mjs index a9732b50665..804984f4e8a 100644 --- a/scripts/subpath-workaround.mjs +++ b/scripts/subpath-workaround.mjs @@ -24,9 +24,16 @@ async function run() { `Found ${subpathHelperFile.subpathNames.length} subpaths and ${subpathHelperFile.subpathFoldersBarrel.length} subpath barrels`, ); - const allFilesNames = [...subpathHelperFile.subpathNames, ...subpathHelperFile.subpathFoldersBarrel, 'dist']; + const allFilesNames = [ + ...subpathHelperFile.subpathNames, + ...subpathHelperFile.subpathFoldersBarrel, + 'dist', + 'scripts', + ]; const hasAllSubpathsInFiles = pkgFile.files.every(name => allFilesNames.includes(name)); + console.log(allFilesNames, pkgFile.files); + if (!hasAllSubpathsInFiles) { throw new Error('Not all subpaths from the package.json "files" array are in the subpaths.mjs'); } From 322de55ee951b8444b87f8de95e7a99937848716 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 21 Nov 2023 12:02:42 -0600 Subject: [PATCH 29/30] feat(shared): Adjust types to enforce the event structure from event factory methods --- packages/shared/src/telemetry.ts | 3 +- packages/shared/src/telemetry/collector.ts | 73 +----------------- .../src/telemetry/events/component-mounted.ts | 12 ++- .../src/telemetry/events/method-called.ts | 4 +- packages/shared/src/telemetry/types.ts | 74 +++++++++++++++++++ scripts/subpath-workaround.mjs | 2 - 6 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 packages/shared/src/telemetry/types.ts diff --git a/packages/shared/src/telemetry.ts b/packages/shared/src/telemetry.ts index 8abbde0af44..3f80deefed7 100644 --- a/packages/shared/src/telemetry.ts +++ b/packages/shared/src/telemetry.ts @@ -1,3 +1,4 @@ -export { TelemetryCollector, type TelemetryCollectorOptions } from './telemetry/collector'; +export { TelemetryCollector } from './telemetry/collector'; +export type { TelemetryCollectorOptions } from './telemetry/types'; export * from './telemetry/events'; diff --git a/packages/shared/src/telemetry/collector.ts b/packages/shared/src/telemetry/collector.ts index 14b43c09d8e..bccb5a99774 100644 --- a/packages/shared/src/telemetry/collector.ts +++ b/packages/shared/src/telemetry/collector.ts @@ -1,5 +1,5 @@ /** - * The `TelemetryCollector` class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLE environment variable. + * The `TelemetryCollector` class handles collection of telemetry events from Clerk SDKs. Telemetry is opt-out and can be disabled by setting a CLERK_TELEMETRY_DISABLED environment variable. * The `ClerkProvider` also accepts a `telemetry` prop that will be passed to the collector during initialization: * * ```jsx @@ -14,45 +14,7 @@ import type { InstanceType } from '@clerk/types'; import { parsePublishableKey } from '../keys'; import { isTruthy } from '../underscore'; - -export type TelemetryCollectorOptions = { - /** - * If true, telemetry will not be collected. - */ - disabled?: boolean; - /** - * If true, telemetry will not be sent, but collected events will be logged to the console. - */ - debug?: boolean; - /** - * Sampling rate, 0-1 - */ - samplingRate?: number; - /** - * Set a custom buffer size to control how often events are sent - */ - maxBufferSize?: number; - /** - * The publishableKey to associate with the collected events. - */ - publishableKey?: string; - /** - * The secretKey to associate with the collected events. - */ - secretKey?: string; - /** - * The current clerk-js version. - */ - clerkVersion?: string; - /** - * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. - */ - sdk?: string; - /** - * The version of the SDK being used. - */ - sdkVersion?: string; -}; +import type { TelemetryCollectorOptions, TelemetryEvent } from './types'; type TelemetryCollectorConfig = Pick< TelemetryCollectorOptions, @@ -70,36 +32,7 @@ type TelemetryMetadata = Required< instanceType: InstanceType; }; -type TelemetryEvent = { - event: string; - /** - * publishableKey - */ - pk?: string; - /** - * secretKey - */ - sk?: string; - /** - * instanceType - */ - it: InstanceType; - /** - * clerkVersion - */ - cv: string; - /** - * SDK - */ - sdk?: string; - /** - * SDK Version - */ - sdkv?: string; - payload: Record; -}; - -const DEFAULT_CONFIG: Partial> = { +const DEFAULT_CONFIG: Partial = { samplingRate: 1, maxBufferSize: 5, // Production endpoint: https://clerk-telemetry.com diff --git a/packages/shared/src/telemetry/events/component-mounted.ts b/packages/shared/src/telemetry/events/component-mounted.ts index c294d3f0e9f..e298bc8a1b9 100644 --- a/packages/shared/src/telemetry/events/component-mounted.ts +++ b/packages/shared/src/telemetry/events/component-mounted.ts @@ -1,8 +1,13 @@ +import type { TelemetryEventRaw } from '../types'; + const EVENT_COMPONENT_MOUNTED = 'COMPONENT_MOUNTED' as const; type EventComponentMounted = { component: string; appearanceProp: boolean; + elements: boolean; + variables: boolean; + baseTheme: boolean; }; /** @@ -10,13 +15,16 @@ type EventComponentMounted = { */ export function eventComponentMounted( component: string, - props?: Record, -): { event: typeof EVENT_COMPONENT_MOUNTED; payload: EventComponentMounted } { + props?: Record, +): TelemetryEventRaw { return { event: EVENT_COMPONENT_MOUNTED, payload: { component, appearanceProp: Boolean(props?.appearance), + baseTheme: Boolean(props?.appearance?.baseTheme), + elements: Boolean(props?.appearance?.elements), + variables: Boolean(props?.appearance?.variables), }, }; } diff --git a/packages/shared/src/telemetry/events/method-called.ts b/packages/shared/src/telemetry/events/method-called.ts index 000e83d4d80..0c3b6a49338 100644 --- a/packages/shared/src/telemetry/events/method-called.ts +++ b/packages/shared/src/telemetry/events/method-called.ts @@ -1,3 +1,5 @@ +import type { TelemetryEventRaw } from '../types'; + const EVENT_METHOD_CALLED = 'METHOD_CALLED' as const; type EventMethodCalled = { @@ -10,7 +12,7 @@ type EventMethodCalled = { export function eventMethodCalled( method: string, payload?: Record, -): { event: typeof EVENT_METHOD_CALLED; payload: EventMethodCalled } { +): TelemetryEventRaw { return { event: EVENT_METHOD_CALLED, payload: { diff --git a/packages/shared/src/telemetry/types.ts b/packages/shared/src/telemetry/types.ts new file mode 100644 index 00000000000..af902a33854 --- /dev/null +++ b/packages/shared/src/telemetry/types.ts @@ -0,0 +1,74 @@ +import type { InstanceType } from '@clerk/types'; + +export type TelemetryCollectorOptions = { + /** + * If true, telemetry will not be collected. + */ + disabled?: boolean; + /** + * If true, telemetry will not be sent, but collected events will be logged to the console. + */ + debug?: boolean; + /** + * Sampling rate, 0-1 + */ + samplingRate?: number; + /** + * Set a custom buffer size to control how often events are sent + */ + maxBufferSize?: number; + /** + * The publishableKey to associate with the collected events. + */ + publishableKey?: string; + /** + * The secretKey to associate with the collected events. + */ + secretKey?: string; + /** + * The current clerk-js version. + */ + clerkVersion?: string; + /** + * The SDK being used, e.g. `@clerk/nextjs` or `@clerk/remix`. + */ + sdk?: string; + /** + * The version of the SDK being used. + */ + sdkVersion?: string; +}; + +export type TelemetryEvent = { + event: string; + /** + * publishableKey + */ + pk?: string; + /** + * secretKey + */ + sk?: string; + /** + * instanceType + */ + it: InstanceType; + /** + * clerkVersion + */ + cv: string; + /** + * SDK + */ + sdk?: string; + /** + * SDK Version + */ + sdkv?: string; + payload: Record; +}; + +export type TelemetryEventRaw = { + event: TelemetryEvent['event']; + payload: Payload; +}; diff --git a/scripts/subpath-workaround.mjs b/scripts/subpath-workaround.mjs index 804984f4e8a..41fd1da56c2 100644 --- a/scripts/subpath-workaround.mjs +++ b/scripts/subpath-workaround.mjs @@ -32,8 +32,6 @@ async function run() { ]; const hasAllSubpathsInFiles = pkgFile.files.every(name => allFilesNames.includes(name)); - console.log(allFilesNames, pkgFile.files); - if (!hasAllSubpathsInFiles) { throw new Error('Not all subpaths from the package.json "files" array are in the subpaths.mjs'); } From 0cd8e8eb23c96bec03a607e26fb4030dabc1fe38 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 22 Nov 2023 09:32:10 -0600 Subject: [PATCH 30/30] chore(shared): Simplify telemetry.record() API --- packages/shared/src/__tests__/telemetry.test.ts | 14 +++++++------- packages/shared/src/telemetry/collector.ts | 12 ++---------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/shared/src/__tests__/telemetry.test.ts b/packages/shared/src/__tests__/telemetry.test.ts index 7c91bcc1701..4fab9b73ee3 100644 --- a/packages/shared/src/__tests__/telemetry.test.ts +++ b/packages/shared/src/__tests__/telemetry.test.ts @@ -15,7 +15,7 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); jest.runAllTimers(); @@ -33,7 +33,7 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); jest.runAllTimers(); @@ -54,7 +54,7 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); jest.runAllTimers(); @@ -92,7 +92,7 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); jest.runAllTimers(); @@ -128,7 +128,7 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); jest.runAllTimers(); @@ -145,8 +145,8 @@ describe('TelemetryCollector', () => { publishableKey: TEST_PK, }); - collector.record('TEST_EVENT', {}); - collector.record('TEST_EVENT', {}); + collector.record({ event: 'TEST_EVENT', payload: {} }); + collector.record({ event: 'TEST_EVENT', payload: {} }); expect(fetchSpy).toHaveBeenCalled(); diff --git a/packages/shared/src/telemetry/collector.ts b/packages/shared/src/telemetry/collector.ts index bccb5a99774..c9a0dc35e52 100644 --- a/packages/shared/src/telemetry/collector.ts +++ b/packages/shared/src/telemetry/collector.ts @@ -98,16 +98,8 @@ export class TelemetryCollector { return this.#config.debug || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DEBUG)); } - record(event: Pick): void; - record(event: TelemetryEvent['event'], payload: TelemetryEvent['payload']): void; - record( - event: TelemetryEvent['event'] | Pick, - payload?: TelemetryEvent['payload'], - ): void { - const preparedPayload = - typeof event === 'string' - ? this.#preparePayload(event, payload ?? {}) - : this.#preparePayload(event.event, event.payload); + record(event: Pick): void { + const preparedPayload = this.#preparePayload(event.event, event.payload); this.#logEvent(preparedPayload.event, preparedPayload);