Socket
Socket
Sign inDemoInstall

@datadog/browser-rum-core

Package Overview
Dependencies
Maintainers
1
Versions
179
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@datadog/browser-rum-core - npm Package Compare versions

Comparing version 4.39.0 to 4.40.0

2

cjs/boot/startRum.d.ts

@@ -15,3 +15,3 @@ import type { Observable, RawError, ContextManager } from '@datadog/browser-core';

startView: (options?: ViewOptions | undefined, startClocks?: import("@datadog/browser-core").ClocksState | undefined) => void;
lifeCycle: LifeCycle;
lifeCycle: import("@datadog/browser-core").AbstractLifeCycle<import("../domain/lifeCycle").LifeCycleEventMap>;
viewContexts: import("../domain/contexts/viewContexts").ViewContexts;

@@ -18,0 +18,0 @@ session: RumSessionManager;

@@ -53,2 +53,3 @@ "use strict";

var featureFlagContexts = (0, featureFlagContext_1.startFeatureFlagContexts)(lifeCycle);
var session = !(0, browser_core_1.canUseEventBridge)() ? (0, rumSessionManager_1.startRumSessionManager)(configuration, lifeCycle) : (0, rumSessionManager_1.startRumSessionManagerStub)();
if (!(0, browser_core_1.canUseEventBridge)()) {

@@ -59,3 +60,3 @@ var pageExitObservable = (0, browser_core_1.createPageExitObservable)();

});
var batch = (0, startRumBatch_1.startRumBatch)(configuration, lifeCycle, telemetry.observable, reportError, pageExitObservable);
var batch = (0, startRumBatch_1.startRumBatch)(configuration, lifeCycle, telemetry.observable, reportError, pageExitObservable, session.expireObservable);
(0, startCustomerDataTelemetry_1.startCustomerDataTelemetry)(configuration, telemetry, lifeCycle, globalContextManager, userContextManager, featureFlagContexts, batch.flushObservable);

@@ -66,3 +67,2 @@ }

}
var session = !(0, browser_core_1.canUseEventBridge)() ? (0, rumSessionManager_1.startRumSessionManager)(configuration, lifeCycle) : (0, rumSessionManager_1.startRumSessionManagerStub)();
var domMutationObservable = (0, domMutationObservable_1.createDOMMutationObservable)();

@@ -69,0 +69,0 @@ var locationChangeObservable = (0, locationChangeObservable_1.createLocationChangeObservable)(location);

@@ -35,6 +35,3 @@ "use strict";

var urlContext = urlContexts.findUrl(startTime);
// allow to send events if the session was tracked when they start
// except for views which are continuously updated
// TODO: stop sending view updates when session is expired
var session = sessionManager.findTrackedSession(rawRumEvent.type !== "view" /* RumEventType.VIEW */ ? startTime : undefined);
var session = sessionManager.findTrackedSession(startTime);
if (session && viewContext && urlContext) {

@@ -50,3 +47,3 @@ var commonContext = savedCommonContext || buildCommonContext();

},
browser_sdk_version: (0, browser_core_1.canUseEventBridge)() ? "4.39.0" : undefined,
browser_sdk_version: (0, browser_core_1.canUseEventBridge)() ? "4.40.0" : undefined,
},

@@ -53,0 +50,0 @@ application: {

@@ -154,3 +154,4 @@ "use strict";

}
else {
else if ((0, browser_core_1.getType)(option) === 'object' && Array.isArray(option.propagatorTypes)) {
// Ensure we have an array, as we cannot rely on types yet (configuration is provided by users)
option.propagatorTypes.forEach(function (propagatorType) { return usedTracingPropagators.add(propagatorType); });

@@ -157,0 +158,0 @@ }

@@ -6,3 +6,3 @@ export declare function getDisplayContext(): {

};
} | undefined;
};
export declare function resetDisplayContext(): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resetDisplayContext = exports.getDisplayContext = void 0;
var browser_core_1 = require("@datadog/browser-core");
var viewportObservable_1 = require("../../browser/viewportObservable");

@@ -9,5 +8,2 @@ var viewport;

function getDisplayContext() {
if (!(0, browser_core_1.isExperimentalFeatureEnabled)(browser_core_1.ExperimentalFeature.CLICKMAP)) {
return;
}
if (!viewport) {

@@ -14,0 +10,0 @@ viewport = (0, viewportObservable_1.getViewportDimension)();

@@ -1,2 +0,3 @@

import type { Context, PageExitEvent, RawError, RelativeTime, Subscription } from '@datadog/browser-core';
import type { Context, PageExitEvent, RawError, RelativeTime } from '@datadog/browser-core';
import { AbstractLifeCycle } from '@datadog/browser-core';
import type { RumPerformanceEntry } from '../browser/performanceCollection';

@@ -25,36 +26,20 @@ import type { RumEventDomainContext } from '../domainContext.types';

}
export declare class LifeCycle {
private callbacks;
notify(eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, data: RumPerformanceEntry[]): void;
notify(eventType: LifeCycleEventType.REQUEST_STARTED, data: RequestStartEvent): void;
notify(eventType: LifeCycleEventType.REQUEST_COMPLETED, data: RequestCompleteEvent): void;
notify(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, data: AutoAction): void;
notify(eventType: LifeCycleEventType.VIEW_CREATED, data: ViewCreatedEvent): void;
notify(eventType: LifeCycleEventType.VIEW_UPDATED, data: ViewEvent): void;
notify(eventType: LifeCycleEventType.VIEW_ENDED, data: ViewEndedEvent): void;
notify(eventType: LifeCycleEventType.PAGE_EXITED, data: PageExitEvent): void;
notify(eventType: LifeCycleEventType.RAW_ERROR_COLLECTED, data: {
export interface LifeCycleEventMap {
[LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED]: RumPerformanceEntry[];
[LifeCycleEventType.AUTO_ACTION_COMPLETED]: AutoAction;
[LifeCycleEventType.VIEW_CREATED]: ViewCreatedEvent;
[LifeCycleEventType.VIEW_UPDATED]: ViewEvent;
[LifeCycleEventType.VIEW_ENDED]: ViewEndedEvent;
[LifeCycleEventType.REQUEST_STARTED]: RequestStartEvent;
[LifeCycleEventType.REQUEST_COMPLETED]: RequestCompleteEvent;
[LifeCycleEventType.SESSION_EXPIRED]: void;
[LifeCycleEventType.SESSION_RENEWED]: void;
[LifeCycleEventType.PAGE_EXITED]: PageExitEvent;
[LifeCycleEventType.RAW_RUM_EVENT_COLLECTED]: RawRumEventCollectedData;
[LifeCycleEventType.RUM_EVENT_COLLECTED]: RumEvent & Context;
[LifeCycleEventType.RAW_ERROR_COLLECTED]: {
error: RawError;
savedCommonContext?: CommonContext;
customerContext?: Context;
}): void;
notify(eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED): void;
notify(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, data: RawRumEventCollectedData): void;
notify(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, data: RumEvent & Context): void;
subscribe(eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, callback: (data: RumPerformanceEntry[]) => void): Subscription;
subscribe(eventType: LifeCycleEventType.REQUEST_STARTED, callback: (data: RequestStartEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.REQUEST_COMPLETED, callback: (data: RequestCompleteEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, callback: (data: AutoAction) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_CREATED, callback: (data: ViewCreatedEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_UPDATED, callback: (data: ViewEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_ENDED, callback: (data: ViewEndedEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.PAGE_EXITED, callback: (data: PageExitEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.RAW_ERROR_COLLECTED, callback: (data: {
error: RawError;
savedCommonContext?: CommonContext;
customerContext?: Context;
}) => void): Subscription;
subscribe(eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED, callback: () => void): Subscription;
subscribe(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, callback: (data: RawRumEventCollectedData) => void): Subscription;
subscribe(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, callback: (data: RumEvent & Context) => void): Subscription;
};
}

@@ -68,1 +53,5 @@ export interface RawRumEventCollectedData<E extends RawRumEvent = RawRumEvent> {

}
export declare const LifeCycle: {
new (): AbstractLifeCycle<LifeCycleEventMap>;
};
export type LifeCycle = AbstractLifeCycle<LifeCycleEventMap>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifeCycle = void 0;
var LifeCycle = /** @class */ (function () {
function LifeCycle() {
this.callbacks = {};
}
LifeCycle.prototype.notify = function (eventType, data) {
var eventCallbacks = this.callbacks[eventType];
if (eventCallbacks) {
eventCallbacks.forEach(function (callback) { return callback(data); });
}
};
LifeCycle.prototype.subscribe = function (eventType, callback) {
var _this = this;
if (!this.callbacks[eventType]) {
this.callbacks[eventType] = [];
}
this.callbacks[eventType].push(callback);
return {
unsubscribe: function () {
_this.callbacks[eventType] = _this.callbacks[eventType].filter(function (other) { return callback !== other; });
},
};
};
return LifeCycle;
}());
exports.LifeCycle = LifeCycle;
var browser_core_1 = require("@datadog/browser-core");
exports.LifeCycle = (browser_core_1.AbstractLifeCycle);
//# sourceMappingURL=lifeCycle.js.map

@@ -10,3 +10,3 @@ "use strict";

var status = 0 /* ClickChainStatus.WaitingForMoreClicks */;
var maxDurationBetweenClicksTimeout;
var maxDurationBetweenClicksTimeoutId;
appendClick(firstClick);

@@ -16,4 +16,4 @@ function appendClick(click) {

bufferedClicks.push(click);
(0, browser_core_1.clearTimeout)(maxDurationBetweenClicksTimeout);
maxDurationBetweenClicksTimeout = (0, browser_core_1.setTimeout)(dontAcceptMoreClick, exports.MAX_DURATION_BETWEEN_CLICKS);
(0, browser_core_1.clearTimeout)(maxDurationBetweenClicksTimeoutId);
maxDurationBetweenClicksTimeoutId = (0, browser_core_1.setTimeout)(dontAcceptMoreClick, exports.MAX_DURATION_BETWEEN_CLICKS);
}

@@ -27,3 +27,3 @@ function tryFinalize() {

function dontAcceptMoreClick() {
(0, browser_core_1.clearTimeout)(maxDurationBetweenClicksTimeout);
(0, browser_core_1.clearTimeout)(maxDurationBetweenClicksTimeoutId);
if (status === 0 /* ClickChainStatus.WaitingForMoreClicks */) {

@@ -30,0 +30,0 @@ status = 1 /* ClickChainStatus.WaitingForClicksToStop */;

@@ -132,21 +132,15 @@ "use strict";

function computeClickActionBase(event, actionNameAttribute) {
var target;
var position;
if ((0, browser_core_1.isExperimentalFeatureEnabled)(browser_core_1.ExperimentalFeature.CLICKMAP)) {
var rect = event.target.getBoundingClientRect();
target = {
var rect = event.target.getBoundingClientRect();
return {
type: "click" /* ActionType.CLICK */,
target: {
width: Math.round(rect.width),
height: Math.round(rect.height),
selector: (0, getSelectorFromElement_1.getSelectorFromElement)(event.target, actionNameAttribute),
};
position = {
},
position: {
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element
x: Math.round(event.clientX - rect.left),
y: Math.round(event.clientY - rect.top),
};
}
return {
type: "click" /* ActionType.CLICK */,
target: target,
position: position,
},
name: (0, getActionNameFromElement_1.getActionNameFromElement)(event.target, actionNameAttribute),

@@ -153,0 +147,0 @@ };

@@ -33,3 +33,3 @@ "use strict";

startClocks: startClocks,
nonErrorPrefix: browser_core_1.PROVIDED_ERROR_MESSAGE_PREFIX,
nonErrorPrefix: "Provided" /* NonErrorPrefix.PROVIDED */,
source: browser_core_1.ErrorSource.CUSTOM,

@@ -36,0 +36,0 @@ handling: "handled" /* ErrorHandling.HANDLED */,

import type { Duration, RelativeTime } from '@datadog/browser-core';
import type { LifeCycle } from '../../lifeCycle';
export declare const TIMING_MAXIMUM_DELAY: number;
/**
* The initial view can finish quickly, before some metrics can be produced (ex: before the page load
* event, or the first input). Also, we don't want to trigger a view update indefinitely, to avoid
* updates on views that ended a long time ago. Keep watching for metrics after the view ends for a
* limited amount of time.
*/
export declare const KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY: number;
export interface Timings {

@@ -15,6 +22,15 @@ firstContentfulPaint?: Duration;

}
export declare function trackInitialViewTimings(lifeCycle: LifeCycle, callback: (timings: Timings) => void): {
export declare function trackInitialViewTimings(lifeCycle: LifeCycle, setLoadEvent: (loadEnd: Duration) => void, scheduleViewUpdate: () => void): {
stop: () => void;
timings: Timings;
scheduleStop: () => void;
};
export declare function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: Partial<Timings>) => void): {
interface NavigationTimings {
domComplete: Duration;
domContentLoaded: Duration;
domInteractive: Duration;
loadEvent: Duration;
firstByte: Duration | undefined;
}
export declare function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: NavigationTimings) => void): {
stop: () => void;

@@ -48,1 +64,2 @@ };

};
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.trackFirstInputTimings = exports.trackLargestContentfulPaintTiming = exports.trackFirstContentfulPaintTiming = exports.trackNavigationTimings = exports.trackInitialViewTimings = exports.TIMING_MAXIMUM_DELAY = void 0;
exports.trackFirstInputTimings = exports.trackLargestContentfulPaintTiming = exports.trackFirstContentfulPaintTiming = exports.trackNavigationTimings = exports.trackInitialViewTimings = exports.KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY = exports.TIMING_MAXIMUM_DELAY = void 0;
var browser_core_1 = require("@datadog/browser-core");

@@ -9,9 +9,19 @@ var trackFirstHidden_1 = require("./trackFirstHidden");

exports.TIMING_MAXIMUM_DELAY = 10 * browser_core_1.ONE_MINUTE;
function trackInitialViewTimings(lifeCycle, callback) {
/**
* The initial view can finish quickly, before some metrics can be produced (ex: before the page load
* event, or the first input). Also, we don't want to trigger a view update indefinitely, to avoid
* updates on views that ended a long time ago. Keep watching for metrics after the view ends for a
* limited amount of time.
*/
exports.KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY = 5 * browser_core_1.ONE_MINUTE;
function trackInitialViewTimings(lifeCycle, setLoadEvent, scheduleViewUpdate) {
var timings = {};
function setTimings(newTimings) {
(0, browser_core_1.assign)(timings, newTimings);
callback(timings);
scheduleViewUpdate();
}
var stopNavigationTracking = trackNavigationTimings(lifeCycle, setTimings).stop;
var stopNavigationTracking = trackNavigationTimings(lifeCycle, function (newTimings) {
setLoadEvent(newTimings.loadEvent);
setTimings(newTimings);
}).stop;
var stopFCPTracking = trackFirstContentfulPaintTiming(lifeCycle, function (firstContentfulPaint) {

@@ -32,8 +42,13 @@ return setTimings({ firstContentfulPaint: firstContentfulPaint });

}).stop;
function stop() {
stopNavigationTracking();
stopFCPTracking();
stopLCPTracking();
stopFIDTracking();
}
return {
stop: function () {
stopNavigationTracking();
stopFCPTracking();
stopLCPTracking();
stopFIDTracking();
stop: stop,
timings: timings,
scheduleStop: function () {
(0, browser_core_1.setTimeout)(stop, exports.KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY);
},

@@ -40,0 +55,0 @@ };

@@ -22,2 +22,3 @@ import type { Duration, ClocksState, TimeStamp, Observable, RelativeTime } from '@datadog/browser-core';

isActive: boolean;
sessionIsActive: boolean;
loadingTime?: Duration;

@@ -24,0 +25,0 @@ loadingType: ViewLoadingType;

@@ -11,5 +11,4 @@ "use strict";

function trackViews(location, lifeCycle, domMutationObservable, configuration, locationChangeObservable, areViewsTrackedAutomatically, initialViewOptions) {
var _a = trackInitialView(initialViewOptions), stopInitialViewTracking = _a.stop, initialView = _a.initialView;
var currentView = initialView;
var stopViewLifeCycle = startViewLifeCycle().stop;
var currentView = startNewView("initial_load" /* ViewLoadingType.INITIAL_LOAD */, (0, browser_core_1.clocksOrigin)(), initialViewOptions);
startViewLifeCycle();
var locationChangeSubscription;

@@ -19,19 +18,9 @@ if (areViewsTrackedAutomatically) {

}
function trackInitialView(options) {
var initialView = newView(lifeCycle, domMutationObservable, configuration, location, "initial_load" /* ViewLoadingType.INITIAL_LOAD */, (0, browser_core_1.clocksOrigin)(), options);
var stop = (0, trackInitialViewTimings_1.trackInitialViewTimings)(lifeCycle, function (timings) {
initialView.updateTimings(timings);
initialView.scheduleUpdate();
}).stop;
return { initialView: initialView, stop: stop };
function startNewView(loadingType, startClocks, viewOptions) {
return newView(lifeCycle, domMutationObservable, configuration, location, loadingType, startClocks, viewOptions);
}
function trackViewChange(startClocks, viewOptions) {
return newView(lifeCycle, domMutationObservable, configuration, location, "route_change" /* ViewLoadingType.ROUTE_CHANGE */, startClocks, viewOptions);
}
function startViewLifeCycle() {
lifeCycle.subscribe(8 /* LifeCycleEventType.SESSION_RENEWED */, function () {
// do not trigger view update to avoid wrong data
currentView.end();
// Renew view on session renewal
currentView = trackViewChange(undefined, {
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */, undefined, {
name: currentView.name,

@@ -42,2 +31,5 @@ service: currentView.service,

});
lifeCycle.subscribe(7 /* LifeCycleEventType.SESSION_EXPIRED */, function () {
currentView.end({ sessionIsActive: false });
});
// End the current view on page unload

@@ -47,14 +39,4 @@ lifeCycle.subscribe(9 /* LifeCycleEventType.PAGE_EXITED */, function (pageExitEvent) {

currentView.end();
currentView.triggerUpdate();
}
});
// Session keep alive
var keepAliveInterval = (0, browser_core_1.setInterval)(function () {
currentView.triggerUpdate();
}, exports.SESSION_KEEP_ALIVE_INTERVAL);
return {
stop: function () {
(0, browser_core_1.clearInterval)(keepAliveInterval);
},
};
}

@@ -66,5 +48,3 @@ function renewViewOnLocationChange(locationChangeObservable) {

currentView.end();
currentView.triggerUpdate();
currentView = trackViewChange();
return;
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */);
}

@@ -77,13 +57,9 @@ });

currentView.addTiming(name, time);
currentView.scheduleUpdate();
},
startView: function (options, startClocks) {
currentView.end(startClocks);
currentView.triggerUpdate();
currentView = trackViewChange(startClocks, options);
currentView.end({ endClocks: startClocks });
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */, startClocks, options);
},
stop: function () {
locationChangeSubscription === null || locationChangeSubscription === void 0 ? void 0 : locationChangeSubscription.unsubscribe();
stopInitialViewTracking();
stopViewLifeCycle();
currentView.end();

@@ -98,3 +74,2 @@ },

var id = (0, browser_core_1.generateUUID)();
var timings = {};
var customTimings = {};

@@ -104,2 +79,3 @@ var documentVersion = 0;

var location = (0, browser_core_1.shallowClone)(initialLocation);
var sessionIsActive = true;
var name;

@@ -125,6 +101,12 @@ var service;

var _b = (0, trackViewMetrics_1.trackViewMetrics)(lifeCycle, domMutationObservable, configuration, scheduleViewUpdate, loadingType, startClocks), setLoadEvent = _b.setLoadEvent, stopViewMetricsTracking = _b.stop, viewMetrics = _b.viewMetrics;
var _c = (0, trackViewEventCounts_1.trackViewEventCounts)(lifeCycle, id, scheduleViewUpdate), scheduleStopEventCountsTracking = _c.scheduleStop, eventCounts = _c.eventCounts;
var _c = loadingType === "initial_load" /* ViewLoadingType.INITIAL_LOAD */
? (0, trackInitialViewTimings_1.trackInitialViewTimings)(lifeCycle, setLoadEvent, scheduleViewUpdate)
: { scheduleStop: browser_core_1.noop, timings: {} }, scheduleStopInitialViewTimingsTracking = _c.scheduleStop, timings = _c.timings;
var _d = (0, trackViewEventCounts_1.trackViewEventCounts)(lifeCycle, id, scheduleViewUpdate), scheduleStopEventCountsTracking = _d.scheduleStop, eventCounts = _d.eventCounts;
// Session keep alive
var keepAliveIntervalId = (0, browser_core_1.setInterval)(triggerViewUpdate, exports.SESSION_KEEP_ALIVE_INTERVAL);
// Initial view update
triggerViewUpdate();
function triggerViewUpdate() {
cancelScheduleViewUpdate();
documentVersion += 1;

@@ -145,2 +127,3 @@ var currentEnd = endClocks === undefined ? (0, browser_core_1.timeStampNow)() : endClocks.timeStamp;

isActive: endClocks === undefined,
sessionIsActive: sessionIsActive,
eventCounts: eventCounts,

@@ -153,24 +136,25 @@ }, viewMetrics));

version: version,
scheduleUpdate: scheduleViewUpdate,
end: function (clocks) {
if (clocks === void 0) { clocks = (0, browser_core_1.clocksNow)(); }
endClocks = clocks;
end: function (options) {
var _a, _b;
if (options === void 0) { options = {}; }
if (endClocks) {
// view already ended
return;
}
endClocks = (_a = options.endClocks) !== null && _a !== void 0 ? _a : (0, browser_core_1.clocksNow)();
sessionIsActive = (_b = options.sessionIsActive) !== null && _b !== void 0 ? _b : true;
lifeCycle.notify(4 /* LifeCycleEventType.VIEW_ENDED */, { endClocks: endClocks });
(0, browser_core_1.clearInterval)(keepAliveIntervalId);
stopViewMetricsTracking();
scheduleStopInitialViewTimingsTracking();
scheduleStopEventCountsTracking();
},
triggerUpdate: function () {
// cancel any pending view updates execution
cancelScheduleViewUpdate();
triggerViewUpdate();
},
updateTimings: function (newTimings) {
timings = newTimings;
if (newTimings.loadEvent !== undefined) {
setLoadEvent(newTimings.loadEvent);
addTiming: function (name, time) {
if (endClocks) {
return;
}
},
addTiming: function (name, time) {
var relativeTime = (0, browser_core_1.looksLikeRelativeTime)(time) ? time : (0, browser_core_1.elapsed)(startClocks.timeStamp, time);
customTimings[sanitizeTiming(name)] = relativeTime;
scheduleViewUpdate();
},

@@ -177,0 +161,0 @@ };

@@ -59,2 +59,3 @@ "use strict";

has_replay: replayStats ? true : undefined,
is_active: view.sessionIsActive ? undefined : false,
},

@@ -61,0 +62,0 @@ };

import type { RelativeTime } from '@datadog/browser-core';
import { Observable } from '@datadog/browser-core';
import type { RumConfiguration } from './configuration';

@@ -8,2 +9,3 @@ import type { LifeCycle } from './lifeCycle';

expire: () => void;
expireObservable: Observable<void>;
}

@@ -10,0 +12,0 @@ export type RumSession = {

@@ -38,2 +38,3 @@ "use strict";

expire: sessionManager.expire,
expireObservable: sessionManager.expireObservable,
};

@@ -56,2 +57,3 @@ }

expire: browser_core_1.noop,
expireObservable: new browser_core_1.Observable(),
};

@@ -58,0 +60,0 @@ }

@@ -48,4 +48,5 @@ "use strict";

var validationTimeoutId = (0, browser_core_1.setTimeout)((0, browser_core_1.monitor)(function () { return complete({ hadActivity: false }); }), exports.PAGE_ACTIVITY_VALIDATION_DELAY);
var maxDurationTimeoutId = maxDuration &&
(0, browser_core_1.setTimeout)((0, browser_core_1.monitor)(function () { return complete({ hadActivity: true, end: (0, browser_core_1.timeStampNow)() }); }), maxDuration);
var maxDurationTimeoutId = maxDuration !== undefined
? (0, browser_core_1.setTimeout)((0, browser_core_1.monitor)(function () { return complete({ hadActivity: true, end: (0, browser_core_1.timeStampNow)() }); }), maxDuration)
: undefined;
var pageActivitySubscription = pageActivityObservable.subscribe(function (_a) {

@@ -52,0 +53,0 @@ var isBusy = _a.isBusy;

@@ -91,2 +91,3 @@ import type { Context, Duration, ErrorSource, ErrorHandling, ResourceType, ServerDuration, TimeStamp, RawErrorCause } from '@datadog/browser-core';

has_replay: true | undefined;
is_active: false | undefined;
};

@@ -93,0 +94,0 @@ feature_flags?: Context;

@@ -683,2 +683,16 @@ /**

/**
* Session properties
*/
readonly session?: {
/**
* The precondition that led to the creation of the session
*/
readonly start_precondition?: 'app_launch' | 'inactivity_timeout' | 'max_duration' | 'explicit_stop' | 'background_event';
/**
* Whether this session is currently active. Set to false to manually stop a session
*/
readonly is_active?: boolean;
[k: string]: unknown;
};
/**
* Feature flags properties

@@ -685,0 +699,0 @@ */

import type { Context, TelemetryEvent, Observable, RawError, PageExitEvent, FlushEvent } from '@datadog/browser-core';
import type { RumConfiguration } from '../domain/configuration';
import type { LifeCycle } from '../domain/lifeCycle';
export declare function startRumBatch(configuration: RumConfiguration, lifeCycle: LifeCycle, telemetryEventObservable: Observable<TelemetryEvent & Context>, reportError: (error: RawError) => void, pageExitObservable: Observable<PageExitEvent>): RumBatch;
export declare function startRumBatch(configuration: RumConfiguration, lifeCycle: LifeCycle, telemetryEventObservable: Observable<TelemetryEvent & Context>, reportError: (error: RawError) => void, pageExitObservable: Observable<PageExitEvent>, sessionExpireObservable: Observable<void>): RumBatch;
export interface RumBatch {

@@ -6,0 +6,0 @@ flushObservable: Observable<FlushEvent>;

@@ -5,4 +5,4 @@ "use strict";

var browser_core_1 = require("@datadog/browser-core");
function startRumBatch(configuration, lifeCycle, telemetryEventObservable, reportError, pageExitObservable) {
var batch = makeRumBatch(configuration, reportError, pageExitObservable);
function startRumBatch(configuration, lifeCycle, telemetryEventObservable, reportError, pageExitObservable, sessionExpireObservable) {
var batch = makeRumBatch(configuration, reportError, pageExitObservable, sessionExpireObservable);
lifeCycle.subscribe(11 /* LifeCycleEventType.RUM_EVENT_COLLECTED */, function (serverRumEvent) {

@@ -20,3 +20,3 @@ if (serverRumEvent.type === "view" /* RumEventType.VIEW */) {

exports.startRumBatch = startRumBatch;
function makeRumBatch(configuration, reportError, pageExitObservable) {
function makeRumBatch(configuration, reportError, pageExitObservable, sessionExpireObservable) {
var _a = createRumBatch(configuration.rumEndpointBuilder), primaryBatch = _a.batch, primaryFlushController = _a.flushController;

@@ -34,2 +34,3 @@ var replicaBatch;

pageExitObservable: pageExitObservable,
sessionExpireObservable: sessionExpireObservable,
});

@@ -36,0 +37,0 @@ var batch = new browser_core_1.Batch((0, browser_core_1.createHttpRequest)(endpointBuilder, configuration.batchBytesLimit, reportError), flushController, configuration.messageBytesLimit);

@@ -15,3 +15,3 @@ import type { Observable, RawError, ContextManager } from '@datadog/browser-core';

startView: (options?: ViewOptions | undefined, startClocks?: import("@datadog/browser-core").ClocksState | undefined) => void;
lifeCycle: LifeCycle;
lifeCycle: import("@datadog/browser-core").AbstractLifeCycle<import("../domain/lifeCycle").LifeCycleEventMap>;
viewContexts: import("../domain/contexts/viewContexts").ViewContexts;

@@ -18,0 +18,0 @@ session: RumSessionManager;

@@ -50,2 +50,3 @@ import { sendToExtension, createPageExitObservable, addTelemetryConfiguration, startTelemetry, canUseEventBridge, getEventBridge, } from '@datadog/browser-core';

var featureFlagContexts = startFeatureFlagContexts(lifeCycle);
var session = !canUseEventBridge() ? startRumSessionManager(configuration, lifeCycle) : startRumSessionManagerStub();
if (!canUseEventBridge()) {

@@ -56,3 +57,3 @@ var pageExitObservable = createPageExitObservable();

});
var batch = startRumBatch(configuration, lifeCycle, telemetry.observable, reportError, pageExitObservable);
var batch = startRumBatch(configuration, lifeCycle, telemetry.observable, reportError, pageExitObservable, session.expireObservable);
startCustomerDataTelemetry(configuration, telemetry, lifeCycle, globalContextManager, userContextManager, featureFlagContexts, batch.flushObservable);

@@ -63,3 +64,2 @@ }

}
var session = !canUseEventBridge() ? startRumSessionManager(configuration, lifeCycle) : startRumSessionManagerStub();
var domMutationObservable = createDOMMutationObservable();

@@ -66,0 +66,0 @@ var locationChangeObservable = createLocationChangeObservable(location);

@@ -32,6 +32,3 @@ import { combine, isEmptyObject, timeStampNow, currentDrift, display, createEventRateLimiter, canUseEventBridge, } from '@datadog/browser-core';

var urlContext = urlContexts.findUrl(startTime);
// allow to send events if the session was tracked when they start
// except for views which are continuously updated
// TODO: stop sending view updates when session is expired
var session = sessionManager.findTrackedSession(rawRumEvent.type !== "view" /* RumEventType.VIEW */ ? startTime : undefined);
var session = sessionManager.findTrackedSession(startTime);
if (session && viewContext && urlContext) {

@@ -47,3 +44,3 @@ var commonContext = savedCommonContext || buildCommonContext();

},
browser_sdk_version: canUseEventBridge() ? "4.39.0" : undefined,
browser_sdk_version: canUseEventBridge() ? "4.40.0" : undefined,
},

@@ -50,0 +47,0 @@ application: {

@@ -1,2 +0,2 @@

import { arrayFrom, getOrigin, isMatchOption, serializeConfiguration, assign, DefaultPrivacyLevel, display, isPercentage, objectHasValue, validateAndBuildConfiguration, } from '@datadog/browser-core';
import { getType, arrayFrom, getOrigin, isMatchOption, serializeConfiguration, assign, DefaultPrivacyLevel, display, isPercentage, objectHasValue, validateAndBuildConfiguration, } from '@datadog/browser-core';
import { isTracingOption } from './tracing/tracer';

@@ -150,3 +150,4 @@ export function validateAndBuildRumConfiguration(initConfiguration) {

}
else {
else if (getType(option) === 'object' && Array.isArray(option.propagatorTypes)) {
// Ensure we have an array, as we cannot rely on types yet (configuration is provided by users)
option.propagatorTypes.forEach(function (propagatorType) { return usedTracingPropagators.add(propagatorType); });

@@ -153,0 +154,0 @@ }

@@ -6,3 +6,3 @@ export declare function getDisplayContext(): {

};
} | undefined;
};
export declare function resetDisplayContext(): void;

@@ -1,2 +0,1 @@

import { ExperimentalFeature, isExperimentalFeatureEnabled } from '@datadog/browser-core';
import { getViewportDimension, initViewportObservable } from '../../browser/viewportObservable';

@@ -6,5 +5,2 @@ var viewport;

export function getDisplayContext() {
if (!isExperimentalFeatureEnabled(ExperimentalFeature.CLICKMAP)) {
return;
}
if (!viewport) {

@@ -11,0 +7,0 @@ viewport = getViewportDimension();

@@ -1,2 +0,3 @@

import type { Context, PageExitEvent, RawError, RelativeTime, Subscription } from '@datadog/browser-core';
import type { Context, PageExitEvent, RawError, RelativeTime } from '@datadog/browser-core';
import { AbstractLifeCycle } from '@datadog/browser-core';
import type { RumPerformanceEntry } from '../browser/performanceCollection';

@@ -25,36 +26,20 @@ import type { RumEventDomainContext } from '../domainContext.types';

}
export declare class LifeCycle {
private callbacks;
notify(eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, data: RumPerformanceEntry[]): void;
notify(eventType: LifeCycleEventType.REQUEST_STARTED, data: RequestStartEvent): void;
notify(eventType: LifeCycleEventType.REQUEST_COMPLETED, data: RequestCompleteEvent): void;
notify(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, data: AutoAction): void;
notify(eventType: LifeCycleEventType.VIEW_CREATED, data: ViewCreatedEvent): void;
notify(eventType: LifeCycleEventType.VIEW_UPDATED, data: ViewEvent): void;
notify(eventType: LifeCycleEventType.VIEW_ENDED, data: ViewEndedEvent): void;
notify(eventType: LifeCycleEventType.PAGE_EXITED, data: PageExitEvent): void;
notify(eventType: LifeCycleEventType.RAW_ERROR_COLLECTED, data: {
export interface LifeCycleEventMap {
[LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED]: RumPerformanceEntry[];
[LifeCycleEventType.AUTO_ACTION_COMPLETED]: AutoAction;
[LifeCycleEventType.VIEW_CREATED]: ViewCreatedEvent;
[LifeCycleEventType.VIEW_UPDATED]: ViewEvent;
[LifeCycleEventType.VIEW_ENDED]: ViewEndedEvent;
[LifeCycleEventType.REQUEST_STARTED]: RequestStartEvent;
[LifeCycleEventType.REQUEST_COMPLETED]: RequestCompleteEvent;
[LifeCycleEventType.SESSION_EXPIRED]: void;
[LifeCycleEventType.SESSION_RENEWED]: void;
[LifeCycleEventType.PAGE_EXITED]: PageExitEvent;
[LifeCycleEventType.RAW_RUM_EVENT_COLLECTED]: RawRumEventCollectedData;
[LifeCycleEventType.RUM_EVENT_COLLECTED]: RumEvent & Context;
[LifeCycleEventType.RAW_ERROR_COLLECTED]: {
error: RawError;
savedCommonContext?: CommonContext;
customerContext?: Context;
}): void;
notify(eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED): void;
notify(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, data: RawRumEventCollectedData): void;
notify(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, data: RumEvent & Context): void;
subscribe(eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, callback: (data: RumPerformanceEntry[]) => void): Subscription;
subscribe(eventType: LifeCycleEventType.REQUEST_STARTED, callback: (data: RequestStartEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.REQUEST_COMPLETED, callback: (data: RequestCompleteEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, callback: (data: AutoAction) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_CREATED, callback: (data: ViewCreatedEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_UPDATED, callback: (data: ViewEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.VIEW_ENDED, callback: (data: ViewEndedEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.PAGE_EXITED, callback: (data: PageExitEvent) => void): Subscription;
subscribe(eventType: LifeCycleEventType.RAW_ERROR_COLLECTED, callback: (data: {
error: RawError;
savedCommonContext?: CommonContext;
customerContext?: Context;
}) => void): Subscription;
subscribe(eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED, callback: () => void): Subscription;
subscribe(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, callback: (data: RawRumEventCollectedData) => void): Subscription;
subscribe(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, callback: (data: RumEvent & Context) => void): Subscription;
};
}

@@ -68,1 +53,5 @@ export interface RawRumEventCollectedData<E extends RawRumEvent = RawRumEvent> {

}
export declare const LifeCycle: {
new (): AbstractLifeCycle<LifeCycleEventMap>;
};
export type LifeCycle = AbstractLifeCycle<LifeCycleEventMap>;

@@ -1,26 +0,3 @@

var LifeCycle = /** @class */ (function () {
function LifeCycle() {
this.callbacks = {};
}
LifeCycle.prototype.notify = function (eventType, data) {
var eventCallbacks = this.callbacks[eventType];
if (eventCallbacks) {
eventCallbacks.forEach(function (callback) { return callback(data); });
}
};
LifeCycle.prototype.subscribe = function (eventType, callback) {
var _this = this;
if (!this.callbacks[eventType]) {
this.callbacks[eventType] = [];
}
this.callbacks[eventType].push(callback);
return {
unsubscribe: function () {
_this.callbacks[eventType] = _this.callbacks[eventType].filter(function (other) { return callback !== other; });
},
};
};
return LifeCycle;
}());
export { LifeCycle };
import { AbstractLifeCycle } from '@datadog/browser-core';
export var LifeCycle = (AbstractLifeCycle);
//# sourceMappingURL=lifeCycle.js.map

@@ -7,3 +7,3 @@ import { ONE_SECOND, clearTimeout, setTimeout } from '@datadog/browser-core';

var status = 0 /* ClickChainStatus.WaitingForMoreClicks */;
var maxDurationBetweenClicksTimeout;
var maxDurationBetweenClicksTimeoutId;
appendClick(firstClick);

@@ -13,4 +13,4 @@ function appendClick(click) {

bufferedClicks.push(click);
clearTimeout(maxDurationBetweenClicksTimeout);
maxDurationBetweenClicksTimeout = setTimeout(dontAcceptMoreClick, MAX_DURATION_BETWEEN_CLICKS);
clearTimeout(maxDurationBetweenClicksTimeoutId);
maxDurationBetweenClicksTimeoutId = setTimeout(dontAcceptMoreClick, MAX_DURATION_BETWEEN_CLICKS);
}

@@ -24,3 +24,3 @@ function tryFinalize() {

function dontAcceptMoreClick() {
clearTimeout(maxDurationBetweenClicksTimeout);
clearTimeout(maxDurationBetweenClicksTimeoutId);
if (status === 0 /* ClickChainStatus.WaitingForMoreClicks */) {

@@ -27,0 +27,0 @@ status = 1 /* ClickChainStatus.WaitingForClicksToStop */;

@@ -1,2 +0,2 @@

import { includes, timeStampNow, isExperimentalFeatureEnabled, Observable, assign, getRelativeTime, ONE_MINUTE, ValueHistory, generateUUID, clocksNow, ONE_SECOND, elapsed, ExperimentalFeature, } from '@datadog/browser-core';
import { includes, timeStampNow, Observable, assign, getRelativeTime, ONE_MINUTE, ValueHistory, generateUUID, clocksNow, ONE_SECOND, elapsed, } from '@datadog/browser-core';
import { trackEventCounts } from '../../trackEventCounts';

@@ -128,21 +128,15 @@ import { PAGE_ACTIVITY_VALIDATION_DELAY, waitPageActivityEnd } from '../../waitPageActivityEnd';

function computeClickActionBase(event, actionNameAttribute) {
var target;
var position;
if (isExperimentalFeatureEnabled(ExperimentalFeature.CLICKMAP)) {
var rect = event.target.getBoundingClientRect();
target = {
var rect = event.target.getBoundingClientRect();
return {
type: "click" /* ActionType.CLICK */,
target: {
width: Math.round(rect.width),
height: Math.round(rect.height),
selector: getSelectorFromElement(event.target, actionNameAttribute),
};
position = {
},
position: {
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element
x: Math.round(event.clientX - rect.left),
y: Math.round(event.clientY - rect.top),
};
}
return {
type: "click" /* ActionType.CLICK */,
target: target,
position: position,
},
name: getActionNameFromElement(event.target, actionNameAttribute),

@@ -149,0 +143,0 @@ };

@@ -1,2 +0,2 @@

import { PROVIDED_ERROR_MESSAGE_PREFIX, isEmptyObject, assign, ErrorSource, generateUUID, computeRawError, computeStackTrace, Observable, trackRuntimeError, } from '@datadog/browser-core';
import { isEmptyObject, assign, ErrorSource, generateUUID, computeRawError, computeStackTrace, Observable, trackRuntimeError, } from '@datadog/browser-core';
import { trackConsoleError } from './trackConsoleError';

@@ -29,3 +29,3 @@ import { trackReportError } from './trackReportError';

startClocks: startClocks,
nonErrorPrefix: PROVIDED_ERROR_MESSAGE_PREFIX,
nonErrorPrefix: "Provided" /* NonErrorPrefix.PROVIDED */,
source: ErrorSource.CUSTOM,

@@ -32,0 +32,0 @@ handling: "handled" /* ErrorHandling.HANDLED */,

import type { Duration, RelativeTime } from '@datadog/browser-core';
import type { LifeCycle } from '../../lifeCycle';
export declare const TIMING_MAXIMUM_DELAY: number;
/**
* The initial view can finish quickly, before some metrics can be produced (ex: before the page load
* event, or the first input). Also, we don't want to trigger a view update indefinitely, to avoid
* updates on views that ended a long time ago. Keep watching for metrics after the view ends for a
* limited amount of time.
*/
export declare const KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY: number;
export interface Timings {

@@ -15,6 +22,15 @@ firstContentfulPaint?: Duration;

}
export declare function trackInitialViewTimings(lifeCycle: LifeCycle, callback: (timings: Timings) => void): {
export declare function trackInitialViewTimings(lifeCycle: LifeCycle, setLoadEvent: (loadEnd: Duration) => void, scheduleViewUpdate: () => void): {
stop: () => void;
timings: Timings;
scheduleStop: () => void;
};
export declare function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: Partial<Timings>) => void): {
interface NavigationTimings {
domComplete: Duration;
domContentLoaded: Duration;
domInteractive: Duration;
loadEvent: Duration;
firstByte: Duration | undefined;
}
export declare function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: NavigationTimings) => void): {
stop: () => void;

@@ -48,1 +64,2 @@ };

};
export {};

@@ -1,2 +0,2 @@

import { assign, addEventListeners, elapsed, ONE_MINUTE, find, findLast, relativeNow, } from '@datadog/browser-core';
import { setTimeout, assign, addEventListeners, elapsed, ONE_MINUTE, find, findLast, relativeNow, } from '@datadog/browser-core';
import { trackFirstHidden } from './trackFirstHidden';

@@ -6,9 +6,19 @@ // Discard LCP and FCP timings above a certain delay to avoid incorrect data

export var TIMING_MAXIMUM_DELAY = 10 * ONE_MINUTE;
export function trackInitialViewTimings(lifeCycle, callback) {
/**
* The initial view can finish quickly, before some metrics can be produced (ex: before the page load
* event, or the first input). Also, we don't want to trigger a view update indefinitely, to avoid
* updates on views that ended a long time ago. Keep watching for metrics after the view ends for a
* limited amount of time.
*/
export var KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY = 5 * ONE_MINUTE;
export function trackInitialViewTimings(lifeCycle, setLoadEvent, scheduleViewUpdate) {
var timings = {};
function setTimings(newTimings) {
assign(timings, newTimings);
callback(timings);
scheduleViewUpdate();
}
var stopNavigationTracking = trackNavigationTimings(lifeCycle, setTimings).stop;
var stopNavigationTracking = trackNavigationTimings(lifeCycle, function (newTimings) {
setLoadEvent(newTimings.loadEvent);
setTimings(newTimings);
}).stop;
var stopFCPTracking = trackFirstContentfulPaintTiming(lifeCycle, function (firstContentfulPaint) {

@@ -29,8 +39,13 @@ return setTimings({ firstContentfulPaint: firstContentfulPaint });

}).stop;
function stop() {
stopNavigationTracking();
stopFCPTracking();
stopLCPTracking();
stopFIDTracking();
}
return {
stop: function () {
stopNavigationTracking();
stopFCPTracking();
stopLCPTracking();
stopFIDTracking();
stop: stop,
timings: timings,
scheduleStop: function () {
setTimeout(stop, KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY);
},

@@ -37,0 +52,0 @@ };

@@ -22,2 +22,3 @@ import type { Duration, ClocksState, TimeStamp, Observable, RelativeTime } from '@datadog/browser-core';

isActive: boolean;
sessionIsActive: boolean;
loadingTime?: Duration;

@@ -24,0 +25,0 @@ loadingType: ViewLoadingType;

@@ -1,2 +0,2 @@

import { PageExitReason, shallowClone, assign, elapsed, generateUUID, ONE_MINUTE, throttle, clocksNow, clocksOrigin, timeStampNow, display, looksLikeRelativeTime, setInterval, clearInterval, } from '@datadog/browser-core';
import { noop, PageExitReason, shallowClone, assign, elapsed, generateUUID, ONE_MINUTE, throttle, clocksNow, clocksOrigin, timeStampNow, display, looksLikeRelativeTime, setInterval, clearInterval, } from '@datadog/browser-core';
import { trackInitialViewTimings } from './trackInitialViewTimings';

@@ -8,5 +8,4 @@ import { trackViewMetrics } from './trackViewMetrics';

export function trackViews(location, lifeCycle, domMutationObservable, configuration, locationChangeObservable, areViewsTrackedAutomatically, initialViewOptions) {
var _a = trackInitialView(initialViewOptions), stopInitialViewTracking = _a.stop, initialView = _a.initialView;
var currentView = initialView;
var stopViewLifeCycle = startViewLifeCycle().stop;
var currentView = startNewView("initial_load" /* ViewLoadingType.INITIAL_LOAD */, clocksOrigin(), initialViewOptions);
startViewLifeCycle();
var locationChangeSubscription;

@@ -16,19 +15,9 @@ if (areViewsTrackedAutomatically) {

}
function trackInitialView(options) {
var initialView = newView(lifeCycle, domMutationObservable, configuration, location, "initial_load" /* ViewLoadingType.INITIAL_LOAD */, clocksOrigin(), options);
var stop = trackInitialViewTimings(lifeCycle, function (timings) {
initialView.updateTimings(timings);
initialView.scheduleUpdate();
}).stop;
return { initialView: initialView, stop: stop };
function startNewView(loadingType, startClocks, viewOptions) {
return newView(lifeCycle, domMutationObservable, configuration, location, loadingType, startClocks, viewOptions);
}
function trackViewChange(startClocks, viewOptions) {
return newView(lifeCycle, domMutationObservable, configuration, location, "route_change" /* ViewLoadingType.ROUTE_CHANGE */, startClocks, viewOptions);
}
function startViewLifeCycle() {
lifeCycle.subscribe(8 /* LifeCycleEventType.SESSION_RENEWED */, function () {
// do not trigger view update to avoid wrong data
currentView.end();
// Renew view on session renewal
currentView = trackViewChange(undefined, {
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */, undefined, {
name: currentView.name,

@@ -39,2 +28,5 @@ service: currentView.service,

});
lifeCycle.subscribe(7 /* LifeCycleEventType.SESSION_EXPIRED */, function () {
currentView.end({ sessionIsActive: false });
});
// End the current view on page unload

@@ -44,14 +36,4 @@ lifeCycle.subscribe(9 /* LifeCycleEventType.PAGE_EXITED */, function (pageExitEvent) {

currentView.end();
currentView.triggerUpdate();
}
});
// Session keep alive
var keepAliveInterval = setInterval(function () {
currentView.triggerUpdate();
}, SESSION_KEEP_ALIVE_INTERVAL);
return {
stop: function () {
clearInterval(keepAliveInterval);
},
};
}

@@ -63,5 +45,3 @@ function renewViewOnLocationChange(locationChangeObservable) {

currentView.end();
currentView.triggerUpdate();
currentView = trackViewChange();
return;
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */);
}

@@ -74,13 +54,9 @@ });

currentView.addTiming(name, time);
currentView.scheduleUpdate();
},
startView: function (options, startClocks) {
currentView.end(startClocks);
currentView.triggerUpdate();
currentView = trackViewChange(startClocks, options);
currentView.end({ endClocks: startClocks });
currentView = startNewView("route_change" /* ViewLoadingType.ROUTE_CHANGE */, startClocks, options);
},
stop: function () {
locationChangeSubscription === null || locationChangeSubscription === void 0 ? void 0 : locationChangeSubscription.unsubscribe();
stopInitialViewTracking();
stopViewLifeCycle();
currentView.end();

@@ -94,3 +70,2 @@ },

var id = generateUUID();
var timings = {};
var customTimings = {};

@@ -100,2 +75,3 @@ var documentVersion = 0;

var location = shallowClone(initialLocation);
var sessionIsActive = true;
var name;

@@ -121,6 +97,12 @@ var service;

var _b = trackViewMetrics(lifeCycle, domMutationObservable, configuration, scheduleViewUpdate, loadingType, startClocks), setLoadEvent = _b.setLoadEvent, stopViewMetricsTracking = _b.stop, viewMetrics = _b.viewMetrics;
var _c = trackViewEventCounts(lifeCycle, id, scheduleViewUpdate), scheduleStopEventCountsTracking = _c.scheduleStop, eventCounts = _c.eventCounts;
var _c = loadingType === "initial_load" /* ViewLoadingType.INITIAL_LOAD */
? trackInitialViewTimings(lifeCycle, setLoadEvent, scheduleViewUpdate)
: { scheduleStop: noop, timings: {} }, scheduleStopInitialViewTimingsTracking = _c.scheduleStop, timings = _c.timings;
var _d = trackViewEventCounts(lifeCycle, id, scheduleViewUpdate), scheduleStopEventCountsTracking = _d.scheduleStop, eventCounts = _d.eventCounts;
// Session keep alive
var keepAliveIntervalId = setInterval(triggerViewUpdate, SESSION_KEEP_ALIVE_INTERVAL);
// Initial view update
triggerViewUpdate();
function triggerViewUpdate() {
cancelScheduleViewUpdate();
documentVersion += 1;

@@ -141,2 +123,3 @@ var currentEnd = endClocks === undefined ? timeStampNow() : endClocks.timeStamp;

isActive: endClocks === undefined,
sessionIsActive: sessionIsActive,
eventCounts: eventCounts,

@@ -149,24 +132,25 @@ }, viewMetrics));

version: version,
scheduleUpdate: scheduleViewUpdate,
end: function (clocks) {
if (clocks === void 0) { clocks = clocksNow(); }
endClocks = clocks;
end: function (options) {
var _a, _b;
if (options === void 0) { options = {}; }
if (endClocks) {
// view already ended
return;
}
endClocks = (_a = options.endClocks) !== null && _a !== void 0 ? _a : clocksNow();
sessionIsActive = (_b = options.sessionIsActive) !== null && _b !== void 0 ? _b : true;
lifeCycle.notify(4 /* LifeCycleEventType.VIEW_ENDED */, { endClocks: endClocks });
clearInterval(keepAliveIntervalId);
stopViewMetricsTracking();
scheduleStopInitialViewTimingsTracking();
scheduleStopEventCountsTracking();
},
triggerUpdate: function () {
// cancel any pending view updates execution
cancelScheduleViewUpdate();
triggerViewUpdate();
},
updateTimings: function (newTimings) {
timings = newTimings;
if (newTimings.loadEvent !== undefined) {
setLoadEvent(newTimings.loadEvent);
addTiming: function (name, time) {
if (endClocks) {
return;
}
},
addTiming: function (name, time) {
var relativeTime = looksLikeRelativeTime(time) ? time : elapsed(startClocks.timeStamp, time);
customTimings[sanitizeTiming(name)] = relativeTime;
scheduleViewUpdate();
},

@@ -173,0 +157,0 @@ };

@@ -55,2 +55,3 @@ import { isEmptyObject, mapValues, toServerDuration, isNumber } from '@datadog/browser-core';

has_replay: replayStats ? true : undefined,
is_active: view.sessionIsActive ? undefined : false,
},

@@ -57,0 +58,0 @@ };

import type { RelativeTime } from '@datadog/browser-core';
import { Observable } from '@datadog/browser-core';
import type { RumConfiguration } from './configuration';

@@ -8,2 +9,3 @@ import type { LifeCycle } from './lifeCycle';

expire: () => void;
expireObservable: Observable<void>;
}

@@ -10,0 +12,0 @@ export type RumSession = {

@@ -1,2 +0,2 @@

import { noop, performDraw, startSessionManager } from '@datadog/browser-core';
import { Observable, noop, performDraw, startSessionManager } from '@datadog/browser-core';
export var RUM_SESSION_KEY = 'rum';

@@ -35,2 +35,3 @@ export function startRumSessionManager(configuration, lifeCycle) {

expire: sessionManager.expire,
expireObservable: sessionManager.expireObservable,
};

@@ -52,2 +53,3 @@ }

expire: noop,
expireObservable: new Observable(),
};

@@ -54,0 +56,0 @@ }

@@ -44,4 +44,5 @@ import { instrumentMethodAndCallOriginal, matchList, monitor, Observable, timeStampNow, setTimeout, clearTimeout, } from '@datadog/browser-core';

var validationTimeoutId = setTimeout(monitor(function () { return complete({ hadActivity: false }); }), PAGE_ACTIVITY_VALIDATION_DELAY);
var maxDurationTimeoutId = maxDuration &&
setTimeout(monitor(function () { return complete({ hadActivity: true, end: timeStampNow() }); }), maxDuration);
var maxDurationTimeoutId = maxDuration !== undefined
? setTimeout(monitor(function () { return complete({ hadActivity: true, end: timeStampNow() }); }), maxDuration)
: undefined;
var pageActivitySubscription = pageActivityObservable.subscribe(function (_a) {

@@ -48,0 +49,0 @@ var isBusy = _a.isBusy;

@@ -91,2 +91,3 @@ import type { Context, Duration, ErrorSource, ErrorHandling, ResourceType, ServerDuration, TimeStamp, RawErrorCause } from '@datadog/browser-core';

has_replay: true | undefined;
is_active: false | undefined;
};

@@ -93,0 +94,0 @@ feature_flags?: Context;

@@ -683,2 +683,16 @@ /**

/**
* Session properties
*/
readonly session?: {
/**
* The precondition that led to the creation of the session
*/
readonly start_precondition?: 'app_launch' | 'inactivity_timeout' | 'max_duration' | 'explicit_stop' | 'background_event';
/**
* Whether this session is currently active. Set to false to manually stop a session
*/
readonly is_active?: boolean;
[k: string]: unknown;
};
/**
* Feature flags properties

@@ -685,0 +699,0 @@ */

import type { Context, TelemetryEvent, Observable, RawError, PageExitEvent, FlushEvent } from '@datadog/browser-core';
import type { RumConfiguration } from '../domain/configuration';
import type { LifeCycle } from '../domain/lifeCycle';
export declare function startRumBatch(configuration: RumConfiguration, lifeCycle: LifeCycle, telemetryEventObservable: Observable<TelemetryEvent & Context>, reportError: (error: RawError) => void, pageExitObservable: Observable<PageExitEvent>): RumBatch;
export declare function startRumBatch(configuration: RumConfiguration, lifeCycle: LifeCycle, telemetryEventObservable: Observable<TelemetryEvent & Context>, reportError: (error: RawError) => void, pageExitObservable: Observable<PageExitEvent>, sessionExpireObservable: Observable<void>): RumBatch;
export interface RumBatch {

@@ -6,0 +6,0 @@ flushObservable: Observable<FlushEvent>;

import { createFlushController, Batch, combine, createHttpRequest, isTelemetryReplicationAllowed, } from '@datadog/browser-core';
export function startRumBatch(configuration, lifeCycle, telemetryEventObservable, reportError, pageExitObservable) {
var batch = makeRumBatch(configuration, reportError, pageExitObservable);
export function startRumBatch(configuration, lifeCycle, telemetryEventObservable, reportError, pageExitObservable, sessionExpireObservable) {
var batch = makeRumBatch(configuration, reportError, pageExitObservable, sessionExpireObservable);
lifeCycle.subscribe(11 /* LifeCycleEventType.RUM_EVENT_COLLECTED */, function (serverRumEvent) {

@@ -15,3 +15,3 @@ if (serverRumEvent.type === "view" /* RumEventType.VIEW */) {

}
function makeRumBatch(configuration, reportError, pageExitObservable) {
function makeRumBatch(configuration, reportError, pageExitObservable, sessionExpireObservable) {
var _a = createRumBatch(configuration.rumEndpointBuilder), primaryBatch = _a.batch, primaryFlushController = _a.flushController;

@@ -29,2 +29,3 @@ var replicaBatch;

pageExitObservable: pageExitObservable,
sessionExpireObservable: sessionExpireObservable,
});

@@ -31,0 +32,0 @@ var batch = new Batch(createHttpRequest(endpointBuilder, configuration.batchBytesLimit, reportError), flushController, configuration.messageBytesLimit);

{
"name": "@datadog/browser-rum-core",
"version": "4.39.0",
"version": "4.40.0",
"license": "Apache-2.0",

@@ -15,3 +15,3 @@ "main": "cjs/index.js",

"dependencies": {
"@datadog/browser-core": "4.39.0"
"@datadog/browser-core": "4.40.0"
},

@@ -29,3 +29,3 @@ "devDependencies": {

},
"gitHead": "128da89b469c77d6cfdabca15eb3d7ac09c3d5ae"
"gitHead": "4a291821c3e07385b7ab76566bb6aa5d4cc5a80f"
}

@@ -74,2 +74,4 @@ import type { Observable, TelemetryEvent, RawError, ContextManager } from '@datadog/browser-core'

const session = !canUseEventBridge() ? startRumSessionManager(configuration, lifeCycle) : startRumSessionManagerStub()
if (!canUseEventBridge()) {

@@ -80,3 +82,10 @@ const pageExitObservable = createPageExitObservable()

})
const batch = startRumBatch(configuration, lifeCycle, telemetry.observable, reportError, pageExitObservable)
const batch = startRumBatch(
configuration,
lifeCycle,
telemetry.observable,
reportError,
pageExitObservable,
session.expireObservable
)
startCustomerDataTelemetry(

@@ -95,3 +104,2 @@ configuration,

const session = !canUseEventBridge() ? startRumSessionManager(configuration, lifeCycle) : startRumSessionManagerStub()
const domMutationObservable = createDOMMutationObservable()

@@ -98,0 +106,0 @@ const locationChangeObservable = createLocationChangeObservable(location)

@@ -537,15 +537,2 @@ import type { ClocksState, RelativeTime } from '@datadog/browser-core'

})
it('should get current session state for view event', () => {
const rumSessionManager = createRumSessionManagerMock()
spyOn(rumSessionManager, 'findTrackedSession').and.callThrough()
const { lifeCycle } = setupBuilder.withSessionManager(rumSessionManager).build()
notifyRawRumEvent(lifeCycle, {
rawRumEvent: createRawRumEvent(RumEventType.VIEW),
startTime: 123 as RelativeTime,
})
expect(rumSessionManager.findTrackedSession).toHaveBeenCalledWith(undefined)
})
})

@@ -561,2 +548,3 @@

has_replay: undefined,
is_active: undefined,
id: '1234',

@@ -563,0 +551,0 @@ type: 'user',

@@ -92,6 +92,3 @@ import type { Context, RawError, EventRateLimiter, User } from '@datadog/browser-core'

const urlContext = urlContexts.findUrl(startTime)
// allow to send events if the session was tracked when they start
// except for views which are continuously updated
// TODO: stop sending view updates when session is expired
const session = sessionManager.findTrackedSession(rawRumEvent.type !== RumEventType.VIEW ? startTime : undefined)
const session = sessionManager.findTrackedSession(startTime)
if (session && viewContext && urlContext) {

@@ -98,0 +95,0 @@ const commonContext = savedCommonContext || buildCommonContext()

@@ -544,4 +544,12 @@ import { DefaultPrivacyLevel, display } from '@datadog/browser-core'

})
it('should survive a configuration with wrong parameters', () => {
const wrongTracingConfig: RumInitConfiguration = {
...DEFAULT_INIT_CONFIGURATION,
allowedTracingUrls: [42 as any, { match: 'test', propagatorTypes: 42 }, undefined, null, {}],
}
expect(serializeRumConfiguration(wrongTracingConfig).selected_tracing_propagators).toEqual([])
})
})
})
})
import type { Configuration, InitConfiguration, MatchOption, RawTelemetryConfiguration } from '@datadog/browser-core'
import {
getType,
arrayFrom,

@@ -260,3 +261,4 @@ getOrigin,

usedTracingPropagators.add('datadog')
} else {
} else if (getType(option) === 'object' && Array.isArray(option.propagatorTypes)) {
// Ensure we have an array, as we cannot rely on types yet (configuration is provided by users)
option.propagatorTypes.forEach((propagatorType) => usedTracingPropagators.add(propagatorType))

@@ -263,0 +265,0 @@ }

@@ -1,2 +0,1 @@

import { ExperimentalFeature, resetExperimentalFeatures, addExperimentalFeatures } from '@datadog/browser-core'
import { getDisplayContext, resetDisplayContext } from './displayContext'

@@ -6,9 +5,6 @@

afterEach(() => {
resetExperimentalFeatures()
resetDisplayContext()
})
it('should return current display context when ff enabled', () => {
addExperimentalFeatures([ExperimentalFeature.CLICKMAP])
it('should return current display context', () => {
expect(getDisplayContext()).toEqual({

@@ -21,6 +17,2 @@ viewport: {

})
it('should not return current display context when ff disabled', () => {
expect(getDisplayContext()).not.toBeDefined()
})
})

@@ -1,2 +0,1 @@

import { ExperimentalFeature, isExperimentalFeatureEnabled } from '@datadog/browser-core'
import { getViewportDimension, initViewportObservable } from '../../browser/viewportObservable'

@@ -8,6 +7,2 @@

export function getDisplayContext() {
if (!isExperimentalFeatureEnabled(ExperimentalFeature.CLICKMAP)) {
return
}
if (!viewport) {

@@ -14,0 +9,0 @@ viewport = getViewportDimension()

@@ -1,2 +0,3 @@

import type { Context, PageExitEvent, RawError, RelativeTime, Subscription } from '@datadog/browser-core'
import type { Context, PageExitEvent, RawError, RelativeTime } from '@datadog/browser-core'
import { AbstractLifeCycle } from '@datadog/browser-core'
import type { RumPerformanceEntry } from '../browser/performanceCollection'

@@ -39,68 +40,22 @@ import type { RumEventDomainContext } from '../domainContext.types'

export class LifeCycle {
private callbacks: { [key in LifeCycleEventType]?: Array<(data: any) => void> } = {}
notify(eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, data: RumPerformanceEntry[]): void
notify(eventType: LifeCycleEventType.REQUEST_STARTED, data: RequestStartEvent): void
notify(eventType: LifeCycleEventType.REQUEST_COMPLETED, data: RequestCompleteEvent): void
notify(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, data: AutoAction): void
notify(eventType: LifeCycleEventType.VIEW_CREATED, data: ViewCreatedEvent): void
notify(eventType: LifeCycleEventType.VIEW_UPDATED, data: ViewEvent): void
notify(eventType: LifeCycleEventType.VIEW_ENDED, data: ViewEndedEvent): void
notify(eventType: LifeCycleEventType.PAGE_EXITED, data: PageExitEvent): void
notify(
eventType: LifeCycleEventType.RAW_ERROR_COLLECTED,
data: { error: RawError; savedCommonContext?: CommonContext; customerContext?: Context }
): void
notify(eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED): void
notify(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, data: RawRumEventCollectedData): void
notify(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, data: RumEvent & Context): void
notify(eventType: LifeCycleEventType, data?: any) {
const eventCallbacks = this.callbacks[eventType]
if (eventCallbacks) {
eventCallbacks.forEach((callback) => callback(data))
}
// Note: this interface needs to be exported even if it is not used outside of this module, else TS
// fails to build the rum-core package with error TS4058
export interface LifeCycleEventMap {
[LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED]: RumPerformanceEntry[]
[LifeCycleEventType.AUTO_ACTION_COMPLETED]: AutoAction
[LifeCycleEventType.VIEW_CREATED]: ViewCreatedEvent
[LifeCycleEventType.VIEW_UPDATED]: ViewEvent
[LifeCycleEventType.VIEW_ENDED]: ViewEndedEvent
[LifeCycleEventType.REQUEST_STARTED]: RequestStartEvent
[LifeCycleEventType.REQUEST_COMPLETED]: RequestCompleteEvent
[LifeCycleEventType.SESSION_EXPIRED]: void
[LifeCycleEventType.SESSION_RENEWED]: void
[LifeCycleEventType.PAGE_EXITED]: PageExitEvent
[LifeCycleEventType.RAW_RUM_EVENT_COLLECTED]: RawRumEventCollectedData
[LifeCycleEventType.RUM_EVENT_COLLECTED]: RumEvent & Context
[LifeCycleEventType.RAW_ERROR_COLLECTED]: {
error: RawError
savedCommonContext?: CommonContext
customerContext?: Context
}
subscribe(
eventType: LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED,
callback: (data: RumPerformanceEntry[]) => void
): Subscription
subscribe(eventType: LifeCycleEventType.REQUEST_STARTED, callback: (data: RequestStartEvent) => void): Subscription
subscribe(
eventType: LifeCycleEventType.REQUEST_COMPLETED,
callback: (data: RequestCompleteEvent) => void
): Subscription
subscribe(eventType: LifeCycleEventType.AUTO_ACTION_COMPLETED, callback: (data: AutoAction) => void): Subscription
subscribe(eventType: LifeCycleEventType.VIEW_CREATED, callback: (data: ViewCreatedEvent) => void): Subscription
subscribe(eventType: LifeCycleEventType.VIEW_UPDATED, callback: (data: ViewEvent) => void): Subscription
subscribe(eventType: LifeCycleEventType.VIEW_ENDED, callback: (data: ViewEndedEvent) => void): Subscription
subscribe(eventType: LifeCycleEventType.PAGE_EXITED, callback: (data: PageExitEvent) => void): Subscription
subscribe(
eventType: LifeCycleEventType.RAW_ERROR_COLLECTED,
callback: (data: { error: RawError; savedCommonContext?: CommonContext; customerContext?: Context }) => void
): Subscription
subscribe(
eventType: LifeCycleEventType.SESSION_EXPIRED | LifeCycleEventType.SESSION_RENEWED,
callback: () => void
): Subscription
subscribe(
eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED,
callback: (data: RawRumEventCollectedData) => void
): Subscription
subscribe(
eventType: LifeCycleEventType.RUM_EVENT_COLLECTED,
callback: (data: RumEvent & Context) => void
): Subscription
subscribe(eventType: LifeCycleEventType, callback: (data?: any) => void) {
if (!this.callbacks[eventType]) {
this.callbacks[eventType] = []
}
this.callbacks[eventType]!.push(callback)
return {
unsubscribe: () => {
this.callbacks[eventType] = this.callbacks[eventType]!.filter((other) => callback !== other)
},
}
}
}

@@ -115,1 +70,4 @@

}
export const LifeCycle = AbstractLifeCycle<LifeCycleEventMap>
export type LifeCycle = AbstractLifeCycle<LifeCycleEventMap>

@@ -0,1 +1,2 @@

import type { TimeoutId } from '@datadog/browser-core'
import { ONE_SECOND, clearTimeout, setTimeout } from '@datadog/browser-core'

@@ -21,3 +22,3 @@ import type { Click } from './trackClickActions'

let status = ClickChainStatus.WaitingForMoreClicks
let maxDurationBetweenClicksTimeout: number | undefined
let maxDurationBetweenClicksTimeoutId: TimeoutId | undefined
appendClick(firstClick)

@@ -28,4 +29,4 @@

bufferedClicks.push(click)
clearTimeout(maxDurationBetweenClicksTimeout)
maxDurationBetweenClicksTimeout = setTimeout(dontAcceptMoreClick, MAX_DURATION_BETWEEN_CLICKS)
clearTimeout(maxDurationBetweenClicksTimeoutId)
maxDurationBetweenClicksTimeoutId = setTimeout(dontAcceptMoreClick, MAX_DURATION_BETWEEN_CLICKS)
}

@@ -41,3 +42,3 @@

function dontAcceptMoreClick() {
clearTimeout(maxDurationBetweenClicksTimeout)
clearTimeout(maxDurationBetweenClicksTimeoutId)
if (status === ClickChainStatus.WaitingForMoreClicks) {

@@ -44,0 +45,0 @@ status = ClickChainStatus.WaitingForClicksToStop

import type { Context, Duration } from '@datadog/browser-core'
import {
addDuration,
addExperimentalFeatures,
resetExperimentalFeatures,
clocksNow,
timeStampNow,
relativeNow,
ExperimentalFeature,
} from '@datadog/browser-core'
import { addDuration, clocksNow, timeStampNow, relativeNow } from '@datadog/browser-core'
import { createNewEvent } from '@datadog/browser-core/test'

@@ -108,4 +100,8 @@ import type { TestSetupBuilder } from '../../../../test'

frustrationTypes: [],
target: undefined,
position: undefined,
target: {
selector: '#button',
width: 100,
height: 100,
},
position: { x: 50, y: 50 },
events: [domEvent],

@@ -116,28 +112,2 @@ },

describe('when clickmap ff is enabled', () => {
beforeEach(() => {
addExperimentalFeatures([ExperimentalFeature.CLICKMAP])
})
afterEach(() => {
resetExperimentalFeatures()
})
it('should set click position and target', () => {
const { clock } = setupBuilder.build()
emulateClick({ activity: {} })
clock.tick(EXPIRE_DELAY)
expect(events[0]).toEqual(
jasmine.objectContaining({
target: {
selector: '#button',
width: 100,
height: 100,
},
position: { x: 50, y: 50 },
})
)
})
})
it('should keep track of previously validated click actions', () => {

@@ -144,0 +114,0 @@ const { clock } = setupBuilder.build()

@@ -5,3 +5,2 @@ import type { Duration, ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core'

timeStampNow,
isExperimentalFeatureEnabled,
Observable,

@@ -16,3 +15,2 @@ assign,

elapsed,
ExperimentalFeature,
} from '@datadog/browser-core'

@@ -245,23 +243,15 @@ import type { FrustrationType } from '../../../rawRumEvent.types'

function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string): ClickActionBase {
let target: ClickAction['target']
let position: ClickAction['position']
if (isExperimentalFeatureEnabled(ExperimentalFeature.CLICKMAP)) {
const rect = event.target.getBoundingClientRect()
target = {
const rect = event.target.getBoundingClientRect()
return {
type: ActionType.CLICK,
target: {
width: Math.round(rect.width),
height: Math.round(rect.height),
selector: getSelectorFromElement(event.target, actionNameAttribute),
}
position = {
},
position: {
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element
x: Math.round(event.clientX - rect.left),
y: Math.round(event.clientY - rect.top),
}
}
return {
type: ActionType.CLICK,
target,
position,
},
name: getActionNameFromElement(event.target, actionNameAttribute),

@@ -268,0 +258,0 @@ }

import type { Context, RawError, ClocksState } from '@datadog/browser-core'
import {
PROVIDED_ERROR_MESSAGE_PREFIX,
isEmptyObject,

@@ -13,2 +12,3 @@ assign,

trackRuntimeError,
NonErrorPrefix,
} from '@datadog/browser-core'

@@ -77,3 +77,3 @@ import type { RawRumErrorEvent } from '../../../rawRumEvent.types'

startClocks,
nonErrorPrefix: PROVIDED_ERROR_MESSAGE_PREFIX,
nonErrorPrefix: NonErrorPrefix.PROVIDED,
source: ErrorSource.CUSTOM,

@@ -80,0 +80,0 @@ handling: ErrorHandling.HANDLED,

@@ -18,2 +18,3 @@ import type { BuildContext } from '../../../../test'

lifeCycle.subscribe(LifeCycleEventType.VIEW_UPDATED, viewUpdateHandler)
const {

@@ -25,2 +26,6 @@ handler: viewCreateHandler,

lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, viewCreateHandler)
const { handler: viewEndHandler, getViewEvent: getViewEnd, getHandledCount: getViewEndCount } = spyOnViews('view end')
lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, viewEndHandler)
const { stop, startView, addTiming } = trackViews(

@@ -43,2 +48,4 @@ location,

getViewCreateCount,
getViewEnd,
getViewEndCount,
getLatestViewContext: () => ({

@@ -45,0 +52,0 @@ id: getViewCreate(getViewCreateCount() - 1).id,

@@ -16,2 +16,3 @@ import type { Duration, RelativeTime } from '@datadog/browser-core'

import {
KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY,
trackFirstContentfulPaintTiming,

@@ -52,9 +53,15 @@ trackFirstInputTimings,

describe('trackTimings', () => {
describe('trackInitialViewTimings', () => {
let setupBuilder: TestSetupBuilder
let timingsCallback: jasmine.Spy<(value: Partial<Timings>) => void>
let scheduleViewUpdateSpy: jasmine.Spy<() => void>
let trackInitialViewTimingsResult: ReturnType<typeof trackInitialViewTimings>
let setLoadEventSpy: jasmine.Spy<(loadEvent: Duration) => void>
beforeEach(() => {
timingsCallback = jasmine.createSpy()
setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackInitialViewTimings(lifeCycle, timingsCallback))
scheduleViewUpdateSpy = jasmine.createSpy()
setLoadEventSpy = jasmine.createSpy()
setupBuilder = setup().beforeBuild(({ lifeCycle }) => {
trackInitialViewTimingsResult = trackInitialViewTimings(lifeCycle, setLoadEventSpy, scheduleViewUpdateSpy)
return trackInitialViewTimingsResult
})
})

@@ -75,4 +82,4 @@

expect(timingsCallback).toHaveBeenCalledTimes(3)
expect(timingsCallback.calls.mostRecent().args[0]).toEqual({
expect(scheduleViewUpdateSpy).toHaveBeenCalledTimes(3)
expect(trackInitialViewTimingsResult.timings).toEqual({
firstByte: 123 as Duration,

@@ -88,2 +95,29 @@ domComplete: 456 as Duration,

})
it('allows delaying the stop logic', () => {
const { lifeCycle, clock } = setupBuilder.withFakeClock().build()
trackInitialViewTimingsResult.scheduleStop()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [FAKE_NAVIGATION_ENTRY])
expect(scheduleViewUpdateSpy).toHaveBeenCalledTimes(1)
clock.tick(KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY)
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [FAKE_PAINT_ENTRY])
expect(scheduleViewUpdateSpy).toHaveBeenCalledTimes(1)
})
it('calls the `setLoadEvent` callback when the loadEvent timing is known', () => {
const { lifeCycle } = setupBuilder.build()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
FAKE_NAVIGATION_ENTRY,
FAKE_PAINT_ENTRY,
FAKE_FIRST_INPUT_ENTRY,
])
expect(setLoadEventSpy).toHaveBeenCalledOnceWith(567 as Duration)
})
})

@@ -90,0 +124,0 @@

import type { Duration, RelativeTime } from '@datadog/browser-core'
import {
setTimeout,
assign,

@@ -26,2 +27,10 @@ addEventListeners,

/**
* The initial view can finish quickly, before some metrics can be produced (ex: before the page load
* event, or the first input). Also, we don't want to trigger a view update indefinitely, to avoid
* updates on views that ended a long time ago. Keep watching for metrics after the view ends for a
* limited amount of time.
*/
export const KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY = 5 * ONE_MINUTE
export interface Timings {

@@ -39,10 +48,18 @@ firstContentfulPaint?: Duration

export function trackInitialViewTimings(lifeCycle: LifeCycle, callback: (timings: Timings) => void) {
export function trackInitialViewTimings(
lifeCycle: LifeCycle,
setLoadEvent: (loadEnd: Duration) => void,
scheduleViewUpdate: () => void
) {
const timings: Timings = {}
function setTimings(newTimings: Partial<Timings>) {
assign(timings, newTimings)
callback(timings)
scheduleViewUpdate()
}
const { stop: stopNavigationTracking } = trackNavigationTimings(lifeCycle, setTimings)
const { stop: stopNavigationTracking } = trackNavigationTimings(lifeCycle, (newTimings) => {
setLoadEvent(newTimings.loadEvent)
setTimings(newTimings)
})
const { stop: stopFCPTracking } = trackFirstContentfulPaintTiming(lifeCycle, (firstContentfulPaint) =>

@@ -63,8 +80,14 @@ setTimings({ firstContentfulPaint })

function stop() {
stopNavigationTracking()
stopFCPTracking()
stopLCPTracking()
stopFIDTracking()
}
return {
stop: () => {
stopNavigationTracking()
stopFCPTracking()
stopLCPTracking()
stopFIDTracking()
stop,
timings,
scheduleStop: () => {
setTimeout(stop, KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY)
},

@@ -74,3 +97,11 @@ }

export function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: Partial<Timings>) => void) {
interface NavigationTimings {
domComplete: Duration
domContentLoaded: Duration
domInteractive: Duration
loadEvent: Duration
firstByte: Duration | undefined
}
export function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: NavigationTimings) => void) {
const { unsubscribe: stop } = lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => {

@@ -77,0 +108,0 @@ for (const entry of entries) {

@@ -21,5 +21,6 @@ import type { Context, Duration, RelativeTime } from '@datadog/browser-core'

import type { ViewEvent } from './trackViews'
import { THROTTLE_VIEW_UPDATE_PERIOD } from './trackViews'
import { SESSION_KEEP_ALIVE_INTERVAL, THROTTLE_VIEW_UPDATE_PERIOD } from './trackViews'
import type { ViewTest } from './setupViewTest.specHelper'
import { setupViewTest } from './setupViewTest.specHelper'
import { KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY } from './trackInitialViewTimings'

@@ -259,6 +260,27 @@ const FAKE_PAINT_ENTRY: RumPerformancePaintTiming = {

})
it('should not update timings long after the view ended', () => {
const { lifeCycle, clock } = setupBuilder.withFakeClock().build()
const { getViewUpdateCount, startView } = viewTest
startView()
clock.tick(KEEP_TRACKING_TIMINGS_AFTER_VIEW_DELAY)
expect(getViewUpdateCount()).toEqual(4)
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
FAKE_PAINT_ENTRY,
FAKE_LARGEST_CONTENTFUL_PAINT_ENTRY,
FAKE_NAVIGATION_ENTRY,
])
clock.tick(THROTTLE_VIEW_UPDATE_PERIOD)
expect(getViewUpdateCount()).toEqual(4)
})
})
})
describe('renew session', () => {
describe('view lifecycle', () => {
let setupBuilder: TestSetupBuilder

@@ -284,99 +306,201 @@ let viewTest: ViewTest

it('should create new view on renew session', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewCreateCount } = viewTest
describe('expire session', () => {
it('should end the view when the session expires', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewEndCount } = viewTest
expect(getViewCreateCount()).toBe(1)
expect(getViewEndCount()).toBe(0)
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewCreateCount()).toBe(2)
expect(getViewEndCount()).toBe(1)
})
it('should send a final view update', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewUpdateCount, getViewUpdate } = viewTest
expect(getViewUpdateCount()).toBe(1)
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewUpdateCount()).toBe(2)
expect(getViewUpdate(0).sessionIsActive).toBe(true)
expect(getViewUpdate(1).sessionIsActive).toBe(false)
})
it('should not start a new view if the session expired', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewCreateCount } = viewTest
expect(getViewCreateCount()).toBe(1)
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewCreateCount()).toBe(1)
})
it('should not end the view if the view already ended', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewEndCount, getViewUpdateCount } = viewTest
lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, { reason: PageExitReason.UNLOADING })
expect(getViewEndCount()).toBe(1)
expect(getViewUpdateCount()).toBe(2)
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewEndCount()).toBe(1)
expect(getViewUpdateCount()).toBe(2)
})
})
it('should use the current view name, service and version for the new view', () => {
const { lifeCycle, changeLocation } = setupBuilder.build()
const { getViewCreateCount, getViewCreate, startView } = viewTest
describe('renew session', () => {
it('should create new view on renew session', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewCreateCount } = viewTest
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
expect(getViewCreateCount()).toBe(1)
startView({ name: 'view 1', service: 'service 1', version: 'version 1' })
startView({ name: 'view 2', service: 'service 2', version: 'version 2' })
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
startView({ name: 'view 3', service: 'service 3', version: 'version 3' })
changeLocation('/bar')
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
expect(getViewCreateCount()).toBe(2)
})
expect(getViewCreateCount()).toBe(8)
it('should use the current view name, service and version for the new view', () => {
const { lifeCycle, changeLocation } = setupBuilder.build()
const { getViewCreateCount, getViewCreate, startView } = viewTest
expect(getViewCreate(0)).toEqual(
jasmine.objectContaining({
name: 'initial view name',
service: 'initial service',
version: 'initial version',
})
)
expect(getViewCreate(1)).toEqual(
jasmine.objectContaining({
name: 'initial view name',
service: 'initial service',
version: 'initial version',
})
)
expect(getViewCreate(2)).toEqual(
jasmine.objectContaining({
name: 'view 1',
service: 'service 1',
version: 'version 1',
})
)
expect(getViewCreate(3)).toEqual(
jasmine.objectContaining({
name: 'view 2',
service: 'service 2',
version: 'version 2',
})
)
expect(getViewCreate(4)).toEqual(
jasmine.objectContaining({
name: 'view 2',
service: 'service 2',
version: 'version 2',
})
)
expect(getViewCreate(5)).toEqual(
jasmine.objectContaining({
name: 'view 3',
service: 'service 3',
version: 'version 3',
})
)
expect(getViewCreate(6)).toEqual(
jasmine.objectContaining({
name: undefined,
service: undefined,
version: undefined,
})
)
expect(getViewCreate(7)).toEqual(
jasmine.objectContaining({
name: undefined,
service: undefined,
version: undefined,
})
)
resetExperimentalFeatures()
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
startView({ name: 'view 1', service: 'service 1', version: 'version 1' })
startView({ name: 'view 2', service: 'service 2', version: 'version 2' })
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
startView({ name: 'view 3', service: 'service 3', version: 'version 3' })
changeLocation('/bar')
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
expect(getViewCreateCount()).toBe(8)
expect(getViewCreate(0)).toEqual(
jasmine.objectContaining({
name: 'initial view name',
service: 'initial service',
version: 'initial version',
})
)
expect(getViewCreate(1)).toEqual(
jasmine.objectContaining({
name: 'initial view name',
service: 'initial service',
version: 'initial version',
})
)
expect(getViewCreate(2)).toEqual(
jasmine.objectContaining({
name: 'view 1',
service: 'service 1',
version: 'version 1',
})
)
expect(getViewCreate(3)).toEqual(
jasmine.objectContaining({
name: 'view 2',
service: 'service 2',
version: 'version 2',
})
)
expect(getViewCreate(4)).toEqual(
jasmine.objectContaining({
name: 'view 2',
service: 'service 2',
version: 'version 2',
})
)
expect(getViewCreate(5)).toEqual(
jasmine.objectContaining({
name: 'view 3',
service: 'service 3',
version: 'version 3',
})
)
expect(getViewCreate(6)).toEqual(
jasmine.objectContaining({
name: undefined,
service: undefined,
version: undefined,
})
)
expect(getViewCreate(7)).toEqual(
jasmine.objectContaining({
name: undefined,
service: undefined,
version: undefined,
})
)
resetExperimentalFeatures()
})
})
it('should not update the current view when the session is renewed', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewUpdateCount, getViewUpdate } = viewTest
describe('session keep alive', () => {
it('should emit a view update periodically', () => {
const { clock } = setupBuilder.withFakeClock().build()
expect(getViewUpdateCount()).toEqual(1)
const { getViewUpdateCount } = viewTest
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
expect(getViewUpdateCount()).toEqual(1)
expect(getViewUpdateCount()).toEqual(2)
expect(getViewUpdate(0).id).not.toBe(getViewUpdate(1).id)
clock.tick(SESSION_KEEP_ALIVE_INTERVAL)
expect(getViewUpdateCount()).toEqual(2)
})
it('should not send periodical updates after the session has expired', () => {
const { lifeCycle, clock } = setupBuilder.withFakeClock().build()
const { getViewUpdateCount } = viewTest
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewUpdateCount()).toBe(2)
clock.tick(SESSION_KEEP_ALIVE_INTERVAL)
expect(getViewUpdateCount()).toBe(2)
})
})
describe('page exit', () => {
;[
{ exitReason: PageExitReason.UNLOADING, expectViewEnd: true },
{ exitReason: PageExitReason.PAGEHIDE, expectViewEnd: true },
{ exitReason: PageExitReason.FROZEN, expectViewEnd: false },
{ exitReason: PageExitReason.HIDDEN, expectViewEnd: false },
].forEach(({ exitReason, expectViewEnd }) => {
it(`should ${
expectViewEnd ? '' : 'not '
}end the current view when the page is exiting for reason ${exitReason}`, () => {
const { lifeCycle } = setupBuilder.build()
const { getViewEndCount } = viewTest
expect(getViewEndCount()).toEqual(0)
lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, { reason: exitReason })
expect(getViewEndCount()).toEqual(expectViewEnd ? 1 : 0)
})
})
it('should not create a new view when ending the view on page exit', () => {
const { lifeCycle } = setupBuilder.build()
const { getViewCreateCount } = viewTest
expect(getViewCreateCount()).toEqual(1)
lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, { reason: PageExitReason.UNLOADING })
expect(getViewCreateCount()).toEqual(1)
})
})
})

@@ -578,2 +702,17 @@

})
it('should not add custom timing when the session has expired', () => {
const { clock, lifeCycle } = setupBuilder.build()
const { getViewUpdateCount, addTiming } = viewTest
lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED)
expect(getViewUpdateCount()).toBe(2)
addTiming('foo', relativeNow())
clock.tick(THROTTLE_VIEW_UPDATE_PERIOD)
expect(getViewUpdateCount()).toBe(2)
})
})

@@ -580,0 +719,0 @@

import type { Duration, ClocksState, TimeStamp, Observable, Subscription, RelativeTime } from '@datadog/browser-core'
import {
noop,
PageExitReason,

@@ -45,2 +46,3 @@ shallowClone,

isActive: boolean
sessionIsActive: boolean
loadingTime?: Duration

@@ -81,6 +83,5 @@ loadingType: ViewLoadingType

) {
const { stop: stopInitialViewTracking, initialView } = trackInitialView(initialViewOptions)
let currentView = initialView
let currentView = startNewView(ViewLoadingType.INITIAL_LOAD, clocksOrigin(), initialViewOptions)
const { stop: stopViewLifeCycle } = startViewLifeCycle()
startViewLifeCycle()

@@ -92,37 +93,10 @@ let locationChangeSubscription: Subscription

function trackInitialView(options?: ViewOptions) {
const initialView = newView(
lifeCycle,
domMutationObservable,
configuration,
location,
ViewLoadingType.INITIAL_LOAD,
clocksOrigin(),
options
)
const { stop } = trackInitialViewTimings(lifeCycle, (timings) => {
initialView.updateTimings(timings)
initialView.scheduleUpdate()
})
return { initialView, stop }
function startNewView(loadingType: ViewLoadingType, startClocks?: ClocksState, viewOptions?: ViewOptions) {
return newView(lifeCycle, domMutationObservable, configuration, location, loadingType, startClocks, viewOptions)
}
function trackViewChange(startClocks?: ClocksState, viewOptions?: ViewOptions) {
return newView(
lifeCycle,
domMutationObservable,
configuration,
location,
ViewLoadingType.ROUTE_CHANGE,
startClocks,
viewOptions
)
}
function startViewLifeCycle() {
lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
// do not trigger view update to avoid wrong data
currentView.end()
// Renew view on session renewal
currentView = trackViewChange(undefined, {
currentView = startNewView(ViewLoadingType.ROUTE_CHANGE, undefined, {
name: currentView.name,

@@ -134,2 +108,6 @@ service: currentView.service,

lifeCycle.subscribe(LifeCycleEventType.SESSION_EXPIRED, () => {
currentView.end({ sessionIsActive: false })
})
// End the current view on page unload

@@ -139,16 +117,4 @@ lifeCycle.subscribe(LifeCycleEventType.PAGE_EXITED, (pageExitEvent) => {

currentView.end()
currentView.triggerUpdate()
}
})
// Session keep alive
const keepAliveInterval = setInterval(() => {
currentView.triggerUpdate()
}, SESSION_KEEP_ALIVE_INTERVAL)
return {
stop: () => {
clearInterval(keepAliveInterval)
},
}
}

@@ -160,5 +126,3 @@

currentView.end()
currentView.triggerUpdate()
currentView = trackViewChange()
return
currentView = startNewView(ViewLoadingType.ROUTE_CHANGE)
}

@@ -171,13 +135,9 @@ })

currentView.addTiming(name, time)
currentView.scheduleUpdate()
},
startView: (options?: ViewOptions, startClocks?: ClocksState) => {
currentView.end(startClocks)
currentView.triggerUpdate()
currentView = trackViewChange(startClocks, options)
currentView.end({ endClocks: startClocks })
currentView = startNewView(ViewLoadingType.ROUTE_CHANGE, startClocks, options)
},
stop: () => {
locationChangeSubscription?.unsubscribe()
stopInitialViewTracking()
stopViewLifeCycle()
currentView.end()

@@ -199,3 +159,2 @@ },

const id = generateUUID()
let timings: Timings = {}
const customTimings: ViewCustomTimings = {}

@@ -206,2 +165,3 @@ let documentVersion = 0

let sessionIsActive = true
let name: string | undefined

@@ -239,2 +199,7 @@ let service: string | undefined

const { scheduleStop: scheduleStopInitialViewTimingsTracking, timings } =
loadingType === ViewLoadingType.INITIAL_LOAD
? trackInitialViewTimings(lifeCycle, setLoadEvent, scheduleViewUpdate)
: { scheduleStop: noop, timings: {} as Timings }
const { scheduleStop: scheduleStopEventCountsTracking, eventCounts } = trackViewEventCounts(

@@ -246,2 +211,5 @@ lifeCycle,

// Session keep alive
const keepAliveIntervalId = setInterval(triggerViewUpdate, SESSION_KEEP_ALIVE_INTERVAL)
// Initial view update

@@ -251,2 +219,4 @@ triggerViewUpdate()

function triggerViewUpdate() {
cancelScheduleViewUpdate()
documentVersion += 1

@@ -270,2 +240,3 @@ const currentEnd = endClocks === undefined ? timeStampNow() : endClocks.timeStamp

isActive: endClocks === undefined,
sessionIsActive,
eventCounts,

@@ -282,23 +253,24 @@ },

version,
scheduleUpdate: scheduleViewUpdate,
end(clocks = clocksNow()) {
endClocks = clocks
end(options: { endClocks?: ClocksState; sessionIsActive?: boolean } = {}) {
if (endClocks) {
// view already ended
return
}
endClocks = options.endClocks ?? clocksNow()
sessionIsActive = options.sessionIsActive ?? true
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks })
clearInterval(keepAliveIntervalId)
stopViewMetricsTracking()
scheduleStopInitialViewTimingsTracking()
scheduleStopEventCountsTracking()
},
triggerUpdate() {
// cancel any pending view updates execution
cancelScheduleViewUpdate()
triggerViewUpdate()
},
updateTimings(newTimings: Timings) {
timings = newTimings
if (newTimings.loadEvent !== undefined) {
setLoadEvent(newTimings.loadEvent)
addTiming(name: string, time: RelativeTime | TimeStamp) {
if (endClocks) {
return
}
},
addTiming(name: string, time: RelativeTime | TimeStamp) {
const relativeTime = looksLikeRelativeTime(time) ? time : elapsed(startClocks.timeStamp, time)
customTimings[sanitizeTiming(name)] = relativeTime
scheduleViewUpdate()
},

@@ -305,0 +277,0 @@ }

@@ -44,2 +44,3 @@ import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core'

},
sessionIsActive: true,
}

@@ -138,2 +139,3 @@

has_replay: undefined,
is_active: undefined,
},

@@ -144,2 +146,8 @@ feature_flags: undefined,

it('should set session.is_active to false if the session is inactive', () => {
const { lifeCycle, rawRumEvents } = setupBuilder.build()
lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, { ...VIEW, sessionIsActive: false })
expect((rawRumEvents[rawRumEvents.length - 1].rawRumEvent as RawRumViewEvent).session.is_active).toBe(false)
})
it('should include replay information if available', () => {

@@ -146,0 +154,0 @@ const { lifeCycle, rawRumEvents } = setupBuilder.build()

@@ -96,2 +96,3 @@ import type { Duration, ServerDuration, Observable } from '@datadog/browser-core'

has_replay: replayStats ? true : undefined,
is_active: view.sessionIsActive ? undefined : false,
},

@@ -98,0 +99,0 @@ }

import type { RelativeTime } from '@datadog/browser-core'
import { noop, performDraw, startSessionManager } from '@datadog/browser-core'
import { Observable, noop, performDraw, startSessionManager } from '@datadog/browser-core'
import type { RumConfiguration } from './configuration'

@@ -12,2 +12,3 @@ import type { LifeCycle } from './lifeCycle'

expire: () => void
expireObservable: Observable<void>
}

@@ -75,2 +76,3 @@

expire: sessionManager.expire,
expireObservable: sessionManager.expireObservable,
}

@@ -93,2 +95,3 @@ }

expire: noop,
expireObservable: new Observable(),
}

@@ -95,0 +98,0 @@ }

@@ -80,7 +80,8 @@ import type { Subscription, TimeoutId, TimeStamp } from '@datadog/browser-core'

const maxDurationTimeoutId =
maxDuration &&
setTimeout(
monitor(() => complete({ hadActivity: true, end: timeStampNow() })),
maxDuration
)
maxDuration !== undefined
? setTimeout(
monitor(() => complete({ hadActivity: true, end: timeStampNow() })),
maxDuration
)
: undefined

@@ -87,0 +88,0 @@ const pageActivitySubscription = pageActivityObservable.subscribe(({ isBusy }) => {

@@ -106,2 +106,3 @@ import type {

has_replay: true | undefined
is_active: false | undefined
}

@@ -108,0 +109,0 @@ feature_flags?: Context

@@ -735,2 +735,21 @@ /* eslint-disable */

/**
* Session properties
*/
readonly session?: {
/**
* The precondition that led to the creation of the session
*/
readonly start_precondition?:
| 'app_launch'
| 'inactivity_timeout'
| 'max_duration'
| 'explicit_stop'
| 'background_event'
/**
* Whether this session is currently active. Set to false to manually stop a session
*/
readonly is_active?: boolean
[k: string]: unknown
}
/**
* Feature flags properties

@@ -737,0 +756,0 @@ */

@@ -28,5 +28,6 @@ import type {

reportError: (error: RawError) => void,
pageExitObservable: Observable<PageExitEvent>
pageExitObservable: Observable<PageExitEvent>,
sessionExpireObservable: Observable<void>
) {
const batch = makeRumBatch(configuration, reportError, pageExitObservable)
const batch = makeRumBatch(configuration, reportError, pageExitObservable, sessionExpireObservable)

@@ -55,3 +56,4 @@ lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, (serverRumEvent: RumEvent & Context) => {

reportError: (error: RawError) => void,
pageExitObservable: Observable<PageExitEvent>
pageExitObservable: Observable<PageExitEvent>,
sessionExpireObservable: Observable<void>
): RumBatch {

@@ -73,2 +75,3 @@ const { batch: primaryBatch, flushController: primaryFlushController } = createRumBatch(

pageExitObservable,
sessionExpireObservable,
})

@@ -75,0 +78,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc