diff --git a/src/browser/replay/recorder.js b/src/browser/replay/recorder.js index 54a68c97..62a3537a 100644 --- a/src/browser/replay/recorder.js +++ b/src/browser/replay/recorder.js @@ -27,12 +27,17 @@ export default class Recorder { this.options = options; this._recordFn = recordFn; + this._isReady = false; } get isRecording() { return this._stopFn !== null; } + get isReady() { + return this._isReady; + } + get options() { return this._options; } @@ -127,6 +132,9 @@ export default class Recorder { this._stopFn = this._recordFn({ emit: (event, isCheckout) => { + if (!this._ready && event.type === EventType.FullSnapshot) { + this._isReady = true; + } if (this.options.debug?.logEmits) { this._logEvent(event, isCheckout); } @@ -158,6 +166,7 @@ export default class Recorder { this._stopFn(); this._stopFn = null; + this._isReady = false; return this; } @@ -167,6 +176,7 @@ export default class Recorder { previous: [], current: [], }; + this._isReady = false; } _collectEvents() { diff --git a/src/browser/replay/replayManager.js b/src/browser/replay/replayManager.js index 780910af..12053841 100644 --- a/src/browser/replay/replayManager.js +++ b/src/browser/replay/replayManager.js @@ -79,6 +79,10 @@ export default class ReplayManager { * @returns {string} A unique identifier for this replay */ add(replayId, occurrenceUuid) { + if (!this._recorder.isReady) { + logger.warn('ReplayManager.add: Recorder is not ready, cannot export replay'); + return null; + } replayId = replayId || id.gen(8); // Start processing the replay in the background diff --git a/test/browser.replay.recorder.test.js b/test/browser.replay.recorder.test.js index dcbbfccd..c2d45284 100644 --- a/test/browser.replay.recorder.test.js +++ b/test/browser.replay.recorder.test.js @@ -40,6 +40,7 @@ describe('Recorder', function () { const recorder = new Recorder({}, recordFnStub); expect(recorder.isRecording).to.be.false; + expect(recorder.isReady).to.be.false; expect(recorder.options).to.deep.equal({ enabled: undefined, autoStart: undefined, @@ -162,6 +163,21 @@ describe('Recorder', function () { }); }); + it('should be ready after first full snapshot', function () { + const recorder = new Recorder({}, recordFnStub); + recorder.start(); + + // First checkout + emitCallback({ timestamp: 0, type: EventType.Meta, data: {} }, false); + expect(recorder.isReady).to.be.false; + + emitCallback( + { timestamp: 10, type: EventType.FullSnapshot, data: {} }, + false, + ); + expect(recorder.isReady).to.be.true; + }); + it('should handle checkout events correctly', function () { const recorder = new Recorder({}, recordFnStub); recorder.start(); diff --git a/test/replay/unit/replayManager.test.js b/test/replay/unit/replayManager.test.js index 8b901239..fb4ec17f 100644 --- a/test/replay/unit/replayManager.test.js +++ b/test/replay/unit/replayManager.test.js @@ -11,6 +11,7 @@ import id from '../../../src/tracing/id.js'; class MockRecorder { constructor() { this.exportRecordingSpan = sinon.stub(); + this.isReady = true; } } @@ -220,6 +221,20 @@ describe('ReplayManager', function () { expect(id.gen.calledWith(8)).to.be.true; expect(processStub.calledWith('1234567890abcdef', uuid)).to.be.true; }); + + it('should return without replayId when recorder is not ready', function () { + const uuid = '12345678-1234-5678-1234-1234567890ab'; + const processStub = sinon + .stub(replayManager, '_exportSpansAndAddTracingPayload') + .resolves(); + mockRecorder.isReady = false; + + const replayId = replayManager.add(null, uuid); + + expect(replayId).to.be.null; + expect(id.gen.called).to.be.false; + expect(processStub.called).to.be.false; + }); }); describe('send', function () {