diff --git a/.craft.yml b/.craft.yml index bf775c7a276a..c21726412f60 100644 --- a/.craft.yml +++ b/.craft.yml @@ -22,6 +22,7 @@ targets: - name: github includeNames: /^sentry-.*$/ - name: npm + excludeNames: /^sentry-sveltekit-.*$/ - name: registry sdks: 'npm:@sentry/browser': diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f1da348c3fb..6eddb94ba9eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.42.0 + +- feat(core): Add lifecycle hooks (#7370) +- feat(core): Emit hooks for transaction start/finish (#7387) +- feat(nextjs): Connect traces for server components (#7320) +- feat(replay): Attach an error `cause` to send exceptions (#7350) +- feat(replay): Consider user input in form field as "user activity" (#7355) +- feat(replay): Update rrweb to 1.105.0 & add breadcrumb when encountering large mutation (#7314) +- feat(tracing): Expose cancelIdleTimeout and add option to make it permanent (#7236) +- feat(tracing): Track PerformanceObserver interactions as spans (#7331) +- fix(core): Ensure `originalException` has type `unknown` (#7361) +- fix(core): Avoid using `Object.values()` (#7360) +- fix(react): Make redux integration be configurable via `normalizeDepth` (#7379) +- fix(tracing): Record LCP and CLS on transaction finish (#7386) +- ref(browser): Improve type safety of breadcrumbs integration (#7382) +- ref(node): Parallelize disk io when reading source files for context lines (#7374) +- ref(node): Partially remove dynamic `require` calls (#7377) + +**Replay `rrweb` changes:** + +`@sentry-internal/rrweb` was updated from 1.104.1 to 1.105.0 (#7314): + +- feat: Add `onMutation` option to record ([#70](https://github.com/getsentry/rrweb/pull/69)) +- fix: Ensure `` is masked ([#69](https://github.com/getsentry/rrweb/pull/69)) + ## 7.41.0 - feat: Ensure we use the same default `environment` everywhere (#7327) diff --git a/README.md b/README.md index 3afd9ba71aa3..7652c201b21d 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ package. Please refer to the README and instructions of those SDKs for more deta integrations for Express - [`@sentry/angular`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular): Browser SDK with Angular integration enabled +- [`@sentry/angular-ivy`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular-ivy): Browser SDK with + Angular integration enabled including native support for Angular's Ivy rendering engine. - [`@sentry/ember`](https://github.com/getsentry/sentry-javascript/tree/master/packages/ember): Browser SDK with Ember integration enabled - [`@sentry/react`](https://github.com/getsentry/sentry-javascript/tree/master/packages/react): Browser SDK with React diff --git a/package.json b/package.json index f44c07492703..d106efb1743d 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "packages/replay-worker", "packages/serverless", "packages/svelte", + "packages/sveltekit", "packages/tracing", "packages/types", "packages/typescript", diff --git a/packages/angular-ivy/README.md b/packages/angular-ivy/README.md index a02cf781a9ae..c96f89420559 100644 --- a/packages/angular-ivy/README.md +++ b/packages/angular-ivy/README.md @@ -6,16 +6,16 @@ # Official Sentry SDK for Angular with Ivy Compatibility +[![npm version](https://img.shields.io/npm/v/@sentry/angular-ivy.svg)](https://www.npmjs.com/package/@sentry/angular-ivy) +[![npm dm](https://img.shields.io/npm/dm/@sentry/angular-ivy.svg)](https://www.npmjs.com/package/@sentry/angular-ivy) +[![npm dt](https://img.shields.io/npm/dt/@sentry/angular-ivy.svg)](https://www.npmjs.com/package/@sentry/angular-ivy) + ## Links - [Official SDK Docs](https://docs.sentry.io/platforms/javascript/angular/) ## Angular Version Compatibility -**Note**: This SDK is still experimental and not yet stable. -We do not yet make guarantees in terms of breaking changes, version compatibilities or semver. -Please open a Github issue if you experience bugs or would like to share feedback. - This SDK officially supports Angular 12-15 with Angular's new rendering engine, Ivy. If you're using Angular 10, 11 or a newer Angular version with View Engine instead of Ivy, please use [`@sentry/angular`](https://github.com/getsentry/sentry-javascript/blob/develop/packages/angular/README.md). diff --git a/packages/angular/README.md b/packages/angular/README.md index dd288b4951a4..b03b24195fb0 100644 --- a/packages/angular/README.md +++ b/packages/angular/README.md @@ -6,6 +6,10 @@ # Official Sentry SDK for Angular +[![npm version](https://img.shields.io/npm/v/@sentry/angular.svg)](https://www.npmjs.com/package/@sentry/angular) +[![npm dm](https://img.shields.io/npm/dm/@sentry/angular.svg)](https://www.npmjs.com/package/@sentry/angular) +[![npm dt](https://img.shields.io/npm/dt/@sentry/angular.svg)](https://www.npmjs.com/package/@sentry/angular) + ## Links - [Official SDK Docs](https://docs.sentry.io/platforms/javascript/angular/) diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index b54aad03e9b2..d48c2d1efbd9 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable max-lines */ import { getCurrentHub } from '@sentry/core'; -import type { Event, Integration } from '@sentry/types'; +import type { Event as SentryEvent, HandlerDataFetch, Integration, SentryWrappedXMLHttpRequest } from '@sentry/types'; import { addInstrumentationHandler, getEventDescription, @@ -14,6 +14,8 @@ import { import { WINDOW } from '../helpers'; +type HandlerData = Record; + /** JSDoc */ interface BreadcrumbsOptions { console: boolean; @@ -99,7 +101,7 @@ export class Breadcrumbs implements Integration { /** * Adds a breadcrumb for Sentry events or transactions if this option is enabled. */ - public addSentryBreadcrumb(event: Event): void { + public addSentryBreadcrumb(event: SentryEvent): void { if (this.options.sentry) { getCurrentHub().addBreadcrumb( { @@ -120,10 +122,8 @@ export class Breadcrumbs implements Integration { * A HOC that creaes a function that creates breadcrumbs from DOM API calls. * This is a HOC so that we get access to dom options in the closure. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _domBreadcrumb(dom: BreadcrumbsOptions['dom']): (handlerData: { [key: string]: any }) => void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function _innerDomBreadcrumb(handlerData: { [key: string]: any }): void { +function _domBreadcrumb(dom: BreadcrumbsOptions['dom']): (handlerData: HandlerData) => void { + function _innerDomBreadcrumb(handlerData: HandlerData): void { let target; let keyAttrs = typeof dom === 'object' ? dom.serializeAttribute : undefined; @@ -143,9 +143,10 @@ function _domBreadcrumb(dom: BreadcrumbsOptions['dom']): (handlerData: { [key: s // Accessing event.target can throw (see getsentry/raven-js#838, #768) try { - target = handlerData.event.target - ? htmlTreeAsString(handlerData.event.target as Node, { keyAttrs, maxStringLength }) - : htmlTreeAsString(handlerData.event as unknown as Node, { keyAttrs, maxStringLength }); + const event = handlerData.event as Event | Node; + target = _isEvent(event) + ? htmlTreeAsString(event.target, { keyAttrs, maxStringLength }) + : htmlTreeAsString(event, { keyAttrs, maxStringLength }); } catch (e) { target = ''; } @@ -173,8 +174,7 @@ function _domBreadcrumb(dom: BreadcrumbsOptions['dom']): (handlerData: { [key: s /** * Creates breadcrumbs from console API calls */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _consoleBreadcrumb(handlerData: { [key: string]: any }): void { +function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level: string }): void { // This is a hack to fix a Vue3-specific bug that causes an infinite loop of // console warnings. This happens when a Vue template is rendered with // an undeclared variable, which we try to stringify, ultimately causing @@ -216,8 +216,7 @@ function _consoleBreadcrumb(handlerData: { [key: string]: any }): void { /** * Creates breadcrumbs from XHR API calls */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _xhrBreadcrumb(handlerData: { [key: string]: any }): void { +function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRequest }): void { if (handlerData.endTimestamp) { // We only capture complete, non-sentry requests if (handlerData.xhr.__sentry_own_request__) { @@ -249,8 +248,7 @@ function _xhrBreadcrumb(handlerData: { [key: string]: any }): void { /** * Creates breadcrumbs from fetch API calls */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _fetchBreadcrumb(handlerData: { [key: string]: any }): void { +function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch): void { // We only capture complete fetch requests if (!handlerData.endTimestamp) { return; @@ -280,7 +278,7 @@ function _fetchBreadcrumb(handlerData: { [key: string]: any }): void { category: 'fetch', data: { ...handlerData.fetchData, - status_code: handlerData.response.status, + status_code: handlerData.response && handlerData.response.status, }, type: 'http', }, @@ -295,10 +293,9 @@ function _fetchBreadcrumb(handlerData: { [key: string]: any }): void { /** * Creates breadcrumbs from history API calls */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _historyBreadcrumb(handlerData: { [key: string]: any }): void { - let from = handlerData.from; - let to = handlerData.to; +function _historyBreadcrumb(handlerData: HandlerData & { from: string; to: string }): void { + let from: string | undefined = handlerData.from; + let to: string | undefined = handlerData.to; const parsedLoc = parseUrl(WINDOW.location.href); let parsedFrom = parseUrl(from); const parsedTo = parseUrl(to); @@ -325,3 +322,7 @@ function _historyBreadcrumb(handlerData: { [key: string]: any }): void { }, }); } + +function _isEvent(event: unknown): event is Event { + return event && !!(event as Record).target; +} diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 7a90b07925f6..bf1ae616c4d6 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -17,6 +17,7 @@ import type { SessionAggregates, Severity, SeverityLevel, + Transaction, TransactionEvent, Transport, } from '@sentry/types'; @@ -97,6 +98,9 @@ export abstract class BaseClient implements Client { /** Holds flushable */ private _outcomes: { [key: string]: number } = {}; + // eslint-disable-next-line @typescript-eslint/ban-types + private _hooks: Record = {}; + /** * Initializes this client instance. * @@ -351,6 +355,38 @@ export abstract class BaseClient implements Client { } } + // Keep on() & emit() signatures in sync with types' client.ts interface + + /** @inheritdoc */ + public on(hook: 'startTransaction' | 'finishTransaction', callback: (transaction: Transaction) => void): void; + + /** @inheritdoc */ + public on(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; + + /** @inheritdoc */ + public on(hook: string, callback: unknown): void { + if (!this._hooks[hook]) { + this._hooks[hook] = []; + } + + // @ts-ignore We assue the types are correct + this._hooks[hook].push(callback); + } + + /** @inheritdoc */ + public emit(hook: 'startTransaction' | 'finishTransaction', transaction: Transaction): void; + + /** @inheritdoc */ + public emit(hook: 'beforeEnvelope', envelope: Envelope): void; + + /** @inheritdoc */ + public emit(hook: string, ...rest: unknown[]): void { + if (this._hooks[hook]) { + // @ts-ignore we cannot enforce the callback to match the hook + this._hooks[hook].forEach(callback => callback(...rest)); + } + } + /** Updates existing session based on the provided event */ protected _updateSessionFromEvent(session: Session, event: Event): void { let crashed = false; @@ -560,7 +596,7 @@ export abstract class BaseClient implements Client { data: { __sentry__: true, }, - originalException: reason as Error, + originalException: reason, }); throw new SentryError( `Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${reason}`, diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 5a3f99937042..c150c05eaef9 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -183,8 +183,7 @@ export class Hub implements HubInterface { /** * @inheritDoc */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types - public captureException(exception: any, hint?: EventHint): string { + public captureException(exception: unknown, hint?: EventHint): string { const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4()); const syntheticException = new Error('Sentry syntheticException'); this._withClient((client, scope) => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 44d80ce4800f..e5ee3c623d76 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,6 +2,7 @@ export type { ClientClass } from './sdk'; export type { Carrier, Layer } from './hub'; export type { OfflineStore, OfflineTransportOptions } from './transports/offline'; +export * from './tracing'; export { addBreadcrumb, captureException, diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index cc7c977bb5d9..58b63dacc199 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -40,7 +40,7 @@ function filterDuplicates(integrations: Integration[]): Integration[] { integrationsByName[name] = currentInstance; }); - return Object.values(integrationsByName); + return Object.keys(integrationsByName).map(k => integrationsByName[k]); } /** Gets integrations to install */ diff --git a/packages/tracing/src/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts similarity index 77% rename from packages/tracing/src/hubextensions.ts rename to packages/core/src/tracing/hubextensions.ts index 443aac923119..9624df4b709e 100644 --- a/packages/tracing/src/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -1,17 +1,9 @@ -import type { Hub } from '@sentry/core'; -import { getMainCarrier, hasTracingEnabled } from '@sentry/core'; -import type { - ClientOptions, - CustomSamplingContext, - Integration, - IntegrationClass, - Options, - SamplingContext, - TransactionContext, -} from '@sentry/types'; -import { dynamicRequire, isNaN, isNodeEnv, loadModule, logger } from '@sentry/utils'; +import type { ClientOptions, CustomSamplingContext, Options, SamplingContext, TransactionContext } from '@sentry/types'; +import { isNaN, logger } from '@sentry/utils'; -import { registerErrorInstrumentation } from './errors'; +import type { Hub } from '../hub'; +import { getMainCarrier } from '../hub'; +import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { IdleTransaction } from './idletransaction'; import { Transaction } from './transaction'; @@ -194,6 +186,9 @@ The transaction will not be sampled. Please use the ${configInstrumenter} instru if (transaction.sampled) { transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); } + if (client && client.emit) { + client.emit('startTransaction', transaction); + } return transaction; } @@ -221,13 +216,16 @@ export function startIdleTransaction( if (transaction.sampled) { transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); } + if (client && client.emit) { + client.emit('startTransaction', transaction); + } return transaction; } /** - * @private + * Adds tracing extensions to the global hub. */ -export function _addTracingExtensions(): void { +export function addTracingExtensions(): void { const carrier = getMainCarrier(); if (!carrier.__SENTRY__) { return; @@ -240,70 +238,3 @@ export function _addTracingExtensions(): void { carrier.__SENTRY__.extensions.traceHeaders = traceHeaders; } } - -/** - * @private - */ -function _autoloadDatabaseIntegrations(): void { - const carrier = getMainCarrier(); - if (!carrier.__SENTRY__) { - return; - } - - const packageToIntegrationMapping: Record Integration> = { - mongodb() { - const integration = dynamicRequire(module, './integrations/node/mongo') as { - Mongo: IntegrationClass; - }; - return new integration.Mongo(); - }, - mongoose() { - const integration = dynamicRequire(module, './integrations/node/mongo') as { - Mongo: IntegrationClass; - }; - return new integration.Mongo({ mongoose: true }); - }, - mysql() { - const integration = dynamicRequire(module, './integrations/node/mysql') as { - Mysql: IntegrationClass; - }; - return new integration.Mysql(); - }, - pg() { - const integration = dynamicRequire(module, './integrations/node/postgres') as { - Postgres: IntegrationClass; - }; - return new integration.Postgres(); - }, - }; - - const mappedPackages = Object.keys(packageToIntegrationMapping) - .filter(moduleName => !!loadModule(moduleName)) - .map(pkg => { - try { - return packageToIntegrationMapping[pkg](); - } catch (e) { - return undefined; - } - }) - .filter(p => p) as Integration[]; - - if (mappedPackages.length > 0) { - carrier.__SENTRY__.integrations = [...(carrier.__SENTRY__.integrations || []), ...mappedPackages]; - } -} - -/** - * This patches the global object and injects the Tracing extensions methods - */ -export function addExtensionMethods(): void { - _addTracingExtensions(); - - // Detect and automatically load specified integrations. - if (isNodeEnv()) { - _autoloadDatabaseIntegrations(); - } - - // If an error happens globally, we should make sure transaction status is set to error. - registerErrorInstrumentation(); -} diff --git a/packages/tracing/src/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts similarity index 82% rename from packages/tracing/src/idletransaction.ts rename to packages/core/src/tracing/idletransaction.ts index 395b116481b1..49c7b64ea4a0 100644 --- a/packages/tracing/src/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -1,15 +1,17 @@ /* eslint-disable max-lines */ -import type { Hub } from '@sentry/core'; import type { TransactionContext } from '@sentry/types'; import { logger, timestampWithMs } from '@sentry/utils'; +import type { Hub } from '../hub'; import type { Span } from './span'; import { SpanRecorder } from './span'; import { Transaction } from './transaction'; -export const DEFAULT_IDLE_TIMEOUT = 1000; -export const DEFAULT_FINAL_TIMEOUT = 30000; -export const DEFAULT_HEARTBEAT_INTERVAL = 5000; +export const TRACING_DEFAULTS = { + idleTimeout: 1000, + finalTimeout: 30000, + heartbeatInterval: 5000, +}; /** * @inheritDoc @@ -67,6 +69,9 @@ export class IdleTransaction extends Transaction { // We should not use heartbeat if we finished a transaction private _finished: boolean = false; + // Idle timeout was canceled and we should finish the transaction with the last span end. + private _idleTimeoutCanceledPermanently: boolean = false; + private readonly _beforeFinishCallbacks: BeforeFinishCallback[] = []; /** @@ -81,12 +86,12 @@ export class IdleTransaction extends Transaction { * The time to wait in ms until the idle transaction will be finished. This timer is started each time * there are no active spans on this transaction. */ - private readonly _idleTimeout: number = DEFAULT_IDLE_TIMEOUT, + private readonly _idleTimeout: number = TRACING_DEFAULTS.idleTimeout, /** * The final value in ms that a transaction cannot exceed */ - private readonly _finalTimeout: number = DEFAULT_FINAL_TIMEOUT, - private readonly _heartbeatInterval: number = DEFAULT_HEARTBEAT_INTERVAL, + private readonly _finalTimeout: number = TRACING_DEFAULTS.finalTimeout, + private readonly _heartbeatInterval: number = TRACING_DEFAULTS.heartbeatInterval, // Whether or not the transaction should put itself on the scope when it starts and pop itself off when it ends private readonly _onScope: boolean = false, ) { @@ -102,7 +107,7 @@ export class IdleTransaction extends Transaction { _idleHub.configureScope(scope => scope.setSpan(this)); } - this._startIdleTimeout(); + this._restartIdleTimeout(); setTimeout(() => { if (!this._finished) { this.setStatus('deadline_exceeded'); @@ -201,20 +206,37 @@ export class IdleTransaction extends Transaction { } /** - * Cancels the existing idletimeout, if there is one + * Cancels the existing idle timeout, if there is one. + * @param restartOnChildSpanChange Default is `true`. + * If set to false the transaction will end + * with the last child span. */ - private _cancelIdleTimeout(): void { + public cancelIdleTimeout( + endTimestamp?: Parameters[0], + { + restartOnChildSpanChange, + }: { + restartOnChildSpanChange?: boolean; + } = { + restartOnChildSpanChange: true, + }, + ): void { if (this._idleTimeoutID) { clearTimeout(this._idleTimeoutID); this._idleTimeoutID = undefined; + this._idleTimeoutCanceledPermanently = restartOnChildSpanChange === false; + + if (Object.keys(this.activities).length === 0 && this._idleTimeoutCanceledPermanently) { + this.finish(endTimestamp); + } } } /** - * Creates an idletimeout + * Restarts idle timeout, if there is no running idle timeout it will start one. */ - private _startIdleTimeout(endTimestamp?: Parameters[0]): void { - this._cancelIdleTimeout(); + private _restartIdleTimeout(endTimestamp?: Parameters[0]): void { + this.cancelIdleTimeout(); this._idleTimeoutID = setTimeout(() => { if (!this._finished && Object.keys(this.activities).length === 0) { this.finish(endTimestamp); @@ -227,7 +249,7 @@ export class IdleTransaction extends Transaction { * @param spanId The span id that represents the activity */ private _pushActivity(spanId: string): void { - this._cancelIdleTimeout(); + this.cancelIdleTimeout(); __DEBUG_BUILD__ && logger.log(`[Tracing] pushActivity: ${spanId}`); this.activities[spanId] = true; __DEBUG_BUILD__ && logger.log('[Tracing] new activities count', Object.keys(this.activities).length); @@ -246,10 +268,14 @@ export class IdleTransaction extends Transaction { } if (Object.keys(this.activities).length === 0) { - // We need to add the timeout here to have the real endtimestamp of the transaction - // Remember timestampWithMs is in seconds, timeout is in ms - const endTimestamp = timestampWithMs() + this._idleTimeout / 1000; - this._startIdleTimeout(endTimestamp); + const endTimestamp = timestampWithMs(); + if (this._idleTimeoutCanceledPermanently) { + this.finish(endTimestamp); + } else { + // We need to add the timeout here to have the real endtimestamp of the transaction + // Remember timestampWithMs is in seconds, timeout is in ms + this._restartIdleTimeout(endTimestamp + this._idleTimeout / 1000); + } } } diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts new file mode 100644 index 000000000000..fd4949257ceb --- /dev/null +++ b/packages/core/src/tracing/index.ts @@ -0,0 +1,8 @@ +export { startIdleTransaction, addTracingExtensions } from './hubextensions'; +export { IdleTransaction, TRACING_DEFAULTS } from './idletransaction'; +export { Span, spanStatusfromHttpCode } from './span'; +export { Transaction } from './transaction'; +export { extractTraceparentData, getActiveTransaction, stripUrlQueryAndFragment, TRACEPARENT_REGEXP } from './utils'; +// eslint-disable-next-line deprecation/deprecation +export { SpanStatus } from './spanstatus'; +export type { SpanStatusType } from './span'; diff --git a/packages/tracing/src/span.ts b/packages/core/src/tracing/span.ts similarity index 100% rename from packages/tracing/src/span.ts rename to packages/core/src/tracing/span.ts diff --git a/packages/tracing/src/spanstatus.ts b/packages/core/src/tracing/spanstatus.ts similarity index 100% rename from packages/tracing/src/spanstatus.ts rename to packages/core/src/tracing/spanstatus.ts diff --git a/packages/tracing/src/transaction.ts b/packages/core/src/tracing/transaction.ts similarity index 96% rename from packages/tracing/src/transaction.ts rename to packages/core/src/tracing/transaction.ts index 49605e883190..eba498b7e654 100644 --- a/packages/tracing/src/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -1,5 +1,3 @@ -import type { Hub } from '@sentry/core'; -import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core'; import type { Context, Contexts, @@ -13,6 +11,9 @@ import type { } from '@sentry/types'; import { dropUndefinedKeys, logger } from '@sentry/utils'; +import { DEFAULT_ENVIRONMENT } from '../constants'; +import type { Hub } from '../hub'; +import { getCurrentHub } from '../hub'; import { Span as SpanClass, SpanRecorder } from './span'; /** JSDoc */ @@ -140,11 +141,15 @@ export class Transaction extends SpanClass implements TransactionInterface { // just sets the end timestamp super.finish(endTimestamp); + const client = this._hub.getClient(); + if (client && client.emit) { + client.emit('finishTransaction', this); + } + if (this.sampled !== true) { // At this point if `sampled !== true` we want to discard the transaction. __DEBUG_BUILD__ && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.'); - const client = this._hub.getClient(); if (client) { client.recordDroppedEvent('sample_rate', 'transaction'); } diff --git a/packages/tracing/src/utils.ts b/packages/core/src/tracing/utils.ts similarity index 52% rename from packages/tracing/src/utils.ts rename to packages/core/src/tracing/utils.ts index a34db3cf9ec7..5dc64f98d400 100644 --- a/packages/tracing/src/utils.ts +++ b/packages/core/src/tracing/utils.ts @@ -1,6 +1,7 @@ -import type { Hub } from '@sentry/core'; -import { getCurrentHub, hasTracingEnabled as _hasTracingEnabled } from '@sentry/core'; -import type { Options, Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; + +import type { Hub } from '../hub'; +import { getCurrentHub } from '../hub'; /** * The `extractTraceparentData` function and `TRACEPARENT_REGEXP` constant used @@ -16,18 +17,6 @@ import type { Options, Transaction } from '@sentry/types'; */ export { TRACEPARENT_REGEXP, extractTraceparentData } from '@sentry/utils'; -/** - * Determines if tracing is currently enabled. - * - * Tracing is enabled when at least one of `tracesSampleRate` and `tracesSampler` is defined in the SDK config. - * @deprecated This export has moved to `@sentry/core`. This export will be removed from `@sentry/tracing` in v8. - */ -export function hasTracingEnabled( - maybeOptions?: Pick | undefined, -): boolean { - return _hasTracingEnabled(maybeOptions); -} - /** Grabs active transaction off scope, if any */ export function getActiveTransaction(maybeHub?: Hub): T | undefined { const hub = maybeHub || getCurrentHub(); @@ -35,21 +24,5 @@ export function getActiveTransaction(maybeHub?: Hub): T | return scope && (scope.getTransaction() as T | undefined); } -/** - * Converts from milliseconds to seconds - * @param time time in ms - */ -export function msToSec(time: number): number { - return time / 1000; -} - -/** - * Converts from seconds to milliseconds - * @param time time in seconds - */ -export function secToMs(time: number): number { - return time * 1000; -} - // so it can be used in manual instrumentation without necessitating a hard dependency on @sentry/utils export { stripUrlQueryAndFragment } from '@sentry/utils'; diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index d7382ceeeacb..4ee65114fe79 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,4 +1,4 @@ -import type { Event, Span } from '@sentry/types'; +import type { Client, Envelope, Event, Span, Transaction } from '@sentry/types'; import { dsnToString, logger, SentryError, SyncPromise } from '@sentry/utils'; import { Hub, makeSession, Scope } from '../../src'; @@ -1730,4 +1730,47 @@ describe('BaseClient', () => { expect(clearedOutcomes4.length).toEqual(0); }); }); + + describe('hooks', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); + + // Make sure types work for both Client & BaseClient + const scenarios = [ + ['BaseClient', new TestClient(options)], + ['Client', new TestClient(options) as Client], + ] as const; + + describe.each(scenarios)('with client %s', (_, client) => { + it('should call a startTransaction hook', () => { + expect.assertions(1); + + const mockTransaction = { + traceId: '86f39e84263a4de99c326acab3bfe3bd', + } as Transaction; + + client.on?.('startTransaction', transaction => { + expect(transaction).toEqual(mockTransaction); + }); + + client.emit?.('startTransaction', mockTransaction); + }); + + it('should call a beforeEnvelope hook', () => { + expect.assertions(1); + + const mockEnvelope = [ + { + event_id: '12345', + }, + {}, + ] as Envelope; + + client.on?.('beforeEnvelope', envelope => { + expect(envelope).toEqual(mockEnvelope); + }); + + client.emit?.('beforeEnvelope', mockEnvelope); + }); + }); + }); }); diff --git a/packages/e2e-tests/publish-packages.ts b/packages/e2e-tests/publish-packages.ts index 31ba3a6b4eb4..fd03a777315a 100644 --- a/packages/e2e-tests/publish-packages.ts +++ b/packages/e2e-tests/publish-packages.ts @@ -16,11 +16,6 @@ const packageTarballPaths = glob.sync('packages/*/sentry-*.tgz', { // Publish built packages to the fake registry packageTarballPaths.forEach(tarballPath => { - // Don't run publish opentelemetry-node package because it is private. - if (tarballPath.includes('sentry-opentelemetry-node')) { - return; - } - // `--userconfig` flag needs to be before `publish` childProcess.execSync(`npm --userconfig ${__dirname}/test-registry.npmrc publish ${tarballPath}`, { cwd: repositoryRoot, // Can't use __dirname here because npm would try to publish `@sentry-internal/e2e-tests` diff --git a/packages/e2e-tests/run.ts b/packages/e2e-tests/run.ts index 2709feb3a040..842911863f02 100644 --- a/packages/e2e-tests/run.ts +++ b/packages/e2e-tests/run.ts @@ -174,6 +174,7 @@ type RecipeResult = { type Recipe = { testApplicationName: string; buildCommand?: string; + buildAssertionCommand?: string; buildTimeoutSeconds?: number; tests: { testName: string; @@ -212,9 +213,9 @@ const recipeResults: RecipeResult[] = recipePaths.map(recipePath => { console.log(buildCommandProcess.stdout.replace(/^/gm, '[BUILD OUTPUT] ')); console.log(buildCommandProcess.stderr.replace(/^/gm, '[BUILD OUTPUT] ')); - const error: undefined | (Error & { code?: string }) = buildCommandProcess.error; + const buildCommandProcessError: undefined | (Error & { code?: string }) = buildCommandProcess.error; - if (error?.code === 'ETIMEDOUT') { + if (buildCommandProcessError?.code === 'ETIMEDOUT') { processShouldExitWithError = true; printCIErrorMessage( @@ -239,6 +240,58 @@ const recipeResults: RecipeResult[] = recipePaths.map(recipePath => { testResults: [], }; } + + if (recipe.buildAssertionCommand) { + console.log( + `Running E2E test build assertion for test application "${recipe.testApplicationName}"${dependencyOverridesInformationString}`, + ); + const buildAssertionCommandProcess = childProcess.spawnSync(recipe.buildAssertionCommand, { + cwd: path.dirname(recipePath), + input: buildCommandProcess.stdout, + encoding: 'utf8', + shell: true, // needed so we can pass the build command in as whole without splitting it up into args + timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000, + env: { + ...process.env, + ...envVarsToInject, + }, + }); + + // Prepends some text to the output build command's output so we can distinguish it from logging in this script + console.log(buildAssertionCommandProcess.stdout.replace(/^/gm, '[BUILD ASSERTION OUTPUT] ')); + console.log(buildAssertionCommandProcess.stderr.replace(/^/gm, '[BUILD ASSERTION OUTPUT] ')); + + const buildAssertionCommandProcessError: undefined | (Error & { code?: string }) = + buildAssertionCommandProcess.error; + + if (buildAssertionCommandProcessError?.code === 'ETIMEDOUT') { + processShouldExitWithError = true; + + printCIErrorMessage( + `Build assertion in test application "${recipe.testApplicationName}" (${path.dirname( + recipePath, + )}) timed out!`, + ); + + return { + dependencyOverrides, + buildFailed: true, + testResults: [], + }; + } else if (buildAssertionCommandProcess.status !== 0) { + processShouldExitWithError = true; + + printCIErrorMessage( + `Build assertion in test application "${recipe.testApplicationName}" (${path.dirname(recipePath)}) failed!`, + ); + + return { + dependencyOverrides, + buildFailed: true, + testResults: [], + }; + } + } } const testResults: TestResult[] = recipe.tests.map(test => { diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/client-component/page.tsx b/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/client-component/page.tsx deleted file mode 100644 index b1d7f708cfae..000000000000 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/client-component/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client'; - -export default function Page() { - return ( -
-

Press to throw:

- -
- ); -} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/layout.tsx b/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/layout.tsx deleted file mode 100644 index f3ef34cd8b91..000000000000 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/page.tsx b/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/page.tsx deleted file mode 100644 index 1caf96ed7786..000000000000 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; -import Link from 'next/link'; - -export default function Home() { - return ( -
- { - Sentry.captureException(new Error('I am a click error!')); - }} - /> - - navigate - -
- ); -} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/user/[id]/page.tsx b/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/user/[id]/page.tsx deleted file mode 100644 index bdb52ea5547a..000000000000 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/app/user/[id]/page.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export default async function Home() { - const dynamid = await (await fetch('http://example.com', { cache: 'no-store' })).text(); // do a fetch request so that this server component is always rendered when requested - return

I am a blank page :) {dynamid}

; -} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/.gitignore b/packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/.gitignore rename to packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/.npmrc b/packages/e2e-tests/test-applications/nextjs-app-dir/.npmrc similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/.npmrc rename to packages/e2e-tests/test-applications/nextjs-app-dir/.npmrc diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/error.tsx new file mode 100644 index 000000000000..c1e3874f7ea8 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/client-component)

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/layout.tsx new file mode 100644 index 000000000000..7b447d23cbf8 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/layout.tsx @@ -0,0 +1,10 @@ +'use client'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/client-component)

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/loading.tsx new file mode 100644 index 000000000000..9b6cf994d322 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/loading.tsx @@ -0,0 +1,7 @@ +export default function Loading() { + return ( +
+

Loading (/client-component)

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/not-found.tsx new file mode 100644 index 000000000000..deef8f3078b4 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/client-component)

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/page.tsx new file mode 100644 index 000000000000..64012f948278 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/page.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { ClientErrorDebugTools } from '../../components/client-error-debug-tools'; + +export default function Page() { + return ( +
+

Page (/client-component)

+ +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/error.tsx new file mode 100644 index 000000000000..8c52619b80b1 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/client-component/[...parameters])

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/layout.tsx new file mode 100644 index 000000000000..a387722a8fcf --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/layout.tsx @@ -0,0 +1,10 @@ +'use client'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/client-component/[...parameters])

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/loading.tsx new file mode 100644 index 000000000000..27a8d577240e --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/loading.tsx @@ -0,0 +1,7 @@ +export default function Loading() { + return ( +
+

Loading (/client-component/parameter/[...parameters])

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/not-found.tsx new file mode 100644 index 000000000000..c56f5aa409f5 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/client-component/[...parameters])

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/page.tsx new file mode 100644 index 000000000000..31fa4ee21be5 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[...parameters]/page.tsx @@ -0,0 +1,15 @@ +import { ClientErrorDebugTools } from '../../../../components/client-error-debug-tools'; + +export default function Page({ params }: { params: Record }) { + return ( +
+

Page (/client-component/[...parameters])

+

Params: {JSON.stringify(params['parameters'])}

+ +
+ ); +} + +export async function generateStaticParams() { + return [{ parameters: ['foo', 'bar', 'baz'] }]; +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/error.tsx new file mode 100644 index 000000000000..92948207a2fe --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/client-component/[parameter])

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/layout.tsx new file mode 100644 index 000000000000..0d13dbc6bcac --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/layout.tsx @@ -0,0 +1,10 @@ +'use client'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/client-component/[parameter])

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/loading.tsx new file mode 100644 index 000000000000..94ad013d3986 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/loading.tsx @@ -0,0 +1,7 @@ +export default function Loading() { + return ( +
+

Loading (/client-component/[parameter])

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/not-found.tsx new file mode 100644 index 000000000000..48c215c930e2 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/client-component/[parameter])

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/page.tsx new file mode 100644 index 000000000000..2b9c28b922ac --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/client-component/parameter/[parameter]/page.tsx @@ -0,0 +1,15 @@ +import { ClientErrorDebugTools } from '../../../../components/client-error-debug-tools'; + +export default function Page({ params }: { params: Record }) { + return ( +
+

Page (/client-component/[parameter])

+

Parameter: {JSON.stringify(params['parameter'])}

+ +
+ ); +} + +export async function generateStaticParams() { + return [{ parameter: '42' }]; +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/error.tsx new file mode 100644 index 000000000000..02a192259aec --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/)

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx new file mode 100644 index 000000000000..35c4704735e7 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx @@ -0,0 +1,38 @@ +import { TransactionContextProvider } from '../components/transaction-context'; +import Link from 'next/link'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + +
+

Layout (/)

+
    +
  • + / +
  • +
  • + /client-component +
  • +
  • + /client-component/parameter/42 +
  • +
  • + /client-component/parameter/foo/bar/baz +
  • +
  • + /server-component +
  • +
  • + /server-component/parameter/42 +
  • +
  • + /server-component/parameter/foo/bar/baz +
  • +
+ {children} +
+ + + ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/loading.tsx new file mode 100644 index 000000000000..1c89093040e8 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/loading.tsx @@ -0,0 +1,7 @@ +export default function Loading() { + return ( +
+

Loading (/)

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/not-found.tsx new file mode 100644 index 000000000000..5e7b156553a6 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/)

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/page.tsx new file mode 100644 index 000000000000..edaffa368ace --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/page.tsx @@ -0,0 +1,10 @@ +import { ClientErrorDebugTools } from '../components/client-error-debug-tools'; + +export default function Page() { + return ( +
+

Page (/)

+ +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/error.tsx new file mode 100644 index 000000000000..8c728017a4c9 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/server-component)

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/layout.tsx new file mode 100644 index 000000000000..3e6a95d2bc49 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/layout.tsx @@ -0,0 +1,8 @@ +export default async function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/server-component)

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/loading.tsx new file mode 100644 index 000000000000..70deffd9dea6 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/loading.tsx @@ -0,0 +1,7 @@ +export default async function Loading() { + return ( +
+

Loading (/server-component)

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/not-found.tsx new file mode 100644 index 000000000000..57b040b4210d --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/server-component)

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/page.tsx new file mode 100644 index 000000000000..d318ee23968d --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/page.tsx @@ -0,0 +1,12 @@ +import { ClientErrorDebugTools } from '../../components/client-error-debug-tools'; + +export const dynamic = 'force-dynamic'; + +export default function Page() { + return ( +
+

Page (/server-component)

+ +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/error.tsx new file mode 100644 index 000000000000..44c78430b2aa --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/server-component/[...parameters])

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/layout.tsx new file mode 100644 index 000000000000..b34d5a11488f --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/layout.tsx @@ -0,0 +1,8 @@ +export default async function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/server-component/[...parameters])

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/loading.tsx new file mode 100644 index 000000000000..f0fa262fa780 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/loading.tsx @@ -0,0 +1,7 @@ +export default async function Loading() { + return ( +
+

Loading (/server-component/[...parameters])

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/not-found.tsx new file mode 100644 index 000000000000..30da42c88a17 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/server-component/[...parameters])

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/page.tsx new file mode 100644 index 000000000000..5d9d6c8262c5 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[...parameters]/page.tsx @@ -0,0 +1,13 @@ +import { ClientErrorDebugTools } from '../../../../components/client-error-debug-tools'; + +export const dynamic = 'force-dynamic'; + +export default async function Page({ params }: { params: Record }) { + return ( +
+

Page (/server-component/[...parameters])

+

Params: {JSON.stringify(params['parameters'])}

+ +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/error.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/error.tsx new file mode 100644 index 000000000000..37ba7515505f --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/error.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+

Error (/server-component/[parameter])

+ + Error: {error.toString()} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/layout.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/layout.tsx new file mode 100644 index 000000000000..013b62f15ff5 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/layout.tsx @@ -0,0 +1,8 @@ +export default async function Layout({ children }: { children: React.ReactNode }) { + return ( +
+

Layout (/server-component/[parameter])

+ {children} +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/loading.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/loading.tsx new file mode 100644 index 000000000000..6fb1e6e9d479 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/loading.tsx @@ -0,0 +1,7 @@ +export default async function Loading() { + return ( +
+

Loading (/server-component/[parameter])

+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/not-found.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/not-found.tsx new file mode 100644 index 000000000000..9150cdeca2ad --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/not-found.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found (/server-component/[parameter])

; +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/page.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/page.tsx new file mode 100644 index 000000000000..f88fe1cd4a06 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/app/server-component/parameter/[parameter]/page.tsx @@ -0,0 +1,13 @@ +import { ClientErrorDebugTools } from '../../../../components/client-error-debug-tools'; + +export const dynamic = 'force-dynamic'; + +export default async function Page({ params }: { params: Record }) { + return ( +
+

Page (/server-component/[parameter])

+

Parameter: {JSON.stringify(params['parameter'])}

+ +
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts new file mode 100644 index 000000000000..cb7adf38cde8 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts @@ -0,0 +1,16 @@ +import * as fs from 'fs'; +import * as assert from 'assert/strict'; + +const stdin = fs.readFileSync(0).toString(); + +// Assert that all static components stay static and ally dynamic components stay dynamic + +assert.match(stdin, /○ \/client-component/); +assert.match(stdin, /● \/client-component\/parameter\/\[\.\.\.parameters\]/); +assert.match(stdin, /● \/client-component\/parameter\/\[parameter\]/); + +assert.match(stdin, /λ \/server-component/); +assert.match(stdin, /λ \/server-component\/parameter\/\[\.\.\.parameters\]/); +assert.match(stdin, /λ \/server-component\/parameter\/\[parameter\]/); + +export {}; diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx new file mode 100644 index 000000000000..9eeaa227996f --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx @@ -0,0 +1,124 @@ +'use client'; + +import { useContext, useState } from 'react'; +import { TransactionContext } from './transaction-context'; +import { captureException } from '@sentry/nextjs'; + +export function ClientErrorDebugTools() { + const transactionContextValue = useContext(TransactionContext); + const [transactionName, setTransactionName] = useState(''); + + const [isFetchingAPIRoute, setIsFetchingAPIRoute] = useState(); + const [isFetchingEdgeAPIRoute, setIsFetchingEdgeAPIRoute] = useState(); + const [isFetchingExternalAPIRoute, setIsFetchingExternalAPIRoute] = useState(); + const [renderError, setRenderError] = useState(); + + if (renderError) { + throw new Error('Render Error'); + } + + return ( +
+ {transactionContextValue.transactionActive ? ( + + ) : ( + <> + { + setTransactionName(e.target.value); + }} + /> + + + )} +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx b/packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx new file mode 100644 index 000000000000..a357439bee1f --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { createContext, PropsWithChildren, useState } from 'react'; +import { Transaction } from '@sentry/types'; +import { startTransaction, getCurrentHub } from '@sentry/nextjs'; + +export const TransactionContext = createContext< + { transactionActive: false; start: (transactionName: string) => void } | { transactionActive: true; stop: () => void } +>({ + transactionActive: false, + start: () => undefined, +}); + +export function TransactionContextProvider({ children }: PropsWithChildren) { + const [transaction, setTransaction] = useState(undefined); + + return ( + { + transaction.finish(); + setTransaction(undefined); + }, + } + : { + transactionActive: false, + start: (transactionName: string) => { + const t = startTransaction({ name: transactionName }); + getCurrentHub().getScope()?.setSpan(t); + setTransaction(t); + }, + } + } + > + {children} + + ); +} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/globals.d.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/globals.d.ts similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/globals.d.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/globals.d.ts diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/next-env.d.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/next-env.d.ts similarity index 75% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/next-env.d.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/next-env.d.ts index 7aa8e8ef74e1..fd36f9494e2c 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/next-env.d.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -/// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/next.config.js b/packages/e2e-tests/test-applications/nextjs-app-dir/next.config.js similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/next.config.js rename to packages/e2e-tests/test-applications/nextjs-app-dir/next.config.js diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/package.json b/packages/e2e-tests/test-applications/nextjs-app-dir/package.json similarity index 96% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/package.json rename to packages/e2e-tests/test-applications/nextjs-app-dir/package.json index 8ed25dbf0b8c..7459a5d03b12 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/package.json +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/package.json @@ -16,7 +16,7 @@ "@types/node": "18.11.17", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", - "next": "13.2.1", + "next": "13.2.3", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "4.9.4" diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/edge-endpoint.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/edge-endpoint.ts new file mode 100644 index 000000000000..d8af89f2e9d5 --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/edge-endpoint.ts @@ -0,0 +1,3 @@ +export const config = { runtime: 'edge' }; + +export default () => new Response('Hello world!'); diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint.ts new file mode 100644 index 000000000000..2ca75a33ba7e --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint.ts @@ -0,0 +1,9 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +type Data = { + name: string; +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); +} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/playwright.config.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts similarity index 97% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/playwright.config.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts index 4e7a3cbd804d..ae466dab4350 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/playwright.config.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts @@ -27,8 +27,6 @@ const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, /* `next dev` is incredibly buggy with the app dir */ retries: testEnv === 'development' ? 3 : 0, - /* Opt out of parallel tests on CI. */ - workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.client.config.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.client.config.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.edge.config.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/sentry.edge.config.ts similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.edge.config.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/sentry.edge.config.ts diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.server.config.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/sentry.server.config.ts similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/sentry.server.config.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/sentry.server.config.ts diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/start-event-proxy.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.ts similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/start-event-proxy.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.ts diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/test-recipe.json b/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json similarity index 75% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/test-recipe.json rename to packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json index d1fc9eafa240..b3ec72add40f 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/test-recipe.json +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json @@ -2,6 +2,7 @@ "$schema": "../../test-recipe-schema.json", "testApplicationName": "nextjs-13-app-dir", "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "buildAssertionCommand": "yarn ts-node --script-mode assert-build.ts", "tests": [ { "testName": "Prod Mode", @@ -17,6 +18,11 @@ "dependencyOverrides": { "next": "latest" } + }, + { + "dependencyOverrides": { + "next": "canary" + } } ] } diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/devErrorSymbolification.test.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/devErrorSymbolification.test.ts similarity index 57% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/devErrorSymbolification.test.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/tests/devErrorSymbolification.test.ts index 25e2b9d19067..2f7173cee315 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/devErrorSymbolification.test.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/devErrorSymbolification.test.ts @@ -8,28 +8,28 @@ test.describe('dev mode error symbolification', () => { } test('should have symbolicated dev errors', async ({ page }) => { - await page.goto('/client-component'); + await page.goto('/'); const errorEventPromise = waitForError('nextjs-13-app-dir', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'client-component-button-click-error'; + return errorEvent?.exception?.values?.[0]?.value === 'Click Error'; }); - await page.locator('id=exception-button').click(); + await page.getByText('Throw error').click(); const errorEvent = await errorEventPromise; const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( expect.objectContaining({ - filename: 'app/client-component/page.tsx', - abs_path: 'webpack-internal:///(app-client)/./app/client-component/page.tsx', function: 'onClick', - in_app: true, - lineno: 10, + filename: 'components/client-error-debug-tools.tsx', + abs_path: 'webpack-internal:///(app-client)/./components/client-error-debug-tools.tsx', + lineno: 54, colno: 16, - pre_context: [' id="exception-button"', ' onClick={() => {'], - context_line: " throw new Error('client-component-button-click-error');", - post_context: [' }}', ' >', ' throw'], + in_app: true, + pre_context: [' {'], + context_line: " throw new Error('Click Error');", + post_context: [' }}', ' >', ' Throw error'], }), ); }); diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/exceptions.test.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts similarity index 88% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/exceptions.test.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts index d1b88470fd37..ea96490b79ce 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/exceptions.test.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts @@ -11,11 +11,10 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await page.goto('/'); const errorEventPromise = waitForError('nextjs-13-app-dir', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'I am a click error!'; + return errorEvent?.exception?.values?.[0]?.value === 'Click Error'; }); - const exceptionButton = page.locator('id=exception-button'); - await exceptionButton.click(); + await page.getByText('Throw error').click(); const errorEvent = await errorEventPromise; const exceptionEventId = errorEvent.event_id; diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/tests/trace.test.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/trace.test.ts new file mode 100644 index 000000000000..99d03266d01f --- /dev/null +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/trace.test.ts @@ -0,0 +1,30 @@ +import { test } from '@playwright/test'; +import { waitForTransaction } from '../../../test-utils/event-proxy-server'; + +if (process.env.TEST_ENV === 'production') { + // TODO: Fix that this is flakey on dev server - might be an SDK bug + test('Sends connected traces for server components', async ({ page }, testInfo) => { + await page.goto('/client-component'); + + const clientTransactionName = `e2e-next-js-app-dir: ${testInfo.title}`; + + const serverComponentTransaction = waitForTransaction('nextjs-13-app-dir', async transactionEvent => { + return ( + transactionEvent?.transaction === 'Page Server Component (/server-component)' && + (await clientTransactionPromise).contexts?.trace?.trace_id === transactionEvent.contexts?.trace?.trace_id + ); + }); + + const clientTransactionPromise = waitForTransaction('nextjs-13-app-dir', transactionEvent => { + return transactionEvent?.transaction === clientTransactionName; + }); + + await page.getByPlaceholder('Transaction name').fill(clientTransactionName); + await page.getByText('Start transaction').click(); + await page.getByRole('link', { name: /^\/server-component$/ }).click(); + await page.getByText('Page (/server-component)').isVisible(); + await page.getByText('Stop transaction').click(); + + await serverComponentTransaction; + }); +} diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/transactions.test.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts similarity index 96% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/transactions.test.ts rename to packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index 2b166954074c..5ef4e6f28b5f 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/transactions.test.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -52,11 +52,11 @@ if (process.env.TEST_ENV === 'production') { const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'function.nextjs' && - transactionEvent?.transaction === 'Page Server Component (/user/[id])' + transactionEvent?.transaction === 'Page Server Component (/server-component/parameter/[...parameters])' ); }); - await page.goto('/user/4'); + await page.goto('/server-component/parameter/1337/42'); const transactionEvent = await serverComponentTransactionPromise; const transactionEventId = transactionEvent.event_id; diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/tsconfig.json b/packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json similarity index 100% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/tsconfig.json rename to packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json diff --git a/packages/e2e-tests/test-applications/nextjs-13-app-dir/yarn.lock b/packages/e2e-tests/test-applications/nextjs-app-dir/yarn.lock similarity index 72% rename from packages/e2e-tests/test-applications/nextjs-13-app-dir/yarn.lock rename to packages/e2e-tests/test-applications/nextjs-app-dir/yarn.lock index 14727ded24f5..6a36d48f01f4 100644 --- a/packages/e2e-tests/test-applications/nextjs-13-app-dir/yarn.lock +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/yarn.lock @@ -27,88 +27,88 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@next/env@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.2.1.tgz#082d42cfc0c794e9185d7b4133d71440ba2e795d" - integrity sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== +"@next/env@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.2.3.tgz#77ca49edb3c1d7c5263bb8f2ebe686080e98279e" + integrity sha512-FN50r/E+b8wuqyRjmGaqvqNDuWBWYWQiigfZ50KnSFH0f+AMQQyaZl+Zm2+CIpKk0fL9QxhLxOpTVA3xFHgFow== "@next/font@13.0.7": version "13.0.7" resolved "https://registry.yarnpkg.com/@next/font/-/font-13.0.7.tgz#e0046376edb0ce592d9cfddea8f4ab321eb1515a" integrity sha512-39SzuoMI6jbrIzPs3KtXdKX03OrVp6Y7kRHcoVmOg69spiBzruPJ5x5DQSfN+OXqznbvVBNZBXnmdnSqs3qXiA== -"@next/swc-android-arm-eabi@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.1.tgz#67f2580fbbe05ee006220688972c5e3a555fc741" - integrity sha512-Yua7mUpEd1wzIT6Jjl3dpRizIfGp9NR4F2xeRuQv+ae+SDI1Em2WyM9m46UL+oeW5GpMiEHoaBagr47RScZFmQ== - -"@next/swc-android-arm64@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.1.tgz#460a02b69eb23bb5f402266bcea9cadae59415c1" - integrity sha512-Bifcr2f6VwInOdq1uH/9lp8fH7Nf7XGkIx4XceVd32LPJqG2c6FZU8ZRBvTdhxzXVpt5TPtuXhOP4Ij9UPqsVw== - -"@next/swc-darwin-arm64@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.1.tgz#8b8530ff417802027471aee2419f78a58a863ccb" - integrity sha512-gvqm+fGMYxAkwBapH0Vvng5yrb6HTkIvZfY4oEdwwYrwuLdkjqnJygCMgpNqIFmAHSXgtlWxfYv1VC8sjN81Kw== - -"@next/swc-darwin-x64@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.1.tgz#80aebb3329a1e4568a28de1ee177780b3d50330c" - integrity sha512-HGqVqmaZWj6zomqOZUVbO5NhlABL0iIaxTmd0O5B0MoMa5zpDGoaHSG+fxgcWMXcGcxmUNchv1NfNOYiTKoHOg== - -"@next/swc-freebsd-x64@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.1.tgz#250ea2ab7e1734f22d11c677c463fab9ac33a516" - integrity sha512-N/a4JarAq+E+g+9K2ywJUmDIgU2xs2nA+BBldH0oq4zYJMRiUhL0iaN9G4e72VmGOJ61L/3W6VN8RIUOwTLoqQ== - -"@next/swc-linux-arm-gnueabihf@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.1.tgz#fe6bb29ed348a5f8ecae3740df22a8d8130c474a" - integrity sha512-WaFoerF/eRbhbE57TaIGJXbQAERADZ/RZ45u6qox9beb5xnWsyYgzX+WuN7Tkhyvga0/aMuVYFzS9CEay7D+bw== - -"@next/swc-linux-arm64-gnu@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.1.tgz#4781b927fc5e421f3cea2b29e5d38e5e4837b198" - integrity sha512-R+Jhc1/RJTnncE9fkePboHDNOCm1WJ8daanWbjKhfPySMyeniKYRwGn5SLYW3S8YlRS0QVdZaaszDSZWgUcsmA== - -"@next/swc-linux-arm64-musl@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.1.tgz#c2ba0a121b0255ba62450916bc70e6d0e26cbc98" - integrity sha512-oI1UfZPidGAVddlL2eOTmfsuKV9EaT1aktIzVIxIAgxzQSdwsV371gU3G55ggkurzfdlgF3GThFePDWF0d8dmw== - -"@next/swc-linux-x64-gnu@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.1.tgz#573c220f8b087e5d131d1fba58d3e1a670b220ad" - integrity sha512-PCygPwrQmS+7WUuAWWioWMZCzZm4PG91lfRxToLDg7yIm/3YfAw5N2EK2TaM9pzlWdvHQAqRMX/oLvv027xUiA== - -"@next/swc-linux-x64-musl@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.1.tgz#950b5bb920b322ca7b447efbd12a9c7a10c3a642" - integrity sha512-sUAKxo7CFZYGHNxheGh9nIBElLYBM6md/liEGfOTwh/xna4/GTTcmkGWkF7PdnvaYNgcPIQgHIMYiAa6yBKAVw== - -"@next/swc-win32-arm64-msvc@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.1.tgz#dbff3c4f5a3812a7059dac05804148a0f98682db" - integrity sha512-qDmyEjDBpl/vBXxuOOKKWmPQOcARcZIMach1s7kjzaien0SySut/PHRlj56sosa81Wt4hTGhfhZ1R7g1n7+B8w== - -"@next/swc-win32-ia32-msvc@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.1.tgz#7d2c17be7b8d9963984f5c15cc2588127101f620" - integrity sha512-2joqFQ81ZYPg6DcikIzQn3DgjKglNhPAozx6dL5sCNkr1CPMD0YIkJgT3CnYyMHQ04Qi3Npv0XX3MD6LJO8OCA== - -"@next/swc-win32-x64-msvc@13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.1.tgz#09713c6a925461f414e89422851326d1625bd4d2" - integrity sha512-r3+0fSaIZT6N237iMzwUhfNwjhAFvXjqB+4iuW+wcpxW+LHm1g/IoxN8eSRcb8jPItC86JxjAxpke0QL97qd6g== +"@next/swc-android-arm-eabi@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.3.tgz#85eed560c87c7996558c868a117be9780778f192" + integrity sha512-mykdVaAXX/gm+eFO2kPeVjnOCKwanJ9mV2U0lsUGLrEdMUifPUjiXKc6qFAIs08PvmTMOLMNnUxqhGsJlWGKSw== + +"@next/swc-android-arm64@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.3.tgz#8ac54ca9795a48afc4631b4823a4864bd5db0129" + integrity sha512-8XwHPpA12gdIFtope+n9xCtJZM3U4gH4vVTpUwJ2w1kfxFmCpwQ4xmeGSkR67uOg80yRMuF0h9V1ueo05sws5w== + +"@next/swc-darwin-arm64@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.3.tgz#f674e3c65aec505b6d218a662ade3fe248ccdbda" + integrity sha512-TXOubiFdLpMfMtaRu1K5d1I9ipKbW5iS2BNbu8zJhoqrhk3Kp7aRKTxqFfWrbliAHhWVE/3fQZUYZOWSXVQi1w== + +"@next/swc-darwin-x64@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.3.tgz#a15ea7fb4c46034a8f5e387906d0cad08387075a" + integrity sha512-GZctkN6bJbpjlFiS5pylgB2pifHvgkqLAPumJzxnxkf7kqNm6rOGuNjsROvOWVWXmKhrzQkREO/WPS2aWsr/yw== + +"@next/swc-freebsd-x64@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.3.tgz#f7ac6ae4f7d706ff2431f33e40230a554c8c2cbc" + integrity sha512-rK6GpmMt/mU6MPuav0/M7hJ/3t8HbKPCELw/Uqhi4732xoq2hJ2zbo2FkYs56y6w0KiXrIp4IOwNB9K8L/q62g== + +"@next/swc-linux-arm-gnueabihf@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.3.tgz#84ad9e9679d55542a23b590ad9f2e1e9b2df62f7" + integrity sha512-yeiCp/Odt1UJ4KUE89XkeaaboIDiVFqKP4esvoLKGJ0fcqJXMofj4ad3tuQxAMs3F+qqrz9MclqhAHkex1aPZA== + +"@next/swc-linux-arm64-gnu@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.3.tgz#56f9175bc632d647c60b9e8bedc0875edf92d8b7" + integrity sha512-/miIopDOUsuNlvjBjTipvoyjjaxgkOuvlz+cIbbPcm1eFvzX2ltSfgMgty15GuOiR8Hub4FeTSiq3g2dmCkzGA== + +"@next/swc-linux-arm64-musl@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.3.tgz#7d4cf00e8f1729a3de464da0624773f5d0d14888" + integrity sha512-sujxFDhMMDjqhruup8LLGV/y+nCPi6nm5DlFoThMJFvaaKr/imhkXuk8uCTq4YJDbtRxnjydFv2y8laBSJVC2g== + +"@next/swc-linux-x64-gnu@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.3.tgz#17de404910c4ebf7a1d366b19334d7e27e126ab0" + integrity sha512-w5MyxPknVvC9LVnMenAYMXMx4KxPwXuJRMQFvY71uXg68n7cvcas85U5zkdrbmuZ+JvsO5SIG8k36/6X3nUhmQ== + +"@next/swc-linux-x64-musl@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.3.tgz#07cb7b7f3a3a98034e2533f82638a9b099ba4ab1" + integrity sha512-CTeelh8OzSOVqpzMFMFnVRJIFAFQoTsI9RmVJWW/92S4xfECGcOzgsX37CZ8K982WHRzKU7exeh7vYdG/Eh4CA== + +"@next/swc-win32-arm64-msvc@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.3.tgz#b9ac98c954c71ec9de45d3497a8585096b873152" + integrity sha512-7N1KBQP5mo4xf52cFCHgMjzbc9jizIlkTepe9tMa2WFvEIlKDfdt38QYcr9mbtny17yuaIw02FXOVEytGzqdOQ== + +"@next/swc-win32-ia32-msvc@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.3.tgz#5ec48653a48fd664e940c69c96bba698fdae92eb" + integrity sha512-LzWD5pTSipUXTEMRjtxES/NBYktuZdo7xExJqGDMnZU8WOI+v9mQzsmQgZS/q02eIv78JOCSemqVVKZBGCgUvA== + +"@next/swc-win32-x64-msvc@13.2.3": + version "13.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.3.tgz#cd432f280beb8d8de5b7cd2501e9f502e9f3dd72" + integrity sha512-aLG2MaFs4y7IwaMTosz2r4mVbqRyCnMoFqOcmfTi7/mAS+G4IMH0vJp4oLdbshqiVoiVuKrAfqtXj55/m7Qu1Q== "@playwright/test@^1.27.1": - version "1.31.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.31.1.tgz#39d6873dc46af135f12451d79707db7d1357455d" - integrity sha512-IsytVZ+0QLDh1Hj83XatGp/GsI1CDJWbyDaBGbainsh0p2zC7F4toUocqowmjS6sQff2NGT3D9WbDj/3K2CJiA== + version "1.31.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.31.2.tgz#426d8545143a97a6fed250a2a27aa1c8e5e2548e" + integrity sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw== dependencies: "@types/node" "*" - playwright-core "1.31.1" + playwright-core "1.31.2" optionalDependencies: fsevents "2.3.2" @@ -140,9 +140,9 @@ integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/node@*": - version "18.14.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.1.tgz#90dad8476f1e42797c49d6f8b69aaf9f876fc69f" - integrity sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ== + version "18.14.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" + integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== "@types/node@18.11.17": version "18.11.17" @@ -200,9 +200,9 @@ arg@^4.1.0: integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== caniuse-lite@^1.0.30001406: - version "1.0.30001457" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz#6af34bb5d720074e2099432aa522c21555a18301" - integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== + version "1.0.30001460" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz#31d2e26f0a2309860ed3eff154e03890d9d851a7" + integrity sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ== client-only@0.0.1: version "0.0.1" @@ -251,40 +251,40 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== -next@13.2.1: - version "13.2.1" - resolved "https://registry.yarnpkg.com/next/-/next-13.2.1.tgz#34d823f518632b36379863228ed9f861c335b9c0" - integrity sha512-qhgJlDtG0xidNViJUPeQHLGJJoT4zDj/El7fP3D3OzpxJDUfxsm16cK4WTMyvSX1ciIfAq05u+0HqFAa+VJ+Hg== +next@13.2.3: + version "13.2.3" + resolved "https://registry.yarnpkg.com/next/-/next-13.2.3.tgz#92d170e7aca421321f230ff80c35c4751035f42e" + integrity sha512-nKFJC6upCPN7DWRx4+0S/1PIOT7vNlCT157w9AzbXEgKy6zkiPKEt5YyRUsRZkmpEqBVrGgOqNfwecTociyg+w== dependencies: - "@next/env" "13.2.1" + "@next/env" "13.2.3" "@swc/helpers" "0.4.14" caniuse-lite "^1.0.30001406" postcss "8.4.14" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-android-arm-eabi" "13.2.1" - "@next/swc-android-arm64" "13.2.1" - "@next/swc-darwin-arm64" "13.2.1" - "@next/swc-darwin-x64" "13.2.1" - "@next/swc-freebsd-x64" "13.2.1" - "@next/swc-linux-arm-gnueabihf" "13.2.1" - "@next/swc-linux-arm64-gnu" "13.2.1" - "@next/swc-linux-arm64-musl" "13.2.1" - "@next/swc-linux-x64-gnu" "13.2.1" - "@next/swc-linux-x64-musl" "13.2.1" - "@next/swc-win32-arm64-msvc" "13.2.1" - "@next/swc-win32-ia32-msvc" "13.2.1" - "@next/swc-win32-x64-msvc" "13.2.1" + "@next/swc-android-arm-eabi" "13.2.3" + "@next/swc-android-arm64" "13.2.3" + "@next/swc-darwin-arm64" "13.2.3" + "@next/swc-darwin-x64" "13.2.3" + "@next/swc-freebsd-x64" "13.2.3" + "@next/swc-linux-arm-gnueabihf" "13.2.3" + "@next/swc-linux-arm64-gnu" "13.2.3" + "@next/swc-linux-arm64-musl" "13.2.3" + "@next/swc-linux-x64-gnu" "13.2.3" + "@next/swc-linux-x64-musl" "13.2.3" + "@next/swc-win32-arm64-msvc" "13.2.3" + "@next/swc-win32-ia32-msvc" "13.2.3" + "@next/swc-win32-x64-msvc" "13.2.3" picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -playwright-core@1.31.1: - version "1.31.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.1.tgz#4deeebbb8fb73b512593fe24bea206d8fd85ff7f" - integrity sha512-JTyX4kV3/LXsvpHkLzL2I36aCdml4zeE35x+G5aPc4bkLsiRiQshU5lWeVpHFAuC8xAcbI6FDcw/8z3q2xtJSQ== +playwright-core@1.31.2: + version "1.31.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.2.tgz#debf4b215d14cb619adb7e511c164d068075b2ed" + integrity sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ== postcss@8.4.14: version "8.4.14" diff --git a/packages/e2e-tests/test-recipe-schema.json b/packages/e2e-tests/test-recipe-schema.json index 9f8bbf03deb5..178ca96ab3d0 100644 --- a/packages/e2e-tests/test-recipe-schema.json +++ b/packages/e2e-tests/test-recipe-schema.json @@ -11,6 +11,10 @@ "type": "string", "description": "Command that is run to install dependencies and build the test application. This command is only run once before all tests. Working directory of the command is the root of the test application." }, + "buildAssertionCommand": { + "type": "string", + "description": "Command to verify build output. This command will be run after the build is complete. The command will receive the STDOUT of the `buildCommand` as STDIN." + }, "buildTimeoutSeconds": { "type": "number", "description": "Timeout for the build command in seconds. Default: 60" diff --git a/packages/e2e-tests/test-utils/event-proxy-server.ts b/packages/e2e-tests/test-utils/event-proxy-server.ts index f914f68f8d88..c61e20d4081d 100644 --- a/packages/e2e-tests/test-utils/event-proxy-server.ts +++ b/packages/e2e-tests/test-utils/event-proxy-server.ts @@ -138,7 +138,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P export async function waitForRequest( proxyServerName: string, - callback: (eventData: SentryRequestCallbackData) => boolean, + callback: (eventData: SentryRequestCallbackData) => Promise | boolean, ): Promise { const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); @@ -157,7 +157,20 @@ export async function waitForRequest( const eventCallbackData: SentryRequestCallbackData = JSON.parse( Buffer.from(eventContents, 'base64').toString('utf8'), ); - if (callback(eventCallbackData)) { + const callbackResult = callback(eventCallbackData); + if (typeof callbackResult !== 'boolean') { + callbackResult.then( + match => { + if (match) { + response.destroy(); + resolve(eventCallbackData); + } + }, + err => { + throw err; + }, + ); + } else if (callbackResult) { response.destroy(); resolve(eventCallbackData); } @@ -175,13 +188,13 @@ export async function waitForRequest( export function waitForEnvelopeItem( proxyServerName: string, - callback: (envelopeItem: EnvelopeItem) => boolean, + callback: (envelopeItem: EnvelopeItem) => Promise | boolean, ): Promise { return new Promise((resolve, reject) => { - waitForRequest(proxyServerName, eventData => { + waitForRequest(proxyServerName, async eventData => { const envelopeItems = eventData.envelope[1]; for (const envelopeItem of envelopeItems) { - if (callback(envelopeItem)) { + if (await callback(envelopeItem)) { resolve(envelopeItem); return true; } @@ -191,11 +204,14 @@ export function waitForEnvelopeItem( }); } -export function waitForError(proxyServerName: string, callback: (transactionEvent: Event) => boolean): Promise { +export function waitForError( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { - waitForEnvelopeItem(proxyServerName, envelopeItem => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && callback(envelopeItemBody as Event)) { + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { resolve(envelopeItemBody as Event); return true; } @@ -206,12 +222,12 @@ export function waitForError(proxyServerName: string, callback: (transactionEven export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: Event) => boolean, + callback: (transactionEvent: Event) => Promise | boolean, ): Promise { return new Promise((resolve, reject) => { - waitForEnvelopeItem(proxyServerName, envelopeItem => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && callback(envelopeItemBody as Event)) { + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { resolve(envelopeItemBody as Event); return true; } diff --git a/packages/e2e-tests/verdaccio-config/config.yaml b/packages/e2e-tests/verdaccio-config/config.yaml index efcf65968273..a5b4f7919cc8 100644 --- a/packages/e2e-tests/verdaccio-config/config.yaml +++ b/packages/e2e-tests/verdaccio-config/config.yaml @@ -128,6 +128,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/sveltekit': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/tracing': access: $all publish: $all diff --git a/packages/integration-tests/playwright.config.ts b/packages/integration-tests/playwright.config.ts index 0b1a4e15bfee..3baf3937c1f7 100644 --- a/packages/integration-tests/playwright.config.ts +++ b/packages/integration-tests/playwright.config.ts @@ -2,6 +2,10 @@ import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { retries: 0, - workers: 3, + // Run tests inside of a single file in parallel + fullyParallel: true, + // Use 3 workers on CI, else use defaults (based on available CPU cores) + // Note that 3 is a random number selected to work well with our CI setup + workers: process.env.CI ? 3 : undefined, }; export default config; diff --git a/packages/integration-tests/suites/replay/errorResponse/test.ts b/packages/integration-tests/suites/replay/errorResponse/test.ts index d81d16196b8e..2c40666096ee 100644 --- a/packages/integration-tests/suites/replay/errorResponse/test.ts +++ b/packages/integration-tests/suites/replay/errorResponse/test.ts @@ -39,6 +39,5 @@ sentryTest('should stop recording after receiving an error response', async ({ g const replay = await getReplaySnapshot(page); - // @ts-ignore private API expect(replay._isEnabled).toBe(false); }); diff --git a/packages/integration-tests/suites/replay/errors/droppedError/test.ts b/packages/integration-tests/suites/replay/errors/droppedError/test.ts index c509dda8206e..5bcb75bfe619 100644 --- a/packages/integration-tests/suites/replay/errors/droppedError/test.ts +++ b/packages/integration-tests/suites/replay/errors/droppedError/test.ts @@ -24,11 +24,15 @@ sentryTest( sentryTest.skip(); } - let callsToSentry = 0; const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + + let callsToSentry = 0; await page.route('https://dsn.ingest.sentry.io/**/*', route => { callsToSentry++; + return route.fulfill({ status: 200, contentType: 'application/json', @@ -46,10 +50,13 @@ sentryTest( const req0 = await reqPromise0; await page.click('#go-background'); - expect(callsToSentry).toEqual(2); // 2 replay events + await reqPromise1; await page.click('#log'); await page.click('#go-background'); + await reqPromise2; + + // Note: The fact that reqPromise1/reqPromise2 are fulfilled prooves that the recording continues const event0 = getReplayEvent(req0); diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts b/packages/integration-tests/suites/replay/multiple-pages/test.ts index 914c7dd856ca..9fc3c3f0481a 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts @@ -45,12 +45,21 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const reqPromise3 = waitForReplayRequest(page, 3); + const reqPromise4 = waitForReplayRequest(page, 4); + const reqPromise5 = waitForReplayRequest(page, 5); + const reqPromise6 = waitForReplayRequest(page, 6); + const reqPromise7 = waitForReplayRequest(page, 7); + const reqPromise8 = waitForReplayRequest(page, 8); + const reqPromise9 = waitForReplayRequest(page, 9); const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); - const replayEvent0 = getReplayEvent(await reqPromise0); - const recording0 = getReplayRecordingContent(await reqPromise0); + const req0 = await reqPromise0; + const replayEvent0 = getReplayEvent(req0); + const recording0 = getReplayRecordingContent(req0); expect(replayEvent0).toEqual(getExpectedReplayEvent({ segment_id: 0 })); expect(normalize(recording0.fullSnapshots)).toMatchSnapshot('seg-0-snap-full'); @@ -58,8 +67,9 @@ sentryTest( await page.click('#go-background'); - const replayEvent1 = getReplayEvent(await reqPromise1); - const recording1 = getReplayRecordingContent(await reqPromise1); + const req1 = await reqPromise1; + const replayEvent1 = getReplayEvent(req1); + const recording1 = getReplayRecordingContent(req1); expect(replayEvent1).toEqual( getExpectedReplayEvent({ segment_id: 1, urls: [], replay_start_timestamp: undefined }), @@ -91,11 +101,9 @@ sentryTest( await page.reload(); - const reqPromise2 = waitForReplayRequest(page, 2); - const reqPromise3 = waitForReplayRequest(page, 3); - - const replayEvent2 = getReplayEvent(await reqPromise2); - const recording2 = getReplayRecordingContent(await reqPromise2); + const req2 = await reqPromise2; + const replayEvent2 = getReplayEvent(req2); + const recording2 = getReplayRecordingContent(req2); expect(replayEvent2).toEqual(getExpectedReplayEvent({ segment_id: 2, replay_start_timestamp: undefined })); expect(normalize(recording2.fullSnapshots)).toMatchSnapshot('seg-2-snap-full'); @@ -103,8 +111,9 @@ sentryTest( await page.click('#go-background'); - const replayEvent3 = getReplayEvent(await reqPromise3); - const recording3 = getReplayRecordingContent(await reqPromise3); + const req3 = await reqPromise3; + const replayEvent3 = getReplayEvent(req3); + const recording3 = getReplayRecordingContent(req3); expect(replayEvent3).toEqual( getExpectedReplayEvent({ segment_id: 3, urls: [], replay_start_timestamp: undefined }), @@ -134,11 +143,9 @@ sentryTest( await page.click('a'); - const reqPromise4 = waitForReplayRequest(page, 4); - const reqPromise5 = waitForReplayRequest(page, 5); - - const replayEvent4 = getReplayEvent(await reqPromise4); - const recording4 = getReplayRecordingContent(await reqPromise4); + const req4 = await reqPromise4; + const replayEvent4 = getReplayEvent(req4); + const recording4 = getReplayRecordingContent(req4); expect(replayEvent4).toEqual( getExpectedReplayEvent({ @@ -161,8 +168,9 @@ sentryTest( await page.click('#go-background'); - const replayEvent5 = getReplayEvent(await reqPromise5); - const recording5 = getReplayRecordingContent(await reqPromise5); + const req5 = await reqPromise5; + const replayEvent5 = getReplayEvent(req5); + const recording5 = getReplayRecordingContent(req5); expect(replayEvent5).toEqual( getExpectedReplayEvent({ @@ -207,9 +215,9 @@ sentryTest( await page.click('#spa-navigation'); - const reqPromise6 = waitForReplayRequest(page, 6); - const replayEvent6 = getReplayEvent(await reqPromise6); - const recording6 = getReplayRecordingContent(await reqPromise6); + const req6 = await reqPromise6; + const replayEvent6 = getReplayEvent(req6); + const recording6 = getReplayRecordingContent(req6); expect(replayEvent6).toEqual( getExpectedReplayEvent({ @@ -231,9 +239,9 @@ sentryTest( await page.click('#go-background'); - const reqPromise7 = waitForReplayRequest(page, 7); - const replayEvent7 = getReplayEvent(await reqPromise7); - const recording7 = getReplayRecordingContent(await reqPromise7); + const req7 = await reqPromise7; + const replayEvent7 = getReplayEvent(req7); + const recording7 = getReplayRecordingContent(req7); expect(replayEvent7).toEqual( getExpectedReplayEvent({ @@ -279,11 +287,9 @@ sentryTest( await page.click('a'); - const reqPromise8 = waitForReplayRequest(page, 8); - const reqPromise9 = waitForReplayRequest(page, 9); - - const replayEvent8 = getReplayEvent(await reqPromise8); - const recording8 = getReplayRecordingContent(await reqPromise8); + const req8 = await reqPromise8; + const replayEvent8 = getReplayEvent(req8); + const recording8 = getReplayRecordingContent(req8); expect(replayEvent8).toEqual( getExpectedReplayEvent({ @@ -296,8 +302,9 @@ sentryTest( await page.click('#go-background'); - const replayEvent9 = getReplayEvent(await reqPromise9); - const recording9 = getReplayRecordingContent(await reqPromise9); + const req9 = await reqPromise9; + const replayEvent9 = getReplayEvent(req9); + const recording9 = getReplayRecordingContent(req9); expect(replayEvent9).toEqual( getExpectedReplayEvent({ diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full-chromium index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full-chromium @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full-chromium index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full-chromium @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full index a6c4b9f7c908..b0aeb348b388 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full @@ -1,152 +1,156 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "********* ****", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "********* ****", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "spa-navigation" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** * *** **********", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/index.html" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 13 + } + ], + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "** **** ** ***** ****", - "id": 20 - } - ], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 21 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 22 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "spa-navigation" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** * *** **********", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n ", + "id": 18 + }, + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/index.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** **** ** ***** ****", + "id": 20 + } + ], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 21 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 22 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full-chromium index a6c4b9f7c908..b0aeb348b388 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full-chromium @@ -1,152 +1,156 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "********* ****", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "********* ****", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "spa-navigation" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** * *** **********", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/index.html" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 13 + } + ], + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "** **** ** ***** ****", - "id": 20 - } - ], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 21 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 22 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "spa-navigation" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** * *** **********", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n ", + "id": 18 + }, + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/index.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** **** ** ***** ****", + "id": 20 + } + ], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 21 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 22 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental index 3035cb50910e..13c75c43bf61 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 90, - "id": 12, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 12, - "x": 41, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental-chromium index 3035cb50910e..13c75c43bf61 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental-chromium @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 90, - "id": 12, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 12, - "x": 41, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental index ddaf5314ca3d..c7be8ab3861a 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental @@ -1,44 +1,53 @@ [ { - "source": 1, - "positions": [ - { - "x": 157, - "y": 90, - "id": 15, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 15, - "x": 157, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 6, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 6, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 15 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 15 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 15, - "x": 157, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 15, - "x": 157, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental-chromium index ddaf5314ca3d..c7be8ab3861a 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental-chromium @@ -1,44 +1,53 @@ [ { - "source": 1, - "positions": [ - { - "x": 157, - "y": 90, - "id": 15, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 15, - "x": 157, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 6, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 6, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 15 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 15 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 15, - "x": 157, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 15, - "x": 157, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 15, + "x": 157, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental index ae20ba0fd987..5b461c8cb66c 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental @@ -1,44 +1,53 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 90, - "id": 12, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 6, + "id": 15 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 6, - "id": 15 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 12, - "x": 41, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental-chromium index ae20ba0fd987..5b461c8cb66c 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental-chromium @@ -1,44 +1,53 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 90, - "id": 12, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 6, + "id": 15 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 6, - "id": 15 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 12 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 12 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 12, - "x": 41, - "y": 90 - }, - { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 12, + "x": 41, + "y": 90 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full-chromium index cfd7928e8d9d..fdccbb1b9387 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full-chromium @@ -1,109 +1,113 @@ [ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "*** ***", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "/page-0.html" + { + "type": 3, + "textContent": "\n ", + "id": 11 }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 2, + "tagName": "a", + "attributes": { + "href": "/page-0.html" + }, + "childNodes": [ + { + "type": 3, + "textContent": "** ** *** ****", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium index 29620a33392b..f612eadc8f80 100644 --- a/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium +++ b/packages/integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium @@ -1,39 +1,44 @@ [ { - "source": 1, - "positions": [ - { - "x": 41, - "y": 18, - "id": 9, - "timeOffset": [timeOffset] - } - ] + "type": 3, + "data": { + "source": 2, + "type": 1, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 1, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 5, + "id": 9 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 5, - "id": 9 + "type": 3, + "data": { + "source": 2, + "type": 0, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] }, { - "source": 2, - "type": 0, - "id": 9, - "x": 41, - "y": 18 - }, - { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18 + "type": 3, + "data": { + "source": 2, + "type": 2, + "id": 9, + "x": 41, + "y": 18 + }, + "timestamp": [timestamp] } ] \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-chromium.json b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-chromium.json index a120d5e44a5e..9ca91c2dc5da 100644 --- a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-chromium.json +++ b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-chromium.json @@ -1,319 +1,323 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" - }, - "childNodes": [], - "isSVG": true, - "id": 25 + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 26 + "childNodes": [], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 27 - } - ], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 28 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 25 + }, + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 26 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 27 + } + ], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 28 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 30 }, - "childNodes": [], - "isSVG": true, - "id": 30 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 31 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 32 + } + ], + "isSVG": true, + "id": 29 + }, + { + "type": 3, + "textContent": "\n ", + "id": 33 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 31 + "childNodes": [], + "id": 34 + }, + { + "type": 3, + "textContent": "\n ", + "id": 35 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" + }, + "childNodes": [], + "id": 36 + }, + { + "type": 3, + "textContent": "\n ", + "id": 37 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 32 - } - ], - "isSVG": true, - "id": 29 - }, - { - "type": 3, - "textContent": "\n ", - "id": 33 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "style": "width:100px;height:100px", - "src": "file:///none.png" + "childNodes": [], + "id": 38 }, - "childNodes": [], - "id": 34 - }, - { - "type": 3, - "textContent": "\n ", - "id": 35 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 39 }, - "childNodes": [], - "id": 36 - }, - { - "type": 3, - "textContent": "\n ", - "id": 37 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "div", + "attributes": { + "class": "nested-hide", + "rr_width": "[1250-1300]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 40 }, - "childNodes": [], - "id": 38 - }, - { - "type": 3, - "textContent": "\n ", - "id": 39 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "class": "nested-hide", - "rr_width": "[1250-1300]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 41 }, - "childNodes": [], - "id": 40 - }, - { - "type": 3, - "textContent": "\n ", - "id": 41 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 42 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 42 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-firefox.json b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-firefox.json index a120d5e44a5e..9ca91c2dc5da 100644 --- a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-firefox.json +++ b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-firefox.json @@ -1,319 +1,323 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" - }, - "childNodes": [], - "isSVG": true, - "id": 25 + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 26 + "childNodes": [], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 27 - } - ], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 28 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 25 + }, + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 26 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 27 + } + ], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 28 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 30 }, - "childNodes": [], - "isSVG": true, - "id": 30 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 31 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 32 + } + ], + "isSVG": true, + "id": 29 + }, + { + "type": 3, + "textContent": "\n ", + "id": 33 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 31 + "childNodes": [], + "id": 34 + }, + { + "type": 3, + "textContent": "\n ", + "id": 35 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" + }, + "childNodes": [], + "id": 36 + }, + { + "type": 3, + "textContent": "\n ", + "id": 37 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 32 - } - ], - "isSVG": true, - "id": 29 - }, - { - "type": 3, - "textContent": "\n ", - "id": 33 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "style": "width:100px;height:100px", - "src": "file:///none.png" + "childNodes": [], + "id": 38 }, - "childNodes": [], - "id": 34 - }, - { - "type": 3, - "textContent": "\n ", - "id": 35 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 39 }, - "childNodes": [], - "id": 36 - }, - { - "type": 3, - "textContent": "\n ", - "id": 37 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "div", + "attributes": { + "class": "nested-hide", + "rr_width": "[1250-1300]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 40 }, - "childNodes": [], - "id": 38 - }, - { - "type": 3, - "textContent": "\n ", - "id": 39 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "class": "nested-hide", - "rr_width": "[1250-1300]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 41 }, - "childNodes": [], - "id": 40 - }, - { - "type": 3, - "textContent": "\n ", - "id": 41 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 42 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 42 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-webkit.json b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-webkit.json index a120d5e44a5e..9ca91c2dc5da 100644 --- a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-webkit.json +++ b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy-webkit.json @@ -1,319 +1,323 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" - }, - "childNodes": [], - "isSVG": true, - "id": 25 + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 26 + "childNodes": [], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 27 - } - ], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 28 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 25 + }, + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 26 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 27 + } + ], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 28 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 30 }, - "childNodes": [], - "isSVG": true, - "id": 30 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 31 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 32 + } + ], + "isSVG": true, + "id": 29 + }, + { + "type": 3, + "textContent": "\n ", + "id": 33 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 31 + "childNodes": [], + "id": 34 + }, + { + "type": 3, + "textContent": "\n ", + "id": 35 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" + }, + "childNodes": [], + "id": 36 + }, + { + "type": 3, + "textContent": "\n ", + "id": 37 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 32 - } - ], - "isSVG": true, - "id": 29 - }, - { - "type": 3, - "textContent": "\n ", - "id": 33 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "style": "width:100px;height:100px", - "src": "file:///none.png" + "childNodes": [], + "id": 38 }, - "childNodes": [], - "id": 34 - }, - { - "type": 3, - "textContent": "\n ", - "id": 35 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 39 }, - "childNodes": [], - "id": 36 - }, - { - "type": 3, - "textContent": "\n ", - "id": 37 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "div", + "attributes": { + "class": "nested-hide", + "rr_width": "[1250-1300]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 40 }, - "childNodes": [], - "id": 38 - }, - { - "type": 3, - "textContent": "\n ", - "id": 39 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "class": "nested-hide", - "rr_width": "[1250-1300]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 41 }, - "childNodes": [], - "id": 40 - }, - { - "type": 3, - "textContent": "\n ", - "id": 41 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 42 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 42 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy.json b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy.json index a120d5e44a5e..9ca91c2dc5da 100644 --- a/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy.json +++ b/packages/integration-tests/suites/replay/privacyBlock/test.ts-snapshots/privacy.json @@ -1,319 +1,323 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" - }, - "childNodes": [], - "isSVG": true, - "id": 25 + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 26 + "childNodes": [], + "id": 19 + }, + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 27 - } - ], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 28 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 25 + }, + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 26 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 27 + } + ], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 28 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 30 }, - "childNodes": [], - "isSVG": true, - "id": 30 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 31 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 32 + } + ], + "isSVG": true, + "id": 29 + }, + { + "type": 3, + "textContent": "\n ", + "id": 33 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 31 + "childNodes": [], + "id": 34 + }, + { + "type": 3, + "textContent": "\n ", + "id": 35 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" + }, + "childNodes": [], + "id": 36 + }, + { + "type": 3, + "textContent": "\n ", + "id": 37 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 32 - } - ], - "isSVG": true, - "id": 29 - }, - { - "type": 3, - "textContent": "\n ", - "id": 33 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "style": "width:100px;height:100px", - "src": "file:///none.png" + "childNodes": [], + "id": 38 }, - "childNodes": [], - "id": 34 - }, - { - "type": 3, - "textContent": "\n ", - "id": 35 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 39 }, - "childNodes": [], - "id": 36 - }, - { - "type": 3, - "textContent": "\n ", - "id": 37 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "div", + "attributes": { + "class": "nested-hide", + "rr_width": "[1250-1300]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 40 }, - "childNodes": [], - "id": 38 - }, - { - "type": 3, - "textContent": "\n ", - "id": 39 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "class": "nested-hide", - "rr_width": "[1250-1300]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 41 }, - "childNodes": [], - "id": 40 - }, - { - "type": 3, - "textContent": "\n ", - "id": 41 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 42 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 42 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-chromium.json b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-chromium.json index 11bf6d7a7fa7..f1b55c0884ef 100644 --- a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-chromium.json +++ b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-chromium.json @@ -1,276 +1,280 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "rr_width": "[200-250]px", - "rr_height": "[200-250]px" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 25 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" + }, + "childNodes": [], + "id": 19 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "rr_width": "[200-250]px", + "rr_height": "[200-250]px" + }, + "childNodes": [], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 25 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 27 }, - "childNodes": [], - "isSVG": true, - "id": 27 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 28 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 29 + } + ], + "isSVG": true, + "id": 26 + }, + { + "type": 3, + "textContent": "\n ", + "id": 30 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "rr_width": "[100-150]px", + "rr_height": "[100-150]px" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 28 + "childNodes": [], + "id": 31 + }, + { + "type": 3, + "textContent": "\n ", + "id": 32 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 29 - } - ], - "isSVG": true, - "id": 26 - }, - { - "type": 3, - "textContent": "\n ", - "id": 30 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "rr_width": "[100-150]px", - "rr_height": "[100-150]px" + "childNodes": [], + "id": 33 }, - "childNodes": [], - "id": 31 - }, - { - "type": 3, - "textContent": "\n ", - "id": 32 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 34 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 35 }, - "childNodes": [], - "id": 33 - }, - { - "type": 3, - "textContent": "\n ", - "id": 34 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 36 }, - "childNodes": [], - "id": 35 - }, - { - "type": 3, - "textContent": "\n ", - "id": 36 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 37 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 37 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-firefox.json b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-firefox.json index 11bf6d7a7fa7..f1b55c0884ef 100644 --- a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-firefox.json +++ b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-firefox.json @@ -1,276 +1,280 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "rr_width": "[200-250]px", - "rr_height": "[200-250]px" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 25 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" + }, + "childNodes": [], + "id": 19 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "rr_width": "[200-250]px", + "rr_height": "[200-250]px" + }, + "childNodes": [], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 25 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 27 }, - "childNodes": [], - "isSVG": true, - "id": 27 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 28 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 29 + } + ], + "isSVG": true, + "id": 26 + }, + { + "type": 3, + "textContent": "\n ", + "id": 30 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "rr_width": "[100-150]px", + "rr_height": "[100-150]px" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 28 + "childNodes": [], + "id": 31 + }, + { + "type": 3, + "textContent": "\n ", + "id": 32 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 29 - } - ], - "isSVG": true, - "id": 26 - }, - { - "type": 3, - "textContent": "\n ", - "id": 30 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "rr_width": "[100-150]px", - "rr_height": "[100-150]px" + "childNodes": [], + "id": 33 }, - "childNodes": [], - "id": 31 - }, - { - "type": 3, - "textContent": "\n ", - "id": 32 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 34 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 35 }, - "childNodes": [], - "id": 33 - }, - { - "type": 3, - "textContent": "\n ", - "id": 34 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 36 }, - "childNodes": [], - "id": 35 - }, - { - "type": 3, - "textContent": "\n ", - "id": 36 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 37 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 37 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-webkit.json b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-webkit.json index 11bf6d7a7fa7..f1b55c0884ef 100644 --- a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-webkit.json +++ b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy-webkit.json @@ -1,276 +1,280 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "rr_width": "[200-250]px", - "rr_height": "[200-250]px" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 25 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" + }, + "childNodes": [], + "id": 19 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "rr_width": "[200-250]px", + "rr_height": "[200-250]px" + }, + "childNodes": [], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 25 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 27 }, - "childNodes": [], - "isSVG": true, - "id": 27 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 28 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 29 + } + ], + "isSVG": true, + "id": 26 + }, + { + "type": 3, + "textContent": "\n ", + "id": 30 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "rr_width": "[100-150]px", + "rr_height": "[100-150]px" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 28 + "childNodes": [], + "id": 31 + }, + { + "type": 3, + "textContent": "\n ", + "id": 32 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 29 - } - ], - "isSVG": true, - "id": 26 - }, - { - "type": 3, - "textContent": "\n ", - "id": 30 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "rr_width": "[100-150]px", - "rr_height": "[100-150]px" + "childNodes": [], + "id": 33 }, - "childNodes": [], - "id": 31 - }, - { - "type": 3, - "textContent": "\n ", - "id": 32 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 34 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 35 }, - "childNodes": [], - "id": 33 - }, - { - "type": 3, - "textContent": "\n ", - "id": 34 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 36 }, - "childNodes": [], - "id": 35 - }, - { - "type": 3, - "textContent": "\n ", - "id": 36 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 37 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 37 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json index f31f8b967d12..f1b55c0884ef 100644 --- a/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json +++ b/packages/integration-tests/suites/replay/privacyDefault/test.ts-snapshots/privacy.json @@ -1,277 +1,280 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + }, + { + "type": 2, + "tagName": "link", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 6 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 7 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 9 }, - "childNodes": [], - "id": 5 - }, - { - "type": 2, - "tagName": "link", - "attributes": { - "rel": "icon", - "type": "image/png", - "href": "file://assets/icon/favicon.png" + { + "type": 2, + "tagName": "button", + "attributes": { + "aria-label": "***** **", + "onclick": "console.log('Test log')" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 11 + } + ], + "id": 10 }, - "childNodes": [], - "id": 6 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 7 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 9 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "aria-label": "***** **", - "onclick": "console.log('Test log')" + { + "type": 3, + "textContent": "\n ", + "id": 12 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 11 - } - ], - "id": 10 - }, - { - "type": 3, - "textContent": "\n ", - "id": 12 - }, - { - "type": 2, - "tagName": "div", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "**** ****** ** ****** ** *******", - "id": 14 - } - ], - "id": 13 - }, - { - "type": 3, - "textContent": "\n ", - "id": 15 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "data-sentry-unmask": "" + { + "type": 2, + "tagName": "div", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "**** ****** ** ****** ** *******", + "id": 14 + } + ], + "id": 13 }, - "childNodes": [ - { - "type": 3, - "textContent": "This should be unmasked due to data attribute", - "id": 17 - } - ], - "id": 16 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "input", - "attributes": { - "placeholder": "*********** ****** ** ******" + { + "type": 3, + "textContent": "\n ", + "id": 15 }, - "childNodes": [], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 20 - }, - { - "type": 2, - "tagName": "div", - "attributes": { - "title": "***** ****** ** ******" + { + "type": 2, + "tagName": "div", + "attributes": { + "data-sentry-unmask": "" + }, + "childNodes": [ + { + "type": 3, + "textContent": "This should be unmasked due to data attribute", + "id": 17 + } + ], + "id": 16 }, - "childNodes": [ - { - "type": 3, - "textContent": "***** ****** ** ******", - "id": 22 - } - ], - "id": 21 - }, - { - "type": 3, - "textContent": "\n ", - "id": 23 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "rr_width": "[200-250]px", - "rr_height": "[200-250]px" + { + "type": 3, + "textContent": "\n ", + "id": 18 }, - "childNodes": [], - "isSVG": true, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "id": 25 - }, - { - "type": 2, - "tagName": "svg", - "attributes": { - "style": "width:200px;height:200px", - "viewBox": "0 0 80 80", - "data-sentry-unblock": "" + { + "type": 2, + "tagName": "input", + "attributes": { + "placeholder": "*********** ****** ** ******" + }, + "childNodes": [], + "id": 19 }, - "childNodes": [ - { - "type": 2, - "tagName": "path", - "attributes": { - "d": "" + { + "type": 3, + "textContent": "\n ", + "id": 20 + }, + { + "type": 2, + "tagName": "div", + "attributes": { + "title": "***** ****** ** ******" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** ****** ** ******", + "id": 22 + } + ], + "id": 21 + }, + { + "type": 3, + "textContent": "\n ", + "id": 23 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "rr_width": "[200-250]px", + "rr_height": "[200-250]px" + }, + "childNodes": [], + "isSVG": true, + "id": 24 + }, + { + "type": 3, + "textContent": "\n ", + "id": 25 + }, + { + "type": 2, + "tagName": "svg", + "attributes": { + "style": "width:200px;height:200px", + "viewBox": "0 0 80 80", + "data-sentry-unblock": "" + }, + "childNodes": [ + { + "type": 2, + "tagName": "path", + "attributes": { + "d": "" + }, + "childNodes": [], + "isSVG": true, + "id": 27 }, - "childNodes": [], - "isSVG": true, - "id": 27 + { + "type": 2, + "tagName": "area", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 28 + }, + { + "type": 2, + "tagName": "rect", + "attributes": {}, + "childNodes": [], + "isSVG": true, + "id": 29 + } + ], + "isSVG": true, + "id": 26 + }, + { + "type": 3, + "textContent": "\n ", + "id": 30 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "rr_width": "[100-150]px", + "rr_height": "[100-150]px" }, - { - "type": 2, - "tagName": "area", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 28 + "childNodes": [], + "id": 31 + }, + { + "type": 3, + "textContent": "\n ", + "id": 32 + }, + { + "type": 2, + "tagName": "img", + "attributes": { + "data-sentry-unblock": "", + "style": "width:100px;height:100px", + "src": "file:///none.png" }, - { - "type": 2, - "tagName": "rect", - "attributes": {}, - "childNodes": [], - "isSVG": true, - "id": 29 - } - ], - "isSVG": true, - "id": 26 - }, - { - "type": 3, - "textContent": "\n ", - "id": 30 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "rr_width": "[100-150]px", - "rr_height": "[100-150]px" + "childNodes": [], + "id": 33 }, - "childNodes": [], - "id": 31 - }, - { - "type": 3, - "textContent": "\n ", - "id": 32 - }, - { - "type": 2, - "tagName": "img", - "attributes": { - "data-sentry-unblock": "", - "style": "width:100px;height:100px", - "src": "file:///none.png" + { + "type": 3, + "textContent": "\n ", + "id": 34 + }, + { + "type": 2, + "tagName": "video", + "attributes": { + "rr_width": "[0-50]px", + "rr_height": "[0-50]px" + }, + "childNodes": [], + "id": 35 }, - "childNodes": [], - "id": 33 - }, - { - "type": 3, - "textContent": "\n ", - "id": 34 - }, - { - "type": 2, - "tagName": "video", - "attributes": { - "rr_width": "[0-50]px", - "rr_height": "[0-50]px" + { + "type": 3, + "textContent": "\n ", + "id": 36 }, - "childNodes": [], - "id": 35 - }, - { - "type": 3, - "textContent": "\n ", - "id": 36 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 37 - } - ], - "id": 8 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n\n", + "id": 37 + } + ], + "id": 8 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/privacyInput/init.js b/packages/integration-tests/suites/replay/privacyInput/init.js new file mode 100644 index 000000000000..a09c517b6a92 --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInput/init.js @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/browser'; +import { Replay } from '@sentry/replay'; + +window.Sentry = Sentry; +window.Replay = new Replay({ + flushMinDelay: 200, + flushMaxDelay: 200, + useCompression: false, + maskAllInputs: false, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/packages/integration-tests/suites/replay/privacyInput/template.html b/packages/integration-tests/suites/replay/privacyInput/template.html new file mode 100644 index 000000000000..735abb395522 --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInput/template.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/privacyInput/test.ts b/packages/integration-tests/suites/replay/privacyInput/test.ts new file mode 100644 index 000000000000..f95e857d5637 --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInput/test.ts @@ -0,0 +1,111 @@ +import { expect } from '@playwright/test'; +import { IncrementalSource } from '@sentry-internal/rrweb'; +import type { inputData } from '@sentry-internal/rrweb/typings/types'; + +import { sentryTest } from '../../../utils/fixtures'; +import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers'; +import { + getIncrementalRecordingSnapshots, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +function isInputMutation( + snap: IncrementalRecordingSnapshot, +): snap is IncrementalRecordingSnapshot & { data: inputData } { + return snap.data.source == IncrementalSource.Input; +} + +sentryTest( + 'should mask input initial value and its changes', + async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => { + // TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation. + if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const reqPromise3 = waitForReplayRequest(page, 3); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + + await reqPromise0; + + const text = 'test'; + + await page.locator('#input').fill(text); + await forceFlushReplay(); + const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation); + const lastSnapshot = snapshots[snapshots.length - 1]; + expect(lastSnapshot.data.text).toBe(text); + + await page.locator('#input-masked').fill(text); + await forceFlushReplay(); + const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation); + const lastSnapshot2 = snapshots2[snapshots2.length - 1]; + expect(lastSnapshot2.data.text).toBe('*'.repeat(text.length)); + + await page.locator('#input-ignore').fill(text); + await forceFlushReplay(); + const snapshots3 = getIncrementalRecordingSnapshots(await reqPromise3).filter(isInputMutation); + expect(snapshots3.length).toBe(0); + }, +); + +sentryTest( + 'should mask textarea initial value and its changes', + async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => { + // TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation. + if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const reqPromise3 = waitForReplayRequest(page, 3); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await reqPromise0; + + const text = 'test'; + await page.locator('#textarea').fill(text); + await forceFlushReplay(); + const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation); + const lastSnapshot = snapshots[snapshots.length - 1]; + expect(lastSnapshot.data.text).toBe(text); + + await page.locator('#textarea-masked').fill(text); + await forceFlushReplay(); + const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation); + const lastSnapshot2 = snapshots2[snapshots2.length - 1]; + expect(lastSnapshot2.data.text).toBe('*'.repeat(text.length)); + + await page.locator('#textarea-ignore').fill(text); + await forceFlushReplay(); + const snapshots3 = getIncrementalRecordingSnapshots(await reqPromise3).filter(isInputMutation); + expect(snapshots3.length).toBe(0); + }, +); diff --git a/packages/integration-tests/suites/replay/privacyInputMaskAll/init.js b/packages/integration-tests/suites/replay/privacyInputMaskAll/init.js new file mode 100644 index 000000000000..6345c0f75f4e --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInputMaskAll/init.js @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/browser'; +import { Replay } from '@sentry/replay'; + +window.Sentry = Sentry; +window.Replay = new Replay({ + flushMinDelay: 200, + flushMaxDelay: 200, + useCompression: false, + maskAllInputs: true, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/packages/integration-tests/suites/replay/privacyInputMaskAll/template.html b/packages/integration-tests/suites/replay/privacyInputMaskAll/template.html new file mode 100644 index 000000000000..404bed05a6d0 --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInputMaskAll/template.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/privacyInputMaskAll/test.ts b/packages/integration-tests/suites/replay/privacyInputMaskAll/test.ts new file mode 100644 index 000000000000..9b4470118422 --- /dev/null +++ b/packages/integration-tests/suites/replay/privacyInputMaskAll/test.ts @@ -0,0 +1,135 @@ +import { expect } from '@playwright/test'; +import { IncrementalSource } from '@sentry-internal/rrweb'; +import type { inputData } from '@sentry-internal/rrweb/typings/types'; + +import { sentryTest } from '../../../utils/fixtures'; +import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers'; +import { + getIncrementalRecordingSnapshots, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +function isInputMutation( + snap: IncrementalRecordingSnapshot, +): snap is IncrementalRecordingSnapshot & { data: inputData } { + return snap.data.source == IncrementalSource.Input; +} + +sentryTest( + 'should mask input initial value and its changes from `maskAllInputs` and allow unmasked selector', + async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => { + // TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation. + if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) { + sentryTest.skip(); + } + + // We want to ensure to check the correct event payloads + let firstInputMutationSegmentId: number | undefined = undefined; + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, (event, res) => { + const check = + firstInputMutationSegmentId === undefined && getIncrementalRecordingSnapshots(res).some(isInputMutation); + + if (check) { + firstInputMutationSegmentId = event.segment_id; + } + + return check; + }); + const reqPromise2 = waitForReplayRequest(page, (event, res) => { + return ( + typeof firstInputMutationSegmentId === 'number' && + firstInputMutationSegmentId < event.segment_id && + getIncrementalRecordingSnapshots(res).some(isInputMutation) + ); + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await reqPromise0; + + const text = 'test'; + + await page.locator('#input').fill(text); + await forceFlushReplay(); + + const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation); + const lastSnapshot = snapshots[snapshots.length - 1]; + expect(lastSnapshot.data.text).toBe('*'.repeat(text.length)); + + await page.locator('#input-unmasked').fill(text); + await forceFlushReplay(); + const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation); + const lastSnapshot2 = snapshots2[snapshots2.length - 1]; + expect(lastSnapshot2.data.text).toBe(text); + }, +); + +sentryTest( + 'should mask textarea initial value and its changes from `maskAllInputs` and allow unmasked selector', + async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => { + // TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation. + if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) { + sentryTest.skip(); + } + + // We want to ensure to check the correct event payloads + let firstInputMutationSegmentId: number | undefined = undefined; + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, (event, res) => { + const check = + firstInputMutationSegmentId === undefined && getIncrementalRecordingSnapshots(res).some(isInputMutation); + + if (check) { + firstInputMutationSegmentId = event.segment_id; + } + + return check; + }); + const reqPromise2 = waitForReplayRequest(page, (event, res) => { + return ( + typeof firstInputMutationSegmentId === 'number' && + firstInputMutationSegmentId < event.segment_id && + getIncrementalRecordingSnapshots(res).some(isInputMutation) + ); + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + + await reqPromise0; + + const text = 'test'; + + await page.locator('#textarea').fill(text); + await forceFlushReplay(); + const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation); + const lastSnapshot = snapshots[snapshots.length - 1]; + expect(lastSnapshot.data.text).toBe('*'.repeat(text.length)); + + await page.locator('#textarea-unmasked').fill(text); + await forceFlushReplay(); + const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation); + const lastSnapshot2 = snapshots2[snapshots2.length - 1]; + expect(lastSnapshot2.data.text).toBe(text); + }, +); diff --git a/packages/integration-tests/suites/replay/sessionExpiry/init.js b/packages/integration-tests/suites/replay/sessionExpiry/init.js new file mode 100644 index 000000000000..3e685021e1fe --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); + +window.Replay._replay.timeouts = { + sessionIdle: 2000, // this is usually 5min, but we want to test this with shorter times + maxSessionLife: 3600000, // default: 60min +}; diff --git a/packages/integration-tests/suites/replay/sessionExpiry/template.html b/packages/integration-tests/suites/replay/sessionExpiry/template.html new file mode 100644 index 000000000000..7223a20f82ba --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/template.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts b/packages/integration-tests/suites/replay/sessionExpiry/test.ts new file mode 100644 index 000000000000..d817e7175840 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts @@ -0,0 +1,74 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { + getFullRecordingSnapshots, + getReplayEvent, + getReplaySnapshot, + normalize, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +// Session should expire after 2s - keep in sync with init.js +const SESSION_TIMEOUT = 2000; + +sentryTest('handles an expired session', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + const req0 = await reqPromise0; + + const replayEvent0 = getReplayEvent(req0); + expect(replayEvent0).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots0 = getFullRecordingSnapshots(req0); + expect(fullSnapshots0.length).toEqual(1); + const stringifiedSnapshot = normalize(fullSnapshots0[0]); + expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json'); + + // We wait for another segment 0 + const reqPromise2 = waitForReplayRequest(page, 0); + + await page.click('#button1'); + const req1 = await reqPromise1; + + const replayEvent1 = getReplayEvent(req1); + expect(replayEvent1).toEqual(getExpectedReplayEvent({ replay_start_timestamp: undefined, segment_id: 1, urls: [] })); + + const replay = await getReplaySnapshot(page); + const oldSessionId = replay.session?.id; + + await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT)); + + await page.click('#button2'); + const req2 = await reqPromise2; + + const replay2 = await getReplaySnapshot(page); + + expect(replay2.session?.id).not.toEqual(oldSessionId); + + const replayEvent2 = getReplayEvent(req2); + expect(replayEvent2).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots2 = getFullRecordingSnapshots(req2); + expect(fullSnapshots2.length).toEqual(1); + const stringifiedSnapshot2 = normalize(fullSnapshots2[0]); + expect(stringifiedSnapshot2).toMatchSnapshot('snapshot-2.json'); +}); diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/init.js b/packages/integration-tests/suites/replay/sessionInactive/init.js new file mode 100644 index 000000000000..f1b0345e71d7 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); + +window.Replay._replay.timeouts = { + sessionIdle: 300000, // default: 5min + maxSessionLife: 2000, // this is usually 60min, but we want to test this with shorter times +}; diff --git a/packages/integration-tests/suites/replay/sessionInactive/template.html b/packages/integration-tests/suites/replay/sessionInactive/template.html new file mode 100644 index 000000000000..7223a20f82ba --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/template.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts b/packages/integration-tests/suites/replay/sessionInactive/test.ts new file mode 100644 index 000000000000..ed53f155feea --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts @@ -0,0 +1,82 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { + getFullRecordingSnapshots, + getReplayEvent, + getReplaySnapshot, + normalize, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +// Session should expire after 2s - keep in sync with init.js +const SESSION_TIMEOUT = 2000; + +sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + const req0 = await reqPromise0; + + const replayEvent0 = getReplayEvent(req0); + expect(replayEvent0).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots0 = getFullRecordingSnapshots(req0); + expect(fullSnapshots0.length).toEqual(1); + const stringifiedSnapshot = normalize(fullSnapshots0[0]); + expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json'); + + await page.click('#button1'); + + // We wait for another segment 0 + const reqPromise1 = waitForReplayRequest(page, 0); + + // Now we wait for the session timeout, nothing should be sent in the meanwhile + await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT)); + + // nothing happened because no activity/inactivity was detected + const replay = await getReplaySnapshot(page); + expect(replay._isEnabled).toEqual(true); + expect(replay._isPaused).toEqual(false); + + // Now we trigger a blur event, which should move the session to paused mode + await page.evaluate(() => { + window.dispatchEvent(new Event('blur')); + }); + + const replay2 = await getReplaySnapshot(page); + expect(replay2._isEnabled).toEqual(true); + expect(replay2._isPaused).toEqual(true); + + // Trigger an action, should re-start the recording + await page.click('#button2'); + const req1 = await reqPromise1; + + const replay3 = await getReplaySnapshot(page); + expect(replay3._isEnabled).toEqual(true); + expect(replay3._isPaused).toEqual(false); + + const replayEvent1 = getReplayEvent(req1); + expect(replayEvent1).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots1 = getFullRecordingSnapshots(req1); + expect(fullSnapshots1.length).toEqual(1); + const stringifiedSnapshot1 = normalize(fullSnapshots1[0]); + expect(stringifiedSnapshot1).toMatchSnapshot('snapshot-1.json'); +}); diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/init.js b/packages/integration-tests/suites/replay/sessionMaxAge/init.js new file mode 100644 index 000000000000..cf98205a5576 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); + +window.Replay._replay.timeouts = { + sessionIdle: 300000, // default: 5min + maxSessionLife: 4000, // this is usually 60min, but we want to test this with shorter times +}; diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/template.html b/packages/integration-tests/suites/replay/sessionMaxAge/template.html new file mode 100644 index 000000000000..7223a20f82ba --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/template.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts new file mode 100644 index 000000000000..89ad76ea4d4a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts @@ -0,0 +1,89 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { + getFullRecordingSnapshots, + getReplayEvent, + getReplaySnapshot, + normalize, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +// Session should be max. 4s long +const SESSION_MAX_AGE = 4000; + +/* + The main difference between this and sessionExpiry test, is that here we wait for the overall time (4s) + in multiple steps (2s, 2s) instead of waiting for the whole time at once (4s). +*/ +sentryTest('handles session that exceeds max age', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + + const replay0 = await getReplaySnapshot(page); + // We use the `initialTimestamp` of the replay to do any time based calculations + const startTimestamp = replay0._context.initialTimestamp; + + const req0 = await reqPromise0; + + const replayEvent0 = getReplayEvent(req0); + expect(replayEvent0).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots0 = getFullRecordingSnapshots(req0); + expect(fullSnapshots0.length).toEqual(1); + const stringifiedSnapshot = normalize(fullSnapshots0[0]); + expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json'); + + // Wait again for a new segment 0 (=new session) + const reqPromise2 = waitForReplayRequest(page, 0); + + // Wait for an incremental snapshot + // Wait half of the session max age (after initial flush), but account for potentially slow runners + const timePassed1 = Date.now() - startTimestamp; + await new Promise(resolve => setTimeout(resolve, Math.max(SESSION_MAX_AGE / 2 - timePassed1, 0))); + await page.click('#button1'); + + const req1 = await reqPromise1; + const replayEvent1 = getReplayEvent(req1); + + expect(replayEvent1).toEqual(getExpectedReplayEvent({ replay_start_timestamp: undefined, segment_id: 1, urls: [] })); + + const replay1 = await getReplaySnapshot(page); + const oldSessionId = replay1.session?.id; + + // Wait for session to expire + const timePassed2 = Date.now() - startTimestamp; + await new Promise(resolve => setTimeout(resolve, Math.max(SESSION_MAX_AGE - timePassed2, 0))); + await page.click('#button2'); + + const req2 = await reqPromise2; + const replay2 = await getReplaySnapshot(page); + + expect(replay2.session?.id).not.toEqual(oldSessionId); + + const replayEvent2 = getReplayEvent(req2); + expect(replayEvent2).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots2 = getFullRecordingSnapshots(req2); + expect(fullSnapshots2.length).toEqual(1); + const stringifiedSnapshot2 = normalize(fullSnapshots2[0]); + expect(stringifiedSnapshot2).toMatchSnapshot('snapshot-2.json'); +}); diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-chromium.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-firefox.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-webkit.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-0.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-chromium.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-chromium.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-chromium.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-firefox.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-firefox.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-firefox.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-webkit.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-webkit.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2-webkit.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2.json b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2.json new file mode 100644 index 000000000000..d510b410a343 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionMaxAge/test.ts-snapshots/snapshot-2.json @@ -0,0 +1,113 @@ +{ + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } + }, + "timestamp": [timestamp] +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-chromium.json b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-chromium.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-chromium.json +++ b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-chromium.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-firefox.json b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-firefox.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-firefox.json +++ b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-firefox.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-webkit.json b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-webkit.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-webkit.json +++ b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed-webkit.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed.json b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed.json +++ b/packages/integration-tests/suites/replay/unicode/compressed/test.ts-snapshots/unicode-compressed.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-chromium.json b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-chromium.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-chromium.json +++ b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-chromium.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-firefox.json b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-firefox.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-firefox.json +++ b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-firefox.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-webkit.json b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-webkit.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-webkit.json +++ b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed-webkit.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed.json b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed.json index 1ffd71d178d8..12e2147fa515 100644 --- a/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed.json +++ b/packages/integration-tests/suites/replay/unicode/uncompressed/test.ts-snapshots/unicode-uncompressed.json @@ -1,123 +1,127 @@ { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" + "type": 2, + "data": { + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "Hi 👋👋👋", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "p", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" + { + "type": 2, + "tagName": "h1", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "Hi 👋👋👋", + "id": 10 + } + ], + "id": 9 }, - "childNodes": [ - { - "type": 3, - "textContent": "✅ Acknowledge", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 18 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "p", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n Adding some unicode characters to the page to make sure they are properly handled. At sentry, we like 🚢🚢🚢. We\n also like 🍕, 🍺 and 🐕. (Some people might actually prefer 🐈‍⬛ but the test author is a dog person, so 🤷‍♂️).\n ", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n\n ", + "id": 14 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "id": "go-background" + }, + "childNodes": [ + { + "type": 3, + "textContent": "✅ Acknowledge", + "id": 16 + } + ], + "id": 15 + }, + { + "type": 3, + "textContent": "\n ", + "id": 17 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 18 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } }, - "initialOffset": { - "left": 0, - "top": 0 - } + "timestamp": [timestamp] } \ No newline at end of file diff --git a/packages/integration-tests/suites/tracing/browsertracing/interactions/assets/script.js b/packages/integration-tests/suites/tracing/browsertracing/interactions/assets/script.js index 5a2aef02028d..89d814bd397d 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/interactions/assets/script.js +++ b/packages/integration-tests/suites/tracing/browsertracing/interactions/assets/script.js @@ -1,4 +1,4 @@ -(() => { +const delay = e => { const startTime = Date.now(); function getElasped() { @@ -6,7 +6,11 @@ return time - startTime; } - while (getElasped() < 105) { + while (getElasped() < 70) { // } -})(); + + e.target.classList.add('clicked'); +}; + +document.querySelector('[data-test-id=interaction-button]').addEventListener('click', delay); diff --git a/packages/integration-tests/suites/tracing/browsertracing/interactions/init.js b/packages/integration-tests/suites/tracing/browsertracing/interactions/init.js index 5229401c2ef5..d30222b7f47e 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/interactions/init.js +++ b/packages/integration-tests/suites/tracing/browsertracing/interactions/init.js @@ -10,6 +10,7 @@ Sentry.init({ idleTimeout: 1000, _experiments: { enableInteractions: true, + enableLongTask: false, }, }), ], diff --git a/packages/integration-tests/suites/tracing/browsertracing/interactions/template.html b/packages/integration-tests/suites/tracing/browsertracing/interactions/template.html index e74a9c17eeb2..e16deb9ee519 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/interactions/template.html +++ b/packages/integration-tests/suites/tracing/browsertracing/interactions/template.html @@ -5,7 +5,7 @@
Rendered Before Long Task
- + diff --git a/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts b/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts index b9a70ebda3ec..faff888fc2e8 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts @@ -1,12 +1,23 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { Event, Span, SpanContext, Transaction } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; +type TransactionJSON = ReturnType & { + spans: ReturnType[]; + contexts: SpanContext; + platform: string; + type: string; +}; + +const wait = (time: number) => new Promise(res => setTimeout(res, time)); + sentryTest('should capture interaction transaction.', async ({ browserName, getLocalTestPath, page }) => { - if (browserName !== 'chromium') { + const supportedBrowsers = ['chromium', 'firefox']; + + if (!supportedBrowsers.includes(browserName)) { sentryTest.skip(); } @@ -14,24 +25,49 @@ sentryTest('should capture interaction transaction.', async ({ browserName, getL const url = await getLocalTestPath({ testDir: __dirname }); - await getFirstSentryEnvelopeRequest(page, url); + await page.goto(url); + await getFirstSentryEnvelopeRequest(page); await page.locator('[data-test-id=interaction-button]').click(); + await page.locator('.clicked[data-test-id=interaction-button]').isVisible(); + + const envelopes = await getMultipleSentryEnvelopeRequests(page, 1); + expect(envelopes).toHaveLength(1); - const envelopes = await getMultipleSentryEnvelopeRequests(page, 1); const eventData = envelopes[0]; - expect(eventData).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'ui.action.click', - }), - }), - platform: 'javascript', - spans: [], - tags: {}, - type: 'transaction', - }), - ); + expect(eventData.contexts).toMatchObject({ trace: { op: 'ui.action.click' } }); + expect(eventData.platform).toBe('javascript'); + expect(eventData.type).toBe('transaction'); + expect(eventData.spans).toHaveLength(1); + + const interactionSpan = eventData.spans![0]; + expect(interactionSpan.op).toBe('ui.interaction.click'); + expect(interactionSpan.description).toBe('body > button.clicked'); + expect(interactionSpan.timestamp).toBeDefined(); + + const interactionSpanDuration = (interactionSpan.timestamp! - interactionSpan.start_timestamp) * 1000; + expect(interactionSpanDuration).toBeGreaterThan(70); + expect(interactionSpanDuration).toBeLessThan(200); +}); + +sentryTest('should create only one transaction per interaction', async ({ browserName, getLocalTestPath, page }) => { + const supportedBrowsers = ['chromium', 'firefox']; + + if (!supportedBrowsers.includes(browserName)) { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); + + const url = await getLocalTestPath({ testDir: __dirname }); + await page.goto(url); + await getFirstSentryEnvelopeRequest(page); + + for (let i = 0; i < 4; i++) { + await wait(100); + await page.locator('[data-test-id=interaction-button]').click(); + const envelope = await getMultipleSentryEnvelopeRequests(page, 1); + expect(envelope[0].spans).toHaveLength(1); + } }); diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts index 28b85d518e80..7511abf60d09 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts @@ -17,8 +17,6 @@ sentryTest('should capture a LCP vital with element details.', async ({ browserN const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); - // Force closure of LCP listener. - await page.click('body'); const eventData = await getFirstSentryEnvelopeRequest(page); expect(eventData.measurements).toBeDefined(); diff --git a/packages/integration-tests/utils/fixtures.ts b/packages/integration-tests/utils/fixtures.ts index 0d4c75353700..05ac906ad2d2 100644 --- a/packages/integration-tests/utils/fixtures.ts +++ b/packages/integration-tests/utils/fixtures.ts @@ -25,6 +25,7 @@ export type TestFixtures = { _autoSnapshotSuffix: void; testDir: string; getLocalTestPath: (options: { testDir: string }) => Promise; + forceFlushReplay: () => Promise; runInChromium: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown; runInFirefox: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown; runInWebkit: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown; @@ -92,6 +93,20 @@ const sentryTest = base.extend({ return fn(...args); }); }, + + forceFlushReplay: ({ page }, use) => { + return use(() => + page.evaluate(` + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + document.dispatchEvent(new Event('visibilitychange')); + `), + ); + }, }); export { sentryTest }; diff --git a/packages/integration-tests/utils/replayHelpers.ts b/packages/integration-tests/utils/replayHelpers.ts index 236431a75ee6..8722fe245a23 100644 --- a/packages/integration-tests/utils/replayHelpers.ts +++ b/packages/integration-tests/utils/replayHelpers.ts @@ -1,4 +1,12 @@ -import type { RecordingEvent, ReplayContainer } from '@sentry/replay/build/npm/types/types'; +import type { fullSnapshotEvent, incrementalSnapshotEvent } from '@sentry-internal/rrweb'; +import { EventType } from '@sentry-internal/rrweb'; +import type { + InternalEventContext, + RecordingEvent, + ReplayContainer, + Session, +} from '@sentry/replay/build/npm/types/types'; +import type { eventWithTime } from '@sentry/replay/build/npm/types/types/rrweb'; import type { Breadcrumb, Event, ReplayEvent } from '@sentry/types'; import pako from 'pako'; import type { Page, Request, Response } from 'playwright'; @@ -14,17 +22,18 @@ export type PerformanceSpan = { data: Record; }; -export type RecordingSnapshot = { - node: SnapshotNode; - initialOffset: number; +export type FullRecordingSnapshot = eventWithTime & { + timestamp: 0; + data: fullSnapshotEvent['data']; }; -type SnapshotNode = { - type: number; - id: number; - childNodes: SnapshotNode[]; +export type IncrementalRecordingSnapshot = eventWithTime & { + timestamp: 0; + data: incrementalSnapshotEvent['data']; }; +export type RecordingSnapshot = FullRecordingSnapshot | IncrementalRecordingSnapshot; + /** * Waits for a replay request to be sent by the page and returns it. * @@ -37,7 +46,13 @@ type SnapshotNode = { * @param segmentId the segment_id of the replay event * @returns */ -export function waitForReplayRequest(page: Page, segmentId?: number): Promise { +export function waitForReplayRequest( + page: Page, + segmentIdOrCallback?: number | ((event: ReplayEvent, res: Response) => boolean), +): Promise { + const segmentId = typeof segmentIdOrCallback === 'number' ? segmentIdOrCallback : undefined; + const callback = typeof segmentIdOrCallback === 'function' ? segmentIdOrCallback : undefined; + return page.waitForResponse(res => { const req = res.request(); @@ -53,6 +68,10 @@ export function waitForReplayRequest(page: Page, segmentId?: number): Promise { - const replayIntegration = await page.evaluate<{ _replay: ReplayContainer }>('window.Replay'); - return replayIntegration._replay; +export async function getReplaySnapshot( + page: Page, +): Promise<{ _isPaused: boolean; _isEnabled: boolean; _context: InternalEventContext; session: Session | undefined }> { + return await page.evaluate(() => { + const replayIntegration = (window as unknown as Window & { Replay: { _replay: ReplayContainer } }).Replay; + const replay = replayIntegration._replay; + + const replaySnapshot = { + _isPaused: replay.isPaused(), + _isEnabled: replay.isEnabled(), + _context: replay.getContext(), + session: replay.session, + }; + + return replaySnapshot; + }); } export const REPLAY_DEFAULT_FLUSH_MAX_DELAY = 5_000; @@ -116,10 +160,10 @@ export function getCustomRecordingEvents(resOrReq: Request | Response): CustomRe } function getAllCustomRrwebRecordingEvents(recordingEvents: RecordingEvent[]): CustomRecordingEvent[] { - return recordingEvents.filter(event => event.type === 5).map(event => event.data as CustomRecordingEvent); + return recordingEvents.filter(isCustomSnapshot).map(event => event.data); } -function getReplayBreadcrumbs(recordingEvents: RecordingEvent[], category?: string): Breadcrumb[] { +function getReplayBreadcrumbs(recordingEvents: RecordingSnapshot[], category?: string): Breadcrumb[] { return getAllCustomRrwebRecordingEvents(recordingEvents) .filter(data => data.tag === 'breadcrumb') .map(data => data.payload) @@ -132,21 +176,31 @@ function getReplayPerformanceSpans(recordingEvents: RecordingEvent[]): Performan .map(data => data.payload) as PerformanceSpan[]; } -export function getFullRecordingSnapshots(resOrReq: Request | Response): RecordingSnapshot[] { +export function getFullRecordingSnapshots(resOrReq: Request | Response): FullRecordingSnapshot[] { const replayRequest = getRequest(resOrReq); - const events = getDecompressedRecordingEvents(replayRequest) as RecordingEvent[]; - return events.filter(event => event.type === 2).map(event => event.data as RecordingSnapshot); + const events = getDecompressedRecordingEvents(replayRequest); + return events.filter(isFullSnapshot); } -function getIncrementalRecordingSnapshots(resOrReq: Request | Response): RecordingSnapshot[] { +export function getIncrementalRecordingSnapshots(resOrReq: Request | Response): IncrementalRecordingSnapshot[] { const replayRequest = getRequest(resOrReq); - const events = getDecompressedRecordingEvents(replayRequest) as RecordingEvent[]; - return events.filter(event => event.type === 3).map(event => event.data as RecordingSnapshot); + const events = getDecompressedRecordingEvents(replayRequest); + return events.filter(isIncrementalSnapshot); } -function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingEvent[] { +function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingSnapshot[] { const replayRequest = getRequest(resOrReq); - return replayEnvelopeRequestParser(replayRequest, 5) as RecordingEvent[]; + return ( + (replayEnvelopeRequestParser(replayRequest, 5) as eventWithTime[]) + .sort((a, b) => a.timestamp - b.timestamp) + // source 1 is MouseMove, which is a bit flaky and we don't care about + .filter( + event => typeof event.data === 'object' && event.data && (event.data as Record).source !== 1, + ) + .map(event => { + return { ...event, timestamp: 0 } as RecordingSnapshot; + }) + ); } export function getReplayRecordingContent(resOrReq: Request | Response): RecordingContent { @@ -231,7 +285,8 @@ export function normalize( const rawString = JSON.stringify(obj, null, 2); let normalizedString = rawString .replace(/"file:\/\/.+(\/.*\.html)"/gm, '"$1"') - .replace(/"timeOffset":\s*-?\d+/gm, '"timeOffset": [timeOffset]'); + .replace(/"timeOffset":\s*-?\d+/gm, '"timeOffset": [timeOffset]') + .replace(/"timestamp":\s*0/gm, '"timestamp": [timestamp]'); if (normalizeNumberAttributes?.length) { // We look for: "attr": "123px", "123", "123%", "123em", "123rem" diff --git a/packages/nextjs/playwright.config.ts b/packages/nextjs/playwright.config.ts index 945e7e508954..d8f73993c815 100644 --- a/packages/nextjs/playwright.config.ts +++ b/packages/nextjs/playwright.config.ts @@ -6,7 +6,11 @@ const config: PlaywrightTestConfig = { use: { baseURL: 'http://localhost:3000', }, - workers: 3, + // Run tests inside of a single file in parallel + fullyParallel: true, + // Use 3 workers on CI, else use defaults (based on available CPU cores) + // Note that 3 is a random number selected to work well with our CI setup + workers: process.env.CI ? 3 : undefined, webServer: { cwd: path.join(__dirname, 'test', 'integration'), command: 'yarn start', diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index d21f3fa92880..cfac0c460a84 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -1,4 +1,6 @@ export type ServerComponentContext = { componentRoute: string; componentType: string; + sentryTraceHeader?: string; + baggageHeader?: string; }; diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index 879855cafa08..fb3e76be72f0 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -119,7 +119,7 @@ export default function wrappingLoader( // https://github.com/vercel/next.js/blob/295f9da393f7d5a49b0c2e15a2f46448dbdc3895/packages/next/build/analysis/get-page-static-info.ts#L37 // https://github.com/vercel/next.js/blob/a1c15d84d906a8adf1667332a3f0732be615afa0/packages/next-swc/crates/core/src/react_server_components.rs#L247 // We do not want to wrap client components - if (userCode.includes('/* __next_internal_client_entry_do_not_use__ */')) { + if (userCode.includes('__next_internal_client_entry_do_not_use__')) { this.callback(null, userCode, userModuleSourceMap); return; } diff --git a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts index 74e8e1a5b1c3..1c937a5f355c 100644 --- a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts @@ -11,6 +11,11 @@ import * as wrapee from '__SENTRY_WRAPPING_TARGET_FILE__'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; +// @ts-ignore This template is only used with the app directory so we know that this dependency exists. +// eslint-disable-next-line import/no-unresolved +import { headers } from 'next/headers'; + +declare function headers(): { get: (header: string) => string | undefined }; type ServerComponentModule = { default: unknown; @@ -22,9 +27,29 @@ const serverComponent = serverComponentModule.default; let wrappedServerComponent; if (typeof serverComponent === 'function') { - wrappedServerComponent = Sentry.wrapServerComponentWithSentry(serverComponent, { - componentRoute: '__ROUTE__', - componentType: '__COMPONENT_TYPE__', + // For some odd Next.js magic reason, `headers()` will not work if used inside `wrapServerComponentsWithSentry`. + // Current assumption is that Next.js applies some loader magic to userfiles, but not files in node_modules. This file + // is technically a userfile so it gets the loader magic applied. + wrappedServerComponent = new Proxy(serverComponent, { + apply: (originalFunction, thisArg, args) => { + let sentryTraceHeader: string | undefined = undefined; + let baggageHeader: string | undefined = undefined; + + // If we call the headers function inside the build phase, Next.js will automatically mark the server component as + // dynamic(SSR) which we do not want in case the users have a static component. + if (process.env.NEXT_PHASE !== 'phase-production-build') { + const headersList = headers(); + sentryTraceHeader = headersList.get('sentry-trace'); + baggageHeader = headersList.get('baggage'); + } + + return Sentry.wrapServerComponentWithSentry(originalFunction, { + componentRoute: '__ROUTE__', + componentType: '__COMPONENT_TYPE__', + sentryTraceHeader, + baggageHeader, + }).apply(thisArg, args); + }, }); } else { wrappedServerComponent = serverComponent; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 47c24e44751f..b0cd45e43084 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -36,6 +36,8 @@ export declare const ErrorBoundary: typeof clientSdk.ErrorBoundary; export declare const showReportDialog: typeof clientSdk.showReportDialog; export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary; +export declare const Span: typeof edgeSdk.Span; + /** * @deprecated Use `wrapApiHandlerWithSentry` instead */ diff --git a/packages/nextjs/src/server/wrapServerComponentWithSentry.ts b/packages/nextjs/src/server/wrapServerComponentWithSentry.ts index 05c17c6bede3..eac6a9405ac6 100644 --- a/packages/nextjs/src/server/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/server/wrapServerComponentWithSentry.ts @@ -1,4 +1,5 @@ import { captureException, getCurrentHub, startTransaction } from '@sentry/core'; +import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils'; import * as domain from 'domain'; import type { ServerComponentContext } from '../common/types'; @@ -20,12 +21,20 @@ export function wrapServerComponentWithSentry any> return domain.create().bind(() => { let maybePromiseResult; + const traceparentData = context.sentryTraceHeader + ? extractTraceparentData(context.sentryTraceHeader) + : undefined; + + const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(context.baggageHeader); + const transaction = startTransaction({ op: 'function.nextjs', name: `${componentType} Server Component (${componentRoute})`, status: 'ok', + ...traceparentData, metadata: { source: 'component', + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, }, }); diff --git a/packages/node/src/declarations.d.ts b/packages/node/src/declarations.d.ts index 7d9d77b82bfb..843b79454518 100644 --- a/packages/node/src/declarations.d.ts +++ b/packages/node/src/declarations.d.ts @@ -1,2 +1 @@ -declare module 'https-proxy-agent'; declare module 'async-limiter'; diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index aa41ae68cdd8..bad96ae43fb2 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -3,7 +3,7 @@ import { addContextToFrame } from '@sentry/utils'; import { readFile } from 'fs'; import { LRUMap } from 'lru_map'; -const FILE_CONTENT_CACHE = new LRUMap(100); +const FILE_CONTENT_CACHE = new LRUMap(100); const DEFAULT_LINES_OF_CONTEXT = 7; // TODO: Replace with promisify when minimum supported node >= v8 @@ -62,9 +62,47 @@ export class ContextLines implements Integration { /** Processes an event and adds context lines */ public async addSourceContext(event: Event): Promise { + // keep a lookup map of which files we've already enqueued to read, + // so we don't enqueue the same file multiple times which would cause multiple i/o reads + const enqueuedReadSourceFileTasks: Record = {}; + const readSourceFileTasks: Promise[] = []; + if (this._contextLines > 0 && event.exception?.values) { for (const exception of event.exception.values) { - if (exception.stacktrace?.frames) { + if (!exception.stacktrace?.frames) { + continue; + } + + // We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache. + // This ends up prioritizes source context for frames at the top of the stack instead of the bottom. + for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) { + const frame = exception.stacktrace.frames[i]; + // Call cache.get to bump the file to the top of the cache and ensure we have not already + // enqueued a read operation for this filename + if ( + frame.filename && + !enqueuedReadSourceFileTasks[frame.filename] && + !FILE_CONTENT_CACHE.get(frame.filename) + ) { + readSourceFileTasks.push(_readSourceFile(frame.filename)); + enqueuedReadSourceFileTasks[frame.filename] = 1; + } + } + } + } + + // check if files to read > 0, if so, await all of them to be read before adding source contexts. + // Normally, Promise.all here could be short circuited if one of the promises rejects, but we + // are guarding from that by wrapping the i/o read operation in a try/catch. + if (readSourceFileTasks.length > 0) { + await Promise.all(readSourceFileTasks); + } + + // Perform the same loop as above, but this time we can assume all files are in the cache + // and attempt to add source context to frames. + if (this._contextLines > 0 && event.exception?.values) { + for (const exception of event.exception.values) { + if (exception.stacktrace && exception.stacktrace.frames) { await this.addSourceContextToFrames(exception.stacktrace.frames); } } @@ -74,18 +112,15 @@ export class ContextLines implements Integration { } /** Adds context lines to frames */ - public async addSourceContextToFrames(frames: StackFrame[]): Promise { - const contextLines = this._contextLines; - + public addSourceContextToFrames(frames: StackFrame[]): void { for (const frame of frames) { // Only add context if we have a filename and it hasn't already been added if (frame.filename && frame.context_line === undefined) { - const sourceFile = await _readSourceFile(frame.filename); + const sourceFileLines = FILE_CONTENT_CACHE.get(frame.filename); - if (sourceFile) { + if (sourceFileLines) { try { - const lines = sourceFile.split('\n'); - addContextToFrame(lines, frame, contextLines); + addContextToFrame(sourceFileLines, frame, this._contextLines); } catch (e) { // anomaly, being defensive in case // unlikely to ever happen in practice but can definitely happen in theory @@ -98,21 +133,34 @@ export class ContextLines implements Integration { /** * Reads file contents and caches them in a global LRU cache. + * If reading fails, mark the file as null in the cache so we don't try again. * * @param filename filepath to read content from. */ -async function _readSourceFile(filename: string): Promise { +async function _readSourceFile(filename: string): Promise { const cachedFile = FILE_CONTENT_CACHE.get(filename); - // We have a cache hit + + // We have already attempted to read this file and failed, do not try again + if (cachedFile === null) { + return null; + } + + // We have a cache hit, return it if (cachedFile !== undefined) { return cachedFile; } - let content: string | null = null; + // Guard from throwing if readFile fails, this enables us to use Promise.all and + // not have it short circuiting if one of the promises rejects + since context lines are added + // on a best effort basis, we want to throw here anyways. + + // If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it + let content: string[] | null = null; try { - content = await readTextFileAsync(filename); + const rawFileContents = await readTextFileAsync(filename); + content = rawFileContents.split('\n'); } catch (_) { - // + // if we fail, we will mark the file as null in the cache and short circuit next time we try to read it } FILE_CONTENT_CACHE.set(filename, content); diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index ea7a9eae173d..017929fbe0eb 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -8,8 +8,8 @@ import { parseSemver, stringMatchesSomePattern, } from '@sentry/utils'; -import type * as http from 'http'; -import type * as https from 'https'; +import * as http from 'http'; +import * as https from 'https'; import { LRUMap } from 'lru_map'; import type { NodeClient } from '../client'; @@ -101,25 +101,17 @@ export class Http implements Integration { // and we will no longer have to do this optional merge, we can just pass `this._tracing` directly. const tracingOptions = this._tracing ? { ...clientOptions, ...this._tracing } : undefined; - // eslint-disable-next-line @typescript-eslint/no-var-requires - const httpModule = require('http'); - const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, httpModule); - fill(httpModule, 'get', wrappedHttpHandlerMaker); - fill(httpModule, 'request', wrappedHttpHandlerMaker); + const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, http); + fill(http, 'get', wrappedHttpHandlerMaker); + fill(http, 'request', wrappedHttpHandlerMaker); // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it. // If we do, we'd get double breadcrumbs and double spans for `https` calls. // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately. if (NODE_VERSION.major && NODE_VERSION.major > 8) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const httpsModule = require('https'); - const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory( - this._breadcrumbs, - tracingOptions, - httpsModule, - ); - fill(httpsModule, 'get', wrappedHttpsHandlerMaker); - fill(httpsModule, 'request', wrappedHttpsHandlerMaker); + const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, https); + fill(https, 'get', wrappedHttpsHandlerMaker); + fill(https, 'request', wrappedHttpsHandlerMaker); } } } diff --git a/packages/node/src/integrations/localvariables.ts b/packages/node/src/integrations/localvariables.ts index 2423cfa30c70..648a9e88d3be 100644 --- a/packages/node/src/integrations/localvariables.ts +++ b/packages/node/src/integrations/localvariables.ts @@ -28,6 +28,19 @@ class AsyncSession implements DebugSession { /** Throws is inspector API is not available */ public constructor() { + /* + TODO: We really should get rid of this require statement below for a couple of reasons: + 1. It makes the integration unusable in the SvelteKit SDK, as it's not possible to use `require` + in SvelteKit server code (at least not by default). + 2. Throwing in a constructor is bad practice + + More context for a future attempt to fix this: + We already tried replacing it with import but didn't get it to work because of async problems. + We still called import in the constructor but assigned to a promise which we "awaited" in + `configureAndConnect`. However, this broke the Node integration tests as no local variables + were reported any more. We probably missed a place where we need to await the promise, too. + */ + // Node can be build without inspector support so this can throw // eslint-disable-next-line @typescript-eslint/no-var-requires const { Session } = require('inspector'); diff --git a/packages/node/src/integrations/onunhandledrejection.ts b/packages/node/src/integrations/onunhandledrejection.ts index aa3916931ba7..1504b6dee6e0 100644 --- a/packages/node/src/integrations/onunhandledrejection.ts +++ b/packages/node/src/integrations/onunhandledrejection.ts @@ -44,8 +44,7 @@ export class OnUnhandledRejection implements Integration { * @param reason string * @param promise promise */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - public sendUnhandledPromise(reason: any, promise: any): void { + public sendUnhandledPromise(reason: unknown, promise: unknown): void { const hub = getCurrentHub(); if (hub.getIntegration(OnUnhandledRejection)) { hub.withScope((scope: Scope) => { diff --git a/packages/node/src/transports/http.ts b/packages/node/src/transports/http.ts index a16f78e79a6f..340bcf4800f0 100644 --- a/packages/node/src/transports/http.ts +++ b/packages/node/src/transports/http.ts @@ -8,6 +8,7 @@ import type { } from '@sentry/types'; import * as http from 'http'; import * as https from 'https'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import { Readable } from 'stream'; import { URL } from 'url'; import { createGzip } from 'zlib'; @@ -74,8 +75,7 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport { // TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node // versions(>= 8) as they had memory leaks when using it: #2555 const agent = proxy - ? // eslint-disable-next-line @typescript-eslint/no-var-requires - (new (require('https-proxy-agent'))(proxy) as http.Agent) + ? (new HttpsProxyAgent(proxy) as http.Agent) : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 }); const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent); diff --git a/packages/node/test/context-lines.test.ts b/packages/node/test/context-lines.test.ts index de6f6f382cd8..b469796214b1 100644 --- a/packages/node/test/context-lines.test.ts +++ b/packages/node/test/context-lines.test.ts @@ -107,4 +107,31 @@ describe('ContextLines', () => { expect(readFileSpy).toHaveBeenCalledTimes(0); }); }); + test.only('does not attempt to readfile multiple times if it fails', async () => { + expect.assertions(1); + contextLines = new ContextLines({}); + + readFileSpy.mockImplementation(() => { + throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'"); + }); + + await addContext([ + { + colno: 1, + filename: '/does/not/exist.js', + lineno: 1, + function: 'fxn1', + }, + ]); + await addContext([ + { + colno: 1, + filename: '/does/not/exist.js', + lineno: 1, + function: 'fxn1', + }, + ]); + + expect(readFileSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/node/test/transports/http.test.ts b/packages/node/test/transports/http.test.ts index d03e917c4f42..58b2710f1ac5 100644 --- a/packages/node/test/transports/http.test.ts +++ b/packages/node/test/transports/http.test.ts @@ -17,11 +17,7 @@ jest.mock('@sentry/core', () => { }; }); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const httpProxyAgent = require('https-proxy-agent'); -jest.mock('https-proxy-agent', () => { - return jest.fn().mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); -}); +import * as httpProxyAgent from 'https-proxy-agent'; const SUCCESS = 200; const RATE_LIMIT = 429; @@ -211,6 +207,11 @@ describe('makeNewHttpTransport()', () => { }); describe('proxy', () => { + const proxyAgentSpy = jest + .spyOn(httpProxyAgent, 'HttpsProxyAgent') + // @ts-ignore + .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); + it('can be configured through option', () => { makeNodeTransport({ ...defaultOptions, @@ -218,8 +219,8 @@ describe('makeNewHttpTransport()', () => { proxy: 'http://example.com', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('http://example.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://example.com'); }); it('can be configured through env variables option', () => { @@ -229,8 +230,8 @@ describe('makeNewHttpTransport()', () => { url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('http://example.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://example.com'); delete process.env.http_proxy; }); @@ -242,8 +243,8 @@ describe('makeNewHttpTransport()', () => { proxy: 'http://bar.com', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('http://bar.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://bar.com'); delete process.env.http_proxy; }); @@ -255,7 +256,7 @@ describe('makeNewHttpTransport()', () => { proxy: 'http://example.com', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; }); @@ -269,7 +270,7 @@ describe('makeNewHttpTransport()', () => { url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; delete process.env.http_proxy; @@ -284,7 +285,7 @@ describe('makeNewHttpTransport()', () => { url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; delete process.env.http_proxy; diff --git a/packages/node/test/transports/https.test.ts b/packages/node/test/transports/https.test.ts index d43b8306bed0..e63898a3b11e 100644 --- a/packages/node/test/transports/https.test.ts +++ b/packages/node/test/transports/https.test.ts @@ -19,11 +19,7 @@ jest.mock('@sentry/core', () => { }; }); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const httpProxyAgent = require('https-proxy-agent'); -jest.mock('https-proxy-agent', () => { - return jest.fn().mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); -}); +import * as httpProxyAgent from 'https-proxy-agent'; const SUCCESS = 200; const RATE_LIMIT = 429; @@ -185,6 +181,11 @@ describe('makeNewHttpsTransport()', () => { }); describe('proxy', () => { + const proxyAgentSpy = jest + .spyOn(httpProxyAgent, 'HttpsProxyAgent') + // @ts-ignore + .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); + it('can be configured through option', () => { makeNodeTransport({ ...defaultOptions, @@ -193,8 +194,8 @@ describe('makeNewHttpsTransport()', () => { proxy: 'https://example.com', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('https://example.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); }); it('can be configured through env variables option (http)', () => { @@ -205,8 +206,8 @@ describe('makeNewHttpsTransport()', () => { url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('https://example.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); delete process.env.http_proxy; }); @@ -218,8 +219,8 @@ describe('makeNewHttpsTransport()', () => { url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('https://example.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); delete process.env.https_proxy; }); @@ -232,8 +233,8 @@ describe('makeNewHttpsTransport()', () => { proxy: 'https://bar.com', }); - expect(httpProxyAgent).toHaveBeenCalledTimes(1); - expect(httpProxyAgent).toHaveBeenCalledWith('https://bar.com'); + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://bar.com'); delete process.env.https_proxy; }); @@ -246,7 +247,7 @@ describe('makeNewHttpsTransport()', () => { proxy: 'https://example.com', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; }); @@ -261,7 +262,7 @@ describe('makeNewHttpsTransport()', () => { url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; delete process.env.http_proxy; @@ -277,7 +278,7 @@ describe('makeNewHttpsTransport()', () => { url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', }); - expect(httpProxyAgent).not.toHaveBeenCalled(); + expect(proxyAgentSpy).not.toHaveBeenCalled(); delete process.env.no_proxy; delete process.env.http_proxy; diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index 5ef64a3d2522..5bac99dbe511 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { configureScope } from '@sentry/browser'; +import { configureScope, getCurrentHub } from '@sentry/browser'; import type { Scope } from '@sentry/types'; +import { addNonEnumerableProperty } from '@sentry/utils'; interface Action { type: T; @@ -105,7 +106,20 @@ function createReduxEnhancer(enhancerOptions?: Partial): /* Set latest state to scope */ const transformedState = options.stateTransformer(newState); if (typeof transformedState !== 'undefined' && transformedState !== null) { - scope.setContext('state', { state: { type: 'redux', value: transformedState } }); + const client = getCurrentHub().getClient(); + const options = client && client.getOptions(); + const normalizationDepth = (options && options.normalizeDepth) || 3; // default state normalization depth to 3 + + // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback + const newStateContext = { state: { type: 'redux', value: transformedState } }; + addNonEnumerableProperty( + newStateContext, + '__sentry_override_normalization_depth__', + 3 + // 3 layers for `state.value.transformedState` + normalizationDepth, // rest for the actual state + ); + + scope.setContext('state', newStateContext); } else { scope.setContext('state', null); } diff --git a/packages/remix/playwright.config.ts b/packages/remix/playwright.config.ts index 0e609bb2efa5..63de33ff74ed 100644 --- a/packages/remix/playwright.config.ts +++ b/packages/remix/playwright.config.ts @@ -5,7 +5,11 @@ const config: PlaywrightTestConfig = { use: { baseURL: 'http://localhost:3000', }, - workers: 3, + // Run tests inside of a single file in parallel + fullyParallel: true, + // Use 3 workers on CI, else use defaults (based on available CPU cores) + // Note that 3 is a random number selected to work well with our CI setup + workers: process.env.CI ? 3 : undefined, webServer: { command: '(cd test/integration/ && yarn build && yarn start)', port: 3000, diff --git a/packages/replay/MIGRATION.md b/packages/replay/MIGRATION.md index c196a735058a..ba6326939970 100644 --- a/packages/replay/MIGRATION.md +++ b/packages/replay/MIGRATION.md @@ -1,3 +1,47 @@ +# End of Replay Beta + +Sentry Replay is now out of Beta. This means that the usual stability guarantees apply. + +Because of experimentation and rapid iteration, during the Beta period some bugs and problems came up which have since been fixed/improved. +We **strongly** recommend anyone using Replay in a version before 7.39.0 to update to 7.39.0 or newer, in order to prevent running Replay with known problems that have since been fixed. + +Below you can find a list of relevant replay issues that have been resolved until 7.39.0: + +## New features / improvements + +- Remove `autoplay` attribute from audio/video tags ([#59](https://github.com/getsentry/rrweb/pull/59)) +- Exclude fetching scripts that use `` ([#52](https://github.com/getsentry/rrweb/pull/52)) +- With maskAllText, mask the attributes: placeholder, title, `aria-label` +- Lower the flush max delay from 15 seconds to 5 seconds (#6761) +- Stop recording when retry fails (#6765) +- Stop without retry when receiving bad API response (#6773) +- Send client_report when replay sending fails (#7093) +- Stop recording when hitting a rate limit (#7018) +- Allow Replay to be used in Electron renderers with nodeIntegration enabled (#6644) +- Do not renew session in error mode (#6948) +- Remove default sample rates for replay (#6878) +- Add `flush` method to integration (#6776) +- Improve compression worker & fallback behavior (#6988, #6936, #6827) +- Improve error handling (#7087, #7094, #7010, getsentry/rrweb#16, #6856) +- Add more default block filters (#7233) + +## Fixes + +- Fix masking inputs on change when `maskAllInputs:false` ([#61](https://github.com/getsentry/rrweb/pull/61)) +- More robust `rootShadowHost` check ([#50](https://github.com/getsentry/rrweb/pull/50)) +- Fix duplicated textarea value ([#62](https://github.com/getsentry/rrweb/pull/62)) +- Handle removed attributes ([#65](https://github.com/getsentry/rrweb/pull/65)) +- Change LCP calculation (#7187, #7225) +- Fix debounced flushes not respecting `maxWait` (#7207, #7208) +- Fix svgs not getting unblocked (#7132) +- Fix missing fetch/xhr requests (#7134) +- Fix feature detection of PerformanceObserver (#7029) +- Fix `checkoutEveryNms` (#6722) +- Fix incorrect uncompressed recording size due to encoding (#6740) +- Ensure dropping replays works (#6522) +- Envelope send should be awaited in try/catch (#6625) +- Improve handling of `maskAllText` selector (#6637) + # Upgrading Replay from 7.34.0 to 7.35.0 - #6645 This release will remove the ability to change the default rrweb recording options (outside of privacy options). The following are the new configuration values all replays will use: diff --git a/packages/replay/README.md b/packages/replay/README.md index 8bb09c498fcd..6dbab272780a 100644 --- a/packages/replay/README.md +++ b/packages/replay/README.md @@ -10,8 +10,6 @@ [![npm dm](https://img.shields.io/npm/dm/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) [![npm dt](https://img.shields.io/npm/dt/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) -**Note: Session Replay is currently in beta.** Functionality may change outside of major version bumps - while we try our best to avoid any breaking changes, semver cannot be guaranteed before Replay is out of beta. You can find more information about upgrading in [MIGRATION.md](./MIGRATION.md). - ## Pre-requisites `@sentry/replay` requires Node 12+, and browsers newer than IE11. @@ -112,11 +110,11 @@ The `replay.(min.)js` bundle will be removed in v8 of the JS SDKs. ```html ``` diff --git a/packages/replay/package.json b/packages/replay/package.json index dc607ff7b0f1..d98abcce9f61 100644 --- a/packages/replay/package.json +++ b/packages/replay/package.json @@ -25,7 +25,7 @@ "fix:prettier": "prettier --write \"{src,test,scripts}/**/*.ts\"", "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --format stylish", - "lint:prettier": "prettier --check \"{src,test,scripts,worker}/**/*.ts\"", + "lint:prettier": "prettier --check \"{src,test,scripts}/**/*.ts\"", "test": "jest", "test:watch": "jest --watch", "bootstrap:demo": "cd demo && yarn", @@ -44,7 +44,7 @@ "devDependencies": { "@babel/core": "^7.17.5", "@sentry-internal/replay-worker": "7.41.0", - "@sentry-internal/rrweb": "1.104.1", + "@sentry-internal/rrweb": "1.105.0", "jsdom-worker": "^0.2.1", "tslib": "^1.9.3" }, diff --git a/packages/replay/src/coreHandlers/addBreadcrumbEvent.ts b/packages/replay/src/coreHandlers/addBreadcrumbEvent.ts index 8022e8a549d2..70fe932910a6 100644 --- a/packages/replay/src/coreHandlers/addBreadcrumbEvent.ts +++ b/packages/replay/src/coreHandlers/addBreadcrumbEvent.ts @@ -12,7 +12,7 @@ export function addBreadcrumbEvent(replay: ReplayContainer, breadcrumb: Breadcru return; } - if (breadcrumb.category === 'ui.click') { + if (['ui.click', 'ui.input'].includes(breadcrumb.category as string)) { replay.triggerUserActivity(); } else { replay.checkAndHandleExpiredSession(); diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 505d53410d1f..939dd0b8375c 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -16,11 +16,11 @@ import type { EventBuffer, InternalEventContext, PopEventContext, - RecordingEvent, RecordingOptions, ReplayContainer as ReplayContainerInterface, ReplayPluginOptions, Session, + Timeouts, } from './types'; import { addEvent } from './util/addEvent'; import { addGlobalListeners } from './util/addGlobalListeners'; @@ -29,6 +29,7 @@ import { createBreadcrumb } from './util/createBreadcrumb'; import { createPerformanceEntries } from './util/createPerformanceEntries'; import { createPerformanceSpans } from './util/createPerformanceSpans'; import { debounce } from './util/debounce'; +import { getHandleRecordingEmit } from './util/handleRecordingEmit'; import { isExpired } from './util/isExpired'; import { isSessionExpired } from './util/isSessionExpired'; import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent'; @@ -54,6 +55,15 @@ export class ReplayContainer implements ReplayContainerInterface { */ public recordingMode: ReplayRecordingMode = 'session'; + /** + * These are here so we can overwrite them in tests etc. + * @hidden + */ + public readonly timeouts: Timeouts = { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + } as const; + /** * Options to pass to `rrweb.record()` */ @@ -145,7 +155,7 @@ export class ReplayContainer implements ReplayContainerInterface { * _performanceObserver, Recording, Sentry SDK, etc) */ public start(): void { - this._setInitialState(); + this.setInitialState(); if (!this._loadAndCheckSession()) { return; @@ -197,7 +207,24 @@ export class ReplayContainer implements ReplayContainerInterface { // Without this, it would record forever, until an error happens, which we don't want // instead, we'll always keep the last 60 seconds of replay before an error happened ...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }), - emit: this._handleRecordingEmit, + emit: getHandleRecordingEmit(this), + onMutation: (mutations: unknown[]) => { + if (this._options._experiments.captureMutationSize) { + const count = mutations.length; + + if (count > 500) { + const breadcrumb = createBreadcrumb({ + category: 'replay.mutations', + data: { + count, + }, + }); + this._createCustomBreadcrumb(breadcrumb); + } + } + // `true` means we use the regular mutation handling by rrweb + return true; + }, }); } catch (err) { this._handleException(err); @@ -368,7 +395,7 @@ export class ReplayContainer implements ReplayContainerInterface { // MAX_SESSION_LIFE. Otherwise non-user activity can trigger a new // session+recording. This creates noisy replays that do not have much // content in them. - if (this._lastActivity && isExpired(this._lastActivity, MAX_SESSION_LIFE)) { + if (this._lastActivity && isExpired(this._lastActivity, this.timeouts.maxSessionLife)) { // Pause recording this.pause(); return; @@ -393,6 +420,25 @@ export class ReplayContainer implements ReplayContainerInterface { return false; } + /** + * Capture some initial state that can change throughout the lifespan of the + * replay. This is required because otherwise they would be captured at the + * first flush. + */ + public setInitialState(): void { + const urlPath = `${WINDOW.location.pathname}${WINDOW.location.hash}${WINDOW.location.search}`; + const url = `${WINDOW.location.origin}${urlPath}`; + + this.performanceEvents = []; + + // Reset _context as well + this._clearContext(); + + this._context.initialUrl = url; + this._context.initialTimestamp = new Date().getTime(); + this._context.urls.push(url); + } + /** A wrapper to conditionally capture exceptions. */ private _handleException(error: unknown): void { __DEBUG_BUILD__ && logger.error('[Replay]', error); @@ -408,7 +454,7 @@ export class ReplayContainer implements ReplayContainerInterface { */ private _loadAndCheckSession(): boolean { const { type, session } = getSession({ - expiry: SESSION_IDLE_DURATION, + timeouts: this.timeouts, stickySession: Boolean(this._options.stickySession), currentSession: this.session, sessionSampleRate: this._options.sessionSampleRate, @@ -418,7 +464,7 @@ export class ReplayContainer implements ReplayContainerInterface { // If session was newly created (i.e. was not loaded from storage), then // enable flag to create the root replay if (type === 'new') { - this._setInitialState(); + this.setInitialState(); } const currentSessionId = this.getSessionId(); @@ -436,25 +482,6 @@ export class ReplayContainer implements ReplayContainerInterface { return true; } - /** - * Capture some initial state that can change throughout the lifespan of the - * replay. This is required because otherwise they would be captured at the - * first flush. - */ - private _setInitialState(): void { - const urlPath = `${WINDOW.location.pathname}${WINDOW.location.hash}${WINDOW.location.search}`; - const url = `${WINDOW.location.origin}${urlPath}`; - - this.performanceEvents = []; - - // Reset _context as well - this._clearContext(); - - this._context.initialUrl = url; - this._context.initialTimestamp = new Date().getTime(); - this._context.urls.push(url); - } - /** * Adds listeners to record events for the replay */ @@ -506,72 +533,6 @@ export class ReplayContainer implements ReplayContainerInterface { } } - /** - * Handler for recording events. - * - * Adds to event buffer, and has varying flushing behaviors if the event was a checkout. - */ - private _handleRecordingEmit: (event: RecordingEvent, isCheckout?: boolean) => void = ( - event: RecordingEvent, - isCheckout?: boolean, - ) => { - // If this is false, it means session is expired, create and a new session and wait for checkout - if (!this.checkAndHandleExpiredSession()) { - __DEBUG_BUILD__ && logger.error('[Replay] Received replay event after session expired.'); - - return; - } - - this.addUpdate(() => { - // The session is always started immediately on pageload/init, but for - // error-only replays, it should reflect the most recent checkout - // when an error occurs. Clear any state that happens before this current - // checkout. This needs to happen before `addEvent()` which updates state - // dependent on this reset. - if (this.recordingMode === 'error' && event.type === 2) { - this._setInitialState(); - } - - // We need to clear existing events on a checkout, otherwise they are - // incremental event updates and should be appended - void addEvent(this, event, isCheckout); - - // Different behavior for full snapshots (type=2), ignore other event types - // See https://github.com/rrweb-io/rrweb/blob/d8f9290ca496712aa1e7d472549480c4e7876594/packages/rrweb/src/types.ts#L16 - if (event.type !== 2) { - return false; - } - - // If there is a previousSessionId after a full snapshot occurs, then - // the replay session was started due to session expiration. The new session - // is started before triggering a new checkout and contains the id - // of the previous session. Do not immediately flush in this case - // to avoid capturing only the checkout and instead the replay will - // be captured if they perform any follow-up actions. - if (this.session && this.session.previousSessionId) { - return true; - } - - // See note above re: session start needs to reflect the most recent - // checkout. - if (this.recordingMode === 'error' && this.session && this._context.earliestEvent) { - this.session.started = this._context.earliestEvent; - this._maybeSaveSession(); - } - - // Flush immediately so that we do not miss the first segment, otherwise - // it can prevent loading on the UI. This will cause an increase in short - // replays (e.g. opening and closing a tab quickly), but these can be - // filtered on the UI. - if (this.recordingMode === 'session') { - // We want to ensure the worker is ready, as otherwise we'd always send the first event uncompressed - void this.flushImmediate(); - } - - return true; - }); - }; - /** * Handle when visibility of the page content changes. Opening a new tab will * cause the state to change to hidden because of content of current page will @@ -620,7 +581,7 @@ export class ReplayContainer implements ReplayContainerInterface { return; } - const expired = isSessionExpired(this.session, SESSION_IDLE_DURATION); + const expired = isSessionExpired(this.session, this.timeouts); if (breadcrumb && !expired) { this._createCustomBreadcrumb(breadcrumb); diff --git a/packages/replay/src/session/getSession.ts b/packages/replay/src/session/getSession.ts index 54b16d9a5414..150fbe12c871 100644 --- a/packages/replay/src/session/getSession.ts +++ b/packages/replay/src/session/getSession.ts @@ -1,16 +1,13 @@ import { logger } from '@sentry/utils'; -import type { Session, SessionOptions } from '../types'; +import type { Session, SessionOptions, Timeouts } from '../types'; import { isSessionExpired } from '../util/isSessionExpired'; import { createSession } from './createSession'; import { fetchSession } from './fetchSession'; import { makeSession } from './Session'; interface GetSessionParams extends SessionOptions { - /** - * The length of time (in ms) which we will consider the session to be expired. - */ - expiry: number; + timeouts: Timeouts; /** * The current session (e.g. if stickySession is off) @@ -22,7 +19,7 @@ interface GetSessionParams extends SessionOptions { * Get or create a session */ export function getSession({ - expiry, + timeouts, currentSession, stickySession, sessionSampleRate, @@ -35,7 +32,7 @@ export function getSession({ // If there is a session, check if it is valid (e.g. "last activity" time // should be within the "session idle time", and "session started" time is // within "max session time"). - const isExpired = isSessionExpired(session, expiry); + const isExpired = isSessionExpired(session, timeouts); if (!isExpired) { return { type: 'saved', session }; diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index 174f1da240f8..1ea691f3ec4e 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -18,6 +18,11 @@ export interface SendReplayData { options: ReplayPluginOptions; } +export interface Timeouts { + sessionIdle: number; + maxSessionLife: number; +} + /** * The request payload to worker */ @@ -105,6 +110,7 @@ export interface ReplayPluginOptions extends SessionOptions { _experiments: Partial<{ captureExceptions: boolean; traceInternals: boolean; + captureMutationSize: boolean; }>; } @@ -271,6 +277,7 @@ export interface EventBuffer { /** * Add an event to the event buffer. + * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`. * * Returns a promise that resolves if the event was successfully added, else rejects. */ @@ -289,6 +296,10 @@ export interface ReplayContainer { performanceEvents: AllPerformanceEntry[]; session: Session | undefined; recordingMode: ReplayRecordingMode; + timeouts: { + sessionIdle: number; + maxSessionLife: number; + }; isEnabled(): boolean; isPaused(): boolean; getContext(): InternalEventContext; @@ -304,6 +315,7 @@ export interface ReplayContainer { getOptions(): ReplayPluginOptions; getSessionId(): string | undefined; checkAndHandleExpiredSession(): boolean | void; + setInitialState(): void; } export interface ReplayPerformanceEntry { diff --git a/packages/replay/src/util/addEvent.ts b/packages/replay/src/util/addEvent.ts index 5cf351fd6f9c..357ecaca0e61 100644 --- a/packages/replay/src/util/addEvent.ts +++ b/packages/replay/src/util/addEvent.ts @@ -1,11 +1,11 @@ import { getCurrentHub } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { SESSION_IDLE_DURATION } from '../constants'; import type { AddEventResult, RecordingEvent, ReplayContainer } from '../types'; /** - * Add an event to the event buffer + * Add an event to the event buffer. + * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`. */ export async function addEvent( replay: ReplayContainer, @@ -31,7 +31,7 @@ export async function addEvent( // page has been left open and idle for a long period of time and user // comes back to trigger a new session. The performance entries rely on // `performance.timeOrigin`, which is when the page first opened. - if (timestampInMs + SESSION_IDLE_DURATION < new Date().getTime()) { + if (timestampInMs + replay.timeouts.sessionIdle < new Date().getTime()) { return null; } diff --git a/packages/replay/src/util/handleRecordingEmit.ts b/packages/replay/src/util/handleRecordingEmit.ts new file mode 100644 index 000000000000..e9a4a16b5018 --- /dev/null +++ b/packages/replay/src/util/handleRecordingEmit.ts @@ -0,0 +1,86 @@ +import { logger } from '@sentry/utils'; + +import { saveSession } from '../session/saveSession'; +import type { RecordingEvent, ReplayContainer } from '../types'; +import { addEvent } from './addEvent'; + +type RecordingEmitCallback = (event: RecordingEvent, isCheckout?: boolean) => void; + +/** + * Handler for recording events. + * + * Adds to event buffer, and has varying flushing behaviors if the event was a checkout. + */ +export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCallback { + let hadFirstEvent = false; + + return (event: RecordingEvent, _isCheckout?: boolean) => { + // If this is false, it means session is expired, create and a new session and wait for checkout + if (!replay.checkAndHandleExpiredSession()) { + __DEBUG_BUILD__ && logger.warn('[Replay] Received replay event after session expired.'); + + return; + } + + // `_isCheckout` is only set when the checkout is due to `checkoutEveryNms` + // We also want to treat the first event as a checkout, so we handle this specifically here + const isCheckout = _isCheckout || !hadFirstEvent; + hadFirstEvent = true; + + // The handler returns `true` if we do not want to trigger debounced flush, `false` if we want to debounce flush. + replay.addUpdate(() => { + // The session is always started immediately on pageload/init, but for + // error-only replays, it should reflect the most recent checkout + // when an error occurs. Clear any state that happens before this current + // checkout. This needs to happen before `addEvent()` which updates state + // dependent on this reset. + if (replay.recordingMode === 'error' && isCheckout) { + replay.setInitialState(); + } + + // We need to clear existing events on a checkout, otherwise they are + // incremental event updates and should be appended + void addEvent(replay, event, isCheckout); + + // Different behavior for full snapshots (type=2), ignore other event types + // See https://github.com/rrweb-io/rrweb/blob/d8f9290ca496712aa1e7d472549480c4e7876594/packages/rrweb/src/types.ts#L16 + if (!isCheckout) { + return false; + } + + // If there is a previousSessionId after a full snapshot occurs, then + // the replay session was started due to session expiration. The new session + // is started before triggering a new checkout and contains the id + // of the previous session. Do not immediately flush in this case + // to avoid capturing only the checkout and instead the replay will + // be captured if they perform any follow-up actions. + if (replay.session && replay.session.previousSessionId) { + return true; + } + + // See note above re: session start needs to reflect the most recent + // checkout. + if (replay.recordingMode === 'error' && replay.session) { + const { earliestEvent } = replay.getContext(); + if (earliestEvent) { + replay.session.started = earliestEvent; + + if (replay.getOptions().stickySession) { + saveSession(replay.session); + } + } + } + + // Flush immediately so that we do not miss the first segment, otherwise + // it can prevent loading on the UI. This will cause an increase in short + // replays (e.g. opening and closing a tab quickly), but these can be + // filtered on the UI. + if (replay.recordingMode === 'session') { + // We want to ensure the worker is ready, as otherwise we'd always send the first event uncompressed + void replay.flushImmediate(); + } + + return true; + }); + }; +} diff --git a/packages/replay/src/util/isSessionExpired.ts b/packages/replay/src/util/isSessionExpired.ts index a9d529f0986a..b7025a19cbf6 100644 --- a/packages/replay/src/util/isSessionExpired.ts +++ b/packages/replay/src/util/isSessionExpired.ts @@ -1,16 +1,15 @@ -import { MAX_SESSION_LIFE } from '../constants'; -import type { Session } from '../types'; +import type { Session, Timeouts } from '../types'; import { isExpired } from './isExpired'; /** * Checks to see if session is expired */ -export function isSessionExpired(session: Session, idleTimeout: number, targetTime: number = +new Date()): boolean { +export function isSessionExpired(session: Session, timeouts: Timeouts, targetTime: number = +new Date()): boolean { return ( // First, check that maximum session length has not been exceeded - isExpired(session.started, MAX_SESSION_LIFE, targetTime) || + isExpired(session.started, timeouts.maxSessionLife, targetTime) || // check that the idle timeout has not been exceeded (i.e. user has // performed an action within the last `idleTimeout` ms) - isExpired(session.lastActivity, idleTimeout, targetTime) + isExpired(session.lastActivity, timeouts.sessionIdle, targetTime) ); } diff --git a/packages/replay/src/util/sendReplay.ts b/packages/replay/src/util/sendReplay.ts index e393c8611866..f10bb223d3d9 100644 --- a/packages/replay/src/util/sendReplay.ts +++ b/packages/replay/src/util/sendReplay.ts @@ -41,7 +41,17 @@ export async function sendReplay( // If an error happened here, it's likely that uploading the attachment // failed, we'll can retry with the same events payload if (retryConfig.count >= RETRY_MAX_COUNT) { - throw new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`); + const error = new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`); + + try { + // In case browsers don't allow this property to be writable + // @ts-ignore This needs lib es2022 and newer + error.cause = err; + } catch { + // nothing to do + } + + throw error; } // will retry in intervals of 5, 10, 30 diff --git a/packages/replay/src/util/sendReplayRequest.ts b/packages/replay/src/util/sendReplayRequest.ts index 06e35330937e..2d512915ba47 100644 --- a/packages/replay/src/util/sendReplayRequest.ts +++ b/packages/replay/src/util/sendReplayRequest.ts @@ -115,8 +115,17 @@ export async function sendReplayRequest({ try { response = await transport.send(envelope); - } catch { - throw new Error(UNABLE_TO_SEND_REPLAY); + } catch (err) { + const error = new Error(UNABLE_TO_SEND_REPLAY); + + try { + // In case browsers don't allow this property to be writable + // @ts-ignore This needs lib es2022 and newer + error.cause = err; + } catch { + // nothing to do + } + throw error; } // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore diff --git a/packages/replay/test/integration/autoSaveSession.test.ts b/packages/replay/test/integration/autoSaveSession.test.ts index a640f810d2e7..6fc0539c771d 100644 --- a/packages/replay/test/integration/autoSaveSession.test.ts +++ b/packages/replay/test/integration/autoSaveSession.test.ts @@ -16,11 +16,9 @@ describe('Integration | autoSaveSession', () => { ['with stickySession=true', true, 1], ['with stickySession=false', false, 0], ])('%s', async (_: string, stickySession: boolean, addSummand: number) => { - let saveSessionSpy; + const saveSessionSpy = jest.fn(); jest.mock('../../src/session/saveSession', () => { - saveSessionSpy = jest.fn(); - return { saveSession: saveSessionSpy, }; diff --git a/packages/replay/test/integration/rrweb.test.ts b/packages/replay/test/integration/rrweb.test.ts index 6d324d96208e..3e37df5f2910 100644 --- a/packages/replay/test/integration/rrweb.test.ts +++ b/packages/replay/test/integration/rrweb.test.ts @@ -32,6 +32,7 @@ describe('Integration | rrweb', () => { "maskInputSelector": ".sentry-mask,[data-sentry-mask]", "maskTextFn": undefined, "maskTextSelector": ".sentry-mask,[data-sentry-mask]", + "onMutation": [Function], "slimDOMOptions": "all", "unblockSelector": ".sentry-unblock,[data-sentry-unblock]", "unmaskInputSelector": ".sentry-unmask,[data-sentry-unmask]", diff --git a/packages/replay/test/integration/sendReplayEvent.test.ts b/packages/replay/test/integration/sendReplayEvent.test.ts index 4f17644a241e..867499890bb7 100644 --- a/packages/replay/test/integration/sendReplayEvent.test.ts +++ b/packages/replay/test/integration/sendReplayEvent.test.ts @@ -129,6 +129,26 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); }); + it('update last activity when user uses keyboard input', async () => { + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + + domHandler({ + name: 'input', + }); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + jest.advanceTimersByTime(ELAPSED); + + domHandler({ + name: 'input', + }); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); + }); + it('uploads a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; mockRecord._emitter(TEST_EVENT); @@ -371,6 +391,11 @@ describe('Integration | sendReplayEvent', () => { expect(spyHandleException).toHaveBeenCalledTimes(5); expect(spyHandleException).toHaveBeenLastCalledWith(new Error('Unable to send Replay - max retries exceeded')); + const spyHandleExceptionCall = spyHandleException.mock.calls; + expect(spyHandleExceptionCall[spyHandleExceptionCall.length - 1][0].cause.message).toEqual( + 'Something bad happened', + ); + // No activity has occurred, session's last activity should remain the same expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); diff --git a/packages/replay/test/unit/session/getSession.test.ts b/packages/replay/test/unit/session/getSession.test.ts index 54c259fb7772..4ac92b148e75 100644 --- a/packages/replay/test/unit/session/getSession.test.ts +++ b/packages/replay/test/unit/session/getSession.test.ts @@ -1,4 +1,4 @@ -import { WINDOW } from '../../../src/constants'; +import { MAX_SESSION_LIFE, SESSION_IDLE_DURATION, WINDOW } from '../../../src/constants'; import * as CreateSession from '../../../src/session/createSession'; import * as FetchSession from '../../../src/session/fetchSession'; import { getSession } from '../../../src/session/getSession'; @@ -42,7 +42,10 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session when one does not exist', function () { const { session } = getSession({ - expiry: 900000, + timeouts: { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, }); @@ -66,7 +69,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(new Date().getTime() - 10000)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, }); @@ -79,7 +85,10 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session, when one is expired', function () { const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, currentSession: makeSession({ @@ -102,7 +111,10 @@ describe('Unit | session | getSession', () => { expect(FetchSession.fetchSession()).toBe(null); const { session } = getSession({ - expiry: 900000, + timeouts: { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, sessionSampleRate: 1.0, errorSampleRate: 0.0, @@ -134,7 +146,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(now)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, sessionSampleRate: 1.0, errorSampleRate: 0.0, @@ -157,7 +172,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(new Date().getTime() - 2000)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, ...SAMPLE_RATES, }); @@ -173,7 +191,10 @@ describe('Unit | session | getSession', () => { it('fetches a non-expired non-sticky session', function () { const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, currentSession: makeSession({ diff --git a/packages/replay/test/unit/util/handleRecordingEmit.test.ts b/packages/replay/test/unit/util/handleRecordingEmit.test.ts new file mode 100644 index 000000000000..4762b875ce5b --- /dev/null +++ b/packages/replay/test/unit/util/handleRecordingEmit.test.ts @@ -0,0 +1,86 @@ +import { EventType } from '@sentry-internal/rrweb'; + +import { BASE_TIMESTAMP } from '../..'; +import * as SentryAddEvent from '../../../src/util/addEvent'; +import { getHandleRecordingEmit } from '../../../src/util/handleRecordingEmit'; +import { setupReplayContainer } from '../../utils/setupReplayContainer'; +import { useFakeTimers } from '../../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Unit | util | handleRecordingEmit', () => { + let addEventMock: jest.SpyInstance; + + beforeEach(function () { + jest.setSystemTime(BASE_TIMESTAMP); + addEventMock = jest.spyOn(SentryAddEvent, 'addEvent').mockImplementation(async () => { + // Do nothing + }); + }); + + afterEach(function () { + addEventMock.mockReset(); + }); + + it('interprets first event as checkout event', async function () { + const replay = setupReplayContainer({ + options: { + errorSampleRate: 0, + sessionSampleRate: 1, + }, + }); + + const handler = getHandleRecordingEmit(replay); + + const event = { + type: EventType.FullSnapshot, + data: { + tag: 'test custom', + }, + timestamp: BASE_TIMESTAMP + 10, + }; + + handler(event); + await new Promise(process.nextTick); + + expect(addEventMock).toBeCalledTimes(1); + expect(addEventMock).toHaveBeenLastCalledWith(replay, event, true); + + handler(event); + await new Promise(process.nextTick); + + expect(addEventMock).toBeCalledTimes(2); + expect(addEventMock).toHaveBeenLastCalledWith(replay, event, false); + }); + + it('interprets any event with isCheckout as checkout', async function () { + const replay = setupReplayContainer({ + options: { + errorSampleRate: 0, + sessionSampleRate: 1, + }, + }); + + const handler = getHandleRecordingEmit(replay); + + const event = { + type: EventType.IncrementalSnapshot, + data: { + tag: 'test custom', + }, + timestamp: BASE_TIMESTAMP + 10, + }; + + handler(event, true); + await new Promise(process.nextTick); + + expect(addEventMock).toBeCalledTimes(1); + expect(addEventMock).toHaveBeenLastCalledWith(replay, event, true); + + handler(event, true); + await new Promise(process.nextTick); + + expect(addEventMock).toBeCalledTimes(2); + expect(addEventMock).toHaveBeenLastCalledWith(replay, event, true); + }); +}); diff --git a/packages/replay/test/unit/util/isSessionExpired.test.ts b/packages/replay/test/unit/util/isSessionExpired.test.ts index 381b8ffe6428..627105d322f0 100644 --- a/packages/replay/test/unit/util/isSessionExpired.test.ts +++ b/packages/replay/test/unit/util/isSessionExpired.test.ts @@ -1,3 +1,4 @@ +import { MAX_SESSION_LIFE } from '../../../src/constants'; import { makeSession } from '../../../src/session/Session'; import { isSessionExpired } from '../../../src/util/isSessionExpired'; @@ -14,18 +15,28 @@ function createSession(extra?: Record) { describe('Unit | util | isSessionExpired', () => { it('session last activity is older than expiry time', function () { - expect(isSessionExpired(createSession(), 100, 200)).toBe(true); // Session expired at ts = 100 + expect(isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 100 }, 200)).toBe(true); // Session expired at ts = 100 }); it('session last activity is not older than expiry time', function () { - expect(isSessionExpired(createSession({ lastActivity: 100 }), 150, 200)).toBe(false); // Session expires at ts >= 250 + expect( + isSessionExpired( + createSession({ lastActivity: 100 }), + { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 150 }, + 200, + ), + ).toBe(false); // Session expires at ts >= 250 }); it('session age is not older than max session life', function () { - expect(isSessionExpired(createSession(), 1_800_000, 50_000)).toBe(false); + expect( + isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 1_800_000 }, 50_000), + ).toBe(false); }); it('session age is older than max session life', function () { - expect(isSessionExpired(createSession(), 1_800_000, 1_800_001)).toBe(true); // Session expires at ts >= 1_800_000 + expect( + isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 1_800_000 }, 1_800_001), + ).toBe(true); // Session expires at ts >= 1_800_000 }); }); diff --git a/packages/replay/test/utils/setupReplayContainer.ts b/packages/replay/test/utils/setupReplayContainer.ts index 9a9455a3728a..e6a427e19638 100644 --- a/packages/replay/test/utils/setupReplayContainer.ts +++ b/packages/replay/test/utils/setupReplayContainer.ts @@ -26,7 +26,7 @@ export function setupReplayContainer({ }); clearSession(replay); - replay['_setInitialState'](); + replay.setInitialState(); replay['_loadAndCheckSession'](); replay['_isEnabled'] = true; replay.eventBuffer = createEventBuffer({ diff --git a/packages/sveltekit/.eslintrc.js b/packages/sveltekit/.eslintrc.js new file mode 100644 index 000000000000..2d614f46733b --- /dev/null +++ b/packages/sveltekit/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + env: { + browser: true, + node: true, + }, + extends: ['../../.eslintrc.js'], +}; diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE new file mode 100644 index 000000000000..d11896ba1181 --- /dev/null +++ b/packages/sveltekit/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/sveltekit/README.md b/packages/sveltekit/README.md new file mode 100644 index 000000000000..bb0206a72ac4 --- /dev/null +++ b/packages/sveltekit/README.md @@ -0,0 +1,32 @@ +

+ + Sentry + +

+ +# Official Sentry SDK for SvelteKit + +[![npm version](https://img.shields.io/npm/v/@sentry/sveltekit.svg)](https://www.npmjs.com/package/@sentry/sveltekit) +[![npm dm](https://img.shields.io/npm/dm/@sentry/sveltekit.svg)](https://www.npmjs.com/package/@sentry/sveltekit) +[![npm dt](https://img.shields.io/npm/dt/@sentry/sveltekit.svg)](https://www.npmjs.com/package/@sentry/sveltekit) + + + +## SDK Status + +This SDK is currently in **Alpha state** and we're still experimenting with APIs and functionality. We therefore make no guarantees in terms of semver or breaking changes. If you want to try this SDK and come across a problem, please open a [GitHub Issue](https://github.com/getsentry/sentry-javascript/issues/new/choose). + +## Compatibility + +Currently, the minimum supported version of SvelteKit is `1.0.0`. + +## General + +This package is a wrapper around `@sentry/node` for the server and `@sentry/svelte` for the client, with added functionality related to SvelteKit. + +TODO: Add usage instructions diff --git a/packages/sveltekit/jest.config.js b/packages/sveltekit/jest.config.js new file mode 100644 index 000000000000..222e6a817c7b --- /dev/null +++ b/packages/sveltekit/jest.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../jest/jest.config.js'); + +module.exports = baseConfig; diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json new file mode 100644 index 000000000000..15ace0d3482a --- /dev/null +++ b/packages/sveltekit/package.json @@ -0,0 +1,58 @@ +{ + "name": "@sentry/sveltekit", + "version": "7.41.0", + "description": "Official Sentry SDK for SvelteKit", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "main": "build/cjs/index.server.js", + "module": "build/esm/index.server.js", + "browser": "build/esm/index.client.js", + "types": "build/types/index.types.d.ts", + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@sveltejs/kit": "1.x" + }, + "dependencies": { + "@sentry/core": "7.41.0", + "@sentry/node": "7.41.0", + "@sentry/svelte": "7.41.0", + "@sentry/types": "7.41.0", + "@sentry/utils": "7.41.0" + }, + "devDependencies": { + "@sveltejs/kit": "^1.10.0", + "vite": "^4.0.0" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.js", + "build:types": "tsc -p tsconfig.types.json", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts", + "clean": "rimraf build coverage sentry-sveltekit-*.tgz", + "fix": "run-s fix:eslint fix:prettier", + "fix:eslint": "eslint . --format stylish --fix", + "fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"", + "lint": "run-s lint:prettier lint:eslint", + "lint:eslint": "eslint . --format stylish", + "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"", + "test": "yarn test:unit", + "test:unit": "jest", + "test:watch": "jest --watch" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/sveltekit/rollup.npm.config.js b/packages/sveltekit/rollup.npm.config.js new file mode 100644 index 000000000000..f1f8240d5a7a --- /dev/null +++ b/packages/sveltekit/rollup.npm.config.js @@ -0,0 +1,14 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js'; + +export default + makeNPMConfigVariants( + makeBaseNPMConfig({ + entrypoints: [ + 'src/index.server.ts', + 'src/index.client.ts', + 'src/client/index.ts', + 'src/server/index.ts', + ], + }), + ) +; diff --git a/packages/sveltekit/src/client/index.ts b/packages/sveltekit/src/client/index.ts new file mode 100644 index 000000000000..cdd2d7ba8a6e --- /dev/null +++ b/packages/sveltekit/src/client/index.ts @@ -0,0 +1,4 @@ +export * from '@sentry/svelte'; + +// Just here so that eslint is happy until we export more stuff here +export const PLACEHOLDER_CLIENT = 'PLACEHOLDER'; diff --git a/packages/sveltekit/src/index.client.ts b/packages/sveltekit/src/index.client.ts new file mode 100644 index 000000000000..98d53afc24d2 --- /dev/null +++ b/packages/sveltekit/src/index.client.ts @@ -0,0 +1,7 @@ +export * from './client'; + +/** + * This const serves no purpose besides being an identifier for this file that the SDK multiplexer loader can use to + * determine that this is in fact a file that wants to be multiplexed. + */ +export const _SENTRY_SDK_MULTIPLEXER = true; diff --git a/packages/sveltekit/src/index.server.ts b/packages/sveltekit/src/index.server.ts new file mode 100644 index 000000000000..9bdd72ae4f02 --- /dev/null +++ b/packages/sveltekit/src/index.server.ts @@ -0,0 +1,9 @@ +export * from './server'; + +// This file is the main entrypoint on the server and/or when the package is `require`d + +/** + * This const serves no purpose besides being an identifier for this file that the SDK multiplexer loader can use to + * determine that this is in fact a file that wants to be multiplexed. + */ +export const _SENTRY_SDK_MULTIPLEXER = true; diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts new file mode 100644 index 000000000000..f2eccf6ffb92 --- /dev/null +++ b/packages/sveltekit/src/index.types.ts @@ -0,0 +1,25 @@ +/* eslint-disable import/export */ + +// We export everything from both the client part of the SDK and from the server part. +// Some of the exports collide, which is not allowed, unless we redifine the colliding +// exports in this file - which we do below. +export * from './client'; +export * from './server'; + +import type { Integration, Options, StackParser } from '@sentry/types'; + +import type * as clientSdk from './client'; +import type * as serverSdk from './server'; + +/** Initializes Sentry SvelteKit SDK */ +export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void; + +// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere. +export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations; + +export declare const defaultIntegrations: Integration[]; +export declare const defaultStackParser: StackParser; + +export declare function close(timeout?: number | undefined): PromiseLike; +export declare function flush(timeout?: number | undefined): PromiseLike; +export declare function lastEventId(): string | undefined; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts new file mode 100644 index 000000000000..04654fecd19a --- /dev/null +++ b/packages/sveltekit/src/server/index.ts @@ -0,0 +1,4 @@ +export * from '@sentry/node'; + +// Just here so that eslint is happy until we export more stuff here +export const PLACEHOLDER_SERVER = 'PLACEHOLDER'; diff --git a/packages/sveltekit/test/index.test.ts b/packages/sveltekit/test/index.test.ts new file mode 100644 index 000000000000..6bc0e7072028 --- /dev/null +++ b/packages/sveltekit/test/index.test.ts @@ -0,0 +1,12 @@ +import * as SentryClient from '../src/client'; +import * as SentryServer from '../src/server'; + +describe('SvelteKit SDK', () => { + // This is a place holder test at best to satisfy the test runner + it('exports client and server SDKs', () => { + expect(SentryClient).toBeDefined(); + expect(SentryServer).toBeDefined(); + expect(SentryClient.init).toBeDefined(); + expect(SentryServer.init).toBeDefined(); + }); +}); diff --git a/packages/sveltekit/tsconfig.json b/packages/sveltekit/tsconfig.json new file mode 100644 index 000000000000..bf45a09f2d71 --- /dev/null +++ b/packages/sveltekit/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*"], + + "compilerOptions": { + // package-specific options + } +} diff --git a/packages/sveltekit/tsconfig.test.json b/packages/sveltekit/tsconfig.test.json new file mode 100644 index 000000000000..39a2d7203902 --- /dev/null +++ b/packages/sveltekit/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*"], + + "compilerOptions": { + // should include all types from `./tsconfig.json` plus types for all test frameworks used + "types": ["node", "jest"] + } +} diff --git a/packages/sveltekit/tsconfig.types.json b/packages/sveltekit/tsconfig.types.json new file mode 100644 index 000000000000..65455f66bd75 --- /dev/null +++ b/packages/sveltekit/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types" + } +} diff --git a/packages/tracing/.eslintrc.js b/packages/tracing/.eslintrc.js index 3cabb2be4d99..7a937173064e 100644 --- a/packages/tracing/.eslintrc.js +++ b/packages/tracing/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { extends: ['../../.eslintrc.js'], overrides: [ { - files: ['src/integrations/node/**'], + files: ['src/node/**'], rules: { '@sentry-internal/sdk/no-optional-chaining': 'off', }, diff --git a/packages/tracing/package.json b/packages/tracing/package.json index 8188cb842f5e..24870fadcdd3 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -1,7 +1,7 @@ { "name": "@sentry/tracing", "version": "7.41.0", - "description": "Extensions for Sentry AM", + "description": "Sentry Performance Monitoring Package", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tracing", "author": "Sentry", diff --git a/packages/tracing/src/browser/backgroundtab.ts b/packages/tracing/src/browser/backgroundtab.ts index 8c55e9853901..e061f39925a1 100644 --- a/packages/tracing/src/browser/backgroundtab.ts +++ b/packages/tracing/src/browser/backgroundtab.ts @@ -1,8 +1,7 @@ +import type { IdleTransaction, SpanStatusType } from '@sentry/core'; +import { getActiveTransaction } from '@sentry/core'; import { logger } from '@sentry/utils'; -import type { IdleTransaction } from '../idletransaction'; -import type { SpanStatusType } from '../span'; -import { getActiveTransaction } from '../utils'; import { WINDOW } from './types'; /** diff --git a/packages/tracing/src/browser/browsertracing.ts b/packages/tracing/src/browser/browsertracing.ts index 74b0f6d9b6ad..3575c0a20e91 100644 --- a/packages/tracing/src/browser/browsertracing.ts +++ b/packages/tracing/src/browser/browsertracing.ts @@ -1,14 +1,16 @@ /* eslint-disable max-lines */ -import type { Hub } from '@sentry/core'; +import type { Hub, IdleTransaction } from '@sentry/core'; +import { extractTraceparentData, startIdleTransaction, TRACING_DEFAULTS } from '@sentry/core'; import type { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, getDomElement, logger } from '@sentry/utils'; -import { startIdleTransaction } from '../hubextensions'; -import type { IdleTransaction } from '../idletransaction'; -import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../idletransaction'; -import { extractTraceparentData } from '../utils'; import { registerBackgroundTabDetection } from './backgroundtab'; -import { addPerformanceEntries, startTrackingLongTasks, startTrackingWebVitals } from './metrics'; +import { + addPerformanceEntries, + startTrackingInteractions, + startTrackingLongTasks, + startTrackingWebVitals, +} from './metrics'; import type { RequestInstrumentationOptions } from './request'; import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request'; import { instrumentRoutingWithDefaults } from './router'; @@ -131,9 +133,7 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions { } const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = { - idleTimeout: DEFAULT_IDLE_TIMEOUT, - finalTimeout: DEFAULT_FINAL_TIMEOUT, - heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, + ...TRACING_DEFAULTS, markBackgroundTransactions: true, routingInstrumentation: instrumentRoutingWithDefaults, startTransactionOnLocationChange: true, @@ -169,6 +169,8 @@ export class BrowserTracing implements Integration { private _latestRouteName?: string; private _latestRouteSource?: TransactionSource; + private _collectWebVitals: () => void; + public constructor(_options?: Partial) { this.options = { ...DEFAULT_BROWSER_TRACING_OPTIONS, @@ -190,10 +192,13 @@ export class BrowserTracing implements Integration { this.options.tracePropagationTargets = _options.tracingOrigins; } - startTrackingWebVitals(); + this._collectWebVitals = startTrackingWebVitals(); if (this.options.enableLongTask) { startTrackingLongTasks(); } + if (this.options._experiments.enableInteractions) { + startTrackingInteractions(); + } } /** @@ -308,6 +313,7 @@ export class BrowserTracing implements Integration { heartbeatInterval, ); idleTransaction.registerBeforeFinishCallback(transaction => { + this._collectWebVitals(); addPerformanceEntries(transaction); }); diff --git a/packages/tracing/src/browser/index.ts b/packages/tracing/src/browser/index.ts index dcf4a08270fd..3ed465eea6ca 100644 --- a/packages/tracing/src/browser/index.ts +++ b/packages/tracing/src/browser/index.ts @@ -1,3 +1,5 @@ +export * from '../exports'; + export type { RequestInstrumentationOptions } from './request'; export { BrowserTracing, BROWSER_TRACING_INTEGRATION_ID } from './browsertracing'; diff --git a/packages/tracing/src/browser/metrics/index.ts b/packages/tracing/src/browser/metrics/index.ts index b59f344899d2..788f21aafaa4 100644 --- a/packages/tracing/src/browser/metrics/index.ts +++ b/packages/tracing/src/browser/metrics/index.ts @@ -1,10 +1,9 @@ /* eslint-disable max-lines */ +import type { IdleTransaction, Transaction } from '@sentry/core'; +import { getActiveTransaction } from '@sentry/core'; import type { Measurements } from '@sentry/types'; import { browserPerformanceTimeOrigin, htmlTreeAsString, logger } from '@sentry/utils'; -import type { IdleTransaction } from '../../idletransaction'; -import type { Transaction } from '../../transaction'; -import { getActiveTransaction, msToSec } from '../../utils'; import { WINDOW } from '../types'; import { onCLS } from '../web-vitals/getCLS'; import { onFID } from '../web-vitals/getFID'; @@ -14,6 +13,14 @@ import { observe } from '../web-vitals/lib/observe'; import type { NavigatorDeviceMemory, NavigatorNetworkInformation } from '../web-vitals/types'; import { _startChild, isMeasurementValue } from './utils'; +/** + * Converts from milliseconds to seconds + * @param time time in ms + */ +function msToSec(time: number): number { + return time / 1000; +} + function getBrowserPerformanceAPI(): Performance | undefined { return WINDOW && WINDOW.addEventListener && WINDOW.performance; } @@ -26,17 +33,30 @@ let _clsEntry: LayoutShift | undefined; /** * Start tracking web vitals + * + * @returns A function that forces web vitals collection */ -export function startTrackingWebVitals(): void { +export function startTrackingWebVitals(): () => void { const performance = getBrowserPerformanceAPI(); if (performance && browserPerformanceTimeOrigin) { if (performance.mark) { WINDOW.performance.mark('sentry-tracing-init'); } - _trackCLS(); - _trackLCP(); _trackFID(); + const clsCallback = _trackCLS(); + const lcpCallback = _trackLCP(); + + return (): void => { + if (clsCallback) { + clsCallback(); + } + if (lcpCallback) { + lcpCallback(); + } + }; } + + return () => undefined; } /** @@ -64,12 +84,40 @@ export function startTrackingLongTasks(): void { observe('longtask', entryHandler); } +/** + * Start tracking interaction events. + */ +export function startTrackingInteractions(): void { + const entryHandler = (entries: PerformanceEventTiming[]): void => { + for (const entry of entries) { + const transaction = getActiveTransaction() as IdleTransaction | undefined; + if (!transaction) { + return; + } + + if (entry.name === 'click') { + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const duration = msToSec(entry.duration); + + transaction.startChild({ + description: htmlTreeAsString(entry.target), + op: `ui.interaction.${entry.name}`, + startTimestamp: startTime, + endTimestamp: startTime + duration, + }); + } + } + }; + + observe('event', entryHandler, { durationThreshold: 0 }); +} + /** Starts tracking the Cumulative Layout Shift on the current page. */ -function _trackCLS(): void { +function _trackCLS(): ReturnType { // See: // https://web.dev/evolving-cls/ // https://web.dev/cls-web-tooling/ - onCLS(metric => { + return onCLS(metric => { const entry = metric.entries.pop(); if (!entry) { return; @@ -82,8 +130,8 @@ function _trackCLS(): void { } /** Starts tracking the Largest Contentful Paint on the current page. */ -function _trackLCP(): void { - onLCP(metric => { +function _trackLCP(): ReturnType { + return onLCP(metric => { const entry = metric.entries.pop(); if (!entry) { return; diff --git a/packages/tracing/src/browser/metrics/utils.ts b/packages/tracing/src/browser/metrics/utils.ts index 7c30a9090245..80bf01b9c333 100644 --- a/packages/tracing/src/browser/metrics/utils.ts +++ b/packages/tracing/src/browser/metrics/utils.ts @@ -1,7 +1,6 @@ +import type { Transaction } from '@sentry/core'; import type { Span, SpanContext } from '@sentry/types'; -import type { Transaction } from '../../transaction'; - /** * Checks if a given value is a valid measurement value. */ diff --git a/packages/tracing/src/browser/web-vitals/getCLS.ts b/packages/tracing/src/browser/web-vitals/getCLS.ts index 3abddfac07cb..fdd1e867adfa 100644 --- a/packages/tracing/src/browser/web-vitals/getCLS.ts +++ b/packages/tracing/src/browser/web-vitals/getCLS.ts @@ -18,7 +18,7 @@ import { bindReporter } from './lib/bindReporter'; import { initMetric } from './lib/initMetric'; import { observe } from './lib/observe'; import { onHidden } from './lib/onHidden'; -import type { CLSMetric, ReportCallback } from './types'; +import type { CLSMetric, ReportCallback, StopListening } from './types'; /** * Calculates the [CLS](https://web.dev/cls/) value for the current page and @@ -41,7 +41,7 @@ import type { CLSMetric, ReportCallback } from './types'; * hidden. As a result, the `callback` function might be called multiple times * during the same page load._ */ -export const onCLS = (onReport: ReportCallback): void => { +export const onCLS = (onReport: ReportCallback): StopListening | undefined => { const metric = initMetric('CLS', 0); let report: ReturnType; @@ -89,9 +89,15 @@ export const onCLS = (onReport: ReportCallback): void => { if (po) { report = bindReporter(onReport, metric); - onHidden(() => { + const stopListening = (): void => { handleEntries(po.takeRecords() as CLSMetric['entries']); report(true); - }); + }; + + onHidden(stopListening); + + return stopListening; } + + return; }; diff --git a/packages/tracing/src/browser/web-vitals/getLCP.ts b/packages/tracing/src/browser/web-vitals/getLCP.ts index bf834c07ce4e..37e37c01eebd 100644 --- a/packages/tracing/src/browser/web-vitals/getLCP.ts +++ b/packages/tracing/src/browser/web-vitals/getLCP.ts @@ -20,7 +20,7 @@ import { getVisibilityWatcher } from './lib/getVisibilityWatcher'; import { initMetric } from './lib/initMetric'; import { observe } from './lib/observe'; import { onHidden } from './lib/onHidden'; -import type { LCPMetric, ReportCallback } from './types'; +import type { LCPMetric, ReportCallback, StopListening } from './types'; const reportedMetricIDs: Record = {}; @@ -30,7 +30,7 @@ const reportedMetricIDs: Record = {}; * relevant `largest-contentful-paint` performance entry used to determine the * value). The reported value is a `DOMHighResTimeStamp`. */ -export const onLCP = (onReport: ReportCallback): void => { +export const onLCP = (onReport: ReportCallback): StopListening | undefined => { const visibilityWatcher = getVisibilityWatcher(); const metric = initMetric('LCP'); let report: ReturnType; @@ -75,5 +75,9 @@ export const onLCP = (onReport: ReportCallback): void => { }); onHidden(stopListening, true); + + return stopListening; } + + return; }; diff --git a/packages/tracing/src/browser/web-vitals/types.ts b/packages/tracing/src/browser/web-vitals/types.ts index ef8de70c12bb..b4096b2678f6 100644 --- a/packages/tracing/src/browser/web-vitals/types.ts +++ b/packages/tracing/src/browser/web-vitals/types.ts @@ -135,6 +135,7 @@ declare global { interface PerformanceEventTiming extends PerformanceEntry { duration: DOMHighResTimeStamp; interactionId?: number; + readonly target: Node | null; } // https://wicg.github.io/layout-instability/#sec-layout-shift-attribution diff --git a/packages/tracing/src/browser/web-vitals/types/base.ts b/packages/tracing/src/browser/web-vitals/types/base.ts index 5dc45f00558d..ea2764c8ea64 100644 --- a/packages/tracing/src/browser/web-vitals/types/base.ts +++ b/packages/tracing/src/browser/web-vitals/types/base.ts @@ -104,3 +104,5 @@ export interface ReportOpts { * loading. This is equivalent to the corresponding `readyState` value. */ export type LoadState = 'loading' | 'dom-interactive' | 'dom-content-loaded' | 'complete'; + +export type StopListening = () => void; diff --git a/packages/tracing/src/errors.ts b/packages/tracing/src/errors.ts index 1952fb75a915..3d41e43830f0 100644 --- a/packages/tracing/src/errors.ts +++ b/packages/tracing/src/errors.ts @@ -1,8 +1,7 @@ +import type { SpanStatusType } from '@sentry/core'; +import { getActiveTransaction } from '@sentry/core'; import { addInstrumentationHandler, logger } from '@sentry/utils'; -import type { SpanStatusType } from './span'; -import { getActiveTransaction } from './utils'; - /** * Configures global error listeners */ diff --git a/packages/tracing/src/exports/index.ts b/packages/tracing/src/exports/index.ts new file mode 100644 index 000000000000..12c6e451e5a5 --- /dev/null +++ b/packages/tracing/src/exports/index.ts @@ -0,0 +1,15 @@ +export { + extractTraceparentData, + getActiveTransaction, + hasTracingEnabled, + IdleTransaction, + Span, + // eslint-disable-next-line deprecation/deprecation + SpanStatus, + spanStatusfromHttpCode, + startIdleTransaction, + stripUrlQueryAndFragment, + TRACEPARENT_REGEXP, + Transaction, +} from '@sentry/core'; +export type { SpanStatusType } from '@sentry/core'; diff --git a/packages/tracing/src/extensions.ts b/packages/tracing/src/extensions.ts new file mode 100644 index 000000000000..e3c2ab511804 --- /dev/null +++ b/packages/tracing/src/extensions.ts @@ -0,0 +1,72 @@ +import { addTracingExtensions, getMainCarrier } from '@sentry/core'; +import type { Integration, IntegrationClass } from '@sentry/types'; +import { dynamicRequire, isNodeEnv, loadModule } from '@sentry/utils'; + +import { registerErrorInstrumentation } from './errors'; + +/** + * @private + */ +function _autoloadDatabaseIntegrations(): void { + const carrier = getMainCarrier(); + if (!carrier.__SENTRY__) { + return; + } + + const packageToIntegrationMapping: Record Integration> = { + mongodb() { + const integration = dynamicRequire(module, './node/integrations/mongo') as { + Mongo: IntegrationClass; + }; + return new integration.Mongo(); + }, + mongoose() { + const integration = dynamicRequire(module, './node/integrations/mongo') as { + Mongo: IntegrationClass; + }; + return new integration.Mongo({ mongoose: true }); + }, + mysql() { + const integration = dynamicRequire(module, './node/integrations/mysql') as { + Mysql: IntegrationClass; + }; + return new integration.Mysql(); + }, + pg() { + const integration = dynamicRequire(module, './node/integrations/postgres') as { + Postgres: IntegrationClass; + }; + return new integration.Postgres(); + }, + }; + + const mappedPackages = Object.keys(packageToIntegrationMapping) + .filter(moduleName => !!loadModule(moduleName)) + .map(pkg => { + try { + return packageToIntegrationMapping[pkg](); + } catch (e) { + return undefined; + } + }) + .filter(p => p) as Integration[]; + + if (mappedPackages.length > 0) { + carrier.__SENTRY__.integrations = [...(carrier.__SENTRY__.integrations || []), ...mappedPackages]; + } +} + +/** + * This patches the global object and injects the Tracing extensions methods + */ +export function addExtensionMethods(): void { + addTracingExtensions(); + + // Detect and automatically load specified integrations. + if (isNodeEnv()) { + _autoloadDatabaseIntegrations(); + } + + // If an error happens globally, we should make sure transaction status is set to error. + registerErrorInstrumentation(); +} diff --git a/packages/tracing/src/index.bundle.ts b/packages/tracing/src/index.bundle.ts index fce29d33758e..a753bab932d4 100644 --- a/packages/tracing/src/index.bundle.ts +++ b/packages/tracing/src/index.bundle.ts @@ -58,9 +58,9 @@ import type { Integration } from '@sentry/types'; import { GLOBAL_OBJ } from '@sentry/utils'; import { BrowserTracing } from './browser'; -import { addExtensionMethods } from './hubextensions'; +import { addExtensionMethods } from './extensions'; -export { Span } from './span'; +export { Span } from '@sentry/core'; let windowIntegrations = {}; diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts index 012c21ff825b..b8456bc6b017 100644 --- a/packages/tracing/src/index.ts +++ b/packages/tracing/src/index.ts @@ -1,8 +1,7 @@ -import { addExtensionMethods } from './hubextensions'; -import * as Integrations from './integrations'; +export * from './exports'; -export type { RequestInstrumentationOptions } from './browser'; -export type { SpanStatusType } from './span'; +import { addExtensionMethods } from './extensions'; +import * as Integrations from './node/integrations'; export { Integrations }; @@ -22,15 +21,14 @@ export { Integrations }; // const instance = new BrowserTracing(); // // For an example of of the new usage of BrowserTracing, see @sentry/nextjs index.client.ts -export { BrowserTracing, BROWSER_TRACING_INTEGRATION_ID } from './browser'; +export { + BrowserTracing, + BROWSER_TRACING_INTEGRATION_ID, + instrumentOutgoingRequests, + defaultRequestInstrumentationOptions, +} from './browser'; -export { Span, spanStatusfromHttpCode } from './span'; -// eslint-disable-next-line deprecation/deprecation -export { SpanStatus } from './spanstatus'; -export { Transaction } from './transaction'; -export { instrumentOutgoingRequests, defaultRequestInstrumentationOptions } from './browser'; -export { IdleTransaction } from './idletransaction'; -export { startIdleTransaction } from './hubextensions'; +export type { RequestInstrumentationOptions } from './browser'; // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -42,12 +40,3 @@ if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) { } export { addExtensionMethods }; - -export { - extractTraceparentData, - getActiveTransaction, - // eslint-disable-next-line deprecation/deprecation - hasTracingEnabled, - stripUrlQueryAndFragment, - TRACEPARENT_REGEXP, -} from './utils'; diff --git a/packages/tracing/src/integrations/index.ts b/packages/tracing/src/integrations/index.ts deleted file mode 100644 index 7522599aa4cf..000000000000 --- a/packages/tracing/src/integrations/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { Express } from './node/express'; -export { Postgres } from './node/postgres'; -export { Mysql } from './node/mysql'; -export { Mongo } from './node/mongo'; -export { Prisma } from './node/prisma'; -export { GraphQL } from './node/graphql'; -export { Apollo } from './node/apollo'; - -// TODO(v7): Remove this export -// Please see `src/index.ts` for more details. -export { BrowserTracing } from '../browser'; diff --git a/packages/tracing/src/node/index.ts b/packages/tracing/src/node/index.ts new file mode 100644 index 000000000000..eac5910c32c7 --- /dev/null +++ b/packages/tracing/src/node/index.ts @@ -0,0 +1,3 @@ +export * from '../exports'; + +export * from './integrations'; diff --git a/packages/tracing/src/integrations/node/apollo.ts b/packages/tracing/src/node/integrations/apollo.ts similarity index 100% rename from packages/tracing/src/integrations/node/apollo.ts rename to packages/tracing/src/node/integrations/apollo.ts diff --git a/packages/tracing/src/integrations/node/express.ts b/packages/tracing/src/node/integrations/express.ts similarity index 100% rename from packages/tracing/src/integrations/node/express.ts rename to packages/tracing/src/node/integrations/express.ts diff --git a/packages/tracing/src/integrations/node/graphql.ts b/packages/tracing/src/node/integrations/graphql.ts similarity index 100% rename from packages/tracing/src/integrations/node/graphql.ts rename to packages/tracing/src/node/integrations/graphql.ts diff --git a/packages/tracing/src/node/integrations/index.ts b/packages/tracing/src/node/integrations/index.ts new file mode 100644 index 000000000000..91554d84fccd --- /dev/null +++ b/packages/tracing/src/node/integrations/index.ts @@ -0,0 +1,11 @@ +export { Express } from './express'; +export { Postgres } from './postgres'; +export { Mysql } from './mysql'; +export { Mongo } from './mongo'; +export { Prisma } from './prisma'; +export { GraphQL } from './graphql'; +export { Apollo } from './apollo'; + +// TODO(v8): Remove this export +// Please see `src/index.ts` for more details. +export { BrowserTracing } from '../../browser'; diff --git a/packages/tracing/src/integrations/node/mongo.ts b/packages/tracing/src/node/integrations/mongo.ts similarity index 100% rename from packages/tracing/src/integrations/node/mongo.ts rename to packages/tracing/src/node/integrations/mongo.ts diff --git a/packages/tracing/src/integrations/node/mysql.ts b/packages/tracing/src/node/integrations/mysql.ts similarity index 100% rename from packages/tracing/src/integrations/node/mysql.ts rename to packages/tracing/src/node/integrations/mysql.ts diff --git a/packages/tracing/src/integrations/node/postgres.ts b/packages/tracing/src/node/integrations/postgres.ts similarity index 100% rename from packages/tracing/src/integrations/node/postgres.ts rename to packages/tracing/src/node/integrations/postgres.ts diff --git a/packages/tracing/src/integrations/node/prisma.ts b/packages/tracing/src/node/integrations/prisma.ts similarity index 100% rename from packages/tracing/src/integrations/node/prisma.ts rename to packages/tracing/src/node/integrations/prisma.ts diff --git a/packages/tracing/src/integrations/node/utils/node-utils.ts b/packages/tracing/src/node/integrations/utils/node-utils.ts similarity index 100% rename from packages/tracing/src/integrations/node/utils/node-utils.ts rename to packages/tracing/src/node/integrations/utils/node-utils.ts diff --git a/packages/tracing/test/browser/backgroundtab.test.ts b/packages/tracing/test/browser/backgroundtab.test.ts index 47b7f14d8a7d..ddbf76baa6c6 100644 --- a/packages/tracing/test/browser/backgroundtab.test.ts +++ b/packages/tracing/test/browser/backgroundtab.test.ts @@ -2,8 +2,8 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, makeMain } from '@sentry/core'; import { JSDOM } from 'jsdom'; +import { addExtensionMethods } from '../../src'; import { registerBackgroundTabDetection } from '../../src/browser/backgroundtab'; -import { addExtensionMethods } from '../../src/hubextensions'; import { getDefaultBrowserClientOptions } from '../testutils'; describe('registerBackgroundTabDetection', () => { diff --git a/packages/tracing/test/browser/browsertracing.test.ts b/packages/tracing/test/browser/browsertracing.test.ts index 2d42187d13da..e2bee71db0a8 100644 --- a/packages/tracing/test/browser/browsertracing.test.ts +++ b/packages/tracing/test/browser/browsertracing.test.ts @@ -1,17 +1,16 @@ import { BrowserClient, WINDOW } from '@sentry/browser'; -import { Hub, makeMain } from '@sentry/core'; +import { Hub, makeMain, TRACING_DEFAULTS } from '@sentry/core'; +import * as hubExtensions from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents } from '@sentry/types'; import type { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { JSDOM } from 'jsdom'; +import type { IdleTransaction } from '../../src'; +import { getActiveTransaction } from '../../src'; import type { BrowserTracingOptions } from '../../src/browser/browsertracing'; import { BrowserTracing, getMetaContent } from '../../src/browser/browsertracing'; import { defaultRequestInstrumentationOptions } from '../../src/browser/request'; import { instrumentRoutingWithDefaults } from '../../src/browser/router'; -import * as hubExtensions from '../../src/hubextensions'; -import type { IdleTransaction } from '../../src/idletransaction'; -import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../../src/idletransaction'; -import { getActiveTransaction } from '../../src/utils'; import { getDefaultBrowserClientOptions } from '../testutils'; let mockChangeHistory: ({ to, from }: { to: string; from?: string }) => void = () => undefined; @@ -29,7 +28,14 @@ jest.mock('@sentry/utils', () => { }; }); -jest.mock('../../src/browser/metrics'); +const mockStartTrackingWebVitals = jest.fn().mockReturnValue(() => () => {}); + +jest.mock('../../src/browser/metrics', () => ({ + addPerformanceEntries: jest.fn(), + startTrackingInteractions: jest.fn(), + startTrackingLongTasks: jest.fn(), + startTrackingWebVitals: () => mockStartTrackingWebVitals(), +})); const instrumentOutgoingRequestsMock = jest.fn(); jest.mock('./../../src/browser/request', () => { @@ -58,6 +64,8 @@ describe('BrowserTracing', () => { hub = new Hub(new BrowserClient(options)); makeMain(hub); document.head.innerHTML = ''; + + mockStartTrackingWebVitals.mockClear(); }); afterEach(() => { @@ -86,9 +94,7 @@ describe('BrowserTracing', () => { expect(browserTracing.options).toEqual({ _experiments: {}, enableLongTask: true, - idleTimeout: DEFAULT_IDLE_TIMEOUT, - finalTimeout: DEFAULT_FINAL_TIMEOUT, - heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, + ...TRACING_DEFAULTS, markBackgroundTransactions: true, routingInstrumentation: instrumentRoutingWithDefaults, startTransactionOnLocationChange: true, @@ -109,9 +115,7 @@ describe('BrowserTracing', () => { enableLongTask: false, }, enableLongTask: false, - idleTimeout: DEFAULT_IDLE_TIMEOUT, - finalTimeout: DEFAULT_FINAL_TIMEOUT, - heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, + ...TRACING_DEFAULTS, markBackgroundTransactions: true, routingInstrumentation: instrumentRoutingWithDefaults, startTransactionOnLocationChange: true, @@ -128,9 +132,7 @@ describe('BrowserTracing', () => { expect(browserTracing.options).toEqual({ _experiments: {}, enableLongTask: false, - idleTimeout: DEFAULT_IDLE_TIMEOUT, - finalTimeout: DEFAULT_FINAL_TIMEOUT, - heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, + ...TRACING_DEFAULTS, markBackgroundTransactions: true, routingInstrumentation: instrumentRoutingWithDefaults, startTransactionOnLocationChange: true, @@ -361,7 +363,7 @@ describe('BrowserTracing', () => { span.finish(); // activities = 0 expect(mockFinish).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT); + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); expect(mockFinish).toHaveBeenCalledTimes(1); }); @@ -378,6 +380,17 @@ describe('BrowserTracing', () => { jest.advanceTimersByTime(2000); expect(mockFinish).toHaveBeenCalledTimes(1); }); + + it('calls `_collectWebVitals` if enabled', () => { + createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); + const transaction = getActiveTransaction(hub) as IdleTransaction; + + const span = transaction.startChild(); // activities = 1 + span.finish(); // activities = 0 + + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); + expect(mockStartTrackingWebVitals).toHaveBeenCalledTimes(1); + }); }); describe('heartbeatInterval', () => { diff --git a/packages/tracing/test/browser/request.test.ts b/packages/tracing/test/browser/request.test.ts index 30a6ca3a2a9a..952362107878 100644 --- a/packages/tracing/test/browser/request.test.ts +++ b/packages/tracing/test/browser/request.test.ts @@ -3,10 +3,9 @@ import * as sentryCore from '@sentry/core'; import * as utils from '@sentry/utils'; import type { Transaction } from '../../src'; -import { Span, spanStatusfromHttpCode } from '../../src'; +import { addExtensionMethods, Span, spanStatusfromHttpCode } from '../../src'; import type { FetchData, XHRData } from '../../src/browser/request'; import { fetchCallback, instrumentOutgoingRequests, shouldAttachHeaders, xhrCallback } from '../../src/browser/request'; -import { addExtensionMethods } from '../../src/hubextensions'; import { getDefaultBrowserClientOptions } from '../testutils'; beforeAll(() => { diff --git a/packages/tracing/test/errors.test.ts b/packages/tracing/test/errors.test.ts index 554ee3b3d8c7..70a2e4561671 100644 --- a/packages/tracing/test/errors.test.ts +++ b/packages/tracing/test/errors.test.ts @@ -1,9 +1,8 @@ import { BrowserClient } from '@sentry/browser'; -import { Hub, makeMain } from '@sentry/core'; +import { addTracingExtensions, Hub, makeMain } from '@sentry/core'; import type { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { registerErrorInstrumentation } from '../src/errors'; -import { _addTracingExtensions } from '../src/hubextensions'; import { getDefaultBrowserClientOptions } from './testutils'; const mockAddInstrumentationHandler = jest.fn(); @@ -28,7 +27,7 @@ jest.mock('@sentry/utils', () => { }); beforeAll(() => { - _addTracingExtensions(); + addTracingExtensions(); }); describe('registerErrorHandlers()', () => { diff --git a/packages/tracing/test/hub.test.ts b/packages/tracing/test/hub.test.ts index df183f2b95eb..85c0b7791374 100644 --- a/packages/tracing/test/hub.test.ts +++ b/packages/tracing/test/hub.test.ts @@ -4,10 +4,8 @@ import { Hub, makeMain } from '@sentry/core'; import * as utilsModule from '@sentry/utils'; // for mocking import { logger } from '@sentry/utils'; +import { addExtensionMethods, extractTraceparentData, TRACEPARENT_REGEXP, Transaction } from '../src'; import { BrowserTracing } from '../src/browser/browsertracing'; -import { addExtensionMethods } from '../src/hubextensions'; -import { Transaction } from '../src/transaction'; -import { extractTraceparentData, TRACEPARENT_REGEXP } from '../src/utils'; import { addDOMPropertiesToGlobal, getDefaultBrowserClientOptions, diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index 79cd3f546bf9..563c2220959c 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -1,14 +1,8 @@ import { BrowserClient } from '@sentry/browser'; -import { Hub } from '@sentry/core'; - -import { - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, - DEFAULT_IDLE_TIMEOUT, - IdleTransaction, - IdleTransactionSpanRecorder, -} from '../src/idletransaction'; -import { Span } from '../src/span'; +import { TRACING_DEFAULTS } from '@sentry/core'; + +import { Hub, IdleTransaction, Span } from '../../core/src'; +import { IdleTransactionSpanRecorder } from '../../core/src/tracing/idletransaction'; import { getDefaultBrowserClientOptions } from './testutils'; const dsn = 'https://123@sentry.io/42'; @@ -24,9 +18,9 @@ describe('IdleTransaction', () => { const transaction = new IdleTransaction( { name: 'foo' }, hub, - DEFAULT_IDLE_TIMEOUT, - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, + TRACING_DEFAULTS.idleTimeout, + TRACING_DEFAULTS.finalTimeout, + TRACING_DEFAULTS.heartbeatInterval, true, ); transaction.initSpanRecorder(10); @@ -49,9 +43,9 @@ describe('IdleTransaction', () => { const transaction = new IdleTransaction( { name: 'foo' }, hub, - DEFAULT_IDLE_TIMEOUT, - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, + TRACING_DEFAULTS.idleTimeout, + TRACING_DEFAULTS.finalTimeout, + TRACING_DEFAULTS.heartbeatInterval, true, ); transaction.initSpanRecorder(10); @@ -68,9 +62,9 @@ describe('IdleTransaction', () => { const transaction = new IdleTransaction( { name: 'foo', sampled: false }, hub, - DEFAULT_IDLE_TIMEOUT, - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, + TRACING_DEFAULTS.idleTimeout, + TRACING_DEFAULTS.finalTimeout, + TRACING_DEFAULTS.heartbeatInterval, true, ); @@ -125,7 +119,7 @@ describe('IdleTransaction', () => { expect(transaction.activities).toMatchObject({ [span.spanId]: true, [childSpan.spanId]: true }); span.finish(); - jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT + 1); + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout + 1); expect(mockFinish).toHaveBeenCalledTimes(0); expect(transaction.activities).toMatchObject({ [childSpan.spanId]: true }); @@ -203,7 +197,7 @@ describe('IdleTransaction', () => { const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub); transaction.initSpanRecorder(10); - jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT); + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); expect(transaction.endTimestamp).toBeDefined(); }); @@ -212,7 +206,7 @@ describe('IdleTransaction', () => { transaction.initSpanRecorder(10); transaction.startChild({}); - jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT); + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); expect(transaction.endTimestamp).toBeUndefined(); }); @@ -253,6 +247,45 @@ describe('IdleTransaction', () => { }); }); + describe('cancelIdleTimeout', () => { + it('permanent idle timeout cancel finishes transaction if there are no activities', () => { + const idleTimeout = 10; + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + transaction.initSpanRecorder(10); + + const span = transaction.startChild({}); + span.finish(); + + jest.advanceTimersByTime(2); + + transaction.cancelIdleTimeout(undefined, { restartOnChildSpanChange: false }); + + expect(transaction.endTimestamp).toBeDefined(); + }); + + it('default idle cancel timeout is restarted by child span change', () => { + const idleTimeout = 10; + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + transaction.initSpanRecorder(10); + + const span = transaction.startChild({}); + span.finish(); + + jest.advanceTimersByTime(2); + + transaction.cancelIdleTimeout(); + + const span2 = transaction.startChild({}); + span2.finish(); + + jest.advanceTimersByTime(8); + expect(transaction.endTimestamp).toBeUndefined(); + + jest.advanceTimersByTime(2); + expect(transaction.endTimestamp).toBeDefined(); + }); + }); + describe('heartbeat', () => { it('does not mark transaction as `DeadlineExceeded` if idle timeout has not been reached', () => { // 20s to exceed 3 heartbeats @@ -263,23 +296,23 @@ describe('IdleTransaction', () => { expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(transaction.status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(transaction.status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 3 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(transaction.status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); }); it('finishes a transaction after 3 beats', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); + const transaction = new IdleTransaction({ name: 'foo' }, hub, TRACING_DEFAULTS.idleTimeout); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); @@ -287,20 +320,20 @@ describe('IdleTransaction', () => { transaction.startChild({}); // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 3 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(1); }); it('resets after new activities are added', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, 50000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, TRACING_DEFAULTS.idleTimeout, 50000); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); @@ -308,42 +341,42 @@ describe('IdleTransaction', () => { transaction.startChild({}); // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); const span = transaction.startChild(); // push activity // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); transaction.startChild(); // push activity transaction.startChild(); // push activity // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); span.finish(); // pop activity // Beat 1 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 3 - jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL); + jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); expect(mockFinish).toHaveBeenCalledTimes(1); // Heartbeat does not keep going after finish has been called diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts index 117cfd6ab704..981693875d7a 100644 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ b/packages/tracing/test/integrations/apollo-nestjs.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { Apollo } from '../../src/integrations/node/apollo'; -import { Span } from '../../src/span'; +import { Span } from '../../src'; +import { Apollo } from '../../src/node/integrations/apollo'; import { getTestClient } from '../testutils'; type ApolloResolverGroup = { @@ -43,6 +43,10 @@ class GraphQLFactory { } } +// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. +/* eslint-disable no-var */ +var mockFactory = GraphQLFactory; + // mock for @nestjs/graphql package jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); @@ -50,7 +54,7 @@ jest.mock('@sentry/utils', () => { ...actual, loadModule() { return { - GraphQLFactory, + GraphQLFactory: mockFactory, }; }, }; diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index 8b9946ba4a70..910c866505e8 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { Apollo } from '../../src/integrations/node/apollo'; -import { Span } from '../../src/span'; +import { Span } from '../../src'; +import { Apollo } from '../../src/node/integrations/apollo'; import { getTestClient } from '../testutils'; type ApolloResolverGroup = { @@ -45,6 +45,10 @@ class ApolloServerBase { } } +// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. +/* eslint-disable no-var */ +var mockClient = ApolloServerBase; + // mock for ApolloServer package jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); @@ -52,7 +56,7 @@ jest.mock('@sentry/utils', () => { ...actual, loadModule() { return { - ApolloServerBase, + ApolloServerBase: mockClient, }; }, }; diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index 265bbe0fb0d8..5d61a4acd5cb 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { GraphQL } from '../../src/integrations/node/graphql'; -import { Span } from '../../src/span'; +import { Span } from '../../src'; +import { GraphQL } from '../../src/node/integrations/graphql'; import { getTestClient } from '../testutils'; const GQLExecute = { @@ -12,13 +12,17 @@ const GQLExecute = { }, }; +// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. +/* eslint-disable no-var */ +var mockClient = GQLExecute; + // mock for 'graphql/execution/execution.js' package jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); return { ...actual, loadModule() { - return GQLExecute; + return mockClient; }, }; }); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index e362db6f0c47..5c3dd11255c4 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { Mongo } from '../../../src/integrations/node/mongo'; -import { Span } from '../../../src/span'; +import { Span } from '../../../src'; +import { Mongo } from '../../../src/node/integrations/mongo'; import { getTestClient } from '../../testutils'; class Collection { @@ -25,13 +25,17 @@ class Collection { } } +// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. +/* eslint-disable no-var */ +var mockCollection = Collection; + jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); return { ...actual, loadModule() { return { - Collection, + Collection: mockCollection, }; }, }; diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index 2ef3754b28ef..08aa64cf368d 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { Postgres } from '../../../src/integrations/node/postgres'; -import { Span } from '../../../src/span'; +import { Span } from '../../../src'; +import { Postgres } from '../../../src/node/integrations/postgres'; import { getTestClient } from '../../testutils'; class PgClient { @@ -23,6 +23,10 @@ class PgClient { } } +// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. +/* eslint-disable no-var */ +var mockClient = PgClient; + // mock for 'pg' / 'pg-native' package jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); @@ -30,9 +34,9 @@ jest.mock('@sentry/utils', () => { ...actual, loadModule() { return { - Client: PgClient, + Client: mockClient, native: { - Client: PgClient, + Client: mockClient, }, }; }, diff --git a/packages/tracing/test/integrations/node/prisma.test.ts b/packages/tracing/test/integrations/node/prisma.test.ts index d2adb685fc9d..95892f9c1686 100644 --- a/packages/tracing/test/integrations/node/prisma.test.ts +++ b/packages/tracing/test/integrations/node/prisma.test.ts @@ -2,8 +2,8 @@ import { Hub, Scope } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { Prisma } from '../../../src/integrations/node/prisma'; -import { Span } from '../../../src/span'; +import { Span } from '../../../src'; +import { Prisma } from '../../../src/node/integrations/prisma'; import { getTestClient } from '../../testutils'; type PrismaMiddleware = (params: unknown, next: (params?: unknown) => Promise) => Promise; diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 96a71811eec9..38231b40e2ca 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -2,8 +2,7 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, makeMain, Scope } from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; -import { Span, Transaction } from '../src'; -import { TRACEPARENT_REGEXP } from '../src/utils'; +import { Span, TRACEPARENT_REGEXP, Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; describe('Span', () => { diff --git a/packages/tracing/test/transaction.test.ts b/packages/tracing/test/transaction.test.ts index fb49dcc4d909..dae87c3a2f20 100644 --- a/packages/tracing/test/transaction.test.ts +++ b/packages/tracing/test/transaction.test.ts @@ -1,7 +1,6 @@ import { BrowserClient, Hub } from '@sentry/browser'; -import { addExtensionMethods } from '../src'; -import { Transaction } from '../src/transaction'; +import { addExtensionMethods, Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; describe('`Transaction` class', () => { diff --git a/packages/tracing/test/tsconfig.json b/packages/tracing/test/tsconfig.json index 074ceb45a9db..6c6ff4423fde 100644 --- a/packages/tracing/test/tsconfig.json +++ b/packages/tracing/test/tsconfig.json @@ -1,6 +1,5 @@ // TODO Once https://github.com/microsoft/TypeScript/issues/33094 is done (if it ever is), this file can disappear, as // it's purely a placeholder to satisfy VSCode. - { "extends": "../tsconfig.test.json", diff --git a/packages/tracing/test/utils.test.ts b/packages/tracing/test/utils.test.ts index 95b58397ca66..67e529e59ae9 100644 --- a/packages/tracing/test/utils.test.ts +++ b/packages/tracing/test/utils.test.ts @@ -1,4 +1,4 @@ -import { extractTraceparentData, hasTracingEnabled } from '../src/utils'; +import { extractTraceparentData, hasTracingEnabled } from '../src'; describe('hasTracingEnabled (deprecated)', () => { const tracesSampler = () => 1; diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 28d23025ce84..71e2ac5a96d6 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -1,6 +1,7 @@ import type { EventDropReason } from './clientreport'; import type { DataCategory } from './datacategory'; import type { DsnComponents } from './dsn'; +import type { Envelope } from './envelope'; import type { Event, EventHint } from './event'; import type { Integration, IntegrationClass } from './integration'; import type { ClientOptions } from './options'; @@ -8,6 +9,7 @@ import type { Scope } from './scope'; import type { SdkMetadata } from './sdkmetadata'; import type { Session, SessionAggregates } from './session'; import type { Severity, SeverityLevel } from './severity'; +import type { Transaction } from './transaction'; import type { Transport } from './transport'; /** @@ -147,4 +149,29 @@ export interface Client { * @param event The dropped event. */ recordDroppedEvent(reason: EventDropReason, dataCategory: DataCategory, event?: Event): void; + + // HOOKS + // TODO(v8): Make the hooks non-optional. + + /** + * Register a callback for transaction start and finish. + */ + on?(hook: 'startTransaction' | 'finishTransaction', callback: (transaction: Transaction) => void): void; + + /** + * Register a callback for transaction start and finish. + */ + on?(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; + + /** + * Fire a hook event for transaction start and finish. Expects to be given a transaction as the + * second argument. + */ + emit?(hook: 'startTransaction' | 'finishTransaction', transaction: Transaction): void; + + /* + * Fire a hook event for envelope creation and sending. Expects to be given an envelope as the + * second argument. + */ + emit?(hook: 'beforeEnvelope', envelope: Envelope): void; } diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index 7d21456d8044..61783ef9361b 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -75,7 +75,7 @@ export interface EventHint { event_id?: string; captureContext?: CaptureContext; syntheticException?: Error | null; - originalException?: Error | string | null; + originalException?: unknown; attachments?: Attachment[]; data?: any; integrations?: string[]; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7f7ed86cfc9a..30df53a682d5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -94,5 +94,6 @@ export type { export type { User, UserFeedback } from './user'; export type { WrappedFunction } from './wrappedfunction'; export type { Instrumenter } from './instrumenter'; +export type { HandlerDataFetch, SentryWrappedXMLHttpRequest } from './instrument'; export type { BrowserClientReplayOptions } from './browseroptions'; diff --git a/packages/types/src/instrument.ts b/packages/types/src/instrument.ts new file mode 100644 index 000000000000..85045217c45b --- /dev/null +++ b/packages/types/src/instrument.ts @@ -0,0 +1,23 @@ +type XHRSendInput = null | Blob | BufferSource | FormData | URLSearchParams | string; + +export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest { + [key: string]: any; + __sentry_xhr__?: { + method?: string; + url?: string; + status_code?: number; + body?: XHRSendInput; + }; +} + +interface SentryFetchData { + method: string; + url: string; +} + +export interface HandlerDataFetch { + args: any[]; + fetchData: SentryFetchData; + startTimestamp: number; + response?: Response; +} diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index cc3c1986b06d..68ee52789de9 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-types */ -import type { WrappedFunction } from '@sentry/types'; +import type { HandlerDataFetch, SentryWrappedXMLHttpRequest, WrappedFunction } from '@sentry/types'; import { isInstanceOf, isString } from './is'; import { CONSOLE_LEVELS, logger } from './logger'; @@ -136,7 +136,7 @@ function instrumentFetch(): void { fill(WINDOW, 'fetch', function (originalFetch: () => void): () => void { return function (...args: any[]): void { - const handlerData = { + const handlerData: HandlerDataFetch = { args, fetchData: { method: getFetchMethod(args), @@ -175,19 +175,6 @@ function instrumentFetch(): void { }); } -type XHRSendInput = null | Blob | BufferSource | FormData | URLSearchParams | string; - -/** JSDoc */ -interface SentryWrappedXMLHttpRequest extends XMLHttpRequest { - [key: string]: any; - __sentry_xhr__?: { - method?: string; - url?: string; - status_code?: number; - body?: XHRSendInput; - }; -} - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /** Extract `method` from fetch call arguments */ function getFetchMethod(fetchArgs: any[] = []): string { diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index b10f78f39e98..508442c2d14e 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -100,8 +100,17 @@ function visit( return value as ObjOrArray; } + // Do not normalize objects that we know have already been normalized. As a general rule, the + // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that + // have already been normalized. + let overriddenDepth = depth; + + if (typeof (value as ObjOrArray)['__sentry_override_normalization_depth__'] === 'number') { + overriddenDepth = (value as ObjOrArray)['__sentry_override_normalization_depth__'] as number; + } + // We're also done if we've reached the max depth - if (depth === 0) { + if (overriddenDepth === 0) { // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`. return stringified.replace('object ', ''); } @@ -117,7 +126,7 @@ function visit( try { const jsonValue = valueWithToJSON.toJSON(); // We need to normalize the return value of `.toJSON()` in case it has circular references - return visit('', jsonValue, depth - 1, maxProperties, memo); + return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo); } catch (err) { // pass (The built-in `toJSON` failed, but we can still try to do it ourselves) } @@ -146,7 +155,7 @@ function visit( // Recursively visit all the child nodes const visitValue = visitable[visitKey]; - normalized[visitKey] = visit(visitKey, visitValue, depth - 1, maxProperties, memo); + normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo); numAdded++; } diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index 1319805e9488..94676c1449da 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -582,4 +582,58 @@ describe('normalize()', () => { expect(result?.foo?.bar?.boo?.bam?.pow).not.toBe('poof'); }); }); + + describe('overrides normalization depth with a non-enumerable property __sentry_override_normalization_depth__', () => { + test('by increasing depth if it is higher', () => { + const normalizationTarget = { + foo: 'bar', + baz: 42, + obj: { + obj: { + obj: { + bestSmashCharacter: 'Cpt. Falcon', + }, + }, + }, + }; + + addNonEnumerableProperty(normalizationTarget, '__sentry_override_normalization_depth__', 3); + + const result = normalize(normalizationTarget, 1); + + expect(result).toEqual({ + baz: 42, + foo: 'bar', + obj: { + obj: { + obj: '[Object]', + }, + }, + }); + }); + + test('by decreasing depth if it is lower', () => { + const normalizationTarget = { + foo: 'bar', + baz: 42, + obj: { + obj: { + obj: { + bestSmashCharacter: 'Cpt. Falcon', + }, + }, + }, + }; + + addNonEnumerableProperty(normalizationTarget, '__sentry_override_normalization_depth__', 1); + + const result = normalize(normalizationTarget, 3); + + expect(result).toEqual({ + baz: 42, + foo: 'bar', + obj: '[Object]', + }); + }); + }); }); diff --git a/rollup/plugins/bundlePlugins.js b/rollup/plugins/bundlePlugins.js index be91a495fc1e..49dcaefbdf73 100644 --- a/rollup/plugins/bundlePlugins.js +++ b/rollup/plugins/bundlePlugins.js @@ -116,9 +116,8 @@ export function makeTerserPlugin() { '_driver', '_initStorage', '_support', - // We want to keept he _replay and _isEnabled variable unmangled to enable integration tests to access it + // We want to keep some replay fields unmangled to enable integration tests to access them '_replay', - '_isEnabled', // We also can't mangle rrweb private fields when bundling rrweb in the replay CDN bundles '_cssText', // We want to keep the _integrations variable unmangled to send all installed integrations from replay diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index 4e6efaec9d0e..ebe252a95240 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -16,7 +16,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [ ]; // These packages don't support Node 8 for syntax or dependency reasons. -const NODE_8_SKIP_TESTS_PACKAGES = ['@sentry/gatsby', '@sentry/serverless', '@sentry/nextjs', '@sentry/remix']; +const NODE_8_SKIP_TESTS_PACKAGES = ['@sentry/gatsby', '@sentry/serverless', '@sentry/nextjs', '@sentry/remix', '@sentry/sveltekit']; // We have to downgrade some of our dependencies in order to run tests in Node 8 and 10. const NODE_8_LEGACY_DEPENDENCIES = [ @@ -28,12 +28,15 @@ const NODE_8_LEGACY_DEPENDENCIES = [ 'lerna@3.13.4', ]; -const NODE_10_SKIP_TESTS_PACKAGES = ['@sentry/remix']; +const NODE_10_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit']; const NODE_10_LEGACY_DEPENDENCIES = ['jsdom@16.x', 'lerna@3.13.4']; -const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix']; +const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit']; const NODE_12_LEGACY_DEPENDENCIES = ['lerna@3.13.4']; +const NODE_14_SKIP_TESTS_PACKAGES = ['@sentry/sveltekit']; + + type JSONValue = string | number | boolean | null | JSONArray | JSONObject; type JSONObject = { @@ -131,6 +134,9 @@ function runTests(): void { installLegacyDeps(NODE_12_LEGACY_DEPENDENCIES); es6ifyTestTSConfig('utils'); break; + case '14': + NODE_14_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); + break; } runWithIgnores(Array.from(ignores)); diff --git a/scripts/prepack.ts b/scripts/prepack.ts index ace6846db457..bcad9dee0ef8 100644 --- a/scripts/prepack.ts +++ b/scripts/prepack.ts @@ -13,14 +13,33 @@ const NPM_BUILD_DIR = 'build/npm'; const BUILD_DIR = 'build'; const NPM_IGNORE = fs.existsSync('.npmignore') ? '.npmignore' : '../../.npmignore'; -const ASSETS = ['README.md', 'LICENSE', 'package.json', NPM_IGNORE]; -const ENTRY_POINTS = ['main', 'module', 'types', 'browser']; +const ASSETS = ['README.md', 'LICENSE', 'package.json', NPM_IGNORE] as const; +const ENTRY_POINTS = ['main', 'module', 'types', 'browser'] as const; +const EXPORT_MAP_ENTRY_POINT = 'exports'; +const TYPES_VERSIONS_ENTRY_POINT = 'typesVersions'; const packageWithBundles = process.argv.includes('--bundles'); const buildDir = packageWithBundles ? NPM_BUILD_DIR : BUILD_DIR; +type PackageJsonEntryPoints = Record; + +interface PackageJson extends Record, PackageJsonEntryPoints { + [EXPORT_MAP_ENTRY_POINT]: { + [key: string]: { + import: string; + require: string; + types: string; + }; + }; + [TYPES_VERSIONS_ENTRY_POINT]: { + [key: string]: { + [key: string]: string[]; + }; + }; +} + // eslint-disable-next-line @typescript-eslint/no-var-requires -const pkgJson: { [key: string]: unknown } = require(path.resolve('package.json')); +const pkgJson: PackageJson = require(path.resolve('package.json')); // check if build dir exists if (!fs.existsSync(path.resolve(buildDir))) { @@ -44,13 +63,29 @@ ASSETS.forEach(asset => { // package.json modifications const newPackageJsonPath = path.resolve(buildDir, 'package.json'); // eslint-disable-next-line @typescript-eslint/no-var-requires -const newPkgJson: { [key: string]: unknown } = require(newPackageJsonPath); +const newPkgJson: PackageJson = require(newPackageJsonPath); // modify entry points to point to correct paths (i.e. strip out the build directory) ENTRY_POINTS.filter(entryPoint => newPkgJson[entryPoint]).forEach(entryPoint => { - newPkgJson[entryPoint] = (newPkgJson[entryPoint] as string).replace(`${buildDir}/`, ''); + newPkgJson[entryPoint] = newPkgJson[entryPoint].replace(`${buildDir}/`, ''); }); +if (newPkgJson[EXPORT_MAP_ENTRY_POINT]) { + Object.entries(newPkgJson[EXPORT_MAP_ENTRY_POINT]).forEach(([key, val]) => { + newPkgJson[EXPORT_MAP_ENTRY_POINT][key] = Object.entries(val).reduce((acc, [key, val]) => { + return { ...acc, [key]: val.replace(`${buildDir}/`, '') }; + }, {} as typeof val); + }); +} + +if (newPkgJson[TYPES_VERSIONS_ENTRY_POINT]) { + Object.entries(newPkgJson[TYPES_VERSIONS_ENTRY_POINT]).forEach(([key, val]) => { + newPkgJson[TYPES_VERSIONS_ENTRY_POINT][key] = Object.entries(val).reduce((acc, [key, val]) => { + return { ...acc, [key]: val.map(v => v.replace(`${buildDir}/`, '')) }; + }, {}); + }); +} + delete newPkgJson.scripts; delete newPkgJson.volta; delete newPkgJson.jest; diff --git a/yarn.lock b/yarn.lock index aa778db88c85..77998803e637 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2345,6 +2345,116 @@ broccoli-funnel "^3.0.5" ember-cli-babel "^7.23.1" +"@esbuild/android-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" + integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== + +"@esbuild/android-arm@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" + integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== + +"@esbuild/android-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" + integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== + +"@esbuild/darwin-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" + integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== + +"@esbuild/darwin-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" + integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== + +"@esbuild/freebsd-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" + integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== + +"@esbuild/freebsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" + integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== + +"@esbuild/linux-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" + integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== + +"@esbuild/linux-arm@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" + integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== + +"@esbuild/linux-ia32@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" + integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== + +"@esbuild/linux-loong64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" + integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== + +"@esbuild/linux-mips64el@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" + integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== + +"@esbuild/linux-ppc64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" + integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== + +"@esbuild/linux-riscv64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" + integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== + +"@esbuild/linux-s390x@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" + integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== + +"@esbuild/linux-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" + integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== + +"@esbuild/netbsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" + integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== + +"@esbuild/openbsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" + integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== + +"@esbuild/sunos-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" + integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== + +"@esbuild/win32-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" + integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== + +"@esbuild/win32-ia32@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" + integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== + +"@esbuild/win32-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" + integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -3589,6 +3699,11 @@ optionalDependencies: fsevents "2.3.2" +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + "@polka/url@^1.0.0-next.9": version "1.0.0-next.12" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28" @@ -3917,17 +4032,17 @@ semver "7.3.2" semver-intersect "1.4.0" -"@sentry-internal/rrweb-snapshot@1.104.1": - version "1.104.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-1.104.1.tgz#baa1e951be432b8b8345833f5ed926144c62601f" - integrity sha512-IefNuRsyG8xoo6RNqTRoIaaarhjV1NO7JPiohYyyeSWDu26hK4mKlwDLlY41A5fRq8i49fiISoLU1EKueYFGOA== +"@sentry-internal/rrweb-snapshot@1.105.0": + version "1.105.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-1.105.0.tgz#b95d056e3602ab075049b31354942051137f3de2" + integrity sha512-UsrCVB5PVKTd3nXidUQSFQPjMHi2p5RzcujdBVuZfOJmRAqkHW6fN3CM4H6vJh0L5bgs+O/MFTNSv+iiAyVrBQ== -"@sentry-internal/rrweb@1.104.1": - version "1.104.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-1.104.1.tgz#a4c25f8daf59327d528540f872d301f4bc866dcc" - integrity sha512-kdf/tfMsIr3mr6IGZwpBcrS05GWyKbkAhv/4GkFevw9VSeHozTn2pPx6gU9M4WskN7NiZp4wvJ+4uLgwWdZSwg== +"@sentry-internal/rrweb@1.105.0": + version "1.105.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-1.105.0.tgz#c4238692753ed910d0fc23887361cc4d5d5112ab" + integrity sha512-RwJiQXaYzvLhqkAJtvwxu6WuO3OnedTpWtfdJX73kYujleSzoIBRNUXBs03Ir9E48UN5gzKu/eRG76QdH+e8Ow== dependencies: - "@sentry-internal/rrweb-snapshot" "1.104.1" + "@sentry-internal/rrweb-snapshot" "1.105.0" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1" @@ -4090,6 +4205,37 @@ dependencies: highlight.js "^9.15.6" +"@sveltejs/kit@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.10.0.tgz#17d3565e5903f6d2c0730197fd875c2cf921ad01" + integrity sha512-0P35zHrByfbF3Ym3RdQL+RvzgsCDSyO3imSwuZ67XAD5HoCQFF3a8Mhh0V3sObz3rc5aJd4Qn82UpAihJqZ6gQ== + dependencies: + "@sveltejs/vite-plugin-svelte" "^2.0.0" + "@types/cookie" "^0.5.1" + cookie "^0.5.0" + devalue "^4.3.0" + esm-env "^1.0.0" + kleur "^4.1.5" + magic-string "^0.30.0" + mime "^3.0.0" + sade "^1.8.1" + set-cookie-parser "^2.5.1" + sirv "^2.0.2" + tiny-glob "^0.2.9" + undici "5.20.0" + +"@sveltejs/vite-plugin-svelte@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.3.tgz#3d276eab341638dd58691a3de610774e155a7578" + integrity sha512-o+cguBFdwIGtRbNkYOyqTM7KvRUffxh5bfK4oJsWKG2obu+v/cbpT03tJrGl58C7tRXo/aEC0/axN5FVHBj0nA== + dependencies: + debug "^4.3.4" + deepmerge "^4.3.0" + kleur "^4.1.5" + magic-string "^0.29.0" + svelte-hmr "^0.15.1" + vitefu "^0.2.4" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -4280,6 +4426,11 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== +"@types/cookie@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.1.tgz#b29aa1f91a59f35e29ff8f7cb24faf1a3a750554" + integrity sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g== + "@types/cors@2.8.12", "@types/cors@^2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" @@ -8265,6 +8416,13 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + byte-size@7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/byte-size/-/byte-size-7.0.0.tgz#36528cd1ca87d39bd9abd51f5715dc93b6ceb032" @@ -9394,7 +9552,7 @@ cookie-signature@^1.1.0: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.0.tgz#4deed303f5f095e7a02c979e3fcb19157f5eaeea" integrity sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA== -cookie@0.5.0: +cookie@0.5.0, cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== @@ -10271,6 +10429,11 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +deepmerge@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" + integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -10522,6 +10685,11 @@ detective-typescript@^7.0.0: node-source-walk "^4.2.0" typescript "^3.9.7" +devalue@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.0.tgz#d86db8fee63a70317c2355be0d3d1b4d8f89a44e" + integrity sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA== + dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -11889,6 +12057,34 @@ esbuild@0.13.8: esbuild-windows-64 "0.13.8" esbuild-windows-arm64 "0.13.8" +esbuild@^0.16.14: + version "0.16.17" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" + integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== + optionalDependencies: + "@esbuild/android-arm" "0.16.17" + "@esbuild/android-arm64" "0.16.17" + "@esbuild/android-x64" "0.16.17" + "@esbuild/darwin-arm64" "0.16.17" + "@esbuild/darwin-x64" "0.16.17" + "@esbuild/freebsd-arm64" "0.16.17" + "@esbuild/freebsd-x64" "0.16.17" + "@esbuild/linux-arm" "0.16.17" + "@esbuild/linux-arm64" "0.16.17" + "@esbuild/linux-ia32" "0.16.17" + "@esbuild/linux-loong64" "0.16.17" + "@esbuild/linux-mips64el" "0.16.17" + "@esbuild/linux-ppc64" "0.16.17" + "@esbuild/linux-riscv64" "0.16.17" + "@esbuild/linux-s390x" "0.16.17" + "@esbuild/linux-x64" "0.16.17" + "@esbuild/netbsd-x64" "0.16.17" + "@esbuild/openbsd-x64" "0.16.17" + "@esbuild/sunos-x64" "0.16.17" + "@esbuild/win32-arm64" "0.16.17" + "@esbuild/win32-ia32" "0.16.17" + "@esbuild/win32-x64" "0.16.17" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -12171,6 +12367,11 @@ eslint@7.32.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esm-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413" + integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA== + esm@^3.2.4: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" @@ -16618,6 +16819,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kleur@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + klona@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" @@ -17468,6 +17674,20 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.29.0.tgz#f034f79f8c43dba4ae1730ffb5e8c4e084b16cf3" + integrity sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + +magic-string@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" + integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0, make-dir@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -17855,6 +18075,11 @@ mime@^2.3.1, mime@^2.4.4, mime@^2.5.2: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mime@~2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" @@ -18264,6 +18489,11 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + mrmime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" @@ -21457,7 +21687,7 @@ postcss@^8.1.10, postcss@^8.1.7, postcss@^8.2.15: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7: +postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.21: version "8.4.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== @@ -23054,6 +23284,13 @@ rollup@^2.45.1: optionalDependencies: fsevents "~2.3.2" +rollup@^3.10.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.18.0.tgz#2354ba63ba66d6a09c652c3ea0dbcd9dad72bbde" + integrity sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg== + optionalDependencies: + fsevents "~2.3.2" + rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" @@ -23123,6 +23360,13 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -23448,6 +23692,11 @@ set-cookie-parser@^2.4.8: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== +set-cookie-parser@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" + integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -23617,6 +23866,15 @@ sirv@^1.0.7: mime "^2.3.1" totalist "^1.0.0" +sirv@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760" + integrity sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w== + dependencies: + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^3.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -24326,6 +24584,11 @@ streamroller@^3.0.2: debug "^4.1.1" fs-extra "^10.0.0" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -24739,6 +25002,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svelte-hmr@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.1.tgz#d11d878a0bbb12ec1cba030f580cd2049f4ec86b" + integrity sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA== + svelte-jester@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/svelte-jester/-/svelte-jester-2.3.2.tgz#9eb818da30807bbcc940b6130d15b2c34408d64f" @@ -25205,7 +25473,7 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-glob@0.2.9: +tiny-glob@0.2.9, tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== @@ -25335,6 +25603,11 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +totalist@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd" + integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw== + touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -25782,6 +26055,13 @@ underscore@>=1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +undici@5.20.0: + version "5.20.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.20.0.tgz#6327462f5ce1d3646bcdac99da7317f455bcc263" + integrity sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g== + dependencies: + busboy "^1.6.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -26238,6 +26518,23 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite@^4.0.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.1.4.tgz#170d93bcff97e0ebc09764c053eebe130bfe6ca0" + integrity sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg== + dependencies: + esbuild "^0.16.14" + postcss "^8.4.21" + resolve "^1.22.1" + rollup "^3.10.0" + optionalDependencies: + fsevents "~2.3.2" + +vitefu@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.4.tgz#212dc1a9d0254afe65e579351bed4e25d81e0b35" + integrity sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g== + vm-browserify@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"