Created
April 23, 2019 13:56
-
-
Save bjacobel/2b24dc6573121178696e98bd8fc28b3f to your computer and use it in GitHub Desktop.
nr-spa-118.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// modules are defined as an array | |
// [ module function, map of requires ] | |
// | |
// map of requireuires is short require name -> numeric require | |
// | |
// anything defined in a previous bundle is accessed via the | |
// orig method which is the require for previous bundles | |
(function (modules, cache, entry) { // eslint-disable-line no-extra-parens | |
// Save the require from previous bundle to this closure if any | |
var previousRequire = typeof __nr_require === 'function' && __nr_require | |
function newRequire (name, jumped) { | |
if (!cache[name]) { | |
if (!modules[name]) { | |
// if we cannot find the the module within our internal map or | |
// cache jump to the current global require ie. the last bundle | |
// that was added to the page. | |
var currentRequire = typeof __nr_require === 'function' && __nr_require | |
if (!jumped && currentRequire) return currentRequire(name, true) | |
// If there are other bundles on this page the require from the | |
// previous one is saved to 'previousRequire'. Repeat this as | |
// many times as there are bundles until the module is found or | |
// we exhaust the require chain. | |
if (previousRequire) return previousRequire(name, true) | |
throw new Error("Cannot find module '" + name + "'") | |
} | |
var m = cache[name] = {exports: {}} | |
modules[name][0].call(m.exports, function (x) { | |
var id = modules[name][1][x] | |
return newRequire(id || x) | |
}, m, m.exports) | |
} | |
return cache[name].exports | |
} | |
for (var i = 0; i < entry.length; i++) newRequire(entry[i]) | |
// Override the current require with this new one | |
return newRequire | |
}) | |
({1:[function(require,module,exports){ | |
// Safely add an event listener to window in any browser | |
module.exports = function (sType, callback) { | |
if ('addEventListener' in window) { | |
return window.addEventListener(sType, callback, false) | |
} else if ('attachEvent' in window) { | |
return window.attachEvent('on' + sType, callback) | |
} | |
} | |
},{}],2:[function(require,module,exports){ | |
// This product includes Apache 2.0 licensed source derived from 'episodes' | |
// by Steve Souders. Repository: https://github.com/stevesouders/episodes | |
// | |
// Copyright 2010 Google Inc. | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
// | |
// See the source code here: | |
// https://github.com/stevesouders/episodes | |
// | |
// This product includes MIT licensed source generated from 'Browserify' | |
// by James Halliday. Repository: https://github.com/substack/node-browserify. | |
// | |
// This product includes MIT licensed source derived from 'TraceKit' | |
// by Onur Cakmak. Repository: https://github.com/occ/TraceKit | |
// | |
// TraceKit - Cross brower stack traces - github.com/occ/TraceKit | |
// | |
// Copyright (c) 2013 Onur Can Cakmak onur.cakmak@gmail.com and all TraceKit | |
// contributors. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the 'Software'), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
// All other components of this product are | |
// Copyright (c) 2010-2013 New Relic, Inc. All rights reserved. | |
// | |
// Certain inventions disclosed in this file may be claimed within | |
// patents owned or patent applications filed by New Relic, Inc. or third | |
// parties. | |
// | |
// Subject to the terms of this notice, New Relic grants you a | |
// nonexclusive, nontransferable license, without the right to | |
// sublicense, to (a) install and execute one copy of these files on any | |
// number of workstations owned or controlled by you and (b) distribute | |
// verbatim copies of these files to third parties. As a condition to the | |
// foregoing grant, you must provide this notice along with each copy you | |
// distribute and you must not remove, alter, or obscure this notice. All | |
// other use, reproduction, modification, distribution, or other | |
// exploitation of these files is strictly prohibited, except as may be set | |
// forth in a separate written license agreement between you and New | |
// Relic. The terms of any such license agreement will control over this | |
// notice. The license stated above will be automatically terminated and | |
// revoked if you exceed its scope or violate any of the terms of this | |
// notice. | |
// | |
// This License does not grant permission to use the trade names, | |
// trademarks, service marks, or product names of New Relic, except as | |
// required for reasonable and customary use in describing the origin of | |
// this file and reproducing the content of this notice. You may not | |
// mark or brand this file with any trade name, trademarks, service | |
// marks, or product names other than the original brand (if any) | |
// provided by New Relic. | |
// | |
// Unless otherwise expressly agreed by New Relic in a separate written | |
// license agreement, these files are provided AS IS, WITHOUT WARRANTY OF | |
// ANY KIND, including without any implied warranties of MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE, TITLE, or NON-INFRINGEMENT. As a | |
// condition to your use of these files, you are solely responsible for | |
// such use. New Relic will have no liability to you for direct, | |
// indirect, consequential, incidental, special, or punitive damages or | |
// for lost profits or data. | |
// | |
// | |
var mapOwn = require(35) | |
var aggregatedData = {} | |
module.exports = { | |
store: store, | |
take: take, | |
get: get | |
} | |
// Items with the same type and name get aggregated together | |
// params are example data from the aggregated items | |
// metrics are the numeric values to be aggregated | |
function store (type, name, params, newMetrics, customParams) { | |
if (!aggregatedData[type]) aggregatedData[type] = {} | |
var bucket = aggregatedData[type][name] | |
if (!bucket) { | |
bucket = aggregatedData[type][name] = { params: params || {} } | |
if (customParams) { | |
bucket.custom = customParams | |
} | |
} | |
bucket.metrics = aggregateMetrics(newMetrics, bucket.metrics) | |
return bucket | |
} | |
function aggregateMetrics (newMetrics, oldMetrics) { | |
if (!oldMetrics) oldMetrics = {count: 0} | |
oldMetrics.count += 1 | |
mapOwn(newMetrics, function (key, value) { | |
oldMetrics[key] = updateMetric(value, oldMetrics[key]) | |
}) | |
return oldMetrics | |
} | |
function updateMetric (value, metric) { | |
// When there is only one data point, the c (count), min, max, and sos (sum of squares) params are superfluous. | |
if (!metric) return {t: value} | |
// but on the second data point, we need to calculate the other values before aggregating in new values | |
if (metric && !metric.c) { | |
metric = { | |
t: metric.t, | |
min: metric.t, | |
max: metric.t, | |
sos: metric.t * metric.t, | |
c: 1 | |
} | |
} | |
metric.c += 1 | |
metric.t += value | |
metric.sos += value * value | |
if (value > metric.max) metric.max = value | |
if (value < metric.min) metric.min = value | |
return metric | |
} | |
function get (type, name) { | |
// if name is passed, get a single bucket | |
if (name) return aggregatedData[type] && aggregatedData[type][name] | |
// else, get all buckets of that type | |
return aggregatedData[type] | |
} | |
// Like get, but for many types and it deletes the retrieved content from the aggregatedData | |
function take (types) { | |
var results = {} | |
var type = '' | |
var hasData = false | |
for (var i = 0; i < types.length; i++) { | |
type = types[i] | |
results[type] = toArray(aggregatedData[type]) | |
if (results[type].length) hasData = true | |
delete aggregatedData[type] | |
} | |
return hasData ? results : null | |
} | |
function toArray (obj) { | |
if (typeof obj !== 'object') return [] | |
return mapOwn(obj, getValue) | |
} | |
function getValue (key, value) { | |
return value | |
} | |
},{}],3:[function(require,module,exports){ | |
var register = require(14) | |
var harvest = require(8) | |
var agg = require(2) | |
var single = require(16) | |
var submitData = require(20) | |
var mapOwn = require(35) | |
var loader = require("loader") | |
var handle = require("handle") | |
var cycle = 0 | |
harvest.on('jserrors', function () { | |
return { body: agg.take([ 'cm' ]) } | |
}) | |
var api = { | |
finished: single(finished), | |
setPageViewName: setPageViewName, | |
setErrorHandler: setErrorHandler, | |
addToTrace: addToTrace, | |
inlineHit: inlineHit, | |
addRelease: addRelease | |
} | |
// Hook all of the api functions up to the queues/stubs created in loader/api.js | |
mapOwn(api, function (fnName, fn) { | |
register('api-' + fnName, fn, 'api') | |
}) | |
// All API functions get passed the time they were called as their | |
// first parameter. These functions can be called asynchronously. | |
function setPageViewName (t, name, host) { | |
if (typeof name !== 'string') return | |
if (name.charAt(0) !== '/') name = '/' + name | |
loader.customTransaction = (host || 'http://custom.transaction') + name | |
} | |
function finished (t, providedTime) { | |
var time = providedTime ? providedTime - loader.offset : t | |
agg.store('cm', 'finished', { name: 'finished' }, { time: time }) | |
addToTrace(t, { name: 'finished', start: time + loader.offset, origin: 'nr' }) | |
handle('api-addPageAction', [ time, 'finished' ]) | |
} | |
function addToTrace (t, evt) { | |
if (!(evt && typeof evt === 'object' && evt.name && evt.start)) return | |
var report = { | |
n: evt.name, | |
s: evt.start - loader.offset, | |
e: (evt.end || evt.start) - loader.offset, | |
o: evt.origin || '', | |
t: 'api' | |
} | |
handle('bstApi', [report]) | |
} | |
// NREUM.inlineHit(request_name, queue_time, app_time, total_be_time, dom_time, fe_time) | |
// | |
// request_name - the 'web page' name or service name | |
// queue_time - the amount of time spent in the app tier queue | |
// app_time - the amount of time spent in the application code | |
// total_be_time - the total roundtrip time of the remote service call | |
// dom_time - the time spent processing the result of the service call (or user defined) | |
// fe_time - the time spent rendering the result of the service call (or user defined) | |
function inlineHit (t, request_name, queue_time, app_time, total_be_time, dom_time, fe_time) { | |
request_name = window.encodeURIComponent(request_name) | |
cycle += 1 | |
if (!loader.info.beacon) return | |
var url = 'https://' + loader.info.beacon + '/1/' + loader.info.licenseKey | |
url += '?a=' + loader.info.applicationID + '&' | |
url += 't=' + request_name + '&' | |
url += 'qt=' + ~~queue_time + '&' | |
url += 'ap=' + ~~app_time + '&' | |
url += 'be=' + ~~total_be_time + '&' | |
url += 'dc=' + ~~dom_time + '&' | |
url += 'fe=' + ~~fe_time + '&' | |
url += 'c=' + cycle | |
submitData.img(url) | |
} | |
function setErrorHandler (t, handler) { | |
loader.onerror = handler | |
} | |
var releaseCount = 0 | |
function addRelease (t, name, id) { | |
if (++releaseCount > 10) return | |
loader.releaseIds[name.slice(-200)] = ('' + id).slice(-200) | |
} | |
},{}],4:[function(require,module,exports){ | |
var withHash = /([^?#]*)[^#]*(#[^?]*|$).*/ | |
var withoutHash = /([^?#]*)().*/ | |
module.exports = function cleanURL (url, keepHash) { | |
return url.replace(keepHash ? withHash : withoutHash, '$1$2') | |
} | |
},{}],5:[function(require,module,exports){ | |
var baseEE = require("ee") | |
var mapOwn = require(35) | |
var handlers = require(14).handlers | |
module.exports = function drain (group) { | |
var bufferedEventsInGroup = baseEE.backlog[group] | |
var groupHandlers = handlers[group] | |
if (groupHandlers) { | |
// don't cache length, buffer can grow while processing | |
for (var i = 0; bufferedEventsInGroup && i < bufferedEventsInGroup.length; ++i) { // eslint-disable-line no-unmodified-loop-condition | |
emitEvent(bufferedEventsInGroup[i], groupHandlers) | |
} | |
mapOwn(groupHandlers, function (eventType, handlerRegistrationList) { | |
mapOwn(handlerRegistrationList, function (i, registration) { | |
// registration is an array of: [targetEE, eventHandler] | |
registration[0].on(eventType, registration[1]) | |
}) | |
}) | |
} | |
delete handlers[group] | |
// Keep the group as a property so we know it was created and drained | |
baseEE.backlog[group] = null | |
} | |
function emitEvent (evt, groupHandlers) { | |
var type = evt[1] | |
mapOwn(groupHandlers[type], function (i, registration) { | |
var sourceEE = evt[0] | |
var ee = registration[0] | |
if (ee === sourceEE) { | |
var handler = registration[1] | |
var ctx = evt[3] | |
var args = evt[2] | |
handler.apply(ctx, args) | |
} | |
}) | |
} | |
},{}],6:[function(require,module,exports){ | |
var mapOwn = require(35) | |
var stringify = require(19) | |
// Characters that are safe in a qs, but get encoded. | |
var charMap = { | |
'%2C': ',', | |
'%3A': ':', | |
'%2F': '/', | |
'%40': '@', | |
'%24': '$', | |
'%3B': ';' | |
} | |
var charList = mapOwn(charMap, function (k) { return k }) | |
var safeEncoded = new RegExp(charList.join('|'), 'g') | |
function real (c) { | |
return charMap[c] | |
} | |
// Encode as URI Component, then unescape anything that is ok in the | |
// query string position. | |
function qs (value) { | |
if (value === null || value === undefined) return 'null' | |
return encodeURIComponent(value).replace(safeEncoded, real) | |
} | |
module.exports = {obj: obj, fromArray: fromArray, qs: qs, param: param} | |
function fromArray (qs, maxBytes) { | |
var bytes = 0 | |
for (var i = 0; i < qs.length; i++) { | |
bytes += qs[i].length | |
if (bytes > maxBytes) return qs.slice(0, i).join('') | |
} | |
return qs.join('') | |
} | |
function obj (payload, maxBytes) { | |
var total = 0 | |
var result = '' | |
mapOwn(payload, function (feature, dataArray) { | |
var intermediate = [] | |
var next | |
var i | |
if (typeof dataArray === 'string') { | |
next = '&' + feature + '=' + qs(dataArray) | |
total += next.length | |
result += next | |
} else if (dataArray.length) { | |
total += 9 | |
for (i = 0; i < dataArray.length; i++) { | |
next = qs(stringify(dataArray[i])) | |
// TODO: Consider more complete ways of handling too much error data. | |
total += next.length | |
if (typeof maxBytes !== 'undefined' && total >= maxBytes) break | |
intermediate.push(next) | |
} | |
result += '&' + feature + '=%5B' + intermediate.join(',') + '%5D' | |
} | |
}) | |
return result | |
} | |
// Constructs an HTTP parameter to add to the BAM router URL | |
function param (name, value) { | |
if (value && typeof (value) === 'string') { | |
return '&' + name + '=' + qs(value) | |
} | |
return '' | |
} | |
},{}],7:[function(require,module,exports){ | |
var mapOwn = require(35) | |
var ee = require("ee") | |
var drain = require(5) | |
module.exports = function activateFeatures (flags) { | |
if (!(flags && typeof flags === 'object')) return | |
mapOwn(flags, function (flag, val) { | |
if (!val || activatedFeatures[flag]) return | |
ee.emit('feat-' + flag, []) | |
activatedFeatures[flag] = true | |
}) | |
drain('feature') | |
} | |
var activatedFeatures = module.exports.active = {} | |
},{}],8:[function(require,module,exports){ | |
var single = require(16) | |
var mapOwn = require(35) | |
var timing = require(13) | |
var encode = require(6) | |
var stringify = require(19) | |
var submitData = require(20) | |
var reduce = require(38) | |
var aggregator = require(2) | |
var stopwatch = require(18) | |
var loader = require("loader") | |
var locationUtil = require(12) | |
var cleanURL = require(4) | |
var version = '1118.0c07c19' | |
var jsonp = 'NREUM.setToken' | |
var _events = {} | |
var haveSendBeacon = !!navigator.sendBeacon | |
// requiring ie version updates the IE version on the loader object | |
require(9) | |
var xhrUsable = loader.ieVersion > 9 || loader.ieVersion === 0 | |
module.exports = { | |
sendRUM: single(sendRUM), // wrapping this in single makes it so that it can only be called once from outside | |
sendFinal: sendAllFromUnload, | |
pingErrors: pingErrors, | |
sendX: sendX, | |
on: on, | |
xhrUsable: xhrUsable | |
} | |
// nr is injected into all send methods. This allows for easier testing | |
// we could require('loader') instead | |
function sendRUM (nr) { | |
if (!nr.info.beacon) return | |
if (nr.info.queueTime) aggregator.store('measures', 'qt', { value: nr.info.queueTime }) | |
if (nr.info.applicationTime) aggregator.store('measures', 'ap', { value: nr.info.applicationTime }) | |
// some time in the past some code will have called stopwatch.mark('starttime', Date.now()) | |
// calling measure like this will create a metric that measures the time differential between | |
// the two marks. | |
stopwatch.measure('be', 'starttime', 'firstbyte') | |
stopwatch.measure('fe', 'firstbyte', 'onload') | |
stopwatch.measure('dc', 'firstbyte', 'domContent') | |
var measuresMetrics = aggregator.get('measures') | |
var measuresQueryString = mapOwn(measuresMetrics, function (metricName, measure) { | |
return '&' + metricName + '=' + measure.params.value | |
}).join('') | |
if (measuresQueryString) { | |
// currently we only have one version of our protocol | |
// in the future we may add more | |
var protocol = '1' | |
var chunksForQueryString = [baseQueryString(nr)] | |
chunksForQueryString.push(measuresQueryString) | |
chunksForQueryString.push(encode.param('tt', nr.info.ttGuid)) | |
chunksForQueryString.push(encode.param('us', nr.info.user)) | |
chunksForQueryString.push(encode.param('ac', nr.info.account)) | |
chunksForQueryString.push(encode.param('pr', nr.info.product)) | |
chunksForQueryString.push(encode.param('af', mapOwn(nr.features, function (k) { return k }).join(','))) | |
if (window.performance && typeof (window.performance.timing) !== 'undefined') { | |
var navTimingApiData = ({ | |
timing: timing.addPT(window.performance.timing, {}), | |
navigation: timing.addPN(window.performance.navigation, {}) | |
}) | |
chunksForQueryString.push(encode.param('perf', stringify(navTimingApiData))) | |
} | |
chunksForQueryString.push(encode.param('xx', nr.info.extra)) | |
chunksForQueryString.push(encode.param('ua', nr.info.userAttributes)) | |
chunksForQueryString.push(encode.param('at', nr.info.atts)) | |
var customJsAttributes = stringify(nr.info.jsAttributes) | |
chunksForQueryString.push(encode.param('ja', customJsAttributes === '{}' ? null : customJsAttributes)) | |
var queryString = encode.fromArray(chunksForQueryString, nr.maxBytes) | |
submitData.jsonp( | |
'https://' + nr.info.beacon + '/' + protocol + '/' + nr.info.licenseKey + queryString, | |
jsonp | |
) | |
} | |
} | |
function sendAllFromUnload (nr) { | |
var sents = mapOwn(_events, function (endpoint) { | |
return sendX(endpoint, nr, { unload: true }) | |
}) | |
return reduce(sents, or) | |
} | |
function or (a, b) { return a || b } | |
function sendX (endpoint, nr, opts) { | |
return send(nr, endpoint, createPayload(endpoint), opts || {}) | |
} | |
function createPayload (type) { | |
var makeBody = add({}) | |
var makeQueryString = add({}) | |
var listeners = (_events[type] || []) | |
for (var i = 0; i < listeners.length; i++) { | |
var singlePayload = listeners[i]() | |
if (singlePayload.body) mapOwn(singlePayload.body, makeBody) | |
if (singlePayload.qs) mapOwn(singlePayload.qs, makeQueryString) | |
} | |
return { body: makeBody(), qs: makeQueryString() } | |
} | |
function send (nr, endpoint, payload, opts) { | |
if (!(nr.info.errorBeacon && payload.body)) return false | |
var url = 'https://' + nr.info.errorBeacon + '/' + endpoint + '/1/' + nr.info.licenseKey + baseQueryString(nr) | |
if (payload.qs) url += encode.obj(payload.qs, nr.maxBytes) | |
var method | |
var useBody | |
var body | |
switch (endpoint) { | |
case 'jserrors': | |
useBody = false | |
method = haveSendBeacon ? submitData.beacon : submitData.img | |
break | |
default: | |
if (opts.needResponse) { | |
useBody = true | |
method = submitData.xhr | |
} else if (opts.unload) { | |
useBody = haveSendBeacon | |
method = haveSendBeacon ? submitData.beacon : submitData.img | |
} else { | |
// `submitData.beacon` was removed, there is an upper limit to the | |
// number of data allowed before it starts failing, so we save it for | |
// unload data | |
if (xhrUsable) { | |
useBody = true | |
method = submitData.xhr | |
} else if (endpoint === 'events') { | |
method = submitData.img | |
} else { | |
return false | |
} | |
} | |
break | |
} | |
var fullUrl = url | |
if (useBody && endpoint === 'events') { | |
body = payload.body.e | |
} else if (useBody) { | |
body = stringify(payload.body) | |
} else { | |
fullUrl = url + encode.obj(payload.body, nr.maxBytes) | |
} | |
var result = method(fullUrl, body) | |
// if beacon request failed, retry with an alternative method | |
if (!result && method === submitData.beacon) { | |
result = submitData.img(url + encode.obj(payload.body, nr.maxBytes)) | |
} | |
return result | |
} | |
function pingErrors (nr) { | |
if (!(nr && nr.info && nr.info.errorBeacon && nr.ieVersion)) return | |
var url = 'https://' + nr.info.errorBeacon + '/jserrors/ping/' + nr.info.licenseKey + baseQueryString(nr) | |
submitData.img(url) | |
} | |
// Constructs the transaction name param for the beacon URL. | |
// Prefers the obfuscated transaction name over the plain text. | |
// Falls back to making up a name. | |
function transactionNameParam (nr) { | |
if (nr.info.transactionName) return encode.param('to', nr.info.transactionName) | |
return encode.param('t', nr.info.tNamePlain || 'Unnamed Transaction') | |
} | |
function on (type, fn) { | |
var listeners = (_events[type] || (_events[type] = [])) | |
listeners.push(fn) | |
} | |
// The stuff that gets sent every time. | |
function baseQueryString (nr) { | |
return ([ | |
'?a=' + nr.info.applicationID, | |
encode.param('sa', (nr.info.sa ? '' + nr.info.sa : '')), | |
encode.param('v', version), | |
transactionNameParam(nr), | |
encode.param('ct', nr.customTransaction), | |
'&rst=' + nr.now(), | |
encode.param('ref', cleanURL(locationUtil.getLocation())) | |
].join('')) | |
} | |
function add (payload) { | |
var hasData = false | |
return function (key, val) { | |
if (val && val.length) { | |
payload[key] = val | |
hasData = true | |
} | |
if (hasData) return payload | |
} | |
} | |
},{}],9:[function(require,module,exports){ | |
var loader = require("loader") | |
var div = document.createElement('div') | |
div.innerHTML = '<!--[if lte IE 6]><div></div><![endif]-->' + | |
'<!--[if lte IE 7]><div></div><![endif]-->' + | |
'<!--[if lte IE 8]><div></div><![endif]-->' + | |
'<!--[if lte IE 9]><div></div><![endif]-->' | |
var len = div.getElementsByTagName('div').length | |
if (len === 4) loader.ieVersion = 6 | |
else if (len === 3) loader.ieVersion = 7 | |
else if (len === 2) loader.ieVersion = 8 | |
else if (len === 1) loader.ieVersion = 9 | |
else loader.ieVersion = 0 | |
module.exports = loader.ieVersion | |
},{}],10:[function(require,module,exports){ | |
var sHash = require(15) | |
var addE = require(1) | |
var startTime = require(17) | |
var stopwatch = require(18) | |
var single = require(16) | |
var harvest = require(8) | |
var registerHandler = require(14) | |
var activateFeatures = require(7) | |
var loader = require("loader") | |
var ffVersion = require(33) | |
var drain = require(5) | |
// api loads registers several event listeners, but does not have any exports | |
require(3) | |
var autorun = typeof (window.NREUM.autorun) !== 'undefined' ? window.NREUM.autorun : true | |
// Features are activated using the legacy setToken function name via JSONP | |
window.NREUM.setToken = activateFeatures | |
if (require(9) === 6) loader.maxBytes = 2000 | |
else loader.maxBytes = 30000 | |
loader.releaseIds = {} | |
var oneFinalHarvest = single(finalHarvest) | |
// | |
// Firefox has a bug wherein a slow-loading resource loaded from the 'pagehide' | |
// or 'unload' event will delay the 'load' event firing on the next page load. | |
// In Firefox versions that support sendBeacon, this doesn't matter, because | |
// we'll use it instead of an image load for our final harvest. | |
// | |
// Some Safari versions never fire the 'unload' event for pages that are being | |
// put into the WebKit page cache, so we *need* to use the pagehide event for | |
// the final submission from Safari. | |
// | |
// Generally speaking, we will try to submit our final harvest from either | |
// pagehide or unload, whichever comes first, but in Firefox, we need to avoid | |
// attempting to submit from pagehide to ensure that we don't slow down loading | |
// of the next page. | |
// | |
if (!ffVersion || navigator.sendBeacon) { | |
addE('pagehide', oneFinalHarvest) | |
} else { | |
addE('beforeunload', oneFinalHarvest) | |
} | |
addE('unload', oneFinalHarvest) | |
registerHandler('mark', stopwatch.mark, 'api') | |
stopwatch.mark('done') | |
drain('api') | |
if (autorun) harvest.sendRUM(loader) | |
// Set a cookie when the page unloads. Consume this cookie on the next page to get a 'start time'. | |
// The navigation start time cookie is removed when the browser supports the web timing API. | |
// Doesn't work in some browsers (Opera). | |
function finalHarvest (e) { | |
harvest.sendFinal(loader, false) | |
// write navigation start time cookie if needed | |
if (startTime.navCookie) { | |
document.cookie = 'NREUM=s=' + Number(new Date()) + '&r=' + sHash(document.location.href) + '&p=' + sHash(document.referrer) + '; path=/' | |
} | |
} | |
},{}],11:[function(require,module,exports){ | |
module.exports = function interval (fn, ms) { | |
setTimeout(function tick () { | |
try { | |
fn() | |
} finally { | |
setTimeout(tick, ms) | |
} | |
}, ms) | |
} | |
},{}],12:[function(require,module,exports){ | |
module.exports = { | |
getLocation: getLocation | |
} | |
function getLocation() { | |
return '' + location | |
} | |
},{}],13:[function(require,module,exports){ | |
// We don't use JSON.stringify directly on the performance timing data for these reasons: | |
// * Chrome has extra data in the performance object that we don't want to send all the time (wasteful) | |
// * Firefox fails to stringify the native object due to - http://code.google.com/p/v8/issues/detail?id=1223 | |
// * The variable names are long and wasteful to transmit | |
// Add Performance Timing values to the given object. | |
// * Values are written relative to an offset to reduce their length (i.e. number of characters). | |
// * The offset is sent with the data | |
// * 0's are not included unless the value is a 'relative zero' | |
// | |
var START = 'Start' | |
var END = 'End' | |
var UNLOAD_EVENT = 'unloadEvent' | |
var REDIRECT = 'redirect' | |
var DOMAIN_LOOKUP = 'domainLookup' | |
var ONNECT = 'onnect' | |
var REQUEST = 'request' | |
var RESPONSE = 'response' | |
var LOAD_EVENT = 'loadEvent' | |
var DOM_CONTENT_LOAD_EVENT = 'domContentLoadedEvent' | |
var navTimingValues = [] | |
module.exports = { | |
addPT: addPT, | |
addPN: addPN, | |
nt: navTimingValues | |
} | |
function addPT (pt, v) { | |
var offset = pt['navigation' + START] | |
v.of = offset | |
addRel(offset, offset, v, 'n') | |
addRel(pt[UNLOAD_EVENT + START], offset, v, 'u') | |
addRel(pt[REDIRECT + START], offset, v, 'r') | |
addRel(pt[UNLOAD_EVENT + END], offset, v, 'ue') | |
addRel(pt[REDIRECT + END], offset, v, 're') | |
addRel(pt['fetch' + START], offset, v, 'f') | |
addRel(pt[DOMAIN_LOOKUP + START], offset, v, 'dn') | |
addRel(pt[DOMAIN_LOOKUP + END], offset, v, 'dne') | |
addRel(pt['c' + ONNECT + START], offset, v, 'c') | |
addRel(pt['secureC' + ONNECT + 'ion' + START], offset, v, 's') | |
addRel(pt['c' + ONNECT + END], offset, v, 'ce') | |
addRel(pt[REQUEST + START], offset, v, 'rq') | |
addRel(pt[RESPONSE + START], offset, v, 'rp') | |
addRel(pt[RESPONSE + END], offset, v, 'rpe') | |
addRel(pt.domLoading, offset, v, 'dl') | |
addRel(pt.domInteractive, offset, v, 'di') | |
addRel(pt[DOM_CONTENT_LOAD_EVENT + START], offset, v, 'ds') | |
addRel(pt[DOM_CONTENT_LOAD_EVENT + END], offset, v, 'de') | |
addRel(pt.domComplete, offset, v, 'dc') | |
addRel(pt[LOAD_EVENT + START], offset, v, 'l') | |
addRel(pt[LOAD_EVENT + END], offset, v, 'le') | |
return v | |
} | |
// Add Performance Navigation values to the given object | |
function addPN (pn, v) { | |
addRel(pn.type, 0, v, 'ty') | |
addRel(pn.redirectCount, 0, v, 'rc') | |
return v | |
} | |
function addRel (value, offset, obj, prop) { | |
var relativeValue | |
if (typeof (value) === 'number' && (value > 0)) { | |
relativeValue = Math.round(value - offset) | |
obj[prop] = relativeValue | |
} | |
navTimingValues.push(relativeValue) | |
} | |
},{}],14:[function(require,module,exports){ | |
var handleEE = require("handle").ee | |
module.exports = defaultRegister | |
defaultRegister.on = registerWithSpecificEmitter | |
var handlers = defaultRegister.handlers = {} | |
function defaultRegister (type, handler, group, ee) { | |
registerWithSpecificEmitter(ee || handleEE, type, handler, group) | |
} | |
function registerWithSpecificEmitter (ee, type, handler, group) { | |
if (!group) group = 'feature' | |
if (!ee) ee = handleEE | |
var groupHandlers = handlers[group] = handlers[group] || {} | |
var list = groupHandlers[type] = groupHandlers[type] || [] | |
list.push([ee, handler]) | |
} | |
},{}],15:[function(require,module,exports){ | |
module.exports = sHash | |
function sHash (s) { | |
var i | |
var h = 0 | |
for (i = 0; i < s.length; i++) { | |
h += ((i + 1) * s.charCodeAt(i)) | |
} | |
return Math.abs(h) | |
} | |
},{}],16:[function(require,module,exports){ | |
var slice = require(36) | |
module.exports = single | |
function single (fn) { | |
var called = false | |
var res | |
return function () { | |
if (called) return res | |
called = true | |
res = fn.apply(this, slice(arguments)) | |
return res | |
} | |
} | |
},{}],17:[function(require,module,exports){ | |
// Use various techniques to determine the time at which this page started and whether to capture navigation timing information | |
var sHash = require(15) | |
var stopwatch = require(18) | |
var loader = require("loader") | |
var ffVersion = require(33) | |
module.exports = { navCookie: true } | |
findStartTime() | |
function findStartTime () { | |
var starttime = findStartWebTiming() || findStartCookie() | |
if (!starttime) return | |
stopwatch.mark('starttime', starttime) | |
// Refine loader.offset | |
loader.offset = starttime | |
} | |
// Find the start time from the Web Timing 'performance' object. | |
// http://test.w3.org/webperf/specs/NavigationTiming/ | |
// http://blog.chromium.org/2010/07/do-you-know-how-slow-your-web-page-is.html | |
function findStartWebTiming () { | |
// FF 7/8 has a bug with the navigation start time, so use cookie instead of native interface | |
if (ffVersion && ffVersion < 9) return | |
var performanceCheck = require(37) | |
if (performanceCheck.exists) { | |
// note that we don't need to use a cookie to record navigation start time | |
module.exports.navCookie = false | |
return window.performance.timing.navigationStart | |
} | |
} | |
// Find the start time based on a cookie set by Episodes in the unload handler. | |
function findStartCookie () { | |
var aCookies = document.cookie.split(' ') | |
for (var i = 0; i < aCookies.length; i++) { | |
if (aCookies[i].indexOf('NREUM=') === 0) { | |
var startPage | |
var referrerPage | |
var aSubCookies = aCookies[i].substring('NREUM='.length).split('&') | |
var startTime | |
var bReferrerMatch | |
for (var j = 0; j < aSubCookies.length; j++) { | |
if (aSubCookies[j].indexOf('s=') === 0) { | |
startTime = aSubCookies[j].substring(2) | |
} else if (aSubCookies[j].indexOf('p=') === 0) { | |
referrerPage = aSubCookies[j].substring(2) | |
// if the sub-cookie is not the last cookie it will have a trailing ';' | |
if (referrerPage.charAt(referrerPage.length - 1) === ';') { | |
referrerPage = referrerPage.substr(0, referrerPage.length - 1) | |
} | |
} else if (aSubCookies[j].indexOf('r=') === 0) { | |
startPage = aSubCookies[j].substring(2) | |
// if the sub-cookie is not the last cookie it will have a trailing ';' | |
if (startPage.charAt(startPage.length - 1) === ';') { | |
startPage = startPage.substr(0, startPage.length - 1) | |
} | |
} | |
} | |
if (startPage) { | |
var docReferrer = sHash(document.referrer) | |
bReferrerMatch = (docReferrer == startPage) // eslint-disable-line | |
if (!bReferrerMatch) { | |
// Navigation did not start at the page that was just exited, check for re-load | |
// (i.e. the page just exited is the current page and the referring pages match) | |
bReferrerMatch = sHash(document.location.href) == startPage && docReferrer == referrerPage // eslint-disable-line | |
} | |
} | |
if (bReferrerMatch && startTime) { | |
var now = new Date().getTime() | |
if ((now - startTime) > 60000) { | |
return | |
} | |
return startTime | |
} | |
} | |
} | |
} | |
},{}],18:[function(require,module,exports){ | |
var aggregator = require(2) | |
var loader = require("loader") | |
var marks = {} | |
module.exports = { | |
mark: mark, | |
measure: measure | |
} | |
function mark (markName, markTime) { | |
if (typeof markTime === 'undefined') markTime = (loader.now() + loader.offset) | |
marks[markName] = markTime | |
} | |
function measure (metricName, startMark, endMark) { | |
var start = marks[startMark] | |
var end = marks[endMark] | |
if (typeof start === 'undefined' || typeof end === 'undefined') return | |
aggregator.store('measures', metricName, { value: end - start }) | |
} | |
},{}],19:[function(require,module,exports){ | |
var mapOwn = require(35) | |
var ee = require("ee") | |
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g // eslint-disable-line | |
var meta = { | |
'\b': '\\b', | |
'\t': '\\t', | |
'\n': '\\n', | |
'\f': '\\f', | |
'\r': '\\r', | |
'"': '\\"', | |
'\\': '\\\\' | |
} | |
module.exports = stringify | |
function stringify (val) { | |
try { | |
return str('', {'': val}) | |
} catch (e) { | |
try { | |
ee.emit('internal-error', [e]) | |
} catch (err) { | |
} | |
} | |
} | |
function quote (string) { | |
escapable.lastIndex = 0 | |
return escapable.test(string) ? '"' + string.replace(escapable, function (a) { | |
var c = meta[a] | |
return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) | |
}) + '"' : '"' + string + '"' | |
} | |
function str (key, holder) { | |
var value = holder[key] | |
switch (typeof value) { | |
case 'string': | |
return quote(value) | |
case 'number': | |
return isFinite(value) ? String(value) : 'null' | |
case 'boolean': | |
return String(value) | |
case 'object': | |
if (!value) { return 'null' } | |
var partial = [] | |
// The value is an array. Stringify every element. Use null as a placeholder | |
// for non-JSON values. | |
if (value instanceof window.Array || Object.prototype.toString.apply(value) === '[object Array]') { | |
var length = value.length | |
for (var i = 0; i < length; i += 1) { | |
partial[i] = str(i, value) || 'null' | |
} | |
return partial.length === 0 ? '[]' : '[' + partial.join(',') + ']' | |
} | |
mapOwn(value, function (k) { | |
var v = str(k, value) | |
if (v) partial.push(quote(k) + ':' + v) | |
}) | |
return partial.length === 0 ? '{}' : '{' + partial.join(',') + '}' | |
} | |
} | |
},{}],20:[function(require,module,exports){ | |
var submitData = module.exports = {} | |
submitData.jsonp = function jsonp (url, jsonp) { | |
var element = document.createElement('script') | |
element.type = 'text/javascript' | |
element.src = url + '&jsonp=' + jsonp | |
var firstScript = document.getElementsByTagName('script')[0] | |
firstScript.parentNode.insertBefore(element, firstScript) | |
return element | |
} | |
submitData.xhr = function xhr (url, body, sync) { | |
var request = new XMLHttpRequest() | |
request.open('POST', url, !sync) | |
try { | |
// Set cookie | |
if ('withCredentials' in request) request.withCredentials = true | |
} catch (e) {} | |
request.setRequestHeader('content-type', 'text/plain') | |
request.send(body) | |
return request | |
} | |
submitData.xhrSync = function xhrSync (url, body) { | |
return submitData.xhr(url, body, true) | |
} | |
submitData.img = function img (url) { | |
var element = new Image() | |
element.src = url | |
return element | |
} | |
submitData.beacon = function (url, body) { | |
return navigator.sendBeacon(url, body) | |
} | |
},{}],21:[function(require,module,exports){ | |
var canonicalFunctionNameRe = /([a-z0-9]+)$/i | |
function canonicalFunctionName (orig) { | |
if (!orig) return | |
var match = orig.match(canonicalFunctionNameRe) | |
if (match) return match[1] | |
return | |
} | |
module.exports = canonicalFunctionName | |
},{}],22:[function(require,module,exports){ | |
// computeStackTrace: cross-browser stack traces in JavaScript | |
// | |
// Syntax: | |
// s = computeStackTrace(exception) // consider using TraceKit.report instead | |
// Returns: | |
// s.name - exception name | |
// s.message - exception message | |
// s.stack[i].url - JavaScript or HTML file URL | |
// s.stack[i].func - function name, or empty for anonymous functions | |
// s.stack[i].line - line number, if known | |
// s.stack[i].column - column number, if known | |
// s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# | |
// s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace | |
// | |
// Supports: | |
// - Firefox: full stack trace with line numbers and unreliable column | |
// number on top frame | |
// - Opera 10: full stack trace with line and column numbers | |
// - Opera 9-: full stack trace with line numbers | |
// - Chrome: full stack trace with line and column numbers | |
// - Safari: line and column number for the topmost stacktrace element | |
// only | |
// - IE: no line numbers whatsoever | |
// Contents of Exception in various browsers. | |
// | |
// SAFARI: | |
// ex.message = Can't find variable: qq | |
// ex.line = 59 | |
// ex.sourceId = 580238192 | |
// ex.sourceURL = http://... | |
// ex.expressionBeginOffset = 96 | |
// ex.expressionCaretOffset = 98 | |
// ex.expressionEndOffset = 98 | |
// ex.name = ReferenceError | |
// | |
// FIREFOX: | |
// ex.message = qq is not defined | |
// ex.fileName = http://... | |
// ex.lineNumber = 59 | |
// ex.stack = ...stack trace... (see the example below) | |
// ex.name = ReferenceError | |
// | |
// CHROME: | |
// ex.message = qq is not defined | |
// ex.name = ReferenceError | |
// ex.type = not_defined | |
// ex.arguments = ['aa'] | |
// ex.stack = ...stack trace... | |
// | |
// INTERNET EXPLORER: | |
// ex.message = ... | |
// ex.name = ReferenceError | |
// | |
// OPERA: | |
// ex.message = ...message... (see the example below) | |
// ex.name = ReferenceError | |
// ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) | |
// ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' | |
var reduce = require(38) | |
var formatStackTrace = require(23) | |
var has = Object.prototype.hasOwnProperty | |
var debug = false | |
var classNameRegex = /function (.+?)\s*\(/ | |
var chrome = /^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i | |
var gecko = /^\s*(?:(\S*|global code)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i | |
var chrome_eval = /^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i | |
var ie_eval = /^\s*at Function code \(Function code:\d+:\d+\)\s*/i | |
module.exports = computeStackTrace | |
function computeStackTrace (ex) { | |
var stack = null | |
try { | |
// This must be tried first because Opera 10 *destroys* | |
// its stacktrace property if you try to access the stack | |
// property first!! | |
stack = computeStackTraceFromStacktraceProp(ex) | |
if (stack) { | |
return stack | |
} | |
} catch (e) { | |
if (debug) { | |
throw e | |
} | |
} | |
try { | |
stack = computeStackTraceFromStackProp(ex) | |
if (stack) { | |
return stack | |
} | |
} catch (e) { | |
if (debug) { | |
throw e | |
} | |
} | |
try { | |
stack = computeStackTraceFromOperaMultiLineMessage(ex) | |
if (stack) { | |
return stack | |
} | |
} catch (e) { | |
if (debug) { | |
throw e | |
} | |
} | |
try { | |
stack = computeStackTraceBySourceAndLine(ex) | |
if (stack) { | |
return stack | |
} | |
} catch (e) { | |
if (debug) { | |
throw e | |
} | |
} | |
try { | |
stack = computeStackTraceWithMessageOnly(ex) | |
if (stack) { | |
return stack | |
} | |
} catch (e) { | |
if (debug) { | |
throw e | |
} | |
} | |
return { | |
'mode': 'failed', | |
'stackString': '', | |
'frames': [] | |
} | |
} | |
/** | |
* Computes stack trace information from the stack property. | |
* Chrome and Gecko use this property. | |
* @param {Error} ex | |
* @return {?Object.<string, *>} Stack trace information. | |
*/ | |
function computeStackTraceFromStackProp (ex) { | |
if (!ex.stack) { | |
return null | |
} | |
var errorInfo = reduce( | |
ex.stack.split('\n'), | |
parseStackProp, | |
{frames: [], stackLines: [], wrapperSeen: false} | |
) | |
if (!errorInfo.frames.length) return null | |
return { | |
'mode': 'stack', | |
'name': ex.name || getClassName(ex), | |
'message': ex.message, | |
'stackString': formatStackTrace(errorInfo.stackLines), | |
'frames': errorInfo.frames | |
} | |
} | |
function parseStackProp (info, line) { | |
var element = getElement(line) | |
if (!element) { | |
info.stackLines.push(line) | |
return info | |
} | |
if (isWrapper(element.func)) info.wrapperSeen = true | |
else info.stackLines.push(line) | |
if (!info.wrapperSeen) info.frames.push(element) | |
return info | |
} | |
function getElement (line) { | |
var parts = line.match(gecko) | |
if (!parts) parts = line.match(chrome) | |
if (parts) { | |
return ({ | |
'url': parts[2], | |
'func': (parts[1] !== 'Anonymous function' && parts[1] !== 'global code' && parts[1]) || null, | |
'line': +parts[3], | |
'column': parts[4] ? +parts[4] : null | |
}) | |
} | |
if (line.match(chrome_eval) || line.match(ie_eval) || line === 'anonymous') { | |
return { 'func': 'evaluated code' } | |
} | |
} | |
function computeStackTraceBySourceAndLine (ex) { | |
if (!('line' in ex)) return null | |
var className = ex.name || getClassName(ex) | |
// Safari does not provide a URL for errors in eval'd code | |
if (!ex.sourceURL) { | |
return ({ | |
'mode': 'sourceline', | |
'name': className, | |
'message': ex.message, | |
'stackString': getClassName(ex) + ': ' + ex.message + '\n in evaluated code', | |
'frames': [{ | |
'func': 'evaluated code' | |
}] | |
}) | |
} | |
var stackString = className + ': ' + ex.message + '\n at ' + ex.sourceURL | |
if (ex.line) { | |
stackString += ':' + ex.line | |
if (ex.column) { | |
stackString += ':' + ex.column | |
} | |
} | |
return ({ | |
'mode': 'sourceline', | |
'name': className, | |
'message': ex.message, | |
'stackString': stackString, | |
'frames': [{ 'url': ex.sourceURL, | |
'line': ex.line, | |
'column': ex.column | |
}] | |
}) | |
} | |
function computeStackTraceWithMessageOnly (ex) { | |
var className = ex.name || getClassName(ex) | |
if (!className) return null | |
return ({ | |
'mode': 'nameonly', | |
'name': className, | |
'message': ex.message, | |
'stackString': className + ': ' + ex.message, | |
'frames': [] | |
}) | |
} | |
function getClassName (obj) { | |
var results = classNameRegex.exec(String(obj.constructor)) | |
return (results && results.length > 1) ? results[1] : 'unknown' | |
} | |
function isWrapper (functionName) { | |
return (functionName && functionName.indexOf('nrWrapper') >= 0) | |
} | |
// TODO: Stop supporting old opera and throw away: | |
// computeStackTraceFromStacktraceProp | |
// computeStackTraceFromOperaMultiLineMessage | |
/** | |
* Computes stack trace information from the stacktrace property. | |
* Opera 10 uses this property. | |
* @param {Error} ex | |
* @return {?Object.<string, *>} Stack trace information. | |
*/ | |
function computeStackTraceFromStacktraceProp (ex) { | |
if (!ex.stacktrace) { | |
return null | |
} | |
// Access and store the stacktrace property before doing ANYTHING | |
// else to it because Opera is not very good at providing it | |
// reliably in other circumstances. | |
var stacktrace = ex.stacktrace | |
var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\(.*\) in (.*):\s*$/i | |
var lines = stacktrace.split('\n') | |
var frames = [] | |
var stackLines = [] | |
var parts | |
var wrapperSeen = false | |
for (var i = 0, j = lines.length; i < j; i += 2) { | |
if ((parts = testRE.exec(lines[i]))) { | |
var element = { | |
'line': +parts[1], | |
'column': +parts[2], | |
'func': parts[3] || parts[4], | |
'url': parts[5] | |
} | |
if (isWrapper(element.func)) wrapperSeen = true | |
else stackLines.push(lines[i]) | |
if (!wrapperSeen) frames.push(element) | |
} else { | |
stackLines.push(lines[i]) | |
} | |
} | |
if (!frames.length) { | |
return null | |
} | |
return { | |
'mode': 'stacktrace', | |
'name': ex.name || getClassName(ex), | |
'message': ex.message, | |
'stackString': formatStackTrace(stackLines), | |
'frames': frames | |
} | |
} | |
/** | |
* NOT TESTED. | |
* Computes stack trace information from an error message that includes | |
* the stack trace. | |
* Opera 9 and earlier use this method if the option to show stack | |
* traces is turned on in opera:config. | |
* @param {Error} ex | |
* @return {?Object.<string, *>} Stack information. | |
*/ | |
function computeStackTraceFromOperaMultiLineMessage (ex) { | |
// Opera includes a stack trace into the exception message. An example is: | |
// | |
// Statement on line 3: Undefined variable: undefinedFunc | |
// Backtrace: | |
// Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz | |
// undefinedFunc(a) | |
// Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy | |
// zzz(x, y, z) | |
// Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx | |
// yyy(a, a, a) | |
// Line 1 of function script | |
// try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } | |
// ... | |
var lines = ex.message.split('\n') | |
if (lines.length < 4) { | |
return null | |
} | |
var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i | |
var lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i | |
var lineRE3 = /^\s*Line (\d+) of function script\s*$/i | |
var frames = [] | |
var stackLines = [] | |
var scripts = document.getElementsByTagName('script') | |
var inlineScriptBlocks = [] | |
var parts | |
var i | |
var len | |
var wrapperSeen = false | |
for (i in scripts) { | |
if (has.call(scripts, i) && !scripts[i].src) { | |
inlineScriptBlocks.push(scripts[i]) | |
} | |
} | |
for (i = 2, len = lines.length; i < len; i += 2) { | |
var item = null | |
if ((parts = lineRE1.exec(lines[i]))) { | |
item = { | |
'url': parts[2], | |
'func': parts[3], | |
'line': +parts[1] | |
} | |
} else if ((parts = lineRE2.exec(lines[i]))) { | |
item = { | |
'url': parts[3], | |
'func': parts[4] | |
} | |
} else if ((parts = lineRE3.exec(lines[i]))) { | |
var url = window.location.href.replace(/#.*$/, '') | |
var line = parts[1] | |
item = { | |
'url': url, | |
'line': line, | |
'func': '' | |
} | |
} | |
if (item) { | |
if (isWrapper(item.func)) wrapperSeen = true | |
else stackLines.push(lines[i]) | |
if (!wrapperSeen) frames.push(item) | |
} | |
} | |
if (!frames.length) { | |
return null // could not parse multiline exception message as Opera stack trace | |
} | |
return { | |
'mode': 'multiline', | |
'name': ex.name || getClassName(ex), | |
'message': lines[0], | |
'stackString': formatStackTrace(stackLines), | |
'frames': frames | |
} | |
} | |
},{}],23:[function(require,module,exports){ | |
var stripNewlinesRegex = /^\n+|\n+$/g | |
module.exports = function (stackLines) { | |
var stackString | |
if (stackLines.length > 100) { | |
var truncatedLines = stackLines.length - 100 | |
stackString = stackLines.slice(0, 50).join('\n') | |
stackString += '\n< ...truncated ' + truncatedLines + ' lines... >\n' | |
stackString += stackLines.slice(-50).join('\n') | |
} else { | |
stackString = stackLines.join('\n') | |
} | |
return stackString.replace(stripNewlinesRegex, '') | |
} | |
},{}],24:[function(require,module,exports){ | |
var agg = require(2) | |
var canonicalFunctionName = require(21) | |
var cleanURL = require(4) | |
var computeStackTrace = require(22) | |
var stringHashCode = require(25) | |
var loader = require("loader") | |
var ee = require("ee") | |
var stackReported = {} | |
var pageviewReported = {} | |
var register = require(14) | |
var harvest = require(8) | |
var interval = require(11) | |
var stringify = require(19) | |
var handle = require("handle") | |
var baseEE = require("ee") | |
var mapOwn = require(35) | |
var errorCache = {} | |
// Make sure loader.offset is as accurate as possible | |
require(17) | |
// bail if not instrumented | |
if (!loader.features.err) return | |
var errorOnPage = false | |
var harvestTimeSeconds = 60 | |
harvest.on('jserrors', function () { | |
var body = agg.take([ 'err', 'ierr' ]) | |
var payload = { body: body, qs: {} } | |
var releaseIds = stringify(loader.releaseIds) | |
if (releaseIds !== '{}') { | |
payload.qs.ri = releaseIds | |
} | |
if (body && body.err && body.err.length && !errorOnPage) { | |
payload.qs.pve = '1' | |
errorOnPage = true | |
} | |
return payload | |
}) | |
harvest.pingErrors(loader) | |
// send errors every minute | |
interval(function () { | |
var sent = harvest.sendX('jserrors', loader) | |
if (!sent) harvest.pingErrors(loader) | |
}, harvestTimeSeconds * 1000) | |
ee.on('feat-err', function () { | |
register('err', storeError) | |
register('ierr', storeError) | |
}) | |
function nameHash (params) { | |
return stringHashCode(params.exceptionClass) ^ params.stackHash | |
} | |
function canonicalizeURL (url, cleanedOrigin) { | |
if (typeof url !== 'string') return '' | |
var cleanedURL = cleanURL(url) | |
if (cleanedURL === cleanedOrigin) { | |
return '<inline>' | |
} else { | |
return cleanedURL | |
} | |
} | |
function buildCanonicalStackString (stackInfo, cleanedOrigin) { | |
var canonicalStack = '' | |
for (var i = 0; i < stackInfo.frames.length; i++) { | |
var frame = stackInfo.frames[i] | |
var func = canonicalFunctionName(frame.func) | |
if (canonicalStack) canonicalStack += '\n' | |
if (func) canonicalStack += func + '@' | |
if (typeof frame.url === 'string') canonicalStack += frame.url | |
if (frame.line) canonicalStack += ':' + frame.line | |
} | |
return canonicalStack | |
} | |
// Strip query parameters and fragments from the stackString property of the | |
// given stackInfo, along with the 'url' properties of each frame in | |
// stackInfo.frames. | |
// | |
// Any URLs that are equivalent to the cleaned version of the origin will also | |
// be replaced with the string '<inline>'. | |
// | |
function canonicalizeStackURLs (stackInfo) { | |
// Currently, loader.origin might contain a fragment, but we don't want to use it | |
// for comparing with frame URLs. | |
var cleanedOrigin = cleanURL(loader.origin) | |
for (var i = 0; i < stackInfo.frames.length; i++) { | |
var frame = stackInfo.frames[i] | |
var originalURL = frame.url | |
var cleanedURL = canonicalizeURL(originalURL, cleanedOrigin) | |
if (cleanedURL && cleanedURL !== frame.url) { | |
frame.url = cleanedURL | |
stackInfo.stackString = stackInfo.stackString.split(originalURL).join(cleanedURL) | |
} | |
} | |
return stackInfo | |
} | |
function storeError (err, time, internal, customAttributes) { | |
// are we in an interaction | |
time = time || loader.now() | |
if (!internal && loader.onerror && loader.onerror(err)) return | |
var stackInfo = canonicalizeStackURLs(computeStackTrace(err)) | |
var canonicalStack = buildCanonicalStackString(stackInfo) | |
var params = { | |
stackHash: stringHashCode(canonicalStack), | |
exceptionClass: stackInfo.name, | |
request_uri: window.location.pathname | |
} | |
if (stackInfo.message) { | |
params.message = '' + stackInfo.message | |
} | |
if (!stackReported[params.stackHash]) { | |
stackReported[params.stackHash] = true | |
params.stack_trace = stackInfo.stackString | |
} else { | |
params.browser_stack_hash = stringHashCode(stackInfo.stackString) | |
} | |
params.releaseIds = stringify(loader.releaseIds) | |
// When debugging stack canonicalization/hashing, uncomment these lines for | |
// more output in the test logs | |
// params.origStack = err.stack | |
// params.canonicalStack = canonicalStack | |
var hash = nameHash(params) | |
if (!pageviewReported[hash]) { | |
params.pageview = 1 | |
pageviewReported[hash] = true | |
} | |
var type = internal ? 'ierr' : 'err' | |
var newMetrics = { time: time } | |
// stn and spa aggregators listen to this event - stn sends the error in its payload, | |
// and spa annotates the error with interaction info | |
handle('errorAgg', [type, hash, params, newMetrics]) | |
if (params._interactionId != null) { | |
// hold on to the error until the interaction finishes | |
errorCache[params._interactionId] = errorCache[params._interactionId] || [] | |
errorCache[params._interactionId].push([type, hash, params, newMetrics, att, customAttributes]) | |
} else { | |
// store custom attributes | |
var customParams = {} | |
var att = loader.info.jsAttributes | |
mapOwn(att, setCustom) | |
if (customAttributes) { | |
mapOwn(customAttributes, setCustom) | |
} | |
var jsAttributesHash = stringHashCode(stringify(customParams)) | |
var aggregateHash = hash + ':' + jsAttributesHash | |
agg.store(type, aggregateHash, params, newMetrics, customParams) | |
} | |
function setCustom (key, val) { | |
customParams[key] = (val && typeof val === 'object' ? stringify(val) : val) | |
} | |
} | |
baseEE.on('interactionSaved', function (interaction) { | |
if (!errorCache[interaction.id]) return | |
errorCache[interaction.id].forEach(function (item) { | |
var customParams = {} | |
var globalCustomParams = item[4] | |
var localCustomParams = item[5] | |
mapOwn(globalCustomParams, setCustom) | |
mapOwn(interaction.root.attrs.custom, setCustom) | |
mapOwn(localCustomParams, setCustom) | |
var params = item[2] | |
params.browserInteractionId = interaction.root.attrs.id | |
delete params._interactionId | |
if (params._interactionNodeId) { | |
params.parentNodeId = params._interactionNodeId.toString() | |
delete params._interactionNodeId | |
} | |
var hash = item[1] + interaction.root.attrs.id | |
var jsAttributesHash = stringHashCode(stringify(customParams)) | |
var aggregateHash = hash + ':' + jsAttributesHash | |
agg.store(item[0], aggregateHash, params, item[3], customParams) | |
function setCustom (key, val) { | |
customParams[key] = (val && typeof val === 'object' ? stringify(val) : val) | |
} | |
}) | |
delete errorCache[interaction.id] | |
}) | |
baseEE.on('interactionDiscarded', function (interaction) { | |
if (!errorCache[interaction.id]) return | |
errorCache[interaction.id].forEach(function (item) { | |
var customParams = {} | |
var globalCustomParams = item[4] | |
var localCustomParams = item[5] | |
mapOwn(globalCustomParams, setCustom) | |
mapOwn(interaction.root.attrs.custom, setCustom) | |
mapOwn(localCustomParams, setCustom) | |
var params = item[2] | |
delete params._interactionId | |
delete params._interactionNodeId | |
var hash = item[1] | |
var jsAttributesHash = stringHashCode(stringify(customParams)) | |
var aggregateHash = hash + ':' + jsAttributesHash | |
agg.store(item[0], aggregateHash, item[2], item[3], customParams) | |
function setCustom (key, val) { | |
customParams[key] = (val && typeof val === 'object' ? stringify(val) : val) | |
} | |
}) | |
delete errorCache[interaction.id] | |
}) | |
},{}],25:[function(require,module,exports){ | |
function stringHashCode (string) { | |
var hash = 0 | |
var charVal | |
if (!string || !string.length) return hash | |
for (var i = 0; i < string.length; i++) { | |
charVal = string.charCodeAt(i) | |
hash = ((hash << 5) - hash) + charVal | |
hash = hash | 0 // Convert to 32bit integer | |
} | |
return hash | |
} | |
module.exports = stringHashCode | |
},{}],26:[function(require,module,exports){ | |
var ee = require("ee") | |
var loader = require("loader") | |
var mapOwn = require(35) | |
var stringify = require(19) | |
var register = require(14) | |
var harvest = require(8) | |
var cleanURL = require(4) | |
var interval = require(11) | |
var eventsPerMinute = 120 | |
var harvestTimeSeconds = 30 | |
var eventsPerHarvest = eventsPerMinute * harvestTimeSeconds / 60 | |
var referrerUrl | |
var events = [] | |
var att = loader.info.jsAttributes = {} | |
if (document.referrer) referrerUrl = cleanURL(document.referrer) | |
register('api-setCustomAttribute', setCustomAttribute, 'api') | |
ee.on('feat-ins', function () { | |
register('api-addPageAction', addPageAction) | |
harvest.on('ins', function () { | |
var payload = ({ | |
qs: { | |
ua: loader.info.userAttributes, | |
at: loader.info.atts | |
}, | |
body: { | |
ins: events | |
} | |
}) | |
events = [] | |
return payload | |
}) | |
interval(function () { | |
harvest.sendX('ins', loader) | |
}, harvestTimeSeconds * 1000) | |
harvest.sendX('ins', loader) | |
}) | |
// WARNING: Insights times are in seconds. EXCEPT timestamp, which is in ms. | |
function addPageAction (t, name, attributes) { | |
if (events.length >= eventsPerHarvest) return | |
var width | |
var height | |
var eventAttributes = {} | |
if (typeof window !== 'undefined' && window.document && window.document.documentElement) { | |
// Doesn't include the nav bar when it disappears in mobile safari | |
// https://github.com/jquery/jquery/blob/10399ddcf8a239acc27bdec9231b996b178224d3/src/dimensions.js#L23 | |
width = window.document.documentElement.clientWidth | |
height = window.document.documentElement.clientHeight | |
} | |
var defaults = { | |
timestamp: t + loader.offset, | |
timeSinceLoad: t / 1000, | |
browserWidth: width, | |
browserHeight: height, | |
referrerUrl: referrerUrl, | |
currentUrl: cleanURL('' + location), | |
pageUrl: cleanURL(loader.origin), | |
eventType: 'PageAction' | |
} | |
mapOwn(defaults, set) | |
mapOwn(att, set) | |
if (attributes && typeof attributes === 'object') { | |
mapOwn(attributes, set) | |
} | |
eventAttributes.actionName = name || '' | |
events.push(eventAttributes) | |
function set (key, val) { | |
eventAttributes[key] = (val && typeof val === 'object' ? stringify(val) : val) | |
} | |
} | |
function setCustomAttribute (t, key, value) { | |
att[key] = value | |
} | |
},{}],27:[function(require,module,exports){ | |
var register = require(14) | |
var parseUrl = require(31) | |
var harvest = require(8) | |
var serializer = require(28) | |
var loader = require("loader") | |
var baseEE = require("ee") | |
var mutationEE = baseEE.get('mutation') | |
var promiseEE = baseEE.get('promise') | |
var historyEE = baseEE.get('history') | |
var eventsEE = baseEE.get('events') | |
var timerEE = baseEE.get('timer') | |
var fetchEE = baseEE.get('fetch') | |
var jsonpEE = baseEE.get('jsonp') | |
var xhrEE = baseEE.get('xhr') | |
var tracerEE = baseEE.get('tracer') | |
var mapOwn = require(35) | |
var navTiming = require(13).nt | |
var dataSize = require(32) | |
var uniqueId = require(34) | |
var INTERACTION_EVENTS = [ | |
'click', | |
'submit', | |
'keypress', | |
'keydown', | |
'keyup', | |
'change' | |
] | |
var MAX_NODES = 128 | |
var MAX_TIMER_BUDGET = 999 | |
var FN_START = 'fn-start' | |
var FN_END = 'fn-end' | |
var CB_START = 'cb-start' | |
var INTERACTION_API = 'api-ixn-' | |
var REMAINING = 'remaining' | |
var INTERACTION = 'interaction' | |
var SPA_NODE = 'spaNode' | |
var JSONP_NODE = 'jsonpNode' | |
var FETCH_START = 'fetch-start' | |
var FETCH_DONE = 'fetch-done' | |
var FETCH_BODY = 'fetch-body-' | |
var JSONP_END = 'jsonp-end' | |
module.exports = function () { | |
return currentNode && currentNode.id | |
} | |
var originals = NREUM.o | |
var origRequest = originals.REQ | |
var originalSetTimeout = originals.ST | |
var originalClearTimeout = originals.CT | |
var initialPageURL = loader.origin | |
var lastSeenUrl = initialPageURL | |
var lastSeenRouteName = null | |
var timerMap = {} | |
var timerBudget = MAX_TIMER_BUDGET | |
var currentNode = null | |
var prevNode = null | |
var nodeOnLastHashUpdate = null | |
var initialPageLoad = null | |
var pageLoaded = false | |
var childTime = 0 | |
var depth = 0 | |
var lastId = 0 | |
// childTime is used when calculating exclusive time for a cb duration. | |
// | |
// Exclusive time will be different than the total time for either callbacks | |
// which synchronously invoke a customTracer callback or, trigger a synchronous | |
// event (eg. onreadystate=1 or popstate). | |
// | |
// At fn-end, childTime will contain the total time of all timed callbacks and | |
// event handlers which executed as a child of the current callback. At the | |
// begining of every callback, childTime is saved to the event context (which at | |
// that time contains the sum of its preceeding siblings) and is reset to 0. The | |
// callback is then executed, and its children may increase childTime. At the | |
// end of the callback, it reports its exclusive time as its | |
// execution time - exlcuded. childTime is then reset to its previous | |
// value, and the totalTime of the callback that just finished executing is | |
// added to the childTime time. | |
// | clock | childTime | ctx.ct | totalTime | exclusive | | |
// click fn-start | 0 | 0 | 0 | | | | |
// | click begining: | 5 | 0 | 0 | | | | |
// | | custom-1 fn-start | 10 | 0 | 0 | | | | |
// | | | custom-1 begining | 15 | 0 | 0 | | | | |
// | | | | custom-2 fn-start | 20 | 0 | 0 | | | | |
// | | | | | custom-2 | 25 | 0 | 0 | | | | |
// | | | | custom-2 fn-end | 30 | 10 | 0 | 10 | 10 | | |
// | | | custom-1 middle | 35 | 10 | 0 | | | | |
// | | | | custom-3 fn-start | 40 | 0 | 10 | | | | |
// | | | | | custom-3 | 45 | 0 | 10 | | | | |
// | | | | custom-3 fn-end | 50 | 20 | 0 | 10 | 10 | | |
// | | | custom-1 ending | 55 | 20 | 0 | | | | |
// | custom-1 fn-end | 60 | 50 | 0 | 50 | 30 | | |
// | click ending: | 65 | 50 | | | | | |
// click fn-end | 70 | 0 | 0 | 70 | 20 | | |
baseEE.on('feat-spa', function () { | |
initialPageLoad = new Interaction('initialPageLoad', 0) | |
initialPageLoad.save = true | |
currentNode = initialPageLoad.root // hint | |
// ensure that checkFinish calls are safe during initialPageLoad | |
initialPageLoad[REMAINING]++ | |
register.on(baseEE, FN_START, callbackStart) | |
register.on(promiseEE, CB_START, callbackStart) | |
function callbackStart () { | |
depth++ | |
this.prevNode = currentNode | |
this.ct = childTime | |
childTime = 0 | |
timerBudget = MAX_TIMER_BUDGET | |
} | |
register.on(baseEE, FN_END, callbackEnd) | |
register.on(promiseEE, 'cb-end', callbackEnd) | |
function callbackEnd () { | |
depth-- | |
var totalTime = this.jsTime || 0 | |
var exclusiveTime = totalTime - childTime | |
childTime = this.ct + totalTime | |
if (currentNode) { | |
// transfer accumulated callback time to the active interaction node | |
// run even if jsTime is 0 to update jsEnd | |
currentNode.callback(exclusiveTime, this[FN_END]) | |
if (this.isTraced) { | |
currentNode.attrs.tracedTime = exclusiveTime | |
} | |
} | |
this.jsTime = currentNode ? 0 : exclusiveTime | |
setCurrentNode(this.prevNode) | |
this.prevNode = null | |
timerBudget = MAX_TIMER_BUDGET | |
} | |
register.on(eventsEE, FN_START, function (args, eventSource) { | |
var ev = args[0] | |
var evName = ev.type | |
var eventNode = ev.__nrNode | |
if (!pageLoaded && evName === 'load' && eventSource === window) { | |
pageLoaded = true | |
// set to null so prevNode is set correctly | |
this.prevNode = currentNode = null | |
if (initialPageLoad) { | |
eventNode = initialPageLoad.root | |
initialPageLoad[REMAINING]-- | |
originalSetTimeout(function () { | |
INTERACTION_EVENTS.push('popstate') | |
}) | |
} | |
} | |
if (eventNode) { | |
// If we've already seen a previous handler for this specific event object, | |
// just restore that. We want multiple handlers for the same event to share | |
// a node. | |
setCurrentNode(eventNode) | |
} else if (evName === 'hashchange') { | |
setCurrentNode(nodeOnLastHashUpdate) | |
nodeOnLastHashUpdate = null | |
} else if (eventSource instanceof XMLHttpRequest) { | |
// If this event was emitted by an XHR, restore the node ID associated with | |
// that XHR. TODO: should this create a node? | |
setCurrentNode(baseEE.context(eventSource).spaNode) | |
} else if (!currentNode) { | |
// Otherwise, if no interaction is currently active, create a new node ID, | |
// and let the aggregator know that we entered a new event handler callback | |
// so that it has a chance to possibly start an interaction. | |
if (INTERACTION_EVENTS.indexOf(evName) !== -1) { | |
setCurrentNode(new Interaction(evName, this[FN_START]).root) | |
if (evName === 'click') { | |
var value = getActionText(ev.target) | |
if (value) { | |
currentNode.attrs.custom['actionText'] = value | |
} | |
} | |
} | |
} | |
ev.__nrNode = currentNode | |
}) | |
// The context supplied to this callback will be shared with the fn-start/fn-end | |
// callbacks that fire around the callback passed to setTimeout originally. | |
register.on(timerEE, 'setTimeout-end', function saveId (args, obj, timerId) { | |
if (!currentNode || (timerBudget - this.timerDuration) < 0) return | |
currentNode[INTERACTION][REMAINING]++ | |
this.timerId = timerId | |
timerMap[timerId] = currentNode | |
this.timerBudget = timerBudget - 50 | |
}) | |
register.on(timerEE, 'clearTimeout-start', function clear (args) { | |
var timerId = args[0] | |
var node = timerMap[timerId] | |
if (node) { | |
var interaction = node[INTERACTION] | |
interaction[REMAINING]-- | |
interaction.checkFinish() | |
delete timerMap[timerId] | |
} | |
}) | |
register.on(timerEE, FN_START, function () { | |
timerBudget = this.timerBudget || MAX_TIMER_BUDGET | |
var id = this.timerId | |
var node = timerMap[id] | |
setCurrentNode(node) | |
delete timerMap[id] | |
if (node) { | |
node[INTERACTION][REMAINING]-- | |
} | |
}) | |
// context is shared with new-xhr event, and is stored on the xhr iteself. | |
register.on(xhrEE, FN_START, function () { | |
setCurrentNode(this[SPA_NODE]) | |
}) | |
// context is stored on the xhr and is shared with all callbacks associated | |
// with the new xhr | |
register.on(xhrEE, 'new-xhr', function () { | |
if (currentNode) { | |
this[SPA_NODE] = currentNode.child('ajax', null, null, true) | |
} | |
}) | |
register.on(xhrEE, 'send-xhr-start', function () { | |
var node = this[SPA_NODE] | |
if (node && !this.sent) { | |
this.sent = true | |
node.jsEnd = node.start = this['send-xhr-start'] | |
node[INTERACTION][REMAINING]++ | |
} | |
}) | |
register.on(baseEE, 'xhr-resolved', function () { | |
var node = this[SPA_NODE] | |
if (node) { | |
var attrs = node.attrs | |
attrs.params = this.params | |
attrs.metrics = this.metrics | |
node.finish(this['xhr-resolved']) | |
} | |
}) | |
register.on(jsonpEE, 'new-jsonp', function (url) { | |
if (currentNode) { | |
var node = this[JSONP_NODE] = currentNode.child('ajax', this[FETCH_START]) | |
node.start = this['new-jsonp'] | |
this.url = url | |
this.status = null | |
} | |
}) | |
register.on(jsonpEE, 'cb-start', function (args) { | |
var node = this[JSONP_NODE] | |
if (node) { | |
setCurrentNode(node) | |
this.status = 200 | |
} | |
}) | |
register.on(jsonpEE, 'jsonp-error', function () { | |
var node = this[JSONP_NODE] | |
if (node) { | |
setCurrentNode(node) | |
this.status = 0 | |
} | |
}) | |
register.on(jsonpEE, JSONP_END, function () { | |
var node = this[JSONP_NODE] | |
if (node) { | |
// if no status is set then cb never fired - so it's not a valid JSONP | |
if (this.status === null) { | |
node[INTERACTION][REMAINING]-- | |
node.cancelled = true | |
return | |
} | |
var attrs = node.attrs | |
var params = attrs.params = {} | |
var parsed = parseUrl(this.url) | |
params.method = 'GET' | |
params.pathname = parsed.pathname | |
params.host = parsed.hostname + ':' + parsed.port | |
params.status = this.status | |
attrs.metrics = { | |
txSize: 0, | |
rxSize: 0 | |
} | |
attrs.isJSONP = true | |
node.jsEnd = this[JSONP_END] | |
node.jsTime = this[CB_START] ? (this[JSONP_END] - this[CB_START]) : 0 | |
node.finish(node.jsEnd) | |
} | |
}) | |
register.on(fetchEE, FETCH_START, function (target, opts) { | |
if (currentNode) { | |
this[SPA_NODE] = currentNode.child('ajax', this[FETCH_START]) | |
this.target = target | |
this.opts = opts | |
} | |
}) | |
register.on(fetchEE, FETCH_BODY + 'start', function (target, opts) { | |
if (currentNode) { | |
this[SPA_NODE] = currentNode | |
currentNode[INTERACTION][REMAINING]++ | |
} | |
}) | |
register.on(fetchEE, FETCH_BODY + 'end', function (args, ctx, bodyPromise) { | |
var node = this[SPA_NODE] | |
if (node) { | |
node[INTERACTION][REMAINING]-- | |
} | |
}) | |
register.on(fetchEE, FETCH_DONE, function (err, res) { | |
var node = this[SPA_NODE] | |
var target = this.target | |
var opts = this.opts || {} | |
if (node) { | |
if (err) { | |
node.cancelled = true | |
node[INTERACTION][REMAINING]-- | |
return | |
} | |
var url, method | |
if (typeof target === 'string') { | |
url = target | |
} else if (typeof target === 'object' && target instanceof origRequest) { | |
url = target.url | |
} | |
method = ('' + (target && target.method || opts.method || 'GET')).toUpperCase() | |
var attrs = node.attrs | |
var params = attrs.params = {} | |
var parsed = parseUrl(url) | |
params.method = method | |
params.pathname = parsed.pathname | |
params.host = parsed.hostname + ':' + parsed.port | |
params.status = res.status | |
attrs.metrics = { | |
txSize: dataSize(opts.body) || 0, | |
rxSize: this.rxSize | |
} | |
attrs.isFetch = true | |
node.finish(this[FETCH_DONE]) | |
} | |
}) | |
register.on(historyEE, 'newURL', function (url, hashChangedDuringCb) { | |
if (currentNode) { | |
if (lastSeenUrl !== url) { | |
currentNode[INTERACTION].routeChange = true | |
} | |
if (hashChangedDuringCb) { | |
nodeOnLastHashUpdate = currentNode | |
} | |
} | |
lastSeenUrl = url | |
}) | |
register.on(mutationEE, FN_START, function () { | |
setCurrentNode(prevNode) | |
}) | |
register.on(promiseEE, 'resolve-start', resolvePromise) | |
register.on(promiseEE, 'executor-err', resolvePromise) | |
register.on(promiseEE, 'propagate', saveNode) | |
register.on(promiseEE, CB_START, function () { | |
var ctx = this.getCtx ? this.getCtx() : this | |
setCurrentNode(ctx[SPA_NODE]) | |
}) | |
register(INTERACTION_API + 'get', function (t) { | |
var interaction = this.ixn = currentNode ? currentNode[INTERACTION] : new Interaction('api', t) | |
if (!currentNode) { | |
interaction.checkFinish() | |
if (depth) setCurrentNode(interaction.root) | |
} | |
}) | |
register(INTERACTION_API + 'actionText', function (t, actionText) { | |
var customAttrs = this.ixn.root.attrs.custom | |
if (actionText) customAttrs.actionText = actionText | |
}) | |
register(INTERACTION_API + 'setName', function (t, name, trigger) { | |
var attrs = this.ixn.root.attrs | |
if (name) attrs.customName = name | |
if (trigger) attrs.trigger = trigger | |
}) | |
register(INTERACTION_API + 'setAttribute', function (t, name, value) { | |
this.ixn.root.attrs.custom[name] = value | |
}) | |
register(INTERACTION_API + 'end', function (timestamp) { | |
var interaction = this.ixn | |
var node = activeNodeFor(interaction) | |
setCurrentNode(null) | |
node.child('customEnd', timestamp).finish(timestamp) | |
interaction.finish() | |
}) | |
register(INTERACTION_API + 'ignore', function () { | |
this.ixn.ignored = true | |
}) | |
register(INTERACTION_API + 'save', function () { | |
this.ixn.save = true | |
}) | |
register(INTERACTION_API + 'tracer', function (timestamp, name, store) { | |
var interaction = this.ixn | |
var parent = activeNodeFor(interaction) | |
var ctx = baseEE.context(store) | |
if (!name) { | |
ctx.inc = ++interaction[REMAINING] | |
return (ctx[SPA_NODE] = parent) | |
} | |
ctx[SPA_NODE] = parent.child('customTracer', timestamp, name) | |
}) | |
register.on(tracerEE, FN_START, tracerDone) | |
register.on(tracerEE, 'no-' + FN_START, tracerDone) | |
function tracerDone (timestamp, interactionContext, hasCb) { | |
var node = this[SPA_NODE] | |
if (!node) return | |
var interaction = node[INTERACTION] | |
var inc = this.inc | |
this.isTraced = true | |
if (inc) { | |
interaction[REMAINING]-- | |
} else if (node) { | |
node.finish(timestamp) | |
} | |
hasCb ? setCurrentNode(node) : interaction.checkFinish() | |
} | |
register(INTERACTION_API + 'getContext', function (t, cb) { | |
var store = this.ixn.root.attrs.store | |
setTimeout(function () { | |
cb(store) | |
}, 0) | |
}) | |
register(INTERACTION_API + 'onEnd', function (t, cb) { | |
this.ixn.handlers.push(cb) | |
}) | |
register('api-routeName', function (t, currentRouteName) { | |
lastSeenRouteName = currentRouteName | |
}) | |
function activeNodeFor (interaction) { | |
return (currentNode && currentNode[INTERACTION] === interaction) ? currentNode : interaction.root | |
} | |
}) | |
function saveNode (val, overwrite) { | |
if (overwrite || !this[SPA_NODE]) this[SPA_NODE] = currentNode | |
} | |
function resolvePromise () { | |
if (!this.resolved) { | |
this.resolved = true | |
this[SPA_NODE] = currentNode | |
} | |
} | |
function setCurrentNode (newNode) { | |
if (!pageLoaded && !newNode && initialPageLoad) newNode = initialPageLoad.root | |
if (currentNode) { | |
currentNode[INTERACTION].checkFinish() | |
} | |
prevNode = currentNode | |
currentNode = (newNode && !newNode[INTERACTION].end) ? newNode : null | |
} | |
function Interaction (eventName, timestamp) { | |
this.id = lastId++ | |
this.nodes = 0 | |
this[REMAINING] = 0 | |
this.finishTimer = null | |
this.lastCb = this.lastFinish = timestamp | |
this.handlers = [] | |
var root = this.root = new InteractionNode(this, null, 'interaction', timestamp) | |
var attrs = root.attrs | |
attrs.trigger = eventName | |
attrs.initialPageURL = loader.origin | |
attrs.oldRoute = lastSeenRouteName | |
attrs.newURL = attrs.oldURL = lastSeenUrl | |
attrs.custom = {} | |
attrs.store = {} | |
} | |
var InteractionPrototype = Interaction.prototype | |
InteractionPrototype.checkFinish = function checkFinish () { | |
var interaction = this | |
var attrs = this.root.attrs | |
if (interaction.finishTimer) { | |
originalClearTimeout(interaction.finishTimer) | |
interaction.finishTimer = null | |
} | |
if (interaction[REMAINING]) return | |
attrs.newURL = lastSeenUrl | |
attrs.newRoute = lastSeenRouteName | |
interaction.finishTimer = originalSetTimeout(function () { | |
interaction.finishTimer = originalSetTimeout(function () { | |
interaction.finishTimer = null | |
if (!interaction[REMAINING]) interaction.finish() | |
}, 1) | |
}, 0) | |
} | |
// serialize report and remove nodes from map | |
InteractionPrototype.finish = function finishInteraction () { | |
var interaction = this | |
var root = interaction.root | |
if (root.end) return | |
if (interaction === initialPageLoad) initialPageLoad = null | |
var endTimestamp = Math.max(interaction.lastCb, interaction.lastFinish) | |
var attrs = root.attrs | |
var customAttrs = attrs.custom | |
// make sure that newrelic[INTERACTION]() works in end handler | |
currentNode = root | |
mapOwn(this.handlers, function (i, cb) { | |
cb(attrs.store) | |
}) | |
setCurrentNode(null) | |
mapOwn(loader.info.jsAttributes, function (attr, value) { | |
if (!(attr in customAttrs)) customAttrs[attr] = value | |
}) | |
root.end = endTimestamp | |
baseEE.emit('interaction', [this]) | |
} | |
function InteractionNode (interaction, parent, type, timestamp) { | |
this[INTERACTION] = interaction | |
this.parent = parent | |
this.id = lastId++ | |
this.type = type | |
this.children = [] | |
this.end = null | |
this.jsEnd = this.start = timestamp | |
this.jsTime = 0 | |
this.attrs = {} | |
} | |
var InteractionNodePrototype = InteractionNode.prototype | |
InteractionNodePrototype.child = function child (type, timestamp, name, dontWait) { | |
var interaction = this[INTERACTION] | |
if (interaction.end || interaction.nodes >= MAX_NODES) return null | |
if (interaction.finishTimer) { | |
originalClearTimeout(interaction.finishTimer) | |
interaction.finishTimer = null | |
} | |
var node = new InteractionNode(interaction, this, type, timestamp) | |
node.attrs.name = name | |
interaction.nodes++ | |
if (!dontWait) interaction[REMAINING]++ | |
return node | |
} | |
InteractionNodePrototype.callback = function addCallbackTime (exclusiveTime, end) { | |
var node = this | |
node.jsTime += exclusiveTime | |
if (end > node.jsEnd) { | |
node.jsEnd = end | |
node[INTERACTION].lastCb = end | |
} | |
} | |
InteractionNodePrototype.finish = function finish (timestamp) { | |
var node = this | |
if (node.end) return | |
node.end = timestamp | |
var parent = node.parent | |
while (parent.cancelled) parent = parent.parent | |
parent.children.push(node) | |
node.parent = null | |
var interaction = this[INTERACTION] | |
interaction[REMAINING]-- | |
interaction.lastFinish = timestamp | |
} | |
var lastInteractionRecord = null | |
harvest.on('events', function () { | |
if (!lastInteractionRecord) return {} | |
var interaction = lastInteractionRecord | |
lastInteractionRecord = null | |
return { body: { e: interaction } } | |
}) | |
baseEE.on('errorAgg', function (type, name, params, metrics) { | |
if (!currentNode) return | |
params._interactionId = currentNode.interaction.id | |
// do not capture parentNodeId when in root node | |
if (currentNode.type && currentNode.type !== 'interaction') { | |
params._interactionNodeId = currentNode.id | |
} | |
}) | |
baseEE.on('interaction', saveInteraction) | |
function getActionText (node) { | |
var nodeType = node.tagName.toLowerCase() | |
var goodNodeTypes = ['a', 'button', 'input'] | |
var isGoodNode = goodNodeTypes.indexOf(nodeType) !== -1 | |
if (isGoodNode) { | |
return node.title || node.value || node.innerText | |
} | |
} | |
function saveInteraction (interaction) { | |
if (interaction.ignored || (!interaction.save && !interaction.routeChange)) { | |
baseEE.emit('interactionDiscarded', [interaction]) | |
return | |
} | |
// assign unique id, this is serialized and used to link interactions with errors | |
interaction.root.attrs.id = uniqueId.generateUuid() | |
baseEE.emit('interactionSaved', [interaction]) | |
lastInteractionRecord = serializer(interaction.root, 0, navTiming, interaction.routeChange) | |
harvest.sendX('events', loader) | |
} | |
},{}],28:[function(require,module,exports){ | |
var cleanURL = require(4) | |
var stringify = require(19) | |
var loader = require("loader") | |
var mapOwn = require(35) | |
module.exports = serializeInteraction | |
var hasOwnProp = Object.prototype.hasOwnProperty | |
var MAX_ATTRIBUTES = 64 | |
function serializeInteraction (root, offset, navTiming, isRouteChange) { | |
offset = offset || 0 | |
var stringTable = Object.hasOwnProperty('create') ? Object.create(null) : {} | |
var stringTableIdx = 0 | |
var isInitialPage = root.attrs.trigger === 'initialPageLoad' | |
var firstTimestamp | |
var typeIdsByName = { | |
interaction: 1, | |
ajax: 2, | |
customTracer: 4 | |
} | |
// Include the hash fragment with all SPA data | |
var includeHashFragment = true | |
return 'bel.4;' + addNode(root, []).join(';') | |
function addNode (node, nodeList) { | |
if (node.type === 'customEnd') return nodeList.push([3, numeric(node.end - firstTimestamp)]) | |
var typeName = node.type | |
var typeId = typeIdsByName[typeName] | |
var startTimestamp = node.start | |
var childCount = node.children.length | |
var attrCount = 0 | |
var apmAttributes = loader.info.atts | |
var hasNavTiming = isInitialPage && navTiming.length && typeId === 1 | |
var children = [] | |
var attrs = node.attrs | |
var metrics = attrs.metrics | |
var params = attrs.params | |
var queueTime = loader.info.queueTime | |
var appTime = loader.info.applicationTime | |
if (typeof firstTimestamp === 'undefined') { | |
startTimestamp += offset | |
firstTimestamp = startTimestamp | |
} else { | |
startTimestamp -= firstTimestamp | |
} | |
var fields = [ | |
numeric(startTimestamp), | |
numeric(node.end - node.start), | |
numeric(node.jsEnd - node.end), | |
numeric(node.jsTime) | |
] | |
switch (typeId) { | |
case 1: | |
// TODO: If we are using custom route names does a url change still represent a route change? | |
fields[2] = numeric(node.jsEnd - firstTimestamp) | |
fields.push( | |
addString(attrs.trigger), | |
addString(cleanURL(attrs.initialPageURL, includeHashFragment)), | |
addString(cleanURL(attrs.oldURL, includeHashFragment)), | |
addString(cleanURL(attrs.newURL, includeHashFragment)), | |
addString(attrs.customName), | |
isInitialPage ? '' : isRouteChange ? 1 : 2, | |
nullable(isInitialPage && queueTime, numeric, true) + | |
nullable(isInitialPage && appTime, numeric, true) + | |
nullable(attrs.oldRoute, addString, true) + | |
nullable(attrs.newRoute, addString, true) + | |
addString(attrs.id), | |
addString(node.id) | |
) | |
addCustomAttributes(attrs.custom) | |
if (apmAttributes) { | |
childCount++ | |
children.push('a,' + addString(apmAttributes)) | |
} | |
break | |
case 2: | |
fields.push( | |
addString(params.method), | |
numeric(params.status), | |
addString(params.host), | |
addString(params.pathname), | |
numeric(metrics.txSize), | |
numeric(metrics.rxSize), | |
attrs.isFetch ? 1 : (attrs.isJSONP ? 2 : ''), | |
addString(node.id) | |
) | |
break | |
case 4: | |
var tracedTime = attrs.tracedTime | |
fields.push( | |
addString(attrs.name), | |
nullable(tracedTime, numeric, true) + | |
addString(node.id) | |
) | |
break | |
} | |
for (var i = 0; i < node.children.length; i++) { | |
addNode(node.children[i], children) | |
} | |
fields.unshift( | |
numeric(typeId), | |
numeric(childCount += attrCount) | |
) | |
nodeList.push(fields) | |
if (childCount) { | |
nodeList.push(children.join(';')) | |
} | |
if (hasNavTiming) { | |
// this build up the navTiming node | |
// it for each navTiming value (pre aggregated in nav-timing.js): | |
// we initialize the seperator to ',' (seperates the nodeType id from the first value) | |
// we initialize the navTiming node to 'b' (the nodeType id) | |
// if the value is present: | |
// we add the seperator followed by the value | |
// otherwise | |
// we add null seperator ('!') to the navTimingNode | |
// we set the seperator to an empty string since we already wrote it above | |
// the reason for writing the null seperator instead of setting the seperator | |
// is to ensure we still write it if the null is the last navTiming value. | |
var seperator = ',' | |
var navTimingNode = 'b' | |
var prev = 0 | |
// get all navTiming values except navigationStart | |
// (since its the same as interaction.start) | |
// and limit to just the first 20 values we know about | |
mapOwn(navTiming.slice(1, 21), function (i, v) { | |
if (v !== void 0) { | |
navTimingNode += seperator + numeric(v - prev) | |
seperator = ',' | |
prev = v | |
} else { | |
navTimingNode += seperator + '!' | |
seperator = '' | |
} | |
}) | |
nodeList.push(navTimingNode) | |
} else if (typeId === 1) { | |
nodeList.push('') | |
} | |
return nodeList | |
function addCustomAttributes (attrs) { | |
mapOwn(attrs, function (key, val) { | |
if (attrCount >= MAX_ATTRIBUTES) return | |
var type = 5 | |
var serializedValue | |
// add key to string table first | |
key = addString(key) | |
switch (typeof val) { | |
case 'object': | |
if (val) { | |
// serialize objects to strings | |
serializedValue = addString(stringify(val)) | |
} else { | |
// null attribute type | |
type = 9 | |
} | |
break | |
case 'number': | |
type = 6 | |
// make sure numbers contain a `.` so they are parsed as doubles | |
serializedValue = val % 1 ? val : val + '.' | |
break | |
case 'boolean': | |
type = val ? 7 : 8 | |
break | |
case 'undefined': | |
// we treet undefined as a null attribute (since dirac does not have a concept of undefined) | |
type = 9 | |
break | |
default: | |
serializedValue = addString(val) | |
} | |
attrCount++ | |
children.push([type, key + (serializedValue ? ',' + serializedValue : '')]) | |
}) | |
} | |
} | |
function addString (str) { | |
if (typeof str === 'undefined' || str === '') return '' | |
str = String(str) | |
if (hasOwnProp.call(stringTable, str)) { | |
return numeric(stringTable[str], true) | |
} else { | |
stringTable[str] = stringTableIdx++ | |
return quoteString(str) | |
} | |
} | |
} | |
function nullable (val, fn, comma) { | |
return val || val === 0 || val === '' | |
? fn(val) + (comma ? ',' : '') | |
: '!' | |
} | |
function numeric (n, noDefault) { | |
if (noDefault) { | |
return Math.floor(n).toString(36) | |
} | |
return (n === undefined || n === 0) ? '' : Math.floor(n).toString(36) | |
} | |
var escapable = /([,\\;])/g | |
function quoteString (str) { | |
return "'" + str.replace(escapable, '\\$1') | |
} | |
},{}],29:[function(require,module,exports){ | |
var loader = require("loader") | |
var registerHandler = require(14) | |
var harvest = require(8) | |
var mapOwn = require(35) | |
var reduce = require(38) | |
var stringify = require(19) | |
var slice = require(36) | |
var parseUrl = require(31) | |
var interval = require(11) | |
if (!harvest.xhrUsable || !loader.xhrWrappable) return | |
var ptid = '' | |
var ignoredEvents = {mouseup: true, mousedown: true} | |
var toAggregate = { | |
typing: [1000, 2000], | |
scrolling: [100, 1000], | |
mousing: [1000, 2000], | |
touching: [1000, 2000] | |
} | |
var rename = { | |
typing: { | |
keydown: true, | |
keyup: true, | |
keypress: true | |
}, | |
mousing: { | |
mousemove: true, | |
mouseenter: true, | |
mouseleave: true, | |
mouseover: true, | |
mouseout: true | |
}, | |
scrolling: { | |
scroll: true | |
}, | |
touching: { | |
touchstart: true, | |
touchmove: true, | |
touchend: true, | |
touchcancel: true, | |
touchenter: true, | |
touchleave: true | |
} | |
} | |
var trace = {} | |
var ee = require("ee") | |
// exports only used for testing | |
module.exports = { | |
_takeSTNs: takeSTNs | |
} | |
// Make sure loader.offset is as accurate as possible | |
require(17) | |
// bail if not instrumented | |
if (!loader.features.stn) return | |
ee.on('feat-stn', function () { | |
storeTiming(window.performance.timing) | |
harvest.on('resources', checkPtid(takeSTNs)) | |
var xhr = harvest.sendX('resources', loader, { needResponse: true }) | |
xhr.addEventListener('load', function () { | |
ptid = this.responseText | |
}, false) | |
registerHandler('bst', storeEvent) | |
registerHandler('bstTimer', storeTimer) | |
registerHandler('bstResource', storeResources) | |
registerHandler('bstHist', storeHist) | |
registerHandler('bstXhrAgg', storeXhrAgg) | |
registerHandler('bstApi', storeSTN) | |
registerHandler('errorAgg', storeErrorAgg) | |
interval(function () { | |
var total = 0 | |
if ((loader.now()) > (15 * 60 * 1000)) { | |
// been collecting for over 15 min, empty trace object and bail | |
trace = {} | |
return | |
} | |
mapOwn(trace, function (name, nodes) { | |
if (nodes && nodes.length) total += nodes.length | |
}) | |
if (total > 30) harvest.sendX('resources', loader) | |
// if harvests aren't working (due to no ptid), | |
// start throwing things away at 1000 nodes. | |
if (total > 1000) trace = {} | |
}, 10000) | |
}) | |
function storeTiming (_t) { | |
var key | |
var val | |
var timeOffset | |
var now = Date.now() | |
// loop iterates through prototype also (for FF) | |
for (key in _t) { | |
val = _t[key] | |
// ignore inherited methods, meaningless 0 values, and bogus timestamps | |
// that are in the future (Microsoft Edge seems to sometimes produce these) | |
if (!(typeof (val) === 'number' && val > 0 && val < now)) continue | |
timeOffset = _t[key] - loader.offset | |
storeSTN({ | |
n: key, | |
s: timeOffset, | |
e: timeOffset, | |
o: 'document', | |
t: 'timing' | |
}) | |
} | |
} | |
function storeTimer (target, start, end, type) { | |
var category = 'timer' | |
if (type === 'requestAnimationFrame') category = type | |
var evt = { | |
n: type, | |
s: start, | |
e: end, | |
o: 'window', | |
t: category | |
} | |
storeSTN(evt) | |
} | |
function storeEvent (currentEvent, target, start, end) { | |
// we find that certain events make the data too noisy to be useful | |
if (currentEvent.type in ignoredEvents) { return false } | |
var evt = { | |
n: evtName(currentEvent.type), | |
s: start, | |
e: end, | |
t: 'event' | |
} | |
try { | |
// webcomponents-lite.js can trigger an exception on currentEvent.target getter because | |
// it does not check currentEvent.currentTarget before calling getRootNode() on it | |
evt.o = evtOrigin(currentEvent.target, target) | |
} catch (e) { | |
evt.o = evtOrigin(null, target) | |
} | |
storeSTN(evt) | |
} | |
function evtName (type) { | |
var name = type | |
mapOwn(rename, function (key, val) { | |
if (type in val) name = key | |
}) | |
return name | |
} | |
function evtOrigin (t, target) { | |
var origin = 'unknown' | |
if (t && t instanceof XMLHttpRequest) { | |
var params = ee.context(t).params | |
origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname | |
} else if (t && typeof (t.tagName) === 'string') { | |
origin = t.tagName.toLowerCase() | |
if (t.id) origin += '#' + t.id | |
if (t.className) origin += '.' + slice(t.classList).join('.') | |
} | |
if (origin === 'unknown') { | |
if (target === document) origin = 'document' | |
else if (target === window) origin = 'window' | |
else if (target instanceof FileReader) origin = 'FileReader' | |
} | |
return origin | |
} | |
function storeHist (path, old, time) { | |
var node = { | |
n: 'history.pushState', | |
s: time, | |
e: time, | |
o: path, | |
t: old | |
} | |
storeSTN(node) | |
} | |
var laststart = 0 | |
function storeResources (resources) { | |
resources.forEach(function (currentResource) { | |
var parsed = parseUrl(currentResource.name) | |
var res = { | |
n: currentResource.initiatorType, | |
s: currentResource.fetchStart | 0, | |
e: currentResource.responseEnd | 0, | |
o: parsed.protocol + '://' + parsed.hostname + ':' + parsed.port + parsed.pathname, // resource.name is actually a URL so it's the source | |
t: currentResource.entryType | |
} | |
// don't recollect old resources | |
if (res.s < laststart) return | |
laststart = res.s | |
storeSTN(res) | |
}) | |
} | |
function storeErrorAgg (type, name, params, metrics) { | |
if (type !== 'err') return | |
var node = { | |
n: 'error', | |
s: metrics.time, | |
e: metrics.time, | |
o: params.message, | |
t: params.stackHash | |
} | |
storeSTN(node) | |
} | |
function storeXhrAgg (type, name, params, metrics) { | |
if (type !== 'xhr') return | |
var node = { | |
n: 'Ajax', | |
s: metrics.time, | |
e: metrics.time + metrics.duration, | |
o: params.status + ' ' + params.method + ': ' + params.host + params.pathname, | |
t: 'ajax' | |
} | |
storeSTN(node) | |
} | |
function storeSTN (stn) { | |
var traceArr = trace[stn.n] | |
if (!traceArr) traceArr = trace[stn.n] = [] | |
traceArr.push(stn) | |
} | |
function checkPtid (fn) { | |
var first = true | |
return function () { | |
if (!(first || ptid)) return {} // only report w/o ptid during first cycle. | |
first = false | |
return fn() | |
} | |
} | |
function takeSTNs () { | |
storeResources(window.performance.getEntriesByType('resource')) | |
var stns = reduce(mapOwn(trace, function (name, nodes) { | |
if (!(name in toAggregate)) return nodes | |
return reduce(mapOwn(reduce(nodes.sort(byStart), smearEvtsByOrigin(name), {}), val), flatten, []) | |
}), flatten, []) | |
if (stns.length === 0) return {} | |
else trace = {} | |
var stnInfo = { | |
qs: {st: '' + loader.offset, ptid: ptid}, | |
body: {res: stns} | |
} | |
if (!ptid) { | |
stnInfo.qs.ua = loader.info.userAttributes | |
stnInfo.qs.at = loader.info.atts | |
var ja = stringify(loader.info.jsAttributes) | |
stnInfo.qs.ja = ja === '{}' ? null : ja | |
} | |
return stnInfo | |
} | |
function byStart (a, b) { | |
return a.s - b.s | |
} | |
function smearEvtsByOrigin (name) { | |
var maxGap = toAggregate[name][0] | |
var maxLen = toAggregate[name][1] | |
var lastO = {} | |
return function (byOrigin, evt) { | |
var lastArr = byOrigin[evt.o] | |
lastArr || (lastArr = byOrigin[evt.o] = []) | |
var last = lastO[evt.o] | |
if (name === 'scrolling' && !trivial(evt)) { | |
lastO[evt.o] = null | |
evt.n = 'scroll' | |
lastArr.push(evt) | |
} else if (last && (evt.s - last.s) < maxLen && last.e > (evt.s - maxGap)) { | |
last.e = evt.e | |
} else { | |
lastO[evt.o] = evt | |
lastArr.push(evt) | |
} | |
return byOrigin | |
} | |
} | |
function val (key, value) { | |
return value | |
} | |
function flatten (a, b) { | |
return a.concat(b) | |
} | |
function trivial (node) { | |
var limit = 4 | |
if (node && typeof node.e === 'number' && typeof node.s === 'number' && (node.e - node.s) < limit) return true | |
else return false | |
} | |
},{}],30:[function(require,module,exports){ | |
var agg = require(2) | |
var register = require(14) | |
var harvest = require(8) | |
var stringify = require(19) | |
var loader = require("loader") | |
var ee = require("ee") | |
var handle = require("handle") | |
// bail if not instrumented | |
if (!loader.features.xhr) return | |
harvest.on('jserrors', function () { | |
return { body: agg.take([ 'xhr' ]) } | |
}) | |
ee.on('feat-err', function () { register('xhr', storeXhr) }) | |
module.exports = storeXhr | |
function storeXhr (params, metrics, start) { | |
metrics.time = start | |
var type = 'xhr' | |
var hash | |
if (params.cat) { | |
hash = stringify([params.status, params.cat]) | |
} else { | |
hash = stringify([params.status, params.host, params.pathname]) | |
} | |
handle('bstXhrAgg', [type, hash, params, metrics]) | |
agg.store(type, hash, params, metrics) | |
} | |
},{}],31:[function(require,module,exports){ | |
module.exports = function parseUrl (url) { | |
var urlEl = document.createElement('a') | |
var location = window.location | |
var ret = {} | |
// Use an anchor dom element to resolve the url natively. | |
urlEl.href = url | |
ret.port = urlEl.port | |
var firstSplit = urlEl.href.split('://') | |
if (!ret.port && firstSplit[1]) { | |
ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1] | |
} | |
if (!ret.port || ret.port === '0') ret.port = (firstSplit[0] === 'https' ? '443' : '80') | |
// Host not provided in IE for relative urls | |
ret.hostname = (urlEl.hostname || location.hostname) | |
ret.pathname = urlEl.pathname | |
ret.protocol = firstSplit[0] | |
// Pathname sometimes doesn't have leading slash (IE 8 and 9) | |
if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname | |
// urlEl.protocol is ':' in old ie when protocol is not specified | |
var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol | |
var sameDomain = urlEl.hostname === document.domain && urlEl.port === location.port | |
// urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin | |
ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain) | |
return ret | |
} | |
},{}],32:[function(require,module,exports){ | |
module.exports = function dataSize (data) { | |
if (typeof data === 'string' && data.length) return data.length | |
if (typeof data !== 'object') return undefined | |
if (typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer && data.byteLength) return data.byteLength | |
if (typeof Blob !== 'undefined' && data instanceof Blob && data.size) return data.size | |
if (typeof FormData !== 'undefined' && data instanceof FormData) return undefined | |
try { | |
return JSON.stringify(data).length | |
} catch (e) { | |
return undefined | |
} | |
} | |
},{}],33:[function(require,module,exports){ | |
var ffVersion = 0 | |
var match = navigator.userAgent.match(/Firefox[\/\s](\d+\.\d+)/) | |
if (match) ffVersion = +match[1] | |
module.exports = ffVersion | |
},{}],34:[function(require,module,exports){ | |
module.exports = { | |
generateUuid: generateUuid | |
} | |
function generateUuid () { | |
var randomVals = null | |
var rvIndex = 0 | |
var crypto = window.crypto || window.msCrypto | |
if (crypto && crypto.getRandomValues) { | |
randomVals = crypto.getRandomValues(new Uint8Array(31)) | |
} | |
function getRandomValue () { | |
if (randomVals) { | |
// same as % 16 | |
return randomVals[rvIndex++] & 15 | |
} else { | |
// TODO: add timestamp to prevent collision with same seed? | |
return Math.random() * 16 | 0 | |
} | |
} | |
// v4 UUID | |
var template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' | |
var id = '' | |
var c | |
for (var i = 0; i < template.length; i++) { | |
c = template[i] | |
if (c === 'x') { | |
id += getRandomValue().toString(16) | |
} else if (c === 'y') { | |
// this is the uuid variant per spec (8, 9, a, b) | |
// % 4, then shift to get values 8-11 | |
c = getRandomValue() & 0x3 | 0x8 | |
id += c.toString(16) | |
} else { | |
id += c | |
} | |
} | |
return id | |
} | |
},{}],35:[function(require,module,exports){ | |
var has = Object.prototype.hasOwnProperty | |
module.exports = mapOwn | |
function mapOwn (obj, fn) { | |
var results = [] | |
var key = '' | |
var i = 0 | |
for (key in obj) { | |
if (has.call(obj, key)) { | |
results[i] = fn(key, obj[key]) | |
i += 1 | |
} | |
} | |
return results | |
} | |
},{}],36:[function(require,module,exports){ | |
/** | |
* Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/> | |
* Build: `lodash modularize modern exports="npm" -o ./npm/` | |
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/> | |
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE> | |
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors | |
* Available under MIT license <http://lodash.com/license> | |
*/ | |
/** | |
* Slices the `collection` from the `start` index up to, but not including, | |
* the `end` index. | |
* | |
* Note: This function is used instead of `Array#slice` to support node lists | |
* in IE < 9 and to ensure dense arrays are returned. | |
* | |
* @private | |
* @param {Array|Object|string} collection The collection to slice. | |
* @param {number} start The start index. | |
* @param {number} end The end index. | |
* @returns {Array} Returns the new array. | |
*/ | |
function slice(array, start, end) { | |
start || (start = 0); | |
if (typeof end == 'undefined') { | |
end = array ? array.length : 0; | |
} | |
var index = -1, | |
length = end - start || 0, | |
result = Array(length < 0 ? 0 : length); | |
while (++index < length) { | |
result[index] = array[start + index]; | |
} | |
return result; | |
} | |
module.exports = slice; | |
},{}],37:[function(require,module,exports){ | |
module.exports = { | |
exists: typeof (window.performance) !== 'undefined' && window.performance.timing && typeof (window.performance.timing.navigationStart) !== 'undefined' | |
} | |
},{}],38:[function(require,module,exports){ | |
module.exports = reduce | |
function reduce (arr, fn, next) { | |
var i = 0 | |
if (typeof next === 'undefined') { | |
next = arr[0] | |
i = 1 | |
} | |
for (i; i < arr.length; i++) { | |
next = fn(next, arr[i]) | |
} | |
return next | |
} | |
},{}]},{},[24,30,29,26,27,10]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment