1var EnvironmentNames; 2(function (EnvironmentNames) { 3 EnvironmentNames["local"] = "local-dev"; 4 EnvironmentNames["dev"] = "dev"; 5 EnvironmentNames["stg"] = "stg"; 6 EnvironmentNames["qa"] = "qa"; 7 EnvironmentNames["prd"] = "prd"; 8})(EnvironmentNames || (EnvironmentNames = {})); 9var ActionTypeEnum; 10(function (ActionTypeEnum) { 11 ActionTypeEnum["page_view"] = "page_view"; 12 ActionTypeEnum["click"] = "click"; 13 ActionTypeEnum["form_submission"] = "form_submission"; 14 ActionTypeEnum["login"] = "login"; 15 ActionTypeEnum["logout"] = "logout"; 16 ActionTypeEnum["search"] = "search"; 17 ActionTypeEnum["video_play"] = "video_play"; 18 ActionTypeEnum["performance"] = "performance"; 19 ActionTypeEnum["purchase"] = "purchase"; 20 ActionTypeEnum["error"] = "error"; 21 ActionTypeEnum["unknown"] = "unknown"; 22})(ActionTypeEnum || (ActionTypeEnum = {})); 23const loggerRoute = '/cb-logger/log'; 24const LOGGER_URL = 'https://crystalblueapi.jetblue.com' + loggerRoute; 25const uuidv4 = () => { 26 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 27 const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; 28 return v.toString(16); 29 }); 30}; 31const additionalDataStorageKey = 'cb-logger-additionalData'; 32const LOGGER_USER_ID = 'loggerUserId'; 33const localStorageId = localStorage.getItem(LOGGER_USER_ID) || uuidv4(); 34localStorage.setItem(LOGGER_USER_ID, localStorageId); 35const LOGGER_SESSION_ID = 'loggerSessionId'; 36const sessionId = sessionStorage.getItem(LOGGER_SESSION_ID) || uuidv4(); 37sessionStorage.setItem(LOGGER_SESSION_ID, sessionId); 38let batchData = []; 39const batchInterval = 3000; 40let batchTimer; 41/** 42 * Use this object to hold the configuration for runtime decision making 43 */ 44let logConfig = { 45 loggerSettings: { 46 // default values 47 capturePageLoad: true, 48 captureError: false, 49 captureClick: false, 50 enableLoggingToService: true, 51 enableConsoleLogs: true, // we can make this false for production and it will turn of console logs 52 batchMessages: false, 53 captureHrefChange: true, 54 performanceConfig: { 55 captureResource: false, 56 capturePaint: false, 57 captureNavigation: false, 58 }, 59 }, 60 subscriptionKey: 'acaa86ebe62f4790948ce7572706cdf5', 61 sessionId: sessionId, 62 localStorageId: localStorageId, 63}; 64/** 65 * helper function to retrieve the context data for the event such as 66 * generated timestamp, sessionId, localStorageId, 67 * @returns FeLoggerSchema - basic logging schema object 68 */ 69let getLogData = () => { 70 let additionalData = {}; 71 try { 72 additionalData = JSON.parse(sessionStorage.getItem(additionalDataStorageKey)); 73 } 74 catch (e) { 75 consolelog('Error parsing additionalData from session storage', e); 76 } 77 let logData = Object.assign(Object.assign(Object.assign({ sessionId: logConfig.sessionId, localStorageId: logConfig.localStorageId, event: ActionTypeEnum.page_view, timestamp: new Date().toISOString(), pageUrl: document.location.href, referrerUrl: document.referrer, userAgent: window.navigator.userAgent }, (logConfig.userId && { userId: logConfig.userId })), (logConfig.applicationInfo && { 78 applicationInfo: logConfig.applicationInfo, 79 })), (additionalData && { 80 additionalData, 81 })); 82 return logData; 83}; 84const onLoadHandler = (event) => { 85 consolelog('onLoadHandler', event); 86 let logData = getLogData(); 87 logData.event = ActionTypeEnum.page_view; 88 logData.pageUrl = event.currentTarget.location.href; 89 logData.details = { 90 properties: { 91 pageViewEvent: 'onload', 92 }, 93 }; 94 felog(logData); 95}; 96const addHrefChangeListener = () => { 97 let oldHref = document.location.href; 98 const body = document.querySelector('body'); 99 const observer = new MutationObserver(() => { 100 if (oldHref !== document.location.href) { 101 //this portion is like a "hrefChangeHandler" 102 let logData = getLogData(); 103 logData.event = ActionTypeEnum.page_view; 104 logData.pageUrl = document.location.href; 105 logData.referrerUrl = oldHref; 106 oldHref = document.location.href; 107 logData.details = { 108 properties: { 109 pageViewEvent: 'urlchange', 110 }, 111 }; 112 felog(logData); 113 } 114 }); 115 body && observer.observe(body, { childList: true, subtree: true }); 116}; 117const errorHandler = (event) => { 118 consolelog('errorHandler', event); 119 let logData = getLogData(); 120 logData.event = ActionTypeEnum.error; 121 logData.details = { 122 properties: { 123 error: { 124 properties: { 125 colno: event.colno, 126 lineno: event.lineno, 127 message: event.message, 128 stack: event.error.stack, 129 source: event.filename, 130 }, 131 }, 132 }, 133 }; 134 felog(logData); 135}; 136const clickHandler = (event) => { 137 consolelog('clickHandler', event); 138 let logData = getLogData(); 139 logData.event = ActionTypeEnum.click; 140 if (event.target instanceof Element) { 141 let targetElement = event.target; 142 logData.details = { 143 properties: { 144 buttonText: targetElement.textContent, 145 }, 146 additionalProperties: { 147 targetElementOuterHtml: targetElement.outerHTML, 148 targetElementId: targetElement.id, 149 }, 150 }; 151 } 152 else { 153 logData.details = { 154 additionalProperties: { 155 notes: 'click event target type is not an Element', 156 }, 157 }; 158 } 159 felog(logData); 160}; 161const beforeUnloadHandler = (event) => { 162 var _a; 163 consolelog('beforeunload', event); 164 //send any batched events 165 ((_a = logConfig.loggerSettings) === null || _a === void 0 ? void 0 : _a.batchMessages) && sendBatchedEvents(); 166}; 167/** 168 * Use this function as the UI to log specific things that this generic logger doesn't automatically log. 169 * @param actionType string to identify the type of action that you are logging. Maps to ActionTypeEnum 170 * @param additionalProperties this is a wildcard object which will be put into the details 171 */ 172const logOtherEvent = (actionType, additionalProperties) => { 173 let logData = getLogData(); 174 logData.event = 175 actionType in ActionTypeEnum ? actionType : ActionTypeEnum.unknown; 176 logData.details = { additionalProperties }; 177 felog(logData); 178}; 179/** 180 * This function only needs to be called once, but may be called multiple times. 181 * It will merge the incoming config with the logger's local config. 182 * It will only overwrite fields which are received in the incoming config, 183 * @param config Incoming runtime configuration for logger 184 */ 185const JBFELoggerSetup = (config) => { 186 var _a, _b, _c, _d; 187 const storedAdditionalData = JSON.parse(sessionStorage.getItem(additionalDataStorageKey)); 188 consolelog('INIT', config); 189 logConfig = Object.assign(Object.assign(Object.assign({}, logConfig), config), { loggerSettings: Object.assign(Object.assign({}, logConfig.loggerSettings), config.loggerSettings), additionalData: Object.assign(Object.assign(Object.assign({}, storedAdditionalData), logConfig.additionalData), config.additionalData), applicationInfo: Object.assign(Object.assign({}, logConfig.applicationInfo), config.applicationInfo) }); 190 sessionStorage.setItem(additionalDataStorageKey, JSON.stringify(logConfig.additionalData)); 191 if ((_a = logConfig.loggerSettings) === null || _a === void 0 ? void 0 : _a.capturePageLoad) { 192 window.addEventListener('load', (event) => { 193 onLoadHandler(event); 194 logConfig.loggerSettings.captureHrefChange && 195 addHrefChangeListener(); 196 }); 197 } 198 if ((_b = logConfig.loggerSettings) === null || _b === void 0 ? void 0 : _b.captureError) { 199 window.addEventListener('error', (event) => { 200 errorHandler(event); 201 }); 202 } 203 if ((_c = logConfig.loggerSettings) === null || _c === void 0 ? void 0 : _c.captureClick) { 204 document.addEventListener('click', (event) => { 205 clickHandler(event); 206 }); 207 } 208 if ((_d = logConfig.loggerSettings) === null || _d === void 0 ? void 0 : _d.performanceConfig) { 209 setupPerformance(logConfig.loggerSettings.performanceConfig); 210 } 211 addEventListener('beforeunload', (event) => beforeUnloadHandler(event)); 212}; 213/** 214 * Use this function to update the run config for the logger mid-session 215 * @param config 216 */ 217const addAddtionalData = (config) => { 218 const storedAdditionalData = JSON.parse(sessionStorage.getItem(additionalDataStorageKey)); 219 const oldData = Object.assign({}, logConfig); 220 logConfig = Object.assign(Object.assign(Object.assign({}, logConfig), config), { loggerSettings: Object.assign(Object.assign({}, logConfig.loggerSettings), config.loggerSettings), additionalData: Object.assign(Object.assign(Object.assign({}, storedAdditionalData), logConfig.additionalData), config.additionalData), applicationInfo: Object.assign(Object.assign({}, logConfig.applicationInfo), config.applicationInfo) }); 221 sessionStorage.setItem(additionalDataStorageKey, JSON.stringify(logConfig.additionalData)); 222 consolelog('Updating Config from', oldData, 'with new data', config, 'Final:', logConfig); 223}; 224/**
225 * handles a performance event in the generic fashion. Prefer to use a specific handler method for each event type to configure what data we log 226 * @param event 227 */ 228const handlePerformanceEntryStream = (entries) => { 229 const logData = getLogData(); 230 logData.event = ActionTypeEnum.performance; 231 const entryList = entries.getEntries(); 232 if (entryList.length === 1 && !entryList[0].name.endsWith(loggerRoute)) 233 logData.performanceEntry = entryList[0]; 234 else if (entryList.length > 0) { 235 const logArray = []; 236 entryList.forEach((entry) => !entry.name.endsWith(loggerRoute) && 237 logArray.push(entry.toJSON())); 238 logData.performanceEntry = logArray; 239 } 240 felog(logData); 241}; 242const setupPerformance = (performanceConfig) => { 243 const entryTypes = []; 244 if (performanceConfig.captureResource) { 245 entryTypes.push('resource'); 246 } 247 if (performanceConfig.captureNavigation) { 248 entryTypes.push('navigation'); 249 } 250 if (performanceConfig.capturePaint) { 251 entryTypes.push('paint'); 252 entryTypes.push('largest-contentful-paint'); 253 } 254 const observer = new PerformanceObserver(handlePerformanceEntryStream); 255 if (entryTypes.length > 0) 256 observer.observe({ entryTypes: entryTypes, buffered: true }); 257}; 258const batchNewEvent = (data) => { 259 consolelog('Batching New Event', data.event, data); 260 batchData.push(data); 261 if (!batchTimer) { 262 batchTimer = setTimeout(() => sendBatchedEvents(), batchInterval); 263 } 264}; 265const sendBatchedEvents = () => { 266 var _a; 267 if (batchData.length > 0) { 268 const body = JSON.stringify({ events: batchData }); 269 consolelog(batchData.length, 'batched events found:', batchData); 270 if ((_a = logConfig.loggerSettings) === null || _a === void 0 ? void 0 : _a.enableLoggingToService) { 271 const myHeaders = new Headers(); 272 myHeaders.append('Content-Type', 'application/json'); 273 myHeaders.append('ocp-apim-subscription-key', logConfig.subscriptionKey); 274 const requestOptions = { 275 method: 'POST', 276 headers: myHeaders, 277 body: body, 278 }; 279 consolelog('sending batched events', 'requestOptions ', requestOptions, 'to', LOGGER_URL); 280 fetch(LOGGER_URL, requestOptions) 281 .then((response) => response.text()) 282 .then((result) => consolelog('result of felog', result)) 283 .catch((error) => consolelog('error sending to felog location', error)); 284 batchTimer = undefined; 285 } 286 batchData = []; 287 } 288 else 289 consolelog('send batched events called with no events in queue'); 290}; 291/** 292 * This function will determine whether we SHOULD console log before we do. 293 * It also prepends "FELOG--->" to calls to the console 294 * @param args list of type any arguments, just like console.log 295 */ 296const consolelog = (...args) => { 297 var _a; 298 ((_a = logConfig.loggerSettings) === null || _a === void 0 ? void 0 : _a.enableConsoleLogs) && 299 console.log('FELOG--->', ...args); 300}; 301/** 302 * This function will determine if we SHOULD send the log to the backend service. 303 * if so, it will wrap with relevent identifiers and send it. 304 * @param data for the event which occurred 305 */ 306const felog = (data) => { 307 var _a, _b; 308 const body = JSON.stringify(data); 309 if ((_a = logConfig.loggerSettings) === null || _a === void 0 ? void 0 : _a.batchMessages) { 310 batchNewEvent(data); 311 } 312 else { 313 consolelog('felog() data', data); 314 if ((_b = logConfig.loggerSettings) === null || _b === void 0 ? void 0 : _b.enableLoggingToService) { 315 const myHeaders = new Headers(); 316 myHeaders.append('Content-Type', 'application/json'); 317 myHeaders.append('ocp-apim-subscription-key', logConfig.subscriptionKey); 318 const requestOptions = { 319 method: 'POST', 320 headers: myHeaders, 321 body: body, 322 }; 323 fetch(LOGGER_URL, requestOptions) 324 .then((response) => response.text()) 325 .then((result) => consolelog('result of felog', result)) 326 .catch((error) => consolelog('error sending to felog location', error)); 327 } 328 } 329};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.