UNPKG

@grafana/faro-web-sdk

Version:

Faro instrumentations, metas, transports for web.

126 lines 5.9 kB
import { apiMessageBus, dateNow, genShortID, merge, USER_ACTION_CANCEL_MESSAGE_TYPE, USER_ACTION_END_MESSAGE_TYPE, USER_ACTION_START_MESSAGE_TYPE, } from '@grafana/faro-core'; import { userActionDataAttributeParsed as userActionDataAttribute } from './const'; import { monitorDomMutations } from './domMutationMonitor'; import { monitorHttpRequests } from './httpRequestMonitor'; import { monitorPerformanceEntries } from './performanceEntriesMonitor'; import { convertDataAttributeName } from './util'; export function getUserEventHandler(faro) { const { api, config } = faro; const httpMonitor = monitorHttpRequests(); const domMutationsMonitor = monitorDomMutations(); const performanceEntriesMonitor = monitorPerformanceEntries(); let allMonitorsSub; let allMonitorsObserver; let timeoutId; let actionRunning = false; function processUserEvent(event) { var _a; const userActionName = getUserActionName(event.target, (_a = config.trackUserActionsDataAttributeName) !== null && _a !== void 0 ? _a : userActionDataAttribute); if (actionRunning || userActionName == null) { return; } actionRunning = true; const startTime = dateNow(); let endTime; const actionId = genShortID(); apiMessageBus.notify({ type: USER_ACTION_START_MESSAGE_TYPE, name: userActionName, startTime: startTime, parentId: actionId, }); // Triggers if no initial action happened within the first 100ms timeoutId = startTimeout(timeoutId, () => { endTime = dateNow(); // Listening for follow up activities stops once action is cancelled (set to false) actionRunning = false; sendUserActionCancelMessage(userActionName, actionId); }); allMonitorsObserver = merge(httpMonitor, domMutationsMonitor, performanceEntriesMonitor); allMonitorsSub = allMonitorsObserver .takeWhile(() => actionRunning) .subscribe(() => { // 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, () => { endTime = dateNow(); const duration = endTime - startTime; const eventType = event.type; // order matters, first emit the user-action-end event and then push the event apiMessageBus.notify({ type: USER_ACTION_END_MESSAGE_TYPE, name: userActionName, id: actionId, startTime, endTime, duration, eventType, }); // Send the final action parent event api.pushEvent(userActionName, { userActionStartTime: startTime.toString(), userActionEndTime: endTime.toString(), userActionDuration: duration.toString(), userActionEventType: eventType, }, undefined, { timestampOverwriteMs: startTime, customPayloadTransformer: (payload) => { payload.action = { id: actionId, name: userActionName, }; return payload; }, }); // Ensure action is blocked until it is fully processed. actionRunning = false; allMonitorsSub === null || allMonitorsSub === void 0 ? void 0 : allMonitorsSub.unsubscribe(); allMonitorsObserver === null || allMonitorsObserver === void 0 ? void 0 : allMonitorsObserver.unsubscribeAll(); }); }); } registerVisibilityChangeHandler(allMonitorsSub, allMonitorsObserver); return processUserEvent; } function getUserActionName(element, dataAttributeName) { const parsedDataAttributeName = convertDataAttributeName(dataAttributeName); const dataset = element.dataset; for (const key in dataset) { if (key === parsedDataAttributeName) { return dataset[key]; } } return undefined; } function startTimeout(timeoutId, cb) { const maxTimeSpanTillUserActionEnd = 100; if (timeoutId) { clearTimeout(timeoutId); } //@ts-expect-error for some reason vscode is using the node types timeoutId = setTimeout(() => { cb(); }, maxTimeSpanTillUserActionEnd); return timeoutId; } function sendUserActionCancelMessage(userActionName, actionId) { apiMessageBus.notify({ type: USER_ACTION_CANCEL_MESSAGE_TYPE, name: userActionName, parentId: actionId, }); } function registerVisibilityChangeHandler(allMonitorsSub, allMonitorsObserver) { // stop monitoring in background tabs document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { // Unsubscribe from all monitors when the tab goes into the background to free up resources (merge.unsubscribe() also unsubscribes from all inner observables) // Monitors will be re-subscribed in the processEvent function when the first user action is detected allMonitorsSub === null || allMonitorsSub === void 0 ? void 0 : allMonitorsSub.unsubscribe(); allMonitorsSub = undefined; allMonitorsObserver === null || allMonitorsObserver === void 0 ? void 0 : allMonitorsObserver.unsubscribeAll(); allMonitorsObserver = undefined; } }); } //# sourceMappingURL=processUserActionEventHandler.js.map