-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(replay): Save replay in sessionStorage and attempt to re-request if necessary #6698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,17 @@ export interface SendReplayData { | |
options: ReplayPluginOptions; | ||
} | ||
|
||
export enum FlushState { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should generally avoid enums as they have a pretty hefty bundle size impact (more details) We could use string constants instead, something along these lines: export type FlushState = 'start' | 'sent-request' | 'complete' | 'error' WDYT? |
||
START = 'start', | ||
SENT_REQUEST = 'sent-request', | ||
COMPLETE = 'complete', | ||
ERROR = 'error', | ||
} | ||
|
||
export type PendingReplayData = Omit<SendReplayData, 'recordingData'|'session'|'options'> & { | ||
recordingData: RecordingEvent[]; | ||
}; | ||
|
||
export type InstrumentationTypeBreadcrumb = 'dom' | 'scope'; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { PENDING_REPLAY_DATA_KEY, PENDING_REPLAY_STATUS_KEY, WINDOW } from '../constants'; | ||
|
||
/** | ||
* Clears pending segment that was previously unable to be sent (e.g. due to page reload). | ||
*/ | ||
export function clearPendingReplay(): void { | ||
WINDOW.sessionStorage.removeItem(PENDING_REPLAY_STATUS_KEY); | ||
WINDOW.sessionStorage.removeItem(PENDING_REPLAY_DATA_KEY); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { PENDING_REPLAY_DATA_KEY, WINDOW } from '../constants'; | ||
import { createEventBuffer } from '../eventBuffer'; | ||
import type { PendingReplayData, SendReplayData } from '../types'; | ||
|
||
/** | ||
* Attempts to flush pending segment that was previously unable to be sent | ||
* (e.g. due to page reload). | ||
*/ | ||
export async function getPendingReplay({ useCompression }: { useCompression: boolean }): Promise<Omit<SendReplayData, 'session'|'options'> | null> { | ||
try { | ||
const leftoverData = WINDOW.sessionStorage.getItem(PENDING_REPLAY_DATA_KEY); | ||
|
||
// Potential data that was not flushed | ||
const parsedData = leftoverData && (JSON.parse(leftoverData) as Partial<PendingReplayData> | null); | ||
const { recordingData, replayId, segmentId, includeReplayStartTimestamp, eventContext, timestamp } = parsedData || {}; | ||
|
||
// Ensure data integrity -- also *skip* includeReplayStartTimestamp as it | ||
// implies a checkout which we do not store due to potential size | ||
if (!recordingData || !replayId || !segmentId || !eventContext || !timestamp || includeReplayStartTimestamp) { | ||
return null; | ||
} | ||
|
||
// start a local eventBuffer | ||
const eventBuffer = createEventBuffer({ | ||
useCompression, | ||
}); | ||
|
||
await Promise.all(recordingData.map(event => eventBuffer.addEvent(event))); | ||
|
||
return { | ||
recordingData: await eventBuffer.finish(), | ||
replayId, | ||
segmentId, | ||
includeReplayStartTimestamp: false, | ||
eventContext, | ||
timestamp, | ||
}; | ||
} catch { | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { PENDING_REPLAY_DATA_KEY, PENDING_REPLAY_STATUS_KEY, WINDOW } from '../constants'; | ||
import type { PendingReplayData } from '../types'; | ||
import { FlushState } from '../types'; | ||
|
||
/** | ||
* | ||
*/ | ||
export function setFlushState(state: FlushState, data?: PendingReplayData): void { | ||
if (data) { | ||
WINDOW.sessionStorage.setItem(PENDING_REPLAY_DATA_KEY, JSON.stringify(data)); | ||
} | ||
|
||
if (state === FlushState.SENT_REQUEST) { | ||
WINDOW.sessionStorage.removeItem(PENDING_REPLAY_DATA_KEY); | ||
} else if (state === FlushState.COMPLETE) { | ||
WINDOW.sessionStorage.removeItem(PENDING_REPLAY_STATUS_KEY); | ||
} else { | ||
WINDOW.sessionStorage.setItem(PENDING_REPLAY_STATUS_KEY, state); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After reading this... this probably isn't necessary. It would let us know at which stage the user dropped off. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this to the SDK where we send the replay request (right after we prepare event), otherwise the restoration request happens before this handler is attached.