diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9bc1ed1baab0..1dd95f37f38a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,4 +2,4 @@ Before submitting a pull request, please take a look at our [Contributing](https://github.com/getsentry/sentry-javascript/blob/master/CONTRIBUTING.md) guidelines and verify: - [ ] If you've added code that should be tested, please add tests. -- [ ] Ensure your code lints and the test suite passes (`yarn lint`) & (`yarn test`). +- [ ] Ensure your code lints and the test suite passes (`yarn build && yarn lint && yarn test`). diff --git a/packages/replay-internal/src/coreHandlers/handleClick.ts b/packages/replay-internal/src/coreHandlers/handleClick.ts index dab5d04657fc..43cbe2e2bc52 100644 --- a/packages/replay-internal/src/coreHandlers/handleClick.ts +++ b/packages/replay-internal/src/coreHandlers/handleClick.ts @@ -16,9 +16,7 @@ import { addBreadcrumbEvent } from './util/addBreadcrumbEvent'; import { getClosestInteractive } from './util/domUtils'; import { onWindowOpen } from './util/onWindowOpen'; -type ClickBreadcrumb = Breadcrumb & { - timestamp: number; -}; +type ClickBreadcrumb = Breadcrumb; interface Click { timestamp: number; @@ -114,8 +112,12 @@ export class ClickDetector implements ReplayClickDetector { return; } + const timestampAsNumber = typeof breadcrumb.timestamp === 'string' + ? new Date(breadcrumb.timestamp).getTime() + : breadcrumb.timestamp || 0; + const newClick: Click = { - timestamp: timestampToS(breadcrumb.timestamp), + timestamp: timestampToS(timestampAsNumber), clickBreadcrumb: breadcrumb, // Set this to 0 so we know it originates from the click breadcrumb clickCount: 0, @@ -209,6 +211,9 @@ export class ClickDetector implements ReplayClickDetector { const isSlowClick = !hadScroll && !hadMutation; const { clickCount, clickBreadcrumb } = click; + const timestampAsNumber = typeof clickBreadcrumb.timestamp === 'string' + ? new Date(clickBreadcrumb.timestamp).getTime() + : clickBreadcrumb.timestamp || 0; // Slow click if (isSlowClick) { @@ -220,7 +225,7 @@ export class ClickDetector implements ReplayClickDetector { const breadcrumb: ReplaySlowClickFrame = { type: 'default', message: clickBreadcrumb.message, - timestamp: clickBreadcrumb.timestamp, + timestamp: timestampAsNumber, category: 'ui.slowClickDetected', data: { ...clickBreadcrumb.data, @@ -243,7 +248,7 @@ export class ClickDetector implements ReplayClickDetector { const breadcrumb: ReplayMultiClickFrame = { type: 'default', message: clickBreadcrumb.message, - timestamp: clickBreadcrumb.timestamp, + timestamp: timestampAsNumber, category: 'ui.multiClick', data: { ...clickBreadcrumb.data, diff --git a/packages/replay-internal/src/coreHandlers/util/addBreadcrumbEvent.ts b/packages/replay-internal/src/coreHandlers/util/addBreadcrumbEvent.ts index a324e5b24b25..7b1f995106ab 100644 --- a/packages/replay-internal/src/coreHandlers/util/addBreadcrumbEvent.ts +++ b/packages/replay-internal/src/coreHandlers/util/addBreadcrumbEvent.ts @@ -19,13 +19,17 @@ export function addBreadcrumbEvent(replay: ReplayContainer, breadcrumb: Breadcru } replay.addUpdate(() => { + const timestampAsNumber = typeof breadcrumb.timestamp === 'string' + ? new Date(breadcrumb.timestamp).getTime() / 1000 + : breadcrumb.timestamp || 0; + // This should never reject // eslint-disable-next-line @typescript-eslint/no-floating-promises replay.throttledAddEvent({ type: EventType.Custom, // TODO: We were converting from ms to seconds for breadcrumbs, spans, // but maybe we should just keep them as milliseconds - timestamp: (breadcrumb.timestamp || 0) * 1000, + timestamp: timestampAsNumber * 1000, data: { tag: 'breadcrumb', // normalize to max. 10 depth and 1_000 properties per object diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 6588a9e4d79d..4838eac9e749 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -700,10 +700,13 @@ export class ReplayContainer implements ReplayContainerInterface { }); this.addUpdate(() => { + const timestampAsNumber = typeof breadcrumb.timestamp === 'string' + ? new Date(breadcrumb.timestamp).getTime() + : breadcrumb.timestamp || 0; // Return `false` if the event _was_ added, as that means we schedule a flush return !addEventSync(this, { type: ReplayEventTypeCustom, - timestamp: breadcrumb.timestamp || 0, + timestamp: timestampAsNumber, data: { tag: 'breadcrumb', payload: breadcrumb, @@ -1003,11 +1006,14 @@ export class ReplayContainer implements ReplayContainerInterface { */ private _createCustomBreadcrumb(breadcrumb: ReplayBreadcrumbFrame): void { this.addUpdate(() => { + const timestampAsNumber = typeof breadcrumb.timestamp === 'string' + ? new Date(breadcrumb.timestamp).getTime() + : breadcrumb.timestamp || 0; // This should never reject // eslint-disable-next-line @typescript-eslint/no-floating-promises this.throttledAddEvent({ type: EventType.Custom, - timestamp: breadcrumb.timestamp || 0, + timestamp: timestampAsNumber, data: { tag: 'breadcrumb', payload: breadcrumb, diff --git a/packages/replay-internal/src/types/replayFrame.ts b/packages/replay-internal/src/types/replayFrame.ts index 48dc4aa72a2a..c87160cc0a6e 100644 --- a/packages/replay-internal/src/types/replayFrame.ts +++ b/packages/replay-internal/src/types/replayFrame.ts @@ -14,7 +14,7 @@ import type { ReplayEventTypeCustom } from './rrweb'; type AnyRecord = Record; // eslint-disable-line @typescript-eslint/no-explicit-any interface ReplayBaseBreadcrumbFrame { - timestamp: number; + timestamp: number | string; /** * For compatibility reasons */ diff --git a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts index 45483ebab5b4..0487f63210d7 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts @@ -47,4 +47,31 @@ describe('Unit | coreHandlers | util | addBreadcrumbEvent', function () { }, ]); }); + + it('handles timestamp as string', async () => { + const baseTimestampAsISOString = new Date(BASE_TIMESTAMP).toISOString() + const breadcrumb: any = { + category: 'console', + message: 'Test message with timestamp as string', + timestamp: baseTimestampAsISOString, + }; + + const replay = setupReplayContainer(); + addBreadcrumbEvent(replay, breadcrumb); + + expect((replay.eventBuffer as EventBufferArray).events).toEqual([ + { + type: 5, + timestamp: BASE_TIMESTAMP, + data: { + tag: 'breadcrumb', + payload: { + category: 'console', + message: 'Test message with timestamp as string', + timestamp: baseTimestampAsISOString, + }, + }, + }, + ]); + }); });