Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/replay-internal/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ import { getHandleRecordingEmit } from './util/handleRecordingEmit';
import { isExpired } from './util/isExpired';
import { isSessionExpired } from './util/isSessionExpired';
import { debug } from './util/logger';
import { resetReplayIdOnDynamicSamplingContext } from './util/resetReplayIdOnDynamicSamplingContext';
import {
resetReplayIdOnDynamicSamplingContext,
setReplayIdOnDynamicSamplingContext,
} from './util/resetReplayIdOnDynamicSamplingContext';
import { closestElementOfNode } from './util/rrweb';
import { sendReplay } from './util/sendReplay';
import { RateLimitError, ReplayDurationLimitError } from './util/sendReplayRequest';
Expand Down Expand Up @@ -616,6 +619,7 @@ export class ReplayContainer implements ReplayContainerInterface {
this._updateUserActivity(activityTime);
this._updateSessionActivity(activityTime);
this._maybeSaveSession();
setReplayIdOnDynamicSamplingContext(this.session.id);
Comment thread
cursor[bot] marked this conversation as resolved.
}

this.startRecording();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,21 @@ export function resetReplayIdOnDynamicSamplingContext(): void {
delete (dsc as Partial<DynamicSamplingContext>).replay_id;
}
}

/**
* Set the `replay_id` field on the cached DSC.
* The cached DSC on the scope (set by browserTracingIntegration) persists across
* session boundaries, and `createDsc` won't fire when a cached DSC already exists.
*/
export function setReplayIdOnDynamicSamplingContext(replayId: string): void {
const dsc = getCurrentScope().getPropagationContext().dsc;
if (dsc) {
dsc.replay_id = replayId;
}

const activeSpan = getActiveSpan();
if (activeSpan) {
const dsc = getDynamicSamplingContextFromSpan(activeSpan);
(dsc as Partial<DynamicSamplingContext>).replay_id = replayId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import '../utils/mock-internal-setTimeout';
import { captureException, getClient } from '@sentry/core';
import { captureException, getClient, getCurrentScope } from '@sentry/core';
import type { MockInstance } from 'vitest';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import {
Expand Down Expand Up @@ -383,6 +383,28 @@ describe('Integration | errorSampleRate', () => {
});
});

it('sets replay_id on DSC after converting from buffer to session mode', async () => {
const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP });
mockRecord._emitter(TEST_EVENT);

// Simulate a cached DSC on the scope (as browserTracingIntegration would set)
getCurrentScope().setPropagationContext({
traceId: '00000000000000000000000000000000',
sampleRand: 0,
dsc: { trace_id: '00000000000000000000000000000000', sampled: 'true' },
});

expect(replay.recordingMode).toBe('buffer');
const dsc = getCurrentScope().getPropagationContext().dsc!;
expect(dsc.replay_id).toBeUndefined();

await replay.sendBufferedReplayOrFlush({ continueRecording: true });
await vi.advanceTimersToNextTimerAsync();

expect(replay.recordingMode).toBe('session');
expect(dsc.replay_id).toBe(replay.getSessionId());
});

// This tests a regression where we were calling flush indiscriminantly in `stop()`
it('does not upload a replay event if error is not sampled', async () => {
// We are trying to replicate the case where error rate is 0 and session
Expand Down
Loading