UNPKG

@grafana/faro-web-sdk

Version:

Faro instrumentations, metas, transports for web.

190 lines 8.53 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUserEventHandler = getUserEventHandler; var faro_core_1 = require("@grafana/faro-core"); var const_1 = require("./const"); var domMutationMonitor_1 = require("./domMutationMonitor"); var httpRequestMonitor_1 = require("./httpRequestMonitor"); var performanceEntriesMonitor_1 = require("./performanceEntriesMonitor"); var util_1 = require("./util"); var maxFollowUpActionTimeRange = 100; function getUserEventHandler(faro) { var api = faro.api, config = faro.config; var httpMonitor = (0, httpRequestMonitor_1.monitorHttpRequests)(); var domMutationsMonitor = (0, domMutationMonitor_1.monitorDomMutations)(); var performanceEntriesMonitor = (0, performanceEntriesMonitor_1.monitorPerformanceEntries)(); var timeoutId; var actionRunning = false; function processUserEvent(event) { var _a; var userActionName; var isApiEventDetected = isApiEvent(event); if (isApiEventDetected) { userActionName = event.name; } else { userActionName = getUserActionName(event.target, (_a = config.trackUserActionsDataAttributeName) !== null && _a !== void 0 ? _a : const_1.userActionDataAttributeParsed); } if (actionRunning || userActionName == null) { return; } actionRunning = true; var startTime = (0, faro_core_1.dateNow)(); var endTime; var actionId = (0, faro_core_1.genShortID)(); faro_core_1.apiMessageBus.notify({ type: faro_core_1.USER_ACTION_START, name: userActionName, startTime: startTime, parentId: actionId, }); // Triggers if no initial action happened within the first 100ms timeoutId = startTimeout(timeoutId, function () { endTime = (0, faro_core_1.dateNow)(); // Listening for follow up activities stops once action is cancelled (set to false) actionRunning = false; sendUserActionCancelMessage(userActionName, actionId); }, maxFollowUpActionTimeRange); var runningRequests = new Map(); var isHalted = false; var pendingActionTimeoutId; var allMonitorsSub = new faro_core_1.Observable() .merge(httpMonitor, domMutationsMonitor, performanceEntriesMonitor) .takeWhile(function () { return actionRunning; }) .filter(function (msg) { // If the user action is in halt state, we only keep listening to ended http requests if (isHalted && !(isRequestEndMessage(msg) && runningRequests.has(msg.request.requestId))) { return false; } return true; }) .subscribe(function (msg) { if (isRequestStartMessage(msg)) { // An action is on halt if it has pending items, like pending HTTP requests. // In this case we start a separate timeout to wait for the requests to finish // If in the halt state, we stop adding Faro signals to the action's buffer (see userActionLifecycleHandler.ts) // But we are still subscribed to runningRequests.set(msg.request.requestId, msg.request); } if (isRequestEndMessage(msg)) { runningRequests.delete(msg.request.requestId); } // A http request, a DOM mutation or a performance entry happened so we have a follow up activity and start the timeout again // If timeout is triggered the user action is done and we send respective messages and events timeoutId = startTimeout(timeoutId, function () { endTime = (0, faro_core_1.dateNow)(); var userActionParentEventProps = __assign({ api: api, userActionName: userActionName, startTime: startTime, endTime: endTime, actionId: actionId, event: event }, (isApiEventDetected ? { attributes: event.attributes } : {})); var hasPendingRequests = runningRequests.size > 0; var isAllPendingRequestsResolved = isHalted && !hasPendingRequests; if (isAllPendingRequestsResolved) { clearTimeout(pendingActionTimeoutId); isHalted = false; } if (hasPendingRequests) { isHalted = true; faro_core_1.apiMessageBus.notify({ type: faro_core_1.USER_ACTION_HALT, name: userActionName, parentId: actionId, reason: 'pending-requests', haltTime: (0, faro_core_1.dateNow)(), }); pendingActionTimeoutId = startTimeout(undefined, function () { unsubscribeAllMonitors(allMonitorsSub); endUserAction(userActionParentEventProps); actionRunning = false; isHalted = false; }, 1000 * 10); } else { unsubscribeAllMonitors(allMonitorsSub); endUserAction(userActionParentEventProps); actionRunning = false; isHalted = false; } }, maxFollowUpActionTimeRange); }); } return processUserEvent; } /** * User action was successfully completed and we send the final event(s) */ function endUserAction(props) { var api = props.api, userActionName = props.userActionName, startTime = props.startTime, endTime = props.endTime, actionId = props.actionId, event = props.event, attributes = props.attributes; var duration = endTime - startTime; var eventType = event.type; // order matters, first emit the user-action-end event and afterwards push the parent event faro_core_1.apiMessageBus.notify({ type: faro_core_1.USER_ACTION_END, name: userActionName, id: actionId, startTime: startTime, endTime: endTime, duration: duration, eventType: eventType, }); // Send the final action parent event api.pushEvent(userActionName, __assign({ userActionStartTime: startTime.toString(), userActionEndTime: endTime.toString(), userActionDuration: duration.toString(), userActionEventType: eventType }, (0, faro_core_1.stringifyObjectValues)(attributes)), undefined, { timestampOverwriteMs: startTime, customPayloadTransformer: function (payload) { payload.action = { id: actionId, name: userActionName, }; return payload; }, }); } function getUserActionName(element, dataAttributeName) { var parsedDataAttributeName = (0, util_1.convertDataAttributeName)(dataAttributeName); var dataset = element.dataset; for (var key in dataset) { if (key === parsedDataAttributeName) { return dataset[key]; } } return undefined; } function startTimeout(timeoutId, cb, delay) { if (timeoutId) { clearTimeout(timeoutId); } //@ts-expect-error for some reason vscode is using the node types timeoutId = setTimeout(function () { cb(); }, delay); return timeoutId; } function sendUserActionCancelMessage(userActionName, actionId) { faro_core_1.apiMessageBus.notify({ type: faro_core_1.USER_ACTION_CANCEL, name: userActionName, parentId: actionId, }); } function unsubscribeAllMonitors(allMonitorsSub) { allMonitorsSub === null || allMonitorsSub === void 0 ? void 0 : allMonitorsSub.unsubscribe(); allMonitorsSub = undefined; } function isRequestStartMessage(msg) { return msg.type === const_1.MESSAGE_TYPE_HTTP_REQUEST_START; } function isRequestEndMessage(msg) { return msg.type === const_1.MESSAGE_TYPE_HTTP_REQUEST_END; } function isApiEvent(apiEvent) { return apiEvent.type === 'apiEvent' && typeof apiEvent.name === 'string'; } //# sourceMappingURL=processUserActionEventHandler.js.map