UNPKG

@microsoft/applicationinsights-core-js

Version:

Microsoft Application Insights Core Javascript SDK

478 lines (476 loc) • 25.8 kB
/* * Application Insights JavaScript SDK - Core, 3.3.6 * Copyright (c) Microsoft and contributors. All rights reserved. */ import { arrForEach, arrIndexOf, getDocument, getWindow, isArray, objForEachKey, objKeys } from "@nevware21/ts-utils"; import { _DYN_EVT_NAME, _DYN_LENGTH, _DYN_PUSH, _DYN_REPLACE, _DYN_SPLICE, _DYN_SPLIT, _DYN_TYPE } from "../__DynamicConstants"; import { createElmNodeData, createUniqueNamespace } from "./DataCacheHelper"; import { STR_EMPTY } from "./InternalConstants"; // Added to help with minfication var strOnPrefix = "on"; var strAttachEvent = "attachEvent"; var strAddEventHelper = "addEventListener"; var strDetachEvent = "detachEvent"; var strRemoveEventListener = "removeEventListener"; var strEvents = "events"; var strVisibilityChangeEvt = "visibilitychange"; var strPageHide = "pagehide"; var strPageShow = "pageshow"; var strUnload = "unload"; var strBeforeUnload = "beforeunload"; var strPageHideNamespace = createUniqueNamespace("aiEvtPageHide"); var strPageShowNamespace = createUniqueNamespace("aiEvtPageShow"); var rRemoveEmptyNs = /\.[\.]+/g; var rRemoveTrailingEmptyNs = /[\.]+$/; var _guid = 1; var elmNodeData = createElmNodeData("events"); var eventNamespace = /^([^.]*)(?:\.(.+)|)/; function _normalizeNamespace(name) { if (name && name[_DYN_REPLACE /* @min:%2ereplace */]) { return name[_DYN_REPLACE /* @min:%2ereplace */](/^[\s\.]+|(?=[\s\.])[\.\s]+$/g, STR_EMPTY); } return name; } function _getEvtNamespace(eventName, evtNamespace) { if (evtNamespace) { var theNamespace_1 = STR_EMPTY; if (isArray(evtNamespace)) { theNamespace_1 = STR_EMPTY; arrForEach(evtNamespace, function (name) { name = _normalizeNamespace(name); if (name) { if (name[0] !== ".") { name = "." + name; } theNamespace_1 += name; } }); } else { theNamespace_1 = _normalizeNamespace(evtNamespace); } if (theNamespace_1) { if (theNamespace_1[0] !== ".") { theNamespace_1 = "." + theNamespace_1; } // We may only have the namespace and not an eventName eventName = (eventName || STR_EMPTY) + theNamespace_1; } } var parsedEvent = (eventNamespace.exec(eventName || STR_EMPTY) || []); return { type: parsedEvent[1], ns: ((parsedEvent[2] || STR_EMPTY).replace(rRemoveEmptyNs, ".").replace(rRemoveTrailingEmptyNs, STR_EMPTY)[_DYN_SPLIT /* @min:%2esplit */](".").sort()).join(".") }; } /** * Get all of the registered events on the target object, this is primarily used for testing cleanup but may also be used by * applications to remove their own events * @param target - The EventTarget that has registered events * @param eventName - [Optional] The name of the event to return the registered handlers and full name (with namespaces) * @param evtNamespace - [Optional] Additional namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace, * if the eventName also includes a namespace the namespace(s) are merged into a single namespace */ export function __getRegisteredEvents(target, eventName, evtNamespace) { var theEvents = []; var eventCache = elmNodeData.get(target, strEvents, {}, false); var evtName = _getEvtNamespace(eventName, evtNamespace); objForEachKey(eventCache, function (evtType, registeredEvents) { arrForEach(registeredEvents, function (value) { if (!evtName[_DYN_TYPE /* @min:%2etype */] || evtName[_DYN_TYPE /* @min:%2etype */] === value[_DYN_EVT_NAME /* @min:%2eevtName */][_DYN_TYPE /* @min:%2etype */]) { if (!evtName.ns || evtName.ns === evtName.ns) { theEvents[_DYN_PUSH /* @min:%2epush */]({ name: value.evtName[_DYN_TYPE /* @min:%2etype */] + (value[_DYN_EVT_NAME /* @min:%2eevtName */].ns ? "." + value[_DYN_EVT_NAME /* @min:%2eevtName */].ns : STR_EMPTY), handler: value.handler }); } } }); }); return theEvents; } // Exported for internal unit testing only function _getRegisteredEvents(target, evtName, addDefault) { if (addDefault === void 0) { addDefault = true; } var aiEvts = elmNodeData.get(target, strEvents, {}, addDefault); var registeredEvents = aiEvts[evtName]; if (!registeredEvents) { registeredEvents = aiEvts[evtName] = []; } return registeredEvents; } function _doDetach(obj, evtName, handlerRef, useCapture) { if (obj && evtName && evtName[_DYN_TYPE /* @min:%2etype */]) { if (obj[strRemoveEventListener]) { obj[strRemoveEventListener](evtName[_DYN_TYPE /* @min:%2etype */], handlerRef, useCapture); } else if (obj[strDetachEvent]) { obj[strDetachEvent](strOnPrefix + evtName[_DYN_TYPE /* @min:%2etype */], handlerRef); } } } function _doAttach(obj, evtName, handlerRef, useCapture) { var result = false; if (obj && evtName && evtName[_DYN_TYPE /* @min:%2etype */] && handlerRef) { if (obj[strAddEventHelper]) { // all browsers except IE before version 9 obj[strAddEventHelper](evtName[_DYN_TYPE /* @min:%2etype */], handlerRef, useCapture); result = true; } else if (obj[strAttachEvent]) { // IE before version 9 obj[strAttachEvent](strOnPrefix + evtName[_DYN_TYPE /* @min:%2etype */], handlerRef); result = true; } } return result; } function _doUnregister(target, events, evtName, unRegFn) { var idx = events[_DYN_LENGTH /* @min:%2elength */]; while (idx--) { var theEvent = events[idx]; if (theEvent) { if (!evtName.ns || evtName.ns === theEvent[_DYN_EVT_NAME /* @min:%2eevtName */].ns) { if (!unRegFn || unRegFn(theEvent)) { _doDetach(target, theEvent[_DYN_EVT_NAME /* @min:%2eevtName */], theEvent.handler, theEvent.capture); // Remove the registered event events[_DYN_SPLICE /* @min:%2esplice */](idx, 1); } } } } } function _unregisterEvents(target, evtName, unRegFn) { if (evtName[_DYN_TYPE /* @min:%2etype */]) { _doUnregister(target, _getRegisteredEvents(target, evtName[_DYN_TYPE /* @min:%2etype */]), evtName, unRegFn); } else { var eventCache = elmNodeData.get(target, strEvents, {}); objForEachKey(eventCache, function (evtType, events) { _doUnregister(target, events, evtName, unRegFn); }); // Cleanup if (objKeys(eventCache)[_DYN_LENGTH /* @min:%2elength */] === 0) { elmNodeData.kill(target, strEvents); } } } export function mergeEvtNamespace(theNamespace, namespaces) { var newNamespaces; if (namespaces) { if (isArray(namespaces)) { newNamespaces = [theNamespace].concat(namespaces); } else { newNamespaces = [theNamespace, namespaces]; } // resort the namespaces so they are always in order newNamespaces = (_getEvtNamespace("xx", newNamespaces).ns)[_DYN_SPLIT /* @min:%2esplit */]("."); } else { newNamespaces = theNamespace; } return newNamespaces; } /** * Binds the specified function to an event, so that the function gets called whenever the event fires on the object * @param obj - Object to add the event too. * @param eventName - String that specifies any of the standard DHTML Events without "on" prefix, if may also include an optional (dot "." prefixed) * namespaces "click" "click.mynamespace" in addition to specific namespaces. * @param handlerRef - Pointer that specifies the function to call when event fires * @param evtNamespace - [Optional] Additional namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace, * if the eventName also includes a namespace the namespace(s) are merged into a single namespace * @param useCapture - [Optional] Defaults to false * @returns True if the function was bound successfully to the event, otherwise false */ export function eventOn(target, eventName, handlerRef, evtNamespace, useCapture) { if (useCapture === void 0) { useCapture = false; } var result = false; if (target) { try { var evtName = _getEvtNamespace(eventName, evtNamespace); result = _doAttach(target, evtName, handlerRef, useCapture); if (result && elmNodeData.accept(target)) { var registeredEvent = { guid: _guid++, evtName: evtName, handler: handlerRef, capture: useCapture }; _getRegisteredEvents(target, evtName.type)[_DYN_PUSH /* @min:%2epush */](registeredEvent); } } catch (e) { // Just Ignore any error so that we don't break any execution path } } return result; } /** * Removes an event handler for the specified event * @param Object - to remove the event from * @param eventName - The name of the event, with optional namespaces or just the namespaces, * such as "click", "click.mynamespace" or ".mynamespace" * @param handlerRef - The callback function that needs to be removed from the given event, when using a * namespace (with or without a qualifying event) this may be null to remove all previously attached event handlers * otherwise this will only remove events with this specific handler. * @param evtNamespace - [Optional] Additional namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace, * if the eventName also includes a namespace the namespace(s) are merged into a single namespace * @param useCapture - [Optional] Defaults to false */ export function eventOff(target, eventName, handlerRef, evtNamespace, useCapture) { if (useCapture === void 0) { useCapture = false; } if (target) { try { var evtName_1 = _getEvtNamespace(eventName, evtNamespace); var found_1 = false; _unregisterEvents(target, evtName_1, function (regEvent) { if ((evtName_1.ns && !handlerRef) || regEvent.handler === handlerRef) { found_1 = true; return true; } return false; }); if (!found_1) { // fallback to try and remove as requested _doDetach(target, evtName_1, handlerRef, useCapture); } } catch (e) { // Just Ignore any error so that we don't break any execution path } } } /** * Binds the specified function to an event, so that the function gets called whenever the event fires on the object * @param obj - Object to add the event too. * @param eventNameWithoutOn - String that specifies any of the standard DHTML Events without "on" prefix and optional (dot "." prefixed) namespaces "click" "click.mynamespace". * @param handlerRef - Pointer that specifies the function to call when event fires * @param useCapture - [Optional] Defaults to false * @returns True if the function was bound successfully to the event, otherwise false */ export function attachEvent(obj, eventNameWithoutOn, handlerRef, useCapture) { if (useCapture === void 0) { useCapture = false; } return eventOn(obj, eventNameWithoutOn, handlerRef, null, useCapture); } /** * Removes an event handler for the specified event * @param Object - to remove the event from * @param eventNameWithoutOn - The name of the event, with optional namespaces or just the namespaces, * such as "click", "click.mynamespace" or ".mynamespace" * @param handlerRef - The callback function that needs to be removed from the given event, when using a * namespace (with or without a qualifying event) this may be null to remove all previously attached event handlers * otherwise this will only remove events with this specific handler. * @param useCapture - [Optional] Defaults to false */ export function detachEvent(obj, eventNameWithoutOn, handlerRef, useCapture) { if (useCapture === void 0) { useCapture = false; } eventOff(obj, eventNameWithoutOn, handlerRef, null, useCapture); } /** * Trys to add an event handler for the specified event to the window, body and document * @param eventName - The name of the event * @param callback - The callback function that needs to be executed for the given event * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. * @returns - true if the handler was successfully added */ export function addEventHandler(eventName, callback, evtNamespace) { var result = false; var w = getWindow(); if (w) { result = eventOn(w, eventName, callback, evtNamespace); result = eventOn(w["body"], eventName, callback, evtNamespace) || result; } var doc = getDocument(); if (doc) { result = eventOn(doc, eventName, callback, evtNamespace) || result; } return result; } /** * Trys to remove event handler(s) for the specified event/namespace to the window, body and document * @param eventName - The name of the event, with optional namespaces or just the namespaces, * such as "click", "click.mynamespace" or ".mynamespace" * @param callback - The callback function that needs to be removed from the given event, when using a * namespace (with or without a qualifying event) this may be null to remove all previously attached event handlers * otherwise this will only remove events with this specific handler. * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. */ export function removeEventHandler(eventName, callback, evtNamespace) { var w = getWindow(); if (w) { eventOff(w, eventName, callback, evtNamespace); eventOff(w["body"], eventName, callback, evtNamespace); } var doc = getDocument(); if (doc) { eventOff(doc, eventName, callback, evtNamespace); } } /** * Bind the listener to the array of events * @param events - An string array of event names to bind the listener to * @param listener - The event callback to call when the event is triggered * @param excludeEvents - [Optional] An array of events that should not be hooked (if possible), unless no other events can be. * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. * @returns true - when at least one of the events was registered otherwise false */ function _addEventListeners(events, listener, excludeEvents, evtNamespace) { var added = false; if (listener && events && events[_DYN_LENGTH /* @min:%2elength */] > 0) { arrForEach(events, function (name) { if (name) { if (!excludeEvents || arrIndexOf(excludeEvents, name) === -1) { added = addEventHandler(name, listener, evtNamespace) || added; } } }); } return added; } /** * Bind the listener to the array of events * @param events - An string array of event names to bind the listener to * @param listener - The event callback to call when the event is triggered * @param excludeEvents - [Optional] An array of events that should not be hooked (if possible), unless no other events can be. * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. * @returns true - when at least one of the events was registered otherwise false */ export function addEventListeners(events, listener, excludeEvents, evtNamespace) { var added = false; if (listener && events && isArray(events)) { added = _addEventListeners(events, listener, excludeEvents, evtNamespace); if (!added && excludeEvents && excludeEvents[_DYN_LENGTH /* @min:%2elength */] > 0) { // Failed to add any listeners and we excluded some, so just attempt to add the excluded events added = _addEventListeners(events, listener, null, evtNamespace); } } return added; } /** * Remove the listener from the array of events * @param events - An string array of event names to bind the listener to * @param listener - The event callback to call when the event is triggered * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. */ export function removeEventListeners(events, listener, evtNamespace) { if (events && isArray(events)) { arrForEach(events, function (name) { if (name) { removeEventHandler(name, listener, evtNamespace); } }); } } /** * Listen to the 'beforeunload', 'unload' and 'pagehide' events which indicates a page unload is occurring, * this does NOT listen to the 'visibilitychange' event as while it does indicate that the page is being hidden * it does not *necessarily* mean that the page is being completely unloaded, it can mean that the user is * just navigating to a different Tab and may come back (without unloading the page). As such you may also * need to listen to the 'addPageHideEventListener' and 'addPageShowEventListener' events. * @param listener - The event callback to call when a page unload event is triggered * @param excludeEvents - [Optional] An array of events that should not be hooked, unless no other events can be. * @param evtNamespace - [Optional] Namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace. * @returns true - when at least one of the events was registered otherwise false */ export function addPageUnloadEventListener(listener, excludeEvents, evtNamespace) { // Hook the unload event for the document, window and body to ensure that the client events are flushed to the server // As just hooking the window does not always fire (on chrome) for page navigation's. return addEventListeners([strBeforeUnload, strUnload, strPageHide], listener, excludeEvents, evtNamespace); } /** * Remove any matching 'beforeunload', 'unload' and 'pagehide' events that may have been added via addEventListener, * addEventListeners, addPageUnloadEventListener or addPageHideEventListener. * @param listener - The specific event callback to to be removed * @param evtNamespace - [Optional] Namespace(s) uniquely identified and removed based on this namespace. * @returns true - when at least one of the events was registered otherwise false */ export function removePageUnloadEventListener(listener, evtNamespace) { removeEventListeners([strBeforeUnload, strUnload, strPageHide], listener, evtNamespace); } /** * Listen to the pagehide and visibility changing to 'hidden' events, because the 'visibilitychange' uses * an internal proxy to detect the visibility state you SHOULD use a unique namespace when if you plan to call * removePageShowEventListener as the remove ignores the listener argument for the 'visibilitychange' event. * @param listener - The event callback to call when a page hide event is triggered * @param excludeEvents - [Optional] An array of events that should not be hooked (if possible), unless no other events can be. * @param evtNamespace - [Optional] A Namespace to append to the event listeners so they can be uniquely identified and removed * based on this namespace. This call also adds an additional unique "pageshow" namespace to the events * so that only the matching "removePageHideEventListener" can remove these events. * Suggestion: pass as true if you are also calling addPageUnloadEventListener as that also hooks pagehide * @returns true - when at least one of the events was registered otherwise false */ export function addPageHideEventListener(listener, excludeEvents, evtNamespace) { function _handlePageVisibility(evt) { var doc = getDocument(); if (listener && doc && doc.visibilityState === "hidden") { listener(evt); } } // add the unique page show namespace to any provided namespace so we can only remove the ones added by "pagehide" var newNamespaces = mergeEvtNamespace(strPageHideNamespace, evtNamespace); var pageUnloadAdded = _addEventListeners([strPageHide], listener, excludeEvents, newNamespaces); if (!excludeEvents || arrIndexOf(excludeEvents, strVisibilityChangeEvt) === -1) { pageUnloadAdded = _addEventListeners([strVisibilityChangeEvt], _handlePageVisibility, excludeEvents, newNamespaces) || pageUnloadAdded; } if (!pageUnloadAdded && excludeEvents) { // Failed to add any listeners and we where requested to exclude some, so just call again without excluding anything pageUnloadAdded = addPageHideEventListener(listener, null, evtNamespace); } return pageUnloadAdded; } /** * Removes the pageHide event listeners added by addPageHideEventListener, because the 'visibilitychange' uses * an internal proxy to detect the visibility state you SHOULD use a unique namespace when calling addPageHideEventListener * as the remove ignores the listener argument for the 'visibilitychange' event. * @param listener - The specific listener to remove for the 'pageshow' event only (ignored for 'visibilitychange') * @param evtNamespace - The unique namespace used when calling addPageShowEventListener */ export function removePageHideEventListener(listener, evtNamespace) { // add the unique page show namespace to any provided namespace so we only remove the ones added by "pagehide" var newNamespaces = mergeEvtNamespace(strPageHideNamespace, evtNamespace); removeEventListeners([strPageHide], listener, newNamespaces); removeEventListeners([strVisibilityChangeEvt], null, newNamespaces); } /** * Listen to the pageshow and visibility changing to 'visible' events, because the 'visibilitychange' uses * an internal proxy to detect the visibility state you SHOULD use a unique namespace when if you plan to call * removePageShowEventListener as the remove ignores the listener argument for the 'visibilitychange' event. * @param listener - The event callback to call when a page is show event is triggered * @param excludeEvents - [Optional] An array of events that should not be hooked (if possible), unless no other events can be. * @param evtNamespace - [Optional/Recommended] A Namespace to append to the event listeners so they can be uniquely * identified and removed based on this namespace. This call also adds an additional unique "pageshow" namespace to the events * so that only the matching "removePageShowEventListener" can remove these events. * @returns true - when at least one of the events was registered otherwise false */ export function addPageShowEventListener(listener, excludeEvents, evtNamespace) { function _handlePageVisibility(evt) { var doc = getDocument(); if (listener && doc && doc.visibilityState === "visible") { listener(evt); } } // add the unique page show namespace to any provided namespace so we can only remove the ones added by "pageshow" var newNamespaces = mergeEvtNamespace(strPageShowNamespace, evtNamespace); var pageShowAdded = _addEventListeners([strPageShow], listener, excludeEvents, newNamespaces); pageShowAdded = _addEventListeners([strVisibilityChangeEvt], _handlePageVisibility, excludeEvents, newNamespaces) || pageShowAdded; if (!pageShowAdded && excludeEvents) { // Failed to add any listeners and we where requested to exclude some, so just call again without excluding anything pageShowAdded = addPageShowEventListener(listener, null, evtNamespace); } return pageShowAdded; } /** * Removes the pageShow event listeners added by addPageShowEventListener, because the 'visibilitychange' uses * an internal proxy to detect the visibility state you SHOULD use a unique namespace when calling addPageShowEventListener * as the remove ignores the listener argument for the 'visibilitychange' event. * @param listener - The specific listener to remove for the 'pageshow' event only (ignored for 'visibilitychange') * @param evtNamespace - The unique namespace used when calling addPageShowEventListener */ export function removePageShowEventListener(listener, evtNamespace) { // add the unique page show namespace to any provided namespace so we only remove the ones added by "pageshow" var newNamespaces = mergeEvtNamespace(strPageShowNamespace, evtNamespace); removeEventListeners([strPageShow], listener, newNamespaces); removeEventListeners([strVisibilityChangeEvt], null, newNamespaces); } //# sourceMappingURL=EventHelpers.js.map