UNPKG

@zendesk/retrace

Version:

define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API

94 lines (86 loc) 3.17 kB
/* eslint-disable prefer-destructuring */ import type { Timestamp } from './types' const JUST_CREATED_THRESHOLD_MS_AGO = 10 /** * Ensures that the input timestamp object has both epoch and performance.now() time. * If no input is provided, it generates a new timestamp with the current time. * * why store both timestamps?: * - epoch time: for reporting absolute time / correlating with backends * - durations: for calculating durations between two timestamps, performance.now() time makes more sense * - both are needed to correctly calculate a drift-adjusted epoch time * * @param input - Partial timestamp object that may contain either epoch or now or both. * @returns Full timestamp object with both epoch and performance.now() time. */ export const ensureTimestamp = (input?: Partial<Timestamp>): Timestamp => { const inputEpoch = input?.epoch const inputNow = input?.now const hasInputEpoch = typeof inputEpoch === 'number' const hasInputNow = typeof inputNow === 'number' if (hasInputEpoch && hasInputNow) { return input as Timestamp } if (hasInputEpoch) { const differenceFromNow = Date.now() - inputEpoch return { epoch: inputEpoch, now: Math.abs(differenceFromNow) < JUST_CREATED_THRESHOLD_MS_AGO ? performance.now() : performance.now() + differenceFromNow, } } if (hasInputNow) { const elapsedTimeSinceInput = performance.now() - inputNow const epoch = Date.now() - elapsedTimeSinceInput return { epoch, now: inputNow, } } // no data provided, use current time return { now: performance.now(), epoch: Date.now(), } } // below inspired by Datadog SDK // https://github.com/DataDog/browser-sdk/blob/16efff71b42530ead10400eb23c096b54e83fad4/packages/core/src/tools/utils/timeUtils.ts#L23-L30 /** * Navigation start slightly change on some rare cases */ let navigationStart: number | undefined function getNavigationStart() { if (navigationStart === undefined) { navigationStart = performance.timing.navigationStart } return navigationStart } /** * Why do we need to account for drift? * - monotonic clock (performance.now()) is not guaranteed to be in sync with wall clock time (Date.now()) * - Earth’s rotation speed is not constant * - browser bugs cause the monotonic clock to not tick when the computer is asleep or when the process is throttled * @see * - https://github.com/w3c/performance-timeline/issues/206 * - https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6 * - https://issues.chromium.org/issues/41450546 */ export function getEpochCorrectedForDrift({ epoch, now }: Timestamp) { // two different time origins const calculatedTimeOrigin = epoch - now const navigationStartEpoch = getNavigationStart() // apply correction only for positive drift if (calculatedTimeOrigin > navigationStartEpoch) { return Math.round(calculatedTimeOrigin + now) } return Math.round(navigationStartEpoch + now) } export const adjustTimestampBy = ( timestamp: Timestamp, adjustment: number, ) => ({ epoch: timestamp.epoch + adjustment, now: timestamp.now + adjustment, })