Created
October 4, 2018 08:33
-
-
Save Azerothian/68e25d52b0a8629faed7531dea3fa9b8 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
! function () { | |
var require = function (file, cwd) { | |
var resolved = require.resolve(file, cwd || "/"), | |
mod = require.modules[resolved]; | |
if (!mod) throw new Error("Failed to resolve module " + file + ", tried " + resolved); | |
var cached = require.cache[resolved], | |
res = cached ? cached.exports : mod(); | |
return res | |
}; | |
require.paths = [], require.modules = {}, require.cache = {}, require.extensions = [".js", ".coffee", ".json"], require._core = { | |
assert: !0, | |
events: !0, | |
fs: !0, | |
path: !0, | |
vm: !0 | |
}, require.resolve = function () { | |
return function (x, cwd) { | |
function loadAsFileSync(x) { | |
if (x = path.normalize(x), require.modules[x]) return x; | |
for (var i = 0; i < require.extensions.length; i++) { | |
var ext = require.extensions[i]; | |
if (require.modules[x + ext]) return x + ext | |
} | |
} | |
function loadAsDirectorySync(x) { | |
x = x.replace(/\/+$/, ""); | |
var pkgfile = path.normalize(x + "/package.json"); | |
if (require.modules[pkgfile]) { | |
var pkg = require.modules[pkgfile](), | |
b = pkg.browserify; | |
if ("object" == typeof b && b.main) { | |
var m = loadAsFileSync(path.resolve(x, b.main)); | |
if (m) return m | |
} else if ("string" == typeof b) { | |
var m = loadAsFileSync(path.resolve(x, b)); | |
if (m) return m | |
} else if (pkg.main) { | |
var m = loadAsFileSync(path.resolve(x, pkg.main)); | |
if (m) return m | |
} | |
} | |
return loadAsFileSync(x + "/index") | |
} | |
function loadNodeModulesSync(x, start) { | |
for (var dirs = nodeModulesPathsSync(start), i = 0; i < dirs.length; i++) { | |
var dir = dirs[i], | |
m = loadAsFileSync(dir + "/" + x); | |
if (m) return m; | |
var n = loadAsDirectorySync(dir + "/" + x); | |
if (n) return n | |
} | |
var m = loadAsFileSync(x); | |
return m ? m : void 0 | |
} | |
function nodeModulesPathsSync(start) { | |
var parts; | |
parts = "/" === start ? [""] : path.normalize(start).split("/"); | |
for (var dirs = [], i = parts.length - 1; i >= 0; i--) | |
if ("node_modules" !== parts[i]) { | |
var dir = parts.slice(0, i + 1).join("/") + "/node_modules"; | |
dirs.push(dir) | |
} return dirs | |
} | |
if (cwd || (cwd = "/"), require._core[x]) return x; | |
var path = require.modules.path(); | |
cwd = path.resolve("/", cwd); | |
var y = cwd || "/"; | |
if (x.match(/^(?:\.\.?\/|\/)/)) { | |
var m = loadAsFileSync(path.resolve(y, x)) || loadAsDirectorySync(path.resolve(y, x)); | |
if (m) return m | |
} | |
var n = loadNodeModulesSync(x, y); | |
if (n) return n; | |
throw new Error("Cannot find module '" + x + "'") | |
} | |
}(), require.alias = function (from, to) { | |
var path = require.modules.path(), | |
res = null; | |
try { | |
res = require.resolve(from + "/package.json", "/") | |
} catch (err) { | |
res = require.resolve(from, "/") | |
} | |
for (var basedir = path.dirname(res), keys = (Object.keys || function (obj) { | |
var res = []; | |
for (var key in obj) res.push(key); | |
return res | |
})(require.modules), i = 0; i < keys.length; i++) { | |
var key = keys[i]; | |
if (key.slice(0, basedir.length + 1) === basedir + "/") { | |
var f = key.slice(basedir.length); | |
require.modules[to + f] = require.modules[basedir + f] | |
} else key === basedir && (require.modules[to] = require.modules[basedir]) | |
} | |
}, | |
function () { | |
var process = {}, | |
global = "undefined" != typeof window ? window : {}, | |
definedProcess = !1; | |
require.define = function (filename, fn) { | |
!definedProcess && require.modules.__browserify_process && (process = require.modules.__browserify_process(), definedProcess = !0); | |
var dirname = require._core[filename] ? "" : require.modules.path().dirname(filename), | |
require_ = function (file) { | |
var requiredModule = require(file, dirname), | |
cached = require.cache[require.resolve(file, dirname)]; | |
return cached && null === cached.parent && (cached.parent = module_), requiredModule | |
}; | |
require_.resolve = function (name) { | |
return require.resolve(name, dirname) | |
}, require_.modules = require.modules, require_.define = require.define, require_.cache = require.cache; | |
var module_ = { | |
id: filename, | |
filename: filename, | |
exports: {}, | |
loaded: !1, | |
parent: null | |
}; | |
require.modules[filename] = function () { | |
return require.cache[filename] = module_, fn.call(module_.exports, require_, module_, module_.exports, dirname, filename, process, global), module_.loaded = !0, module_.exports | |
} | |
} | |
}(), require.define("path", function (require, module, exports, __dirname, __filename, process) { | |
function filter(xs, fn) { | |
for (var res = [], i = 0; i < xs.length; i++) fn(xs[i], i, xs) && res.push(xs[i]); | |
return res | |
} | |
function normalizeArray(parts, allowAboveRoot) { | |
for (var up = 0, i = parts.length; i >= 0; i--) { | |
var last = parts[i]; | |
"." == last ? parts.splice(i, 1) : ".." === last ? (parts.splice(i, 1), up++) : up && (parts.splice(i, 1), up--) | |
} | |
if (allowAboveRoot) | |
for (; up--; up) parts.unshift(".."); | |
return parts | |
} | |
var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; | |
exports.resolve = function () { | |
for (var resolvedPath = "", resolvedAbsolute = !1, i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { | |
var path = i >= 0 ? arguments[i] : process.cwd(); | |
"string" == typeof path && path && (resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = "/" === path.charAt(0)) | |
} | |
return resolvedPath = normalizeArray(filter(resolvedPath.split("/"), function (p) { | |
return !!p | |
}), !resolvedAbsolute).join("/"), (resolvedAbsolute ? "/" : "") + resolvedPath || "." | |
}, exports.normalize = function (path) { | |
var isAbsolute = "/" === path.charAt(0), | |
trailingSlash = "/" === path.slice(-1); | |
return path = normalizeArray(filter(path.split("/"), function (p) { | |
return !!p | |
}), !isAbsolute).join("/"), path || isAbsolute || (path = "."), path && trailingSlash && (path += "/"), (isAbsolute ? "/" : "") + path | |
}, exports.join = function () { | |
var paths = Array.prototype.slice.call(arguments, 0); | |
return exports.normalize(filter(paths, function (p) { | |
return p && "string" == typeof p | |
}).join("/")) | |
}, exports.dirname = function (path) { | |
var dir = splitPathRe.exec(path)[1] || "", | |
isWindows = !1; | |
return dir ? 1 === dir.length || isWindows && dir.length <= 3 && ":" === dir.charAt(1) ? dir : dir.substring(0, dir.length - 1) : "." | |
}, exports.basename = function (path, ext) { | |
var f = splitPathRe.exec(path)[2] || ""; | |
return ext && f.substr(-1 * ext.length) === ext && (f = f.substr(0, f.length - ext.length)), f | |
}, exports.extname = function (path) { | |
return splitPathRe.exec(path)[3] || "" | |
} | |
}), require.define("__browserify_process", function (require, module, exports, __dirname, __filename, process) { | |
var process = module.exports = {}; | |
process.nextTick = function () { | |
var canSetImmediate = "undefined" != typeof window && window.setImmediate, | |
canPost = "undefined" != typeof window && window.postMessage && window.addEventListener; | |
if (canSetImmediate) return function (f) { | |
return window.setImmediate(f) | |
}; | |
if (canPost) { | |
var queue = []; | |
return window.addEventListener("message", function (ev) { | |
if (ev.source === window && "browserify-tick" === ev.data && (ev.stopPropagation(), queue.length > 0)) { | |
var fn = queue.shift(); | |
fn() | |
} | |
}, !0), | |
function (fn) { | |
queue.push(fn), window.postMessage("browserify-tick", "*") | |
} | |
} | |
return function (fn) { | |
setTimeout(fn, 0) | |
} | |
}(), process.title = "browser", process.browser = !0, process.env = {}, process.argv = [], process.binding = function (name) { | |
if ("evals" === name) return require("vm"); | |
throw new Error("No such module. (Possibly not yet loaded)") | |
}, | |
function () { | |
var path, cwd = "/"; | |
process.cwd = function () { | |
return cwd | |
}, process.chdir = function (dir) { | |
path || (path = require("path")), cwd = path.resolve(dir, cwd) | |
} | |
}() | |
}), require.define("/app/aggregator/stableMovingAverage.js", function (require, module) { | |
module.exports = function (windowSize, snapshotResetThreshold) { | |
function reset() { | |
startInd = 0, curSpeed = 0, lastLen = 0, bytes = 0, times = 0, fixedStartInd = !1 | |
} | |
function movingAvg(snapshots) { | |
snapshots.length < snapshotResetThreshold && reset(), lastLen = snapshots.length; | |
var snapshotInd, speed; | |
if (!fixedStartInd) { | |
var sumBytes = 0, | |
sumTime = 0, | |
start = snapshots.length - 1, | |
end = Math.max(0, snapshots.length - windowSize); | |
for (snapshotInd = start; snapshotInd >= end; --snapshotInd) snapshot = snapshots[snapshotInd], sumBytes += snapshot.bytes, sumTime += snapshot.time; | |
speed = sumTime > 0 ? sumBytes / sumTime : 0, speed >= curSpeed ? (startInd = start + 1, curSpeed = speed, bytes = sumBytes, times = sumTime) : fixedStartInd = !0 | |
} | |
for (snapshotInd = startInd; snapshotInd < snapshots.length; ++snapshotInd) snapshot = snapshots[snapshotInd], bytes += snapshot.bytes, times += snapshot.time; | |
return startInd = snapshots.length, times > 0 ? 1e3 * bytes * 8 / times : 0 | |
} | |
var startInd, curSpeed, fixedStartInd, lastLen, bytes, times; | |
return snapshotResetThreshold = snapshotResetThreshold || 5, reset(), movingAvg | |
} | |
}), require.define("/app/stopper/stableDeltaMeasurementsStopper.js", function (require, module) { | |
module.exports = function (config) { | |
function lastWindowMaxInd(measurements, windowSize) { | |
var measurement, curMaxSpeed = 0, | |
curMaxInd = 0, | |
measurementInd = 0, | |
numMeasurements = measurements.length, | |
firstMeasurementInd = Math.max(0, numMeasurements - windowSize); | |
for (measurementInd = numMeasurements - 1; measurementInd >= firstMeasurementInd; --measurementInd) measurement = measurements[measurementInd], measurement.speed >= curMaxSpeed && (curMaxSpeed = measurement.speed, curMaxInd = measurementInd); | |
return curMaxInd | |
} | |
function maxDelta(value, measurements) { | |
var measurementInd, delta, maxDelta = 0; | |
for (measurementInd = 0; measurementInd < measurements.length; ++measurementInd) delta = 100 * Math.abs(measurements[measurementInd].speed - value) / value, delta > maxDelta && (maxDelta = delta); | |
return maxDelta | |
} | |
function isCompleted(metrics) { | |
var maxInd, delta, testTime = void 0 !== metrics.testTime ? metrics.testTime : timer() - startTime, | |
measurements = metrics.progressMeasurements, | |
afterCompleteDuration = metrics.afterCompleteDuration, | |
numMeasurements = measurements.length, | |
curSpeed = metrics.speed; | |
return void 0 === afterCompleteDuration ? testTime >= maxDuration ? !0 : minStableMeasurements > numMeasurements ? !1 : (maxInd = lastWindowMaxInd(measurements, Math.ceil(minStableMeasurements / 2)), numMeasurements - maxInd < Math.ceil(minStableMeasurements / 2) ? !1 : (maxInd = Math.max(0, numMeasurements - minStableMeasurements), minStableMeasurements > numMeasurements - maxInd ? !1 : (delta = maxDelta(curSpeed, measurements.slice(numMeasurements - minStableMeasurements, numMeasurements)), delta > stabilityDelta ? !1 : testTime >= minDuration))) : testTime >= afterCompleteDuration | |
} | |
var minDuration, maxDuration, stabilityDelta, minStableMeasurements, timer, startTime; | |
return minDuration = config.minDuration || 5, maxDuration = config.maxDuration || 60, stabilityDelta = config.stabilityDelta || 5, minStableMeasurements = config.minStableMeasurements || 5, timer = config.timer || require("../timer").time, startTime = timer(), isCompleted | |
} | |
}), require.define("/app/timer.js", function (require, module) { | |
module.exports = function () { | |
var timer; | |
return this.window && window.performance && (timer = window.performance.now || window.performance.webkitNow, timer && (timer = timer.bind(window.performance))), timer || (timer = function () { | |
return (new Date).getTime() | |
}), { | |
time: timer | |
} | |
}() | |
}), require.define("/app/tester.js", function (require, module) { | |
var tester = function () { | |
var MAX_PAYLOAD_BYTES = 26214400, | |
MIN_REQUEST_SIZE = 1024, | |
STABLE_MEASUREMENTS_THRESHOLD = 10, | |
TEST_TYPE = { | |
DOWNLOAD_TEST: "download", | |
UPLOAD_TEST: "upload", | |
LATENCY_TEST: "latency" | |
}, | |
utils = require("./utils"), | |
errors = require("./error"), | |
events = require("./event"), | |
testConfig = require("./config"), | |
factory = (require("circular-buffer"), function (requester, config) { | |
function validateEvent(eventName, callback) { | |
if (void 0 === events.schema[eventName]) throw new errors.TesterEventError("no event with name " + eventName); | |
if (!utils.isFunction(callback)) throw new errors.TesterEventError("function callback is expected for event " + eventName) | |
} | |
function isActive() { | |
return testIsRunning | |
} | |
function stop() { | |
var reqId; | |
testIsRunning = !1, urlGetter.stop(), reportTimeout && clearTimeout(reportTimeout), retryTimeout && clearTimeout(retryTimeout); | |
for (reqId in requesters) requesters[reqId].stop(), pingers[reqId].stop() | |
} | |
function reset() { | |
isActive() && stop(), startTime = null, downloadStartTime = null, testAttempt = 1, workerMeasurements = {}, latencyMeasurements = {}, requesters = {}, pingers = {}, progressMeasurements = [], requestTimeMs = config.startRangeRequestTimeMs, activeWorkersCount = 0, snapshot.reset(), window && window.performance && window.performance.clearResourceTimings && window.performance.clearResourceTimings() | |
} | |
function computeLoadedLatency() { | |
var reqId, value, latencies = [], | |
count = 0; | |
for (reqId in latencyMeasurements) latencyMeasurements[reqId].length > 4 && (value = utils.percentile(latencyMeasurements[reqId], .75), latencies.push(value)), count += latencyMeasurements[reqId].length; | |
if (0 === latencies.length) | |
for (reqId in latencyMeasurements) value = utils.percentile(latencyMeasurements[reqId], .75), latencies.push(value); | |
return { | |
value: Math.min.apply(null, latencies), | |
count: count | |
} | |
} | |
function computeUnloadedLatency() { | |
var reqId, value, latencies = [], | |
count = 0; | |
for (reqId in latencyMeasurements) value = utils.percentile(latencyMeasurements[reqId], .1), latencies.push(value), count += latencyMeasurements[reqId].length; | |
return { | |
value: Math.min.apply(null, latencies), | |
count: count | |
} | |
} | |
function reportEndEvents(data) { | |
var eventData, result = data.result, | |
resultValue = -1; | |
eventData = { | |
start: startTime, | |
downloadStart: downloadStartTime, | |
attempts: testAttempt, | |
numberOfTests: stats[currentTestType].tests, | |
bytes: stats[currentTestType].bytes[stats[currentTestType].bytes.length - 1] | |
}, "success" === result ? (eventData.stable = data.stable, currentTestType === TEST_TYPE.LATENCY_TEST ? (resultValue = data.value, eventData.count = data.count, eventData.value = resultValue) : (resultValue = data.speed, eventData.speed = resultValue, eventData.latency = computeLoadedLatency()), event(events.events.ATTEMPT_SUCCESS, eventData), event(events.events.SUCCESS, eventData), eventData.result = result) : "stop" === result ? (eventData.reason = data.reason || "stopped", event(events.events.STOP, eventData), eventData.result = result) : (eventData.error = data.error, eventData.attempts = testAttempt - 1, event(events.events.ATTEMPT_FAIL, eventData), event(events.events.FAIL, eventData), eventData.result = result), event(events.events.ATTEMPT_END, eventData), event(events.events.END, eventData), stats[currentTestType].results.push(resultValue) | |
} | |
function startTestWorker(workerId, url, attempt) { | |
function sendLatencyRequest(onLatencyComplete) { | |
var rangeUrl = url.replace("/range/", "/range/0-0"); | |
event(events.events.LATENCY_START, { | |
oca: oca | |
}), pinger.upload(rangeUrl, 0, utils.dummy, onLatencyComplete) | |
} | |
function sendDownloadRequest(onDownloadProgress, onDownloadComplete) { | |
var rangeUrl; | |
3 > testAttempt || config.maxBytesInFlight - 1 < activeWorkersCount * MAX_PAYLOAD_BYTES || !req.supportsProgress() ? rangeUrl = url.replace("/range/", "/range/0-" + requestSize) : (rangeUrl = url.replace("/range/", ""), requestSize = MAX_PAYLOAD_BYTES), event(events.events.CONNECTION_START, { | |
oca: oca, | |
range: requestSize, | |
requestUrl: rangeUrl | |
}); | |
try { | |
req.start(rangeUrl, onDownloadProgress, onDownloadComplete, !1, url) | |
} catch (e) { | |
onDownloadComplete({ | |
success: !1, | |
response: { | |
type: "Requester error", | |
message: e | |
} | |
}) | |
} | |
} | |
function sendUploadRequest(onUploadProgress, onUploadComplete) { | |
var rangeUrl; | |
3 > testAttempt || config.maxBytesInFlight - 1 < activeWorkersCount * MAX_PAYLOAD_BYTES || !req.supportsProgress() ? rangeUrl = url.replace("/range/", "/range/0-" + requestSize) : (rangeUrl = url.replace("/range/", ""), requestSize = MAX_PAYLOAD_BYTES), event(events.events.CONNECTION_START, { | |
oca: oca, | |
range: requestSize, | |
requestUrl: rangeUrl | |
}); | |
try { | |
req.upload(rangeUrl, requestSize, onUploadProgress, onUploadComplete) | |
} catch (e) { | |
onUploadComplete({ | |
success: !1, | |
response: { | |
type: "Requester error", | |
message: e | |
} | |
}) | |
} | |
} | |
function workerIsActive() { | |
return void 0 !== workerMeasurements[workerId] | |
} | |
function onProgress(progress) { | |
var progressData; | |
workerIsActive() && isActive() ? progress.success && (progressData = { | |
bytes: progress.bytes, | |
time: progress.end - progress.start, | |
start: progress.start, | |
end: progress.end | |
}, void 0 === progressData.bytes && (progressData.bytes = requestSize), stats[currentTestType].bytes[stats[currentTestType].bytes.length - 1] += progressData.bytes, workerMeasurements[workerId].push(progressData), event(events.events.CONNECTION_PROGRESS, { | |
oca: oca, | |
bytes: progressData.bytes, | |
start: progressData.start, | |
end: progressData.end | |
})) : req.stop() | |
} | |
function onComplete(sendRequest) { | |
var completeFunc = function (data) { | |
var requestTime, eventData = { | |
oca: oca | |
}, | |
partialSuccess = !1; | |
if (workerIsActive() && isActive() && "stopped" !== data.reason) { | |
if (partialSuccess = !data.success && workerMeasurements[workerId].length > 0, !data.success && !partialSuccess) { | |
var error = new errors.DownloadOcaDataError("Could not download oca data"); | |
return error.url = url, error.response = data.response, eventData.error = error, event(events.events.CONNECTION_FAIL, eventData), eventData.result = "fail", event(events.events.CONNECTION_END, eventData), void test(attempt + 1, error) | |
} | |
eventData.bytes = requestSize, eventData.partialSuccess = partialSuccess, event(events.events.CONNECTION_SUCCESS, eventData), eventData.result = "success", req.supportsProgress() ? requestSize = MAX_PAYLOAD_BYTES : (requestTime = data.end - data.start, requestSize = Math.min(Math.floor(requestTimeMs * requestSize / requestTime), 5 * requestSize, MAX_PAYLOAD_BYTES, Math.round(config.maxBytesInFlight / config.connections.max)), requestTimeMs = Math.min(requestTimeMs + config.rangeRequestIncreaseMs, config.maxRangeRequestTimeMs), requestSize = Math.max(requestSize, MIN_REQUEST_SIZE)), event(events.events.CONNECTION_END, eventData), sendRequest(onProgress, completeFunc) | |
} else req.stop(), eventData.reason = "inactive", eventData.result = "stop", event(events.events.CONNECTION_END, eventData) | |
}; | |
return completeFunc | |
} | |
function onLatencyComplete(data) { | |
var latency, scheduleDelay, eventData = { | |
oca: oca | |
}; | |
if (workerIsActive() && isActive() && "stopped" !== data.reason) { | |
if (data.success) latency = data.end - data.start, data.timing || (latency /= 2), eventData.latency = latency, eventData.start = data.start, eventData.end = data.end, latencyMeasurements[workerId].push(latency), eventData.timing = data.timing, event(events.events.LATENCY_SUCCESS, eventData), eventData.result = "success"; | |
else { | |
var error = new errors.DownloadOcaDataError("Could not measure latency to oca " + oca); | |
error.url = url, error.response = data.response, eventData.error = error, event(events.events.LATENCY_FAIL, eventData), eventData.result = "fail" | |
} | |
event(events.events.LATENCY_END, eventData), scheduleDelay = currentTestType !== TEST_TYPE.LATENCY_TEST ? latency ? Math.max(config.latencyMeasurementsFrequencyMs - latency, 0) : config.latencyMeasurementsFrequencyMs : 0, setTimeout(function () { | |
return sendLatencyRequest(onLatencyComplete) | |
}, scheduleDelay) | |
} else pinger.stop(), eventData.reason = "inactive", eventData.result = "stop", event(events.events.LATENCY_END, eventData) | |
} | |
var req = requester(), | |
pinger = requester(), | |
requestSize = config.firstRequestBytes, | |
oca = url.split("/")[2]; | |
requesters[workerId] = req, pingers[workerId] = pinger, workerMeasurements[workerId] = [], latencyMeasurements[workerId] = [], req.supportsProgress() && (requestSize = MAX_PAYLOAD_BYTES), config.measureLatency && currentTestType !== TEST_TYPE.LATENCY_TEST && sendLatencyRequest(onLatencyComplete), currentTestType === TEST_TYPE.DOWNLOAD_TEST ? sendDownloadRequest(onProgress, onComplete(sendDownloadRequest)) : currentTestType === TEST_TYPE.UPLOAD_TEST ? sendUploadRequest(onProgress, onComplete(sendUploadRequest)) : currentTestType === TEST_TYPE.LATENCY_TEST && sendLatencyRequest(onLatencyComplete) | |
} | |
function reportMetrics(interval) { | |
function testTime() { | |
var now = timer(); | |
return (now - (downloadStartTime || startTime)) / 1e3 | |
} | |
var latestSnapshot, resultData, testComplete, currentSpeed, snapshots, testTimeS, activeSnapshot = !1; | |
if (isActive()) | |
if (currentTestType === TEST_TYPE.LATENCY_TEST) resultData = computeUnloadedLatency(), resultData.stable = !0, testComplete = testTime() > 5 && resultData.count > 50, testComplete ? (stop(), resultData.result = "success", setTimeout(function () { | |
reportEndEvents(resultData) | |
}, 10)) : (event(events.events.PROGRESS, resultData), reportTimeout = setTimeout(function () { | |
reportMetrics(interval) | |
}, interval)); | |
else { | |
latestSnapshot = snapshot.compute(workerMeasurements), latestSnapshot && (activeSnapshot = latestSnapshot.end - latestSnapshot.start > 0, event(events.events.SNAPSHOT, { | |
bytes: latestSnapshot.bytes, | |
start: latestSnapshot.start, | |
end: latestSnapshot.end, | |
time: latestSnapshot.time | |
})), snapshots = snapshot.all(), testTimeS = testTime(), activeSnapshot && (currentSpeed = speedAggregator(snapshots), testComplete = testTimeS > config.duration.min && stopper({ | |
testTime: testTimeS, | |
snapshots: snapshots, | |
progressMeasurements: progressMeasurements, | |
speed: currentSpeed | |
})), testTimeS > config.duration.max && (testComplete = !0, currentSpeed = speedAggregator(snapshots)), resultData = { | |
speed: currentSpeed, | |
stable: !0, | |
bytes: stats[currentTestType].bytes[stats[currentTestType].bytes.length - 1], | |
latency: computeLoadedLatency() | |
}, testComplete ? (testTimeS > config.duration.max && (resultData.stopperStable = !1, progressMeasurements.length > STABLE_MEASUREMENTS_THRESHOLD && (resultData.stable = !0)), stop(), resultData.result = "success", setTimeout(function () { | |
reportEndEvents(resultData) | |
}, 10)) : (activeSnapshot && (progressMeasurements.push({ | |
speed: currentSpeed | |
}), event(events.events.PROGRESS, { | |
speed: currentSpeed, | |
bytes: resultData.bytes, | |
latency: resultData.latency | |
})), reportTimeout = setTimeout(function () { | |
reportMetrics(interval) | |
}, interval)); | |
var i, desiredWorkers = activeWorkersCount; | |
if (activeWorkersCount < config.connections.max) | |
for (currentSpeed > 5e7 ? desiredWorkers = config.connections.max : currentSpeed > 1e7 && 5 > activeWorkersCount ? desiredWorkers = 5 : currentSpeed > 1e6 && 3 > activeWorkersCount && (desiredWorkers = 3), currentSpeed > 5e5 && 2 > activeWorkersCount && (desiredWorkers = 3), i = 0; desiredWorkers - activeWorkersCount > i; ++i) startNewWorker() | |
} | |
} | |
function urlRequestFailure(error) { | |
event(events.events.URL_REQUEST_END, { | |
error: error, | |
result: "fail" | |
}), isActive() && test(testAttempt + 1, error) | |
} | |
function urlRequestStop() { | |
event(events.events.URL_REQUEST_END, { | |
result: "stop" | |
}) | |
} | |
function startNewWorker() { | |
function startForUrl(url) { | |
var id = uniqueId(); | |
event(events.events.URL_REQUEST_END, { | |
url: url, | |
result: "success", | |
client: urlGetter.clientInfo() | |
}), downloadStartTime = downloadStartTime || timer(), startTestWorker(id, url, testAttempt) | |
} | |
function restartTest(error) { | |
return urlRequestFailure(error) | |
} | |
activeWorkersCount += 1, event(events.events.URL_REQUEST_START, config.getTestOcasParams), urlGetter.getNext(config.getTestOcasParams, requester, startForUrl, restartTest, urlRequestStop) | |
} | |
function test(attempt, error) { | |
function startTest() { | |
eventData = { | |
attempt: testAttempt | |
}, event(events.events.ATTEMPT_START, eventData), startTime = timer(); | |
var i; | |
if (currentTestType !== TEST_TYPE.LATENCY_TEST) | |
for (i = 0; i < config.connections.min; i++) startNewWorker(); | |
else | |
for (i = 0; i < Math.min(5, config.connections.max); i++) startNewWorker(); | |
reportMetrics(config.progressFrequencyMs) | |
} | |
var eventData; | |
return reset(), testAttempt = attempt || 1, testAttempt > config.maxAttempts ? void reportEndEvents({ | |
result: "fail", | |
error: error | |
}) : (testIsRunning = !0, error && (eventData = { | |
error: error, | |
attempt: testAttempt - 1 | |
}, event(events.events.ATTEMPT_FAIL, eventData), eventData.result = "fail", event(events.events.ATTEMPT_END, eventData), error.url && urlGetter.reportBadUrl(error.url)), void(testAttempt > 1 ? setTimeout(startTest, Math.min(Math.pow(2, testAttempt), 200)) : startTest())) | |
} | |
function event(eventName, data) { | |
var eventInd, genericHandlers, handler, handlers = eventHandlers[eventName]; | |
if (data.testType = currentTestType, handlers && handlers.length > 0) | |
for (eventInd = 0; eventInd < handlers.length; ++eventInd) { | |
handler = handlers[eventInd]; | |
try { | |
handlers[eventInd](utils.simpleCopy(data)) | |
} catch (e) {} | |
} | |
if (genericHandlers = eventHandlers[events.events.ANY], genericHandlers && genericHandlers.length > 0) | |
for (eventInd = 0; eventInd < genericHandlers.length; ++eventInd) genericHandlers[eventInd]({ | |
event: eventName, | |
data: utils.simpleCopy(data) | |
}) | |
} | |
var that, currentTestType, reportTimeout, retryTimeout, requestTimeMs, activeWorkersCount, speedAggregator, stopper, timer, testType, testIsRunning = !1, | |
testAttempt = 1, | |
stats = {}, | |
startTime = null, | |
downloadStartTime = null, | |
workerMeasurements = {}, | |
latencyMeasurements = {}, | |
requesters = {}, | |
pingers = {}, | |
progressMeasurements = [], | |
urlGetter = require("./url_getter")(), | |
uniqueId = utils.uniqueId, | |
snapshot = require("./snapshot"), | |
eventHandlers = {}; | |
for (testType in TEST_TYPE) testType = TEST_TYPE[testType], stats[testType] = {}, stats[testType].tests = 0, stats[testType].results = [], stats[testType].bytes = []; | |
if (!requester) throw new errors.TesterArgumentError("requester factory should be provided to the tester"); | |
return config = testConfig.get(config), timer = config.timer, speedAggregator = config.aggregator, stopper = config.stopper, that = { | |
download: function () { | |
currentTestType = TEST_TYPE.DOWNLOAD_TEST, stats[currentTestType].tests += 1, stats[currentTestType].bytes.push(0), urlGetter.reset(), event(events.events.START, { | |
numberOfTests: stats[currentTestType].tests, | |
type: currentTestType, | |
stats: stats | |
}), test() | |
}, | |
upload: function () { | |
currentTestType = TEST_TYPE.UPLOAD_TEST, stats[currentTestType].tests > 1 && urlGetter.reset(), stats[currentTestType].tests += 1, stats[currentTestType].bytes.push(0), event(events.events.START, { | |
numberOfTests: stats[currentTestType].tests, | |
type: currentTestType, | |
stats: stats | |
}), test() | |
}, | |
latency: function () { | |
currentTestType = TEST_TYPE.LATENCY_TEST, stats[currentTestType].tests > 1 && urlGetter.reset(), stats[currentTestType].tests += 1, stats[currentTestType].bytes.push(0), event(events.events.START, { | |
numberOfTests: stats[currentTestType].tests, | |
type: currentTestType, | |
stats: stats | |
}), test() | |
}, | |
stop: function () { | |
stop(), reportEndEvents({ | |
result: "stop", | |
reason: "user" | |
}) | |
}, | |
on: function (eventName, callback) { | |
validateEvent(eventName, callback); | |
var handlers = eventHandlers[eventName]; | |
return handlers || (handlers = eventHandlers[eventName] = []), handlers.push(callback), that | |
}, | |
off: function (eventName, callback) { | |
validateEvent(eventName, callback); | |
var eventInd, event, handlers = eventHandlers[eventName], | |
outHandlers = []; | |
if (handlers && handlers.length > 0) { | |
for (eventInd = 0; eventInd < handlers.length; ++eventInd) event = handlers[eventInd], event !== callback && outHandlers.push(event); | |
outHandlers.length > 0 && (eventHandlers[eventName] = outHandlers) | |
} | |
return that | |
}, | |
config: function (attr) { | |
return attr ? config[attr] : config | |
}, | |
setConfig: function (attrs) { | |
for (var name in attrs) testConfig.validate(name, attrs[name]) && (config[name] = attrs[name]); | |
return config | |
}, | |
isRunning: function () { | |
return testIsRunning | |
} | |
} | |
}); | |
return factory | |
}; | |
module.exports = tester() | |
}), require.define("/app/utils.js", function (require, module) { | |
var utils = {}; | |
utils.getXHR = function () { | |
var XMLHttpFactories = ["Msxml2.XMLHTTP.6.0", "Msxml3.XMLHTTP", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP", "Msxml2.XMLHTTP.3.0"], | |
xmlhttp = !1; | |
try { | |
if (-1 !== navigator.appVersion.indexOf("MSIE 10")) throw new Error("Error"); | |
xmlhttp = new XDomainRequest | |
} catch (e) { | |
try { | |
xmlhttp = new XMLHttpRequest | |
} catch (e) { | |
for (var i = 0; i < XMLHttpFactories.length; i++) try { | |
xmlhttp = new ActiveXObject(XMLHttpFactories[i]); | |
break | |
} catch (e) {} | |
} | |
} finally { | |
return xmlhttp | |
} | |
}, utils.uniqueId = function () { | |
var id = 0, | |
getId = function () { | |
var myId = id; | |
return id += 1, myId | |
}; | |
return getId | |
}(), utils.dummy = function () {}, utils.isFunction = function (obj) { | |
return !!(obj && obj.constructor && obj.call && obj.apply) | |
}, utils.simpleCopy = function (obj) { | |
var attr, copy = {}; | |
for (attr in obj) copy[attr] = obj[attr]; | |
return copy | |
}, utils.createObject = function (obj) { | |
function F() {} | |
return F.prototype = obj, new F | |
}, utils.median = function (values) { | |
var l, half; | |
return l = values.length, 0 === l ? void 0 : 1 === l ? values[0] : (half = Math.floor(l / 2), values.sort(), l % 2 ? values[half] : (values[half - 1] + values[half]) / 2) | |
}, utils.percentile = function (values, p) { | |
if (0 === values.length) return 0; | |
if ("number" != typeof p) throw new TypeError("p must be a number"); | |
if (values.sort(function (a, b) { | |
return a - b | |
}), 0 >= p) return values[0]; | |
if (p >= 1) return values[values.length - 1]; | |
var index = values.length * p, | |
lower = Math.floor(index), | |
upper = lower + 1, | |
weight = index % 1; | |
return upper >= values.length ? values[lower] : values[lower] * (1 - weight) + values[upper] * weight | |
}, utils.addEventListener = function (elem, event, listener) { | |
elem.addEventListener ? elem.addEventListener(event, listener, !1) : elem.attachEvent("on" + event, listener) | |
}, utils.removeEventListener = function (elem, event, listener) { | |
elem.addEventListener ? elem.removeEventListener(event, listener) : elem.detachEvent("on" + event, listener) | |
}, utils.polyfillObjectKeys = function () { | |
Object.keys || (Object.keys = function () { | |
var hasOwnProperty = Object.prototype.hasOwnProperty, | |
hasDontEnumBug = !{ | |
toString: null | |
}.propertyIsEnumerable("toString"), | |
dontEnums = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"], | |
dontEnumsLength = dontEnums.length; | |
return function (obj) { | |
if ("object" != typeof obj && ("function" != typeof obj || null === obj)) throw new TypeError("Object.keys called on non-object"); | |
var prop, i, result = []; | |
for (prop in obj) hasOwnProperty.call(obj, prop) && result.push(prop); | |
if (hasDontEnumBug) | |
for (i = 0; dontEnumsLength > i; i++) hasOwnProperty.call(obj, dontEnums[i]) && result.push(dontEnums[i]); | |
return result | |
} | |
}()) | |
}, utils.onDomReady = function (callback) { | |
function onReadyIe() { | |
"complete" === document.readyState && (document.detachEvent("onreadystatechange", onReadyIe), callback()) | |
} | |
document.addEventListener ? document.addEventListener("DOMContentLoaded", callback, !1) : document.attachEvent("onreadystatechange", onReadyIe) | |
}, utils.genBlob = function (size) { | |
var blobStr; | |
for (blobStr = ""; 2 * blobStr.length <= size;) blobStr += blobStr + Math.random().toString(); | |
blobStr.length > size ? blobStr = blobStr.substring(0, size) : blobStr += blobStr.substring(0, size - blobStr.length); | |
try { | |
blob = new Blob([blobStr], { | |
type: "application/octet-stream" | |
}) | |
} catch (e) { | |
try { | |
var bb = new(window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder); | |
bb.append(blobStr), blob = bb.getBlob("application/octet-stream") | |
} catch (e) { | |
blob = blobStr | |
} | |
} | |
return blob | |
}, module.exports = utils | |
}), require.define("/app/error.js", function (require, module) { | |
function TesterArgumentError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "TesterArgumentError", this.stack = temp.stack, this.message = temp.message | |
} | |
function TesterEventError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "TesterEventError", this.stack = temp.stack, this.message = temp.message | |
} | |
function RequesterArgumentError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "RequesterArgumentError", this.stack = temp.stack, this.message = temp.message | |
} | |
function GetOcaUrlError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "GetOcaUrlError", this.stack = temp.stack, this.message = temp.message | |
} | |
function NoOcaUrlsError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "NoOcaUrlsError", this.stack = temp.stack, this.message = temp.message | |
} | |
function DownloadOcaDataError() { | |
var temp = Error.apply(this, arguments); | |
temp.name = this.name = "DownloadOcaDataError", this.stack = temp.stack, this.message = temp.message | |
} | |
var errors = {}; | |
TesterArgumentError.prototype = Error.prototype, errors.TesterArgumentError = TesterArgumentError, TesterEventError.prototype = Error.prototype, errors.TesterEventError = TesterEventError, RequesterArgumentError.prototype = Error.prototype, errors.RequesterArgumentError = RequesterArgumentError, GetOcaUrlError.prototype = Error.prototype, errors.GetOcaUrlError = GetOcaUrlError, NoOcaUrlsError.prototype = Error.prototype, errors.NoOcaUrlsError = NoOcaUrlsError, DownloadOcaDataError.prototype = Error.prototype, errors.DownloadOcaDataError = DownloadOcaDataError, module.exports = errors | |
}), require.define("/app/event.js", function (require, module) { | |
var events = { | |
START: "start", | |
END: "end", | |
SUCCESS: "success", | |
FAIL: "fail", | |
STOP: "stop", | |
ATTEMPT_START: "attemptStart", | |
ATTEMPT_END: "attemptEnd", | |
ATTEMPT_SUCCESS: "attemptSuccess", | |
ATTEMPT_FAIL: "attemptFail", | |
PROGRESS: "progress", | |
SNAPSHOT: "snapshot", | |
CONNECTION_START: "connectionStart", | |
CONNECTION_FAIL: "connectionFail", | |
CONNECTION_PROGRESS: "connectionProgress", | |
CONNECTION_SUCCESS: "connectionSuccess", | |
CONNECTION_END: "connectionEnd", | |
LATENCY_START: "latencyStart", | |
LATENCY_FAIL: "latencyFail", | |
LATENCY_SUCCESS: "latencySuccess", | |
LATENCY_END: "latencyEnd", | |
UPLOAD_START: "uploadStart", | |
UPLOAD_FAIL: "uploadFail", | |
UPLOAD_SUCCESS: "uploadSuccess", | |
UPLOAD_END: "uploadEnd", | |
URL_REQUEST_START: "urlRequestStart", | |
URL_REQUEST_END: "urlRequestEnd", | |
AFTER_COMPLETE_ATTEMPT_START: "afterCompleteAttemptStart", | |
AFTER_COMPLETE_ATTEMPT_END: "afterCompleteAttemptEnd", | |
ANY: "event" | |
}, | |
schema = { | |
start: {}, | |
end: {}, | |
success: {}, | |
fail: {}, | |
stop: {}, | |
attemptStart: {}, | |
attemptEnd: {}, | |
attemptSuccess: {}, | |
attemptFail: {}, | |
progress: {}, | |
snapshot: {}, | |
connectionStart: {}, | |
connectionFail: {}, | |
connectionProgress: {}, | |
connectionSuccess: {}, | |
connectionEnd: {}, | |
latencyStart: {}, | |
latencyFail: {}, | |
latencySuccess: {}, | |
latencyEnd: {}, | |
uploadStart: {}, | |
uploadFail: {}, | |
uploadSuccess: {}, | |
uploadEnd: {}, | |
urlRequestStart: {}, | |
urlRequestEnd: {}, | |
afterCompleteAttemptStart: {}, | |
afterCompleteAttemptEnd: {}, | |
event: {} | |
}; | |
module.exports = { | |
events: events, | |
schema: schema | |
} | |
}), require.define("/app/config.js", function (require, module) { | |
module.exports = function () { | |
function positiveNumberValidator(name) { | |
return function (value) { | |
if (0 >= value) throw new errors.TesterArgumentError(name + ": expected value should be above 0, given value: " + value) | |
} | |
} | |
function update(config, overrides) { | |
var name, value; | |
if (overrides) | |
for (name in overrides) value = overrides[name], validate(name, value), config[name] = value; | |
return config | |
} | |
function validate(name, value) { | |
var validator; | |
if (void 0 === defaultConfig[name]) throw new errors.TesterArgumentError("unknown tester config attribute: " + name); | |
return validator = validators[name], validator && validator(value), !0 | |
} | |
var errors = require("./error"), | |
timer = require("./timer"), | |
utils = require("./utils"), | |
validators = { | |
maxAttempts: positiveNumberValidator("maxAttempts"), | |
maxConnections: positiveNumberValidator("maxConnections"), | |
progressFrequencyMs: positiveNumberValidator("progressFrequencyMs"), | |
firstRequestBytes: positiveNumberValidator("firstRequestBytes"), | |
maxBytesInFlight: positiveNumberValidator("maxBytesInFlight"), | |
startRangeRequestTimeMs: positiveNumberValidator("startRangeRequestTimeMs"), | |
maxRangeRequestTimeMs: positiveNumberValidator("maxRangeRequestTimeMs"), | |
rangeRequestIncreaseMs: positiveNumberValidator("rangeRequestIncreaseMs"), | |
latencyMeasurementsWindowSize: positiveNumberValidator("latencyMeasurementsWindowSize"), | |
minLatencyMeasurements: positiveNumberValidator("minLatencyMeasurements"), | |
latencyMeasurementsFrequencyMs: positiveNumberValidator("latencyMeasurementsFrequencyMs"), | |
protocol: function (protocol) { | |
if ("http" !== protocol && "https" !== protocol) throw new errors.TesterArgumentError("protocol: only http and https are supported, given value: " + protocol) | |
}, | |
version: function () { | |
throw new errors.TesterArgumentError("can not override tester version") | |
}, | |
duration: function (duration) { | |
if (void 0 === duration.min) throw new errors.TesterArgumentError("duration: when provided, duration.min is mandatory"); | |
if (void 0 === duration.max) throw new errors.TesterArgumentError("duration: when provided, duration.max is mandatory"); | |
if (duration.min > duration.max) throw new errors.TesterArgumentError("duration: max duration should be higher or equal to min duration, given value: min=" + duration.min + ", max=" + duration.max); | |
if (duration.min < 0) throw new errors.TesterArgumentError("duration.min can not be negative"); | |
if (duration.max < 0) throw new errors.TesterArgumentError("duration.max can not be negative") | |
}, | |
connections: function (connections) { | |
if (void 0 === connections.min) throw new errors.TesterArgumentError("connections: when provided, connections.min is mandatory"); | |
if (void 0 === connections.max) throw new errors.TesterArgumentError("connections: when provided, connections.max is mandatory"); | |
if (connections.min > connections.max) throw new errors.TesterArgumentError("connections: max connections should be higher or equal to min connections, given value: min=" + connections.min + ", max=" + connections.max); | |
if (connections.min <= 0) throw new errors.TesterArgumentError("connections.min should be positive"); | |
if (connections.max <= 0) throw new errors.TesterArgumentError("connections.max should be positive") | |
} | |
}, | |
defaultDuration = { | |
min: 5, | |
max: 30 | |
}, | |
defaultConfig = { | |
version: "0.3.8", | |
maxAttempts: 5, | |
connections: { | |
min: 1, | |
max: 3 | |
}, | |
duration: defaultDuration, | |
progressFrequencyMs: 200, | |
protocol: "https", | |
firstRequestBytes: 2048, | |
maxBytesInFlight: 78643200, | |
collectAfterComplete: !1, | |
getTestOcasParams: {}, | |
startRangeRequestTimeMs: 300, | |
maxRangeRequestTimeMs: 1e3, | |
rangeRequestIncreaseMs: 200, | |
timer: timer.time, | |
measureLatency: !1, | |
latencyMeasurementsWindowSize: 5, | |
minLatencyMeasurements: 3, | |
latencyMeasurementsFrequencyMs: 1e3, | |
aggregator: require("./aggregator/movingAverage")(10), | |
stopper: require("./stopper/stableMeasurementsStopper")({ | |
minDuration: defaultDuration.min, | |
maxDuration: defaultDuration.max, | |
stabilityDelta: 3, | |
minStableMeasurements: 10 | |
}) | |
}; | |
return { | |
get: function (overrides) { | |
var config = utils.simpleCopy(defaultConfig); | |
return overrides && update(config, overrides), config | |
}, | |
validate: function (name, value) { | |
return validate(name, value) | |
} | |
} | |
}() | |
}), require.define("/app/aggregator/movingAverage.js", function (require, module) { | |
module.exports = function (windowSize) { | |
function movingAvg(snapshots) { | |
var snapshotInd, bytes = 0, | |
times = 0, | |
start = snapshots.length - 1, | |
end = Math.max(0, snapshots.length - windowSize); | |
for (snapshotInd = start; snapshotInd >= end; --snapshotInd) snapshot = snapshots[snapshotInd], bytes += snapshot.bytes, times += snapshot.time; | |
return times > 0 ? 1e3 * bytes * 8 / times : 0 | |
} | |
return movingAvg | |
} | |
}), require.define("/app/stopper/stableMeasurementsStopper.js", function (require, module) { | |
module.exports = function (config) { | |
function lastWindowMaxInd(measurements, windowSize) { | |
var measurement, curMaxSpeed = 0, | |
curMaxInd = 0, | |
measurementInd = 0, | |
numMeasurements = measurements.length, | |
firstMeasurementInd = Math.max(0, numMeasurements - windowSize); | |
for (measurementInd = numMeasurements - 1; measurementInd >= firstMeasurementInd; --measurementInd) measurement = measurements[measurementInd], measurement.speed >= curMaxSpeed && (curMaxSpeed = measurement.speed, curMaxInd = measurementInd); | |
return curMaxInd | |
} | |
function maxDelta(value, measurements) { | |
var measurementInd, delta, maxDelta = 0; | |
for (measurementInd = 0; measurementInd < measurements.length; ++measurementInd) delta = 100 * Math.abs(measurements[measurementInd].speed - value) / value, delta > maxDelta && (maxDelta = delta); | |
return delta | |
} | |
function isCompleted(metrics) { | |
var testTime = void 0 !== metrics.testTime ? metrics.testTime : timer() - startTime, | |
measurements = metrics.progressMeasurements, | |
afterCompleteDuration = metrics.afterCompleteDuration, | |
numMeasurements = measurements.length; | |
if (void 0 === afterCompleteDuration) { | |
if (testTime >= maxDuration) return !0; | |
if (minStableMeasurements > numMeasurements) return !1; | |
var maxInd = lastWindowMaxInd(measurements, minStableMeasurements); | |
if (minStableMeasurements > numMeasurements - maxInd) return !1; | |
var delta = maxDelta(measurements[maxInd].speed, measurements.slice(numMeasurements - minStableMeasurements, numMeasurements)); | |
return delta > stabilityDelta ? !1 : testTime >= minDuration | |
} | |
return testTime >= afterCompleteDuration | |
} | |
var minDuration, maxDuration, stabilityDelta, minStableMeasurements, timer, startTime; | |
return minDuration = config.minDuration || 5, maxDuration = config.maxDuration || 60, stabilityDelta = config.stabilityDelta || 5, minStableMeasurements = config.minStableMeasurements || 5, timer = config.timer || require("../timer").time, startTime = timer(), isCompleted | |
} | |
}), require.define("/node_modules/circular-buffer/package.json", function (require, module) { | |
module.exports = { | |
main: "index.js" | |
} | |
}), require.define("/node_modules/circular-buffer/index.js", function (require, module) { | |
function CircularBuffer(capacity) { | |
if (!(this instanceof CircularBuffer)) return new CircularBuffer(capacity); | |
if ("object" == typeof capacity && Array.isArray(capacity._buffer) && "number" == typeof capacity._capacity && "number" == typeof capacity._first && "number" == typeof capacity._size) | |
for (var prop in capacity) capacity.hasOwnProperty(prop) && (this[prop] = capacity[prop]); | |
else { | |
if ("number" != typeof capacity || capacity % 1 != 0 || 1 > capacity) throw new TypeError("Invalid capacity"); | |
this._buffer = new Array(capacity), this._capacity = capacity, this._first = 0, this._size = 0 | |
} | |
} | |
CircularBuffer.prototype = { | |
size: function () { | |
return this._size | |
}, | |
capacity: function () { | |
return this._capacity | |
}, | |
enq: function (value) { | |
this._first > 0 ? this._first-- : this._first = this._capacity - 1, this._buffer[this._first] = value, this._size < this._capacity && this._size++ | |
}, | |
push: function (value) { | |
this._size == this._capacity ? (this._buffer[this._first] = value, this._first = (this._first + 1) % this._capacity) : (this._buffer[(this._first + this._size) % this._capacity] = value, this._size++) | |
}, | |
deq: function () { | |
if (0 == this._size) throw new RangeError("dequeue on empty buffer"); | |
var value = this._buffer[(this._first + this._size - 1) % this._capacity]; | |
return this._size--, value | |
}, | |
pop: function () { | |
return this.deq() | |
}, | |
shift: function () { | |
if (0 == this._size) throw new RangeError("shift on empty buffer"); | |
var value = this._buffer[this._first]; | |
return this._first == this._capacity - 1 ? this._first = 0 : this._first++, this._size--, value | |
}, | |
get: function (start, end) { | |
if (0 == this._size && 0 == start && (void 0 == end || 0 == end)) return []; | |
if ("number" != typeof start || start % 1 != 0 || 0 > start) throw new TypeError("Invalid start"); | |
if (start >= this._size) throw new RangeError("Index past end of buffer: " + start); | |
if (void 0 == end) return this._buffer[(this._first + start) % this._capacity]; | |
if ("number" != typeof end || end % 1 != 0 || 0 > end) throw new TypeError("Invalid end"); | |
if (end >= this._size) throw new RangeError("Index past end of buffer: " + end); | |
return this._first + start >= this._capacity && (start -= this._capacity, end -= this._capacity), this._first + end < this._capacity ? this._buffer.slice(this._first + start, this._first + end + 1) : this._buffer.slice(this._first + start, this._capacity).concat(this._buffer.slice(0, this._first + end + 1 - this._capacity)) | |
}, | |
toarray: function () { | |
return 0 == this._size ? [] : this.get(0, this._size - 1) | |
} | |
}, module.exports = CircularBuffer | |
}), require.define("/app/url_getter.js", function (require, module) { | |
var urlGetter = function () { | |
function badUrlReporter() { | |
function parseUrl(url) { | |
var endpoint, urlParts = url.split("?"), | |
urlData = {}; | |
return urlData.url = url, urlData.normalizedUrl = url, endpoint = urlParts[0], 2 === urlParts.length && (speedtestInd = endpoint.lastIndexOf("/speedtest"), speedtestInd ? (endpoint = endpoint.substring(0, speedtestInd + 10), urlData.normalizedUrl = endpoint + "?" + urlParts[1]) : urlData.normalizedUrl = url), endpoint = endpoint.replace(/.*?:\/\//g, ""), urlData.oca = endpoint.split("/")[0], urlData.site = urlData.oca.split(".")[2], urlData | |
} | |
function reset() { | |
badUrls = {}, ocaFailures = {}, siteFailures = {}, clientInfo = {} | |
} | |
var badUrls, ocaFailures, siteFailures; | |
return reset(), { | |
reset: reset, | |
isBadUrl: function (url) { | |
var urlData = parseUrl(url), | |
ocaFails = void 0 === ocaFailures[urlData.oca] ? 0 : ocaFailures[urlData.oca], | |
siteFails = void 0 === siteFailures[urlData.site] ? 0 : siteFailures[urlData.site]; | |
return url = urlData.normalizedUrl, void 0 !== badUrls[url] || ocaFails >= 2 || siteFails >= 3 | |
}, | |
reportBadUrl: function (url) { | |
var urlData = parseUrl(url); | |
url = urlData.normalizedUrl, badUrls[url] = !0, urlData.oca && (ocaFailures[urlData.oca] = void 0 === ocaFailures[urlData.oca] ? 1 : ocaFailures[urlData.oca] + 1), 1 === ocaFailures[urlData.oca] && urlData.site && (siteFailures[urlData.site] = void 0 === siteFailures[urlData.site] ? 1 : siteFailures[urlData.site] + 1) | |
} | |
} | |
} | |
function parseSpeedTestUrls(responseText) { | |
var response, targets, ind, url, allUrls; | |
for (allUrls = [], response = JSON.parse(responseText), targets = response.targets || [], clientInfo = response.client || {}, clientInfo.servers = [], ind = 0; ind < targets.length; ++ind) url = targets[ind].url, url = url.replace(/speedtest/, "speedtest/range/"), urlReporter.isBadUrl(url) || (testUrls.push(url), clientInfo.servers.push(targets[ind])), allUrls.push(url); | |
return 0 === testUrls.length && (testUrls = allUrls), testUrls | |
} | |
function formatUrl(params) { | |
var param, endpoint = params.endpoint || DEFAULT_PARAMS.endpoint, | |
https = void 0 !== params.https ? params.https : DEFAULT_PARAMS.https, | |
token = params.token || DEFAULT_PARAMS.token, | |
urlCount = params.urlCount || DEFAULT_PARAMS.urlCount, | |
url = "https://" + endpoint + "?https=" + https + "&token=" + token + "&urlCount=" + urlCount; | |
for (param in params.extraParams) params.extraParams.hasOwnProperty(param) && (url += "&" + param + "=" + params.extraParams[param]); | |
return url | |
} | |
function reset() { | |
testUrls = [], curUrlInd = 0, req = void 0 | |
} | |
var DEFAULT_PARAMS, curUrlInd, testUrls, that, errors, req, urlReporter, clientInfo; | |
return errors = require("./error"), dummy = require("./utils").dummy, DEFAULT_PARAMS = { | |
https: !0, | |
token: "YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm", | |
urlCount: 3, | |
endpoint: "api-global.netflix.com/oca/speedtest", | |
extraParams: {} | |
}, that = {}, urlReporter = badUrlReporter(), reset(), that.get = function (urlParams, requester, onComplete, onFail, onStop, refresh) { | |
function returnUrl(urls) { | |
urls.length > 0 ? onComplete(urls[curUrlInd]) : onFail(new errors.NoOcaUrlsError("No urls returned from the endpoint " + url)) | |
} | |
return 0 === testUrls.length || refresh ? void that.refresh(urlParams, requester, returnUrl, onFail, onStop) : void returnUrl(testUrls) | |
}, that.getNext = function (urlParams, requester, onComplete, onFail, onStop, refresh) { | |
return testUrls.length < 3 || refresh ? void that.get(urlParams, requester, onComplete, onFail, onStop, !0) : (curUrlInd = (curUrlInd + 1) % testUrls.length, void(testUrls.length > 0 ? onComplete(testUrls[curUrlInd]) : onFail(new errors.NoOcaUrlsError("No urls returned from the endpoint " + urlParams)))) | |
}, that.reportBadUrl = function (url) { | |
var urlInd, newTestUrls = []; | |
for (urlReporter.reportBadUrl(url), urlInd = 0; urlInd < testUrls.length; ++urlInd) urlReporter.isBadUrl(testUrls[urlInd]) || newTestUrls.push(testUrls[urlInd]); | |
return testUrls = newTestUrls | |
}, that.refresh = function (urlParams, requester, onSuccess, onFail, onStop) { | |
function urlSuccess(data) { | |
req = void 0, data.success ? (reset(), testUrls = parseSpeedTestUrls(data.response), onSuccess(testUrls)) : "stopped" !== data.reason ? (error = new errors.GetOcaUrlError("Could not send request to " + url), error.response = data.response, onFail(error)) : onStop(data) | |
} | |
req = requester(), url = formatUrl(urlParams), req.start(url, dummy, urlSuccess, !0) | |
}, that.stop = function () { | |
req && req.stop() | |
}, that.reset = function () { | |
reset(), urlReporter.reset() | |
}, that.clientInfo = function () { | |
return clientInfo | |
}, that | |
}; | |
module.exports = urlGetter | |
}), require.define("/app/snapshot.js", function (require, module) { | |
module.exports = function () { | |
function reset() { | |
snapshots = [], lastProcessedWorkerMeasurement = {}, overflowMeasurements = {} | |
} | |
function getLatestSnapshotMeasurements(workerMeasurements) { | |
var workerId, snapshotMeasurements, curWorkerMeasurements, workerMeasurementsCount, lastMeasurement, lastMeasurementInd, snapshotEnd, newLastProcessedWorkerMeasurement, curWorkerOverflowMeasurements; | |
snapshotMeasurements = [], newLastProcessedWorkerMeasurement = {}; | |
for (workerId in workerMeasurements) | |
if (workerMeasurements.hasOwnProperty(workerId)) { | |
if (curWorkerMeasurements = workerMeasurements[workerId], workerMeasurementsCount = curWorkerMeasurements.length, lastMeasurementInd = lastProcessedWorkerMeasurement[workerId] || 0, curWorkerOverflowMeasurements = overflowMeasurements[workerId] || [], !(workerMeasurementsCount > lastMeasurementInd || curWorkerOverflowMeasurements.length > 0)) return; | |
overflowMeasurements[workerId] = [], snapshotMeasurements.push({ | |
workerId: workerId, | |
measurements: [].concat(curWorkerOverflowMeasurements, curWorkerMeasurements.slice(lastMeasurementInd, workerMeasurementsCount)) | |
}), lastMeasurement = curWorkerMeasurements[workerMeasurementsCount - 1], (!snapshotEnd || lastMeasurement.end < snapshotEnd) && (snapshotEnd = lastMeasurement.end), newLastProcessedWorkerMeasurement[workerId] = workerMeasurementsCount | |
} return lastProcessedWorkerMeasurement = newLastProcessedWorkerMeasurement, { | |
measurements: snapshotMeasurements, | |
end: snapshotEnd | |
} | |
} | |
function trimSnapshotMeasurements(snapshotMeasurements, snapshotEnd) { | |
var curWorkerMeasurements, workerId, lastMeasurement, measurementId, overflowMeasurement, snapshotInd, snapshot; | |
for (snapshotInd = 0; snapshotInd < snapshotMeasurements.length; ++snapshotInd) { | |
for (snapshot = snapshotMeasurements[snapshotInd], curWorkerMeasurements = snapshot.measurements, workerId = snapshot.workerId, measurementId = curWorkerMeasurements.length - 1; measurementId >= 0 && (lastMeasurement = curWorkerMeasurements[measurementId], lastMeasurement.start >= snapshotEnd); --measurementId) overflowMeasurements[workerId] || (overflowMeasurements[workerId] = []), overflowMeasurements[workerId].push(lastMeasurement); | |
snapshot.measurements = curWorkerMeasurements.slice(0, measurementId + 1), measurementId >= 0 && lastMeasurement && lastMeasurement.end > snapshotEnd && (overflowMeasurement = { | |
start: snapshotEnd, | |
end: lastMeasurement.end | |
}, overflowMeasurement.bytes = lastMeasurement.bytes * (lastMeasurement.end - snapshotEnd) / (lastMeasurement.end - lastMeasurement.start), lastMeasurement.bytes -= overflowMeasurement.bytes, lastMeasurement.end = snapshotEnd, overflowMeasurements[workerId] || (overflowMeasurements[workerId] = []), overflowMeasurements[workerId].push(overflowMeasurement)) | |
} | |
return snapshotMeasurements | |
} | |
function makeSnapshot(snapshotMeasurements) { | |
function minStart(curWorkerMeasurements) { | |
var snapshotInd, min = { | |
workerId: null, | |
measurement: {}, | |
snapshotInd: null | |
}; | |
for (snapshotInd = 0; snapshotInd < snapshotMeasurements.length; ++snapshotInd) { | |
var workerId = snapshotMeasurements[snapshotInd].workerId, | |
measurements = snapshotMeasurements[snapshotInd].measurements, | |
measurementInd = curWorkerMeasurements[workerId] || 0, | |
measurement = measurements[measurementInd]; | |
measurement && (void 0 === min.measurement.start || measurement.start < min.measurement.start) && (min.workerId = workerId, min.measurement = measurement, min.snaphotInd = snapshotInd) | |
} | |
return min | |
} | |
var minTime, maxTime, snapshot, curRange, rangeFinished, curLastTime; | |
for (snapshot = { | |
bytes: 0, | |
time: 0 | |
}, curRange = {}, rangeFinished = 0; rangeFinished < snapshotMeasurements.length;) { | |
var curMin = minStart(curRange); | |
null !== curMin.workerId ? (curRange[curMin.workerId] = (curRange[curMin.workerId] || 0) + 1, curRange[curMin.workerId] >= snapshotMeasurements[curMin.snaphotInd].measurements.length && (rangeFinished += 1), void 0 === minTime && (minTime = curMin.measurement.start, curLastTime = minTime), curLastTime = Math.max(curLastTime, curMin.measurement.start), curMin.measurement.end > curLastTime && (snapshot.time += curMin.measurement.end - curLastTime, curLastTime = curMin.measurement.end), snapshot.bytes += curMin.measurement.bytes) : rangeFinished += 1 | |
} | |
return maxTime = curLastTime, void 0 !== maxTime && void 0 !== minTime ? (snapshot.start = minTime, snapshot.end = maxTime, snapshot) : void 0 | |
} | |
var snapshots, lastProcessedWorkerMeasurement, overflowMeasurements; | |
return reset(), { | |
reset: function () { | |
reset() | |
}, | |
compute: function (workerMeasurements) { | |
var workerSnapshotData = getLatestSnapshotMeasurements(workerMeasurements); | |
if (workerSnapshotData) { | |
var snapshotMeasurements = trimSnapshotMeasurements(workerSnapshotData.measurements, workerSnapshotData.end), | |
snapshot = makeSnapshot(snapshotMeasurements); | |
return snapshot && snapshots.push(snapshot), snapshot | |
} | |
}, | |
length: function () { | |
return snapshots.length | |
}, | |
all: function () { | |
return snapshots | |
} | |
} | |
}() | |
}), require.define("/app/requester/xhr.js", function (require, module) { | |
var requester = function () { | |
function extractTimingInfo(data, url) { | |
var entries, timing, end; | |
window && window.performance && window.performance.getEntriesByName && (entries = window.performance.getEntriesByName(url), 0 != entries.length && (timing = entries[entries.length - 1], end = timing.responseStart || timing.responseEnd, end && (data.timing = timing, data.start = timing.requestStart || timing.connectEnd || timing.startTime || timing.fetchTime || data.start, data.end = end))) | |
} | |
function validate(url, onProgressCallback, onCompleteCallback) { | |
if (!url) throw new errors.RequesterArgumentError("url arguments is mandatory to perform speed test"); | |
if (!onProgressCallback) throw new errors.RequesterArgumentError("onProgressCallback is mandatory"); | |
if (!onCompleteCallback) throw new errors.RequesterArgumentError("onCompleteCallback is mandatory") | |
} | |
function startTest(url, withResponse) { | |
function ontimeout() { | |
testIsRunning = !1, request && request.abort(), onComplete({ | |
success: !1, | |
response: { | |
type: "Timeout", | |
message: "Request timed out. Timeout: " + requestTimeoutMs + "ms" | |
} | |
}) | |
} | |
function onerror() { | |
var headers, errorMessage; | |
clearTimeout(xmlHttpTimeout), errorMessage = this.response, !errorMessage && withResponse && (errorMessage = this.responseText), this.getAllResponseHeaders && (headers = this.getAllResponseHeaders()), onComplete({ | |
success: !1, | |
response: { | |
type: "RequestError", | |
status: this.status, | |
statusText: this.statusText, | |
message: errorMessage, | |
headers: headers, | |
url: url | |
} | |
}) | |
} | |
function onload() { | |
var now, data, responseSize; | |
testIsRunning && (now = timer(), clearTimeout(xmlHttpTimeout), data = { | |
success: !0, | |
start: lastCompleteTime, | |
end: now | |
}, withResponse && (data.response = this.responseText), responseSize = this.response && this.response.size, extractTimingInfo(data, url), onProgress({ | |
start: lastProgressTime, | |
end: data.end, | |
success: !0, | |
bytes: void 0 !== responseSize ? responseSize - lastBytesLoaded : responseSize | |
}), onComplete(data)) | |
} | |
function onprogress(e) { | |
clearTimeout(xmlHttpTimeout); | |
var now, newBytesLoaded; | |
200 !== this.status && 304 !== this.status || !e.lengthComputable || (now = timer(), newBytesLoaded = e.loaded - lastBytesLoaded, lastBytesLoaded = e.loaded, onProgress({ | |
bytes: newBytesLoaded, | |
success: !0, | |
start: lastProgressTime, | |
end: now | |
}), lastProgressTime = now) | |
} | |
var lastBytesLoaded, lastProgressTime, lastCompleteTime, xmlHttpTimeout; | |
lastBytesLoaded = 0, request = xhr(), request.onprogress = onprogress, xmlHttpTimeout = setTimeout(ontimeout, requestTimeoutMs), "onreadystatechange" in request ? request.onreadystatechange = function () { | |
testIsRunning && 4 === this.readyState && (200 === this.status || 304 === this.status ? onload.apply(this) : onerror.apply(this)) | |
} : (request.onerror = onerror, request.onload = onload, request.ontimeout = function () {}), request.open("GET", url, !0), withResponse || (request.overrideMimeType && request.overrideMimeType("text/plain; charset=x-user-defined"), request.responseType = "blob"), lastProgressTime = lastCompleteTime = timer(), request.timeout = 6e4, setTimeout(function () { | |
request && request.send() | |
}, 0) | |
} | |
function uploadTest(url, size) { | |
function ontimeout() { | |
testIsRunning = !1, request && request.abort(), onComplete({ | |
success: !1, | |
response: { | |
type: "Timeout", | |
message: "Request timed out. Timeout: " + requestTimeoutMs + "ms" | |
} | |
}) | |
} | |
function onerror() { | |
var headers, errorMessage; | |
clearTimeout(xmlHttpTimeout), errorMessage = this.response, errorMessage || (errorMessage = this.responseText), this.getAllResponseHeaders && (headers = this.getAllResponseHeaders()), onComplete({ | |
success: !1, | |
response: { | |
type: "RequestError", | |
status: this.status, | |
statusText: this.statusText, | |
message: errorMessage, | |
headers: headers, | |
url: url | |
} | |
}) | |
} | |
function onload() { | |
var now, data; | |
testIsRunning && (now = timer(), clearTimeout(xmlHttpTimeout), data = { | |
success: !0, | |
start: lastCompleteTime, | |
end: now | |
}, extractTimingInfo(data, url), onProgress({ | |
start: lastProgressTime, | |
bytes: size - lastBytesLoaded, | |
end: data.end, | |
success: !0 | |
}), onComplete(data)) | |
} | |
function onprogress(e) { | |
clearTimeout(xmlHttpTimeout); | |
var now, newBytesLoaded; | |
now = timer(), newBytesLoaded = e.loaded - lastBytesLoaded, lastBytesLoaded = e.loaded, onProgress({ | |
bytes: newBytesLoaded, | |
success: !0, | |
start: lastProgressTime, | |
end: now | |
}), lastProgressTime = now | |
} | |
var lastBytesLoaded, lastProgressTime, lastCompleteTime, xmlHttpTimeout; | |
lastBytesLoaded = 0, request = xhr(), request.upload && (request.upload.onprogress = onprogress), xmlHttpTimeout = setTimeout(ontimeout, requestTimeoutMs), "onreadystatechange" in request ? request.onreadystatechange = function () { | |
testIsRunning && 4 === this.readyState && (this.status >= 200 && this.status < 300 || 304 === this.status ? onload.apply(this) : onerror.apply(this)) | |
} : request.upload && (request.upload.onerror = onerror, request.upload.onload = onload, request.upload.ontimeout = function () {}), request.open("POST", url, !0); | |
try { | |
size > 0 && request.setRequestHeader("Content-type", "application/octet-stream") | |
} catch (err) {} | |
var blob = null; | |
size > 0 && (blob = utils.genBlob(size)), lastProgressTime = lastCompleteTime = timer(), request.timeout = 6e4, setTimeout(function () { | |
request && request.send(blob) | |
}, 0) | |
} | |
var testIsRunning, request, errors, xhr, timer, utils, requestTimeoutMs, dummy, onComplete, onProgress, supportsProgress; | |
return requestTimeoutMs = 1e4, errors = require("../error"), utils = require("../utils"), xhr = utils.getXHR, timer = require("../timer").time, dummy = require("../utils").dummy, testIsRunning = !1, { | |
start: function (url, onProgressCallback, onCompleteCallback, withResponse) { | |
validate(url, onProgressCallback, onCompleteCallback), onComplete = onCompleteCallback, onProgress = onProgressCallback, testIsRunning = !0, startTest(url, withResponse) | |
}, | |
upload: function (url, size, onProgressCallback, onCompleteCallback) { | |
validate(url, onProgressCallback, onCompleteCallback), onComplete = onCompleteCallback, onProgress = onProgressCallback, testIsRunning = !0, uploadTest(url, size) | |
}, | |
supportsProgress: function () { | |
return void 0 !== supportsProgress ? supportsProgress : void(supportsProgress = window.XDomainRequest && -1 === navigator.appVersion.indexOf("MSIE 10") ? !1 : "onprogress" in xhr()) | |
}, | |
stop: function () { | |
testIsRunning = !1, request && (request.onreadystatechange = dummy, request.onprogress = dummy, request.abort(), request = void 0, onComplete({ | |
success: !1, | |
reason: "stopped" | |
}), onComplete = dummy, onProgress = dummy) | |
} | |
} | |
}; | |
module.exports = requester | |
}), require.define("/app/logger.js", function (require, module) { | |
function logger(tester, config) { | |
function sendLogs(data) { | |
function onload() {} | |
function onerror() {} | |
var request = require("./utils").getXHR(); | |
request.onload ? (request.onload = onload, request.onerror = onerror) : request.onreadystatechange = function () { | |
4 === this.readyState && (this.status >= 200 && this.status < 400 ? onload() : onerror()) | |
}, request.open("POST", logUrl, !0), request.timeout = 5e3, request.setRequestHeader && request.setRequestHeader("Content-type", "application/json"), request.send(data) | |
} | |
function getPageLoadPerformance() { | |
return window && window.performance && window.performance.timing ? window.performance.timing : {} | |
} | |
function endSessionType(data) { | |
switch (data.result) { | |
case "success": | |
return "SessionEnded"; | |
case "fail": | |
return "ActionFailed"; | |
case "stop": | |
return "SessionCancelled" | |
} | |
} | |
function endConnectionSession(oca, data) { | |
var connectionSessionId = connectionSessionIds[oca]; | |
data.type = endSessionType(data), data.measurements = connectionEvents[oca], nfLogger.endSession(connectionSessionId, data), delete connectionSessionIds[oca], delete connectionEvents[oca] | |
} | |
function endMeasureSession(sessionId, data) { | |
data.type = endSessionType(data), data.snapshots = snapshotEvents, data.progress = progressEvents, data.latencies = latencyEvents, snapshotEvents = [], progressEvents = [], latencyEvents = {}, nfLogger.endSession(sessionId, data) | |
} | |
var DEFAULT_URL = "https://ichnaea.test.netflix.com/cl2", | |
DEFAULT_SOURCE = "netspeed", | |
SPEED_TEST_SESSION = "SpeedTest", | |
MEASURE_ATTEMPT_SESSION = "Measure", | |
CONNECTION_SESSION = "Connection", | |
AFTER_COMPLETE_SESSION = "AfterCompleteMeasure", | |
URL_REQUEST_SESSION = "GetUrl", | |
CLIENT_CONTEXT = "Client"; | |
PAGE_LOAD = "pageLoad"; | |
var logUrl, logSource, that, nfLogger, utils, testSessionId, testAttemptSessionId, afterCompleteSessionId, loggingEnabled = !1, | |
connectionSessionIds = {}, | |
getUrlSessionIds = [], | |
snapshotEvents = [], | |
progressEvents = [], | |
latencyEvents = {}, | |
connectionEvents = {}, | |
events = require("./event").events; | |
return config = config || {}, logUrl = config.url || DEFAULT_URL, logSource = config.source || DEFAULT_SOURCE, utils = require("./utils"), nfLogger = require("nf-cl-logger")({ | |
requestSender: sendLogs, | |
batchInterval: 6e5, | |
batchSize: 1e5, | |
source: logSource | |
}), nfLogger.addContext(CLIENT_CONTEXT, { | |
deviceType: navigator.deviceData ? "Mobile" : "Browser", | |
appVersion: navigator.appVersion, | |
userAgent: navigator.userAgent, | |
tester: tester.config(), | |
referrer: document.referrer, | |
deviceInfo: navigator.deviceData, | |
href: window && window.location && window.location.href || "NA" | |
}), that = { | |
startLogging: function () { | |
loggingEnabled || (loggingEnabled = !0, tester.on(events.START, function (data) { | |
testSessionId = nfLogger.startSession(SPEED_TEST_SESSION, data) | |
}), tester.on(events.END, function (data) { | |
data[PAGE_LOAD] = getPageLoadPerformance(), data.type = endSessionType(data), nfLogger.endSession(testSessionId, data), nfLogger.flush() | |
}), tester.on(events.ATTEMPT_START, function (data) { | |
testAttemptSessionId = nfLogger.startSession(MEASURE_ATTEMPT_SESSION, data) | |
}), tester.on(events.ATTEMPT_END, function (data) { | |
endMeasureSession(testAttemptSessionId, data) | |
}), tester.on(events.CONNECTION_START, function (data) { | |
var oca = data.oca; | |
connectionSessionIds[oca] = nfLogger.startSession(CONNECTION_SESSION, data), connectionEvents[oca] = [] | |
}), tester.on(events.CONNECTION_PROGRESS, function (data) { | |
var oca = data.oca; | |
connectionEvents[oca] && connectionEvents[oca].push({ | |
bytes: data.bytes, | |
start: data.start, | |
end: data.end | |
}) | |
}), tester.on(events.CONNECTION_END, function (data) { | |
endConnectionSession(data.oca, data) | |
}), tester.on(events.URL_REQUEST_START, function (data) { | |
getUrlSessionIds.push(nfLogger.startSession(URL_REQUEST_SESSION, data)) | |
}), tester.on(events.URL_REQUEST_END, function (data) { | |
var sessionId = getUrlSessionIds[0]; | |
sessionId && (data.type = endSessionType(data), nfLogger.endSession(sessionId, data), getUrlSessionIds.shift()) | |
}), tester.on(events.LATENCY_END, function (data) { | |
var oca = data.oca; | |
latencyEvents[oca] || (latencyEvents[oca] = []), latencyEvents[oca].push(data) | |
}), tester.on(events.PROGRESS, function (data) { | |
progressEvents.push(data) | |
}), tester.on(events.SNAPSHOT, function (data) { | |
snapshotEvents.push(data) | |
}), tester.on(events.AFTER_COMPLETE_ATTEMPT_START, function (data) { | |
afterCompleteSessionId = nfLogger.startSession(AFTER_COMPLETE_SESSION, data) | |
}), tester.on(events.AFTER_COMPLETE_ATTEMPT_END, function (data) { | |
endMeasureSession(afterCompleteSessionId, data), nfLogger.flush() | |
}), tester.on(events.ANY, function (data) {})) | |
}, | |
endLogging: function () { | |
loggingEnabled && (loggingEnabled = !1) | |
}, | |
getLogger: function () { | |
return nfLogger | |
}, | |
flush: function () { | |
nfLogger.flush() | |
} | |
} | |
} | |
module.exports = logger | |
}), require.define("/node_modules/nf-cl-logger/package.json", function (require, module) { | |
module.exports = { | |
main: "index.js" | |
} | |
}), require.define("/node_modules/nf-cl-logger/index.js", function (require, module) { | |
"use strict"; | |
module.exports = require("./src/logger") | |
}), require.define("/node_modules/nf-cl-logger/src/logger.js", function (require, module) { | |
"use strict"; | |
function createCompactLogger(optionsArg) { | |
var options = optionsArg || {}; | |
return options.version = options.version || "2.0", options.envelopeName = options.envelopeName || "CompactConsolidatedLoggingEnvelope", new Logger(options) | |
} | |
var Logger = require("./logger-core"); | |
module.exports = createCompactLogger | |
}), require.define("/node_modules/nf-cl-logger/src/logger-core.js", function (require, module) { | |
"use strict"; | |
function Logger() { | |
this._init.apply(this, arguments) | |
} | |
var SCHEMA = require("nf-cl-schema-ui"), | |
VERSION = "2.0.3", | |
MAX_BITS_COUNT = 53, | |
INCREMENTING_BITS_COUNT = 28, | |
RANDOM_BITS_COUNT = MAX_BITS_COUNT - INCREMENTING_BITS_COUNT, | |
INCREMENTING_BITS_MASK = Math.pow(2, INCREMENTING_BITS_COUNT) - 1, | |
RANDOM_BITS_SHIFT = Math.pow(2, RANDOM_BITS_COUNT); | |
Logger.prototype = { | |
constructor: Logger, | |
batchInterval: 3e4, | |
batchSize: 50, | |
timeOffset: 0, | |
source: "", | |
requestSender: null, | |
getClientTime: null, | |
addContext: function (type, data) { | |
var context = this._initContext([type], data); | |
return this._state.pending[context.id] = context, context.id | |
}, | |
removeContext: function (id) { | |
return this._state.pending[id] ? (delete this._state.pending[id], id) : this._state.current[id] ? (this._state.currentDelta.push(this._state.current[id]), delete this._state.current[id], id) : null | |
}, | |
logEvent: function (type, data) { | |
var context = this._initEventContext([type, "DiscreteEvent"], data); | |
return this._snapshot(context), context.id | |
}, | |
startSession: function (type, data) { | |
var context = this._initEventContext([type, "Session"], data); | |
return this._state.current[context.id] = context, this._snapshot(), context.id | |
}, | |
endSession: function (sessionId, data) { | |
var startContext = this._state.current[sessionId]; | |
if (startContext) { | |
var type = data && data.type ? [data.type, "SessionEnded"] : ["SessionEnded"], | |
endContext = this._initEventContext(type, data); | |
return endContext.duration = endContext.time - startContext.time, endContext.sessionId = sessionId, delete this._state.current[sessionId], this._snapshot(endContext, startContext), sessionId | |
} | |
return null | |
}, | |
flush: function () { | |
var state = this._state; | |
if (!state.ending && state.snapshots && state.snapshots.length) { | |
var envelope = { | |
currentState: state.current, | |
reverseDeltas: state.snapshots, | |
type: "CompactConsolidatedLoggingEnvelope", | |
version: 2, | |
clientSendTime: this._timestamp() | |
}; | |
state.snapshots = [], this.requestSender(JSON.stringify(envelope)) | |
} | |
}, | |
serialize: function () { | |
var timer = this._batchTimeout; | |
this._batchTimeout = null; | |
var json = JSON.stringify(this); | |
return this._batchTimeout = timer, json | |
}, | |
sever: function (severedContext) { | |
this.end(severedContext || "Severed"), this._init(this) | |
}, | |
end: function (endingContext) { | |
endingContext && this.addContext(endingContext), this._state.ending = !0, this._stopBatching(); | |
for (var keys = Object.keys(this._state.current).sort(function (a, b) { | |
return b - a | |
}), logId = keys.pop(), i = 0; i < keys.length; i++) { | |
var context = this._state.current[keys[i]], | |
types = context.type; | |
"Session" === types[types.length - 1] && this.endSession(context.id, { | |
type: "SessionCanceled" | |
}) | |
} | |
this.endSession(logId, { | |
type: "SessionEnded" | |
}), this._state.ending = !1, this.flush(), this._state = null | |
}, | |
_init: function (options) { | |
this._initOptions(options), this._startBatching(), options.existingState || this._startLogSession(), this._logInitializedEvent() | |
}, | |
_initOptions: function (options) { | |
options.existingState ? this._restore(options.existingState) : this._initState(), this._initProperties(options) | |
}, | |
_initState: function () { | |
var state = {}; | |
state.sequenceNumber = 0, state.lastIncrementingBits = 0, state.pending = {}, state.current = {}, state.snapshots = [], state.currentDelta = [], this._state = state | |
}, | |
_startLogSession: function () { | |
this.startSession("Log", { | |
source: this.source, | |
schema: { | |
name: SCHEMA.name, | |
version: SCHEMA.version | |
} | |
}) | |
}, | |
_logInitializedEvent: function () { | |
this.logEvent("LoggerInitialized", { | |
version: VERSION | |
}) | |
}, | |
_restore: function (state) { | |
for (var existingState = JSON.parse(state), keys = Object.keys(existingState), i = 0; i < keys.length; i++) { | |
var key = keys[i]; | |
this[key] = existingState[key] | |
} | |
}, | |
_initProperties: function (options) { | |
for (var option in this) "function" != typeof this[option] && options && "_" !== option.charAt(0) && (this[option] = "undefined" != typeof options[option] ? options[option] : this[option]) | |
}, | |
_copyData: function (data) { | |
var copy = {}; | |
for (var field in data) copy[field] = data[field]; | |
return copy | |
}, | |
_initContext: function (type, data) { | |
var context; | |
return context = data ? this._copyData(data) : {}, context.type = SCHEMA && SCHEMA.types[type[0]] ? SCHEMA.types[type[0]] : type, context.id = this._getNextContextId(), context | |
}, | |
_initEventContext: function (type, data) { | |
var context = this._initContext(type, data); | |
return context.sequence = ++this._state.sequenceNumber, "undefined" == typeof context.time && (context.time = this._timestamp()), context | |
}, | |
_getClientTime: function () { | |
return (new Date).getTime() | |
}, | |
_timestamp: function () { | |
var getTime = this.getClientTime || this._getClientTime; | |
return getTime() + this.timeOffset | |
}, | |
_getNextContextId: function () { | |
var currentTimeInSeconds = Math.floor(this._timestamp() / 1e3), | |
incrBitsMask = INCREMENTING_BITS_MASK, | |
bitsShift = RANDOM_BITS_SHIFT, | |
incrementingBits = currentTimeInSeconds & incrBitsMask, | |
randomBits = Math.floor(Math.random() * bitsShift); | |
return incrementingBits <= this._state.lastIncrementingBits && (incrementingBits = this._state.lastIncrementingBits + 1), this._state.lastIncrementingBits = incrementingBits, incrementingBits * bitsShift + randomBits | |
}, | |
_snapshot: function () { | |
for (var count = 1, current = this._state.current, pending = this._state.pending, pendingKeys = Object.keys(pending), i = 0; i < pendingKeys.length; i++) { | |
var key = pendingKeys[i]; | |
current[key] = pending[key], count++ | |
} | |
this._state.pending = {}, this._state.currentDelta.push(count), this._state.currentDelta = [], this._state.snapshots.push(this._state.currentDelta), arguments.length && this._state.currentDelta.push.apply(this._state.currentDelta, arguments), this._state.snapshots.length >= this.batchSize && this.flush() | |
}, | |
_startBatching: function () { | |
var self = this; | |
self._batchTimeout = setTimeout(function () { | |
self.flush(), self._startBatching() | |
}, self.batchInterval) | |
}, | |
_stopBatching: function () { | |
clearTimeout(this._batchTimeout), this._batchTimeout = null | |
} | |
}, module.exports = Logger | |
}), require.define("/node_modules/nf-cl-schema-ui/package.json", function (require, module) { | |
module.exports = { | |
main: "dist/schema/nf-cl-schema-netflixApp.js" | |
} | |
}), require.define("/node_modules/nf-cl-schema-ui/dist/schema/nf-cl-schema-netflixApp.js", function (require, module) { | |
module.exports = { | |
version: "1.19.0", | |
name: "netflixApp", | |
types: { | |
AcceptTermsOfUse: ["AcceptTermsOfUse", "Action", "Session"], | |
AdaptiveEcomFallbackExperience: ["AdaptiveEcomFallbackExperience", "FallbackExperience"], | |
AddCachedVideo: ["AddCachedVideo", "Action", "Session"], | |
AddCachedVideoCommand: ["AddCachedVideoCommand", "Command", "Session"], | |
AddProfile: ["AddProfile", "Action", "Session"], | |
AddToPlaylist: ["AddToPlaylist", "Action", "Session"], | |
AddToPlaylistCommand: ["AddToPlaylistCommand", "Command", "Session"], | |
BackCommand: ["BackCommand", "Command", "Session"], | |
BoxartRenderCanceled: ["BoxartRenderCanceled", "BoxartRenderEnded", "DiscreteEvent"], | |
BoxartRenderFailed: ["BoxartRenderFailed", "BoxartRenderEnded", "DiscreteEvent"], | |
CachedPlay: ["CachedPlay", "Play", "Action", "Session"], | |
CancelCommand: ["CancelCommand", "Command", "Session"], | |
CancelMembership: ["CancelMembership", "Action", "Session"], | |
ChangeValueCommand: ["ChangeValueCommand", "Command", "Session"], | |
CloseApp: ["CloseApp", "Action", "Session"], | |
CloseAppCommand: ["CloseAppCommand", "Command", "Session"], | |
CloseCommand: ["CloseCommand", "Command", "Session"], | |
ConnectWithLineAccount: ["ConnectWithLineAccount", "Action", "Session"], | |
CreateAccount: ["CreateAccount", "Action", "Session"], | |
DeepLinkInput: ["DeepLinkInput", "UserInput"], | |
DeleteProfile: ["DeleteProfile", "Action", "Session"], | |
DirectedGestureInput: ["DirectedGestureInput", "GestureInput", "UserInput"], | |
Download: ["Download", "Action", "Session"], | |
EditPaymentCommand: ["EditPaymentCommand", "Command", "Session"], | |
EditPlanCommand: ["EditPlanCommand", "Command", "Session"], | |
EditProfile: ["EditProfile", "Action", "Session"], | |
EnterFullscreenCommand: ["EnterFullscreenCommand", "Command", "Session"], | |
EnterKidsModeCommand: ["EnterKidsModeCommand", "Command", "Session"], | |
ExitFullscreenCommand: ["ExitFullscreenCommand", "Command", "Session"], | |
ExitKidsModeCommand: ["ExitKidsModeCommand", "Command", "Session"], | |
FastForwardCommand: ["FastForwardCommand", "TrickplayCommand", "Command", "Session"], | |
FillVideoCommand: ["FillVideoCommand", "Command", "Session"], | |
FitVideoCommand: ["FitVideoCommand", "Command", "Session"], | |
ForwardCommand: ["ForwardCommand", "Command", "Session"], | |
GestureInput: ["GestureInput", "UserInput"], | |
HomeCommand: ["HomeCommand", "Command", "Session"], | |
KeyboardInput: ["KeyboardInput", "UserInput"], | |
LolomoDataModel: ["LolomoDataModel", "DataModel"], | |
MobileConnection: ["MobileConnection", "NetworkConnection"], | |
MuteCommand: ["MuteCommand", "Command", "Session"], | |
Navigate: ["Navigate", "Action", "Session"], | |
NavigateBackward: ["NavigateBackward", "Navigate", "Action", "Session"], | |
NavigateForward: ["NavigateForward", "Navigate", "Action", "Session"], | |
NetflixId: ["NetflixId", "ProfileIdentity", "Session"], | |
NotifyUms: ["NotifyUms", "Action", "Session"], | |
PauseCommand: ["PauseCommand", "TrickplayCommand", "Command", "Session"], | |
PauseDownloadCommand: ["PauseDownloadCommand", "Command", "Session"], | |
Play: ["Play", "Action", "Session"], | |
PlayCommand: ["PlayCommand", "Command", "Session"], | |
PlayNextCommand: ["PlayNextCommand", "Command", "Session"], | |
PointerInput: ["PointerInput", "UserInput"], | |
PrepareOnramp: ["PrepareOnramp", "Action", "Session"], | |
PreparePlay: ["PreparePlay", "Action", "Session"], | |
ProcessStateTransition: ["ProcessStateTransition", "Action", "Session"], | |
ProfileGuid: ["ProfileGuid", "ProfileIdentity", "Session"], | |
PushNotificationAcknowledged: ["PushNotificationAcknowledged", "PushNotificationResolved", "DiscreteEvent"], | |
PushNotificationDismissed: ["PushNotificationDismissed", "PushNotificationAcknowledged", "PushNotificationResolved", "DiscreteEvent"], | |
PushNotificationIgnored: ["PushNotificationIgnored", "PushNotificationResolved", "DiscreteEvent"], | |
RedeemGiftCard: ["RedeemGiftCard", "Action", "Session"], | |
RedeemGiftCardCommand: ["RedeemGiftCardCommand", "Command", "Session"], | |
RegisterForPushNotifications: ["RegisterForPushNotifications", "Action", "Session"], | |
RemoveAllCachedVideosCommand: ["RemoveAllCachedVideosCommand", "Command", "Session"], | |
RemoveCachedVideo: ["RemoveCachedVideo", "Action", "Session"], | |
RemoveCachedVideoAndPlayNextCommand: ["RemoveCachedVideoAndPlayNextCommand", "Command", "Session"], | |
RemoveCachedVideoCommand: ["RemoveCachedVideoCommand", "Command", "Session"], | |
RemoveDownloadDevice: ["RemoveDownloadDevice", "Action", "Session"], | |
RemoveFromPlaylist: ["RemoveFromPlaylist", "Action", "Session"], | |
RemoveFromPlaylistCommand: ["RemoveFromPlaylistCommand", "Command", "Session"], | |
RemoveFromViewingActivity: ["RemoveFromViewingActivity", "Action", "Session"], | |
RenderNavigationLevel: ["RenderNavigationLevel", "Action", "Session"], | |
RequestSharedCredentials: ["RequestSharedCredentials", "Action", "Session"], | |
ResumeDownloadCommand: ["ResumeDownloadCommand", "Command", "Session"], | |
RetryDownloadCommand: ["RetryDownloadCommand", "Command", "Session"], | |
RewindCommand: ["RewindCommand", "TrickplayCommand", "Command", "Session"], | |
Search: ["Search", "Action", "Session"], | |
SearchCommand: ["SearchCommand", "Command", "Session"], | |
SearchSuggestionResults: ["SearchSuggestionResults", "DataModel"], | |
SearchSuggestionTitleResults: ["SearchSuggestionTitleResults", "DataModel"], | |
SearchTitleResults: ["SearchTitleResults", "DataModel"], | |
SeekCommand: ["SeekCommand", "TrickplayCommand", "Command", "Session"], | |
SelectCommand: ["SelectCommand", "Command", "Session"], | |
SelectPlan: ["SelectPlan", "Action", "Session"], | |
SelectProfile: ["SelectProfile", "Action", "Session"], | |
SetStarRating: ["SetStarRating", "Action", "Session"], | |
SetThumbRating: ["SetThumbRating", "Action", "Session"], | |
SeveredForVppa: ["SeveredForVppa", "Severed"], | |
SeveredForWebpageUnload: ["SeveredForWebpageUnload", "Severed"], | |
Share: ["Share", "Action", "Session"], | |
ShareCommand: ["ShareCommand", "Command", "Session"], | |
SignIn: ["SignIn", "Action", "Session"], | |
SignInCommand: ["SignInCommand", "Command", "Session"], | |
SignOut: ["SignOut", "Action", "Session"], | |
SignOutCommand: ["SignOutCommand", "Command", "Session"], | |
SignUpCommand: ["SignUpCommand", "Command", "Session"], | |
SkipAheadCommand: ["SkipAheadCommand", "TrickplayCommand", "Command", "Session"], | |
SkipBackCommand: ["SkipBackCommand", "TrickplayCommand", "Command", "Session"], | |
SkipCommand: ["SkipCommand", "Command", "Session"], | |
StartAppExperience: ["StartAppExperience", "Action", "Session"], | |
StartMembership: ["StartMembership", "Action", "Session"], | |
StartMembershipCommand: ["StartMembershipCommand", "Command", "Session"], | |
StartPlay: ["StartPlay", "Action", "Session"], | |
StoreSharedCredentials: ["StoreSharedCredentials", "Action", "Session"], | |
SubmitCommand: ["SubmitCommand", "Command", "Session"], | |
SubmitOnrampResults: ["SubmitOnrampResults", "Action", "Session"], | |
ThrottleSearch: ["ThrottleSearch", "Action", "Session"], | |
UnmuteCommand: ["UnmuteCommand", "Command", "Session"], | |
UnpauseCommand: ["UnpauseCommand", "TrickplayCommand", "Command", "Session"], | |
UpdatePaymentInfo: ["UpdatePaymentInfo", "Action", "Session"], | |
ValidateInput: ["ValidateInput", "Action", "Session"], | |
ValidateMemberId: ["ValidateMemberId", "Action", "Session"], | |
ValidatePin: ["ValidatePin", "Action", "Session"], | |
ViewAccountMenuCommand: ["ViewAccountMenuCommand", "Command", "Session"], | |
ViewAudioSubtitlesSelectorCommand: ["ViewAudioSubtitlesSelectorCommand", "Command", "Session"], | |
ViewCachedVideosCommand: ["ViewCachedVideosCommand", "Command", "Session"], | |
ViewCategoriesCommand: ["ViewCategoriesCommand", "Command", "Session"], | |
ViewDetailsCommand: ["ViewDetailsCommand", "Command", "Session"], | |
ViewEpisodesSelectorCommand: ["ViewEpisodesSelectorCommand", "Command", "Session"], | |
ViewPreviewsCommand: ["ViewPreviewsCommand", "Command", "Session"], | |
ViewProfilesCommand: ["ViewProfilesCommand", "Command", "Session"], | |
ViewSettingsCommand: ["ViewSettingsCommand", "Command", "Session"], | |
ViewTitlesCommand: ["ViewTitlesCommand", "Command", "Session"], | |
VisitorDeviceId: ["VisitorDeviceId", "AccountIdentity", "Session"], | |
VoiceInput: ["VoiceInput", "UserInput"], | |
WatchCreditsCommand: ["WatchCreditsCommand", "Command", "Session"], | |
WifiConnection: ["WifiConnection", "NetworkConnection"], | |
WiredConnection: ["WiredConnection", "NetworkConnection"], | |
"android.SystemBackCommand": ["android.SystemBackCommand", "Command", "Session"], | |
"cs.Call": ["cs.Call", "Action", "Session"], | |
"cs.CallCommand": ["cs.CallCommand", "Command", "Session"], | |
"cs.EndCallCommand": ["cs.EndCallCommand", "Command", "Session"], | |
"edx.AlertsOperation": ["edx.AlertsOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.ApiOperation": ["edx.ApiOperation", "Action", "Session"], | |
"edx.AtlasOperation": ["edx.AtlasOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.ChronosOperation": ["edx.ChronosOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.CommandLineInterface": ["edx.CommandLineInterface", "Action", "Session"], | |
"edx.DashboardsOperation": ["edx.DashboardsOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.ElasticSearchOperation": ["edx.ElasticSearchOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.GitOperation": ["edx.GitOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.HttpRequest": ["edx.HttpRequest", "Action", "Session"], | |
"edx.KeymasterOperation": ["edx.KeymasterOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.MantisOperation": ["edx.MantisOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.NodeQuarkIndexOperation": ["edx.NodeQuarkIndexOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.PagerDutyOperation": ["edx.PagerDutyOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.PrimerIndexOperation": ["edx.PrimerIndexOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.PrimerOperation": ["edx.PrimerOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.RavenOperation": ["edx.RavenOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.SkipperOperation": ["edx.SkipperOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.SpinnakerOperation": ["edx.SpinnakerOperation", "edx.ApiOperation", "Action", "Session"], | |
"edx.TitusOperation": ["edx.TitusOperation", "edx.ApiOperation", "Action", "Session"], | |
"iko.EndCommand": ["iko.EndCommand", "Command", "Session"], | |
"iko.EnterBattleCommand": ["iko.EnterBattleCommand", "Command", "Session"], | |
"iko.Presentation": ["iko.Presentation", "Presentation", "Session"], | |
"ios.DeepLinkInput": ["ios.DeepLinkInput", "UserInput"], | |
"ios.LoadConfigurationService": ["ios.LoadConfigurationService", "Action", "Session"], | |
"ios.LoadDownloadService": ["ios.LoadDownloadService", "Action", "Session"], | |
"ios.LoadIdentityService": ["ios.LoadIdentityService", "Action", "Session"], | |
"ios.LoadNrdService": ["ios.LoadNrdService", "Action", "Session"], | |
"ios.RegisterForPushNotifications": ["ios.RegisterForPushNotifications", "Action", "Session"], | |
"tvui.JankMeasurementReported": ["tvui.JankMeasurementReported", "MeasurementReported", "DiscreteEvent"], | |
"tvui.MetadataDownloadPlayDelay": ["tvui.MetadataDownloadPlayDelay", "tvui.PlayDelay", "Session"], | |
"tvui.PlatformPlayDelay": ["tvui.PlatformPlayDelay", "tvui.PlayDelay", "Session"], | |
"tvui.RequestImeCandidateList": ["tvui.RequestImeCandidateList", "Action", "Session"], | |
"tvui.UiPlayDelay": ["tvui.UiPlayDelay", "tvui.PlayDelay", "Session"], | |
"tvui.VideoPresentationPlayDelay": ["tvui.VideoPresentationPlayDelay", "tvui.PlayDelay", "Session"], | |
"www.ExtendedAreaFocus": ["www.ExtendedAreaFocus", "Focus", "Session"] | |
} | |
} | |
}), require.define("/app/ui.js", function (require, module) { | |
function convertSpeed(result) { | |
var bitsPerS = result.speed, | |
speedMbs = bitsPerS / 1e3 / 1e3 || 0, | |
units = "Mbps"; | |
return 1 > speedMbs ? (speedMbs *= 1e3, units = "Kbps") : speedMbs >= 1e3 && (speedMbs /= 1e3, units = "Gbps"), speedMbs = 9.95 > speedMbs ? (Math.round(10 * speedMbs) / 10).toFixed(1) : 100 > speedMbs ? Math.round(speedMbs) : 10 * Math.round(speedMbs / 10), { | |
speed: speedMbs, | |
units: units | |
} | |
} | |
function convertToMB(result) { | |
var mb = result.bytes / 1e3 / 1e3; | |
return mb = mb > 1 ? 10 > mb ? (Math.round(10 * mb) / 10).toFixed(1) : 10 * Math.round(mb / 10) : (Math.round(100 * mb) / 100).toFixed(2) | |
} | |
function convertLatency(result) { | |
var units = "ms", | |
latency = result.value; | |
return 0 !== latency && !latency && result.latency && (latency = result.latency.value), 0 !== latency && !latency || latency > 1e6 ? { | |
speed: 0, | |
units: units | |
} : (latency >= 1e3 ? (latency = (Math.round(latency / 1e3 * 10) / 10).toFixed(1), units = "s") : latency = Math.round(latency), { | |
speed: latency, | |
units: units | |
}) | |
} | |
var TEST_CONFIG = { | |
showAdvanced: { | |
"default": !1, | |
elem: "always-show-metrics-input" | |
}, | |
measureUploadLatency: { | |
"default": !1, | |
elem: "measure-latency-during-upload" | |
}, | |
minConnections: { | |
"default": 1, | |
testerFunc: function (config) { | |
return config.connections.min | |
}, | |
elem: "min-connections-input" | |
}, | |
maxConnections: { | |
"default": 8, | |
testerFunc: function (config) { | |
return config.connections.max | |
}, | |
elem: "max-connections-input" | |
}, | |
minDuration: { | |
"default": 5, | |
testerFunc: function (config) { | |
return config.duration.min | |
}, | |
elem: "min-duration-input" | |
}, | |
maxDuration: { | |
"default": 30, | |
testerFunc: function (config) { | |
return config.duration.max | |
}, | |
elem: "max-duration-input" | |
}, | |
shouldPersist: { | |
"default": !1, | |
elem: "persist-config-input" | |
} | |
}, | |
ui = function (tester, logger) { | |
function getElement(elementId) { | |
var element; | |
return element || (elemCache[elementId] = element = document.getElementById(elementId)), element | |
} | |
function hideElement(element) { | |
return element.setAttribute("style", "display: none"), element | |
} | |
function showElement(element) { | |
return element.setAttribute("style", "display: block"), element | |
} | |
function removeClass(element, className) { | |
return element.className = element.className.replace(new RegExp("(\\s|^)" + className + "(\\s|$)", "g"), " "), element | |
} | |
function addClass(element, className) { | |
element.className; | |
return hasClass(element, className) || (element.className += " " + className), element | |
} | |
function hasClass(element, className) { | |
var curClass = element.className; | |
return -1 !== curClass.search(new RegExp("(\\s|^)" + className + "(\\s|$)")) | |
} | |
function toggleClass(element, className) { | |
hasClass(element, className) ? removeClass(element, className) : addClass(element, className) | |
} | |
function localizePage(language, localized) { | |
var i, key, elem, localizedElems = document.getElementsByClassName("localized"), | |
alignElems = document.getElementsByClassName("align-container"), | |
localizedLang = localized[language], | |
rightAligned = localizedLang.rightAligned; | |
for (i = 0; i < localizedElems.length; i++) elem = localizedElems[i], key = elem.getAttribute("loc-str"), elem.innerHTML = localizedLang[key]; | |
for (i = 0; i < alignElems.length; ++i) elem = alignElems[i], rightAligned ? addClass(elem, "right-aligned-text") : removeClass(elem, "right-aligned-text") | |
} | |
function setupHelp() { | |
{ | |
var testHelpElem = getElement("test-help-btn"), | |
helpContentElem = getElement("help-content"); | |
getElement("language-selector-container") | |
} | |
utils.addEventListener(testHelpElem, "click", function () { | |
testHelpElem.active ? (removeClass(testHelpElem.children[0], "active"), testHelpElem.active = !1, hideElement(helpContentElem)) : (addClass(testHelpElem.children[0], "active"), testHelpElem.active = !0, showElement(helpContentElem), setTimeout(function () { | |
scrollToElement(testHelpElem) | |
}, 0), logger.logEvent("HelpPresented", { | |
view: "Help" | |
})) | |
}) | |
} | |
function setupLanguageControls() { | |
function addLanguageChangeListener(elem) { | |
utils.addEventListener(elem, "click", function (event) { | |
event.preventDefault(); | |
var language = elem.innerText, | |
langPath = elem.getAttribute("language"); | |
if (window.localized) localizePage(langPath, window.localized), utils.removeEventListener(languageSelectorBtn, toggleSelector), languageSelectorBtn.innerHTML = languageSelectorBtn.innerHTML.replace(languageSelectorBtn.innerText, language), utils.addEventListener(languageSelectorBtn, "click", toggleSelector), curLang = langPath; | |
else { | |
var xhr = utils.getXHR(), | |
path = localizedPath; | |
xhr.onreadystatechange = function () { | |
4 !== this.readyState || 200 !== this.status && 304 !== this.status || (window.localized = JSON.parse(this.responseText), localizePage(langPath, window.localized), utils.removeEventListener(languageSelectorBtn, toggleSelector), languageSelectorBtn.innerHTML = languageSelectorBtn.innerHTML.replace(languageSelectorBtn.innerText, language), utils.addEventListener(languageSelectorBtn, "click", toggleSelector), curLang = langPath) | |
}, (!window.location || "localhost" != window.location.hostname && "https:" !== window.location.protocol) && (path = "https://fast.com" + path), xhr.open("GET", path, !0), xhr.onerror = function (e) {}, logger.logEvent("LanguageChangeStart", { | |
from: curLang, | |
to: langPath | |
}), setTimeout(function () { | |
xhr && xhr.send() | |
}, 0) | |
} | |
}) | |
} | |
function toggleSelector() { | |
var languageSelector = getElement("language-selector"), | |
languageSelectorIcon = getElement("language-selector-icon"); | |
toggleClass(languageSelector, "show"), toggleClass(languageSelectorIcon, "oc-icon-keyboard_arrow_down"), toggleClass(languageSelectorIcon, "oc-icon-keyboard_arrow_up") | |
} | |
var localizedPath = "/localized.json"; | |
window && window.document && window.document.body && (localizedPath = window.document.body.getAttribute("localized")); { | |
var languageSelectorBtn = getElement("language-selector-btn"); | |
getElement("help-content"), getElement("your-speed-message"), getElement("compare-on") | |
} | |
utils.addEventListener(languageSelectorBtn, "click", toggleSelector), utils.addEventListener(document, "click", function (event) { | |
var target = event.target || event.srcElement, | |
languageSelector = getElement("language-selector"); | |
hasClass(target, "dropbtn") || hasClass(languageSelector, "show") && toggleSelector() | |
}); | |
var elem, elemInd, languageOptions = document.querySelectorAll(".language-option"); | |
for (elemInd = 0; elemInd < languageOptions.length; ++elemInd) elem = languageOptions[elemInd], addLanguageChangeListener(elem) | |
} | |
function pause() { | |
tester.isRunning() && tester.stop(), utils.addEventListener(pauseElem, "click", restartTest) | |
} | |
function setupPause() { | |
utils.removeEventListener(pauseElem, "click", restartTest), utils.removeEventListener(pauseElem, "click", pause), utils.addEventListener(pauseElem, "click", pause) | |
} | |
function restartTest() { | |
tester.isRunning() && tester.stop(), setupPause(), reset(), tester.download() | |
} | |
function setupAfterTestActions() { | |
var actionsElem = getElement("after-test-actions"), | |
speedMsg = getElement("your-speed-message"); | |
showElement(actionsElem), showElement(speedMsg), showElement(resultsExplanationElem), showingMoreDetails || showElement(showMoreDetailsBtn) | |
} | |
function reset() { | |
var speedElem = getElement("speed-value"), | |
speedUnitsElem = getElement("speed-units"), | |
speedProgressIndicator = getElement("speed-progress-indicator"), | |
speedProgressIndicatorIcon = getElement("speed-progress-indicator-icon"), | |
actionsElem = getElement("after-test-actions"), | |
unstableResultsElem = getElement("unstable-results-msg"), | |
testErrorElem = getElement("error-results-msg"), | |
speedMsg = getElement("your-speed-message"), | |
infoElem = getElement("test-info-container"), | |
latencyElem = getElement("latency-value"), | |
latencyUnitsElem = getElement("latency-units"), | |
latencyLabelElem = getElement("latency-label"), | |
bufferbloatElem = getElement("bufferbloat-value"), | |
bufferbloatUnitsElem = getElement("bufferbloat-units"), | |
bufferbloatLabelElem = getElement("bufferbloat-label"), | |
bytesDownElem = getElement("down-mb-value"), | |
bytesUpElem = getElement("up-mb-value"), | |
uploadElem = getElement("upload-value"), | |
uploadUnitsElem = getElement("upload-units"), | |
uploadLabelElem = getElement("upload-label"); | |
hideElement(actionsElem), hideElement(unstableResultsElem), hideElement(testErrorElem), hideElement(speedMsg), hideElement(showMoreDetailsBtn), speedElem.innerHTML = 0, speedUnitsElem.innerHTML = " ", removeClass(detailsElem, "succeeded"), removeClass(latencyContainer, "succeeded"), removeClass(speedElem, "succeeded"), removeClass(speedElem, "failed"), removeClass(speedUnitsElem, "succeeded"), removeClass(speedUnitsElem, "failed"), removeClass(speedProgressIndicator, "succeeded"), removeClass(speedProgressIndicator, "stopped"), removeClass(speedProgressIndicator, "failed"), addClass(speedProgressIndicator, "in-progress"), removeClass(speedProgressIndicatorIcon, "oc-icon-refresh"), addClass(speedProgressIndicatorIcon, "oc-icon-pause"), removeClass(infoElem, "succeeded"), removeClass(infoElem, "failed"), removeClass(latencyElem, "succeeded"), removeClass(latencyElem, "failed"), removeClass(latencyUnitsElem, "succeeded"), removeClass(latencyUnitsElem, "failed"), removeClass(latencyLabelElem, "succeeded"), removeClass(latencyLabelElem, "failed"), removeClass(bufferbloatElem, "succeeded"), removeClass(bufferbloatElem, "failed"), removeClass(bufferbloatUnitsElem, "succeeded"), removeClass(bufferbloatUnitsElem, "failed"), removeClass(bufferbloatLabelElem, "succeeded"), removeClass(bufferbloatLabelElem, "failed"), removeClass(uploadElem, "succeeded"), removeClass(uploadElem, "failed"), removeClass(uploadUnitsElem, "succeeded"), removeClass(uploadUnitsElem, "failed"), removeClass(uploadLabelElem, "succeeded"), removeClass(uploadLabelElem, "failed"), latencyElem.innerHTML = "0", bufferbloatElem.innerHTML = "0", uploadElem.innerHTML = "0", bytesDownElem.innerHTML = "0", bytesUpElem.innerHTML = "0" | |
} | |
function scrollToElement(elem) { | |
function findPos(obj) { | |
var curtop = 0; | |
if (obj.offsetParent) { | |
do curtop += obj.offsetTop; while (obj == obj.offsetParent); | |
return curtop | |
} | |
} | |
elem.scrollIntoView ? elem.scrollIntoView() : window.scroll(0, findPos(elem)) | |
} | |
function popupCenter(url, title, w, h) { | |
var dualScreenLeft = void 0 !== window.screenLeft ? window.screenLeft : screen.left, | |
dualScreenTop = void 0 !== window.screenTop ? window.screenTop : screen.top, | |
width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width, | |
height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height, | |
left = width / 2 - w / 2 + dualScreenLeft, | |
top = height / 2 - h / 2 + dualScreenTop, | |
newWindow = window.open(url, title, "scrollbars=yes, width=" + w + ", height=" + h + ", top=" + top + ", left=" + left); | |
newWindow.focus && newWindow.focus() | |
} | |
var fbShareEventListener, twShareEventListener, fbShareBtn, twShareBtn, showMoreDetailsBtn, testAgainBtn, resultsExplanationElem, configContainerElem, detailsElem, latencyContainer, pauseElem, downloadResult, showingMoreDetails, config, elemCache = {}, | |
utils = require("./utils"), | |
events = require("./event").events, | |
curLang = document.body.getAttribute("language"), | |
canUseLocalStorage = !1; | |
try { | |
window && window.localStorage && (window.localStorage.__test__ = 1, canUseLocalStorage = !0) | |
} catch (e) {} | |
var showMoreDetails = function () { | |
if (logger.logEvent("AdvancedMetricsPresented"), tester.isRunning()) { | |
var speedProgressIndicator = getElement("speed-progress-indicator"), | |
speedProgressIndicatorIcon = getElement("speed-progress-indicator-icon"); | |
removeClass(speedProgressIndicator, "succeeded"), removeClass(speedProgressIndicator, "stopped"), removeClass(speedProgressIndicator, "failed"), addClass(speedProgressIndicator, "in-progress"), removeClass(speedProgressIndicatorIcon, "oc-icon-refresh"), addClass(speedProgressIndicatorIcon, "oc-icon-pause"), setupPause() | |
} | |
showingMoreDetails = !0, hideElement(showMoreDetailsBtn), showElement(detailsElem) | |
}, | |
hideMoreDetails = function () { | |
hideElement(detailsElem), showElement(showMoreDetailsBtn), showingMoreDetails = !1 | |
}, | |
updateClientInfo = function (event) { | |
var locationsStr, curLocation, i, client = event.client, | |
locationsMap = {}; | |
for (getElement("user-ip").innerHTML = client.ip, getElement("user-isp").innerHTML = client.isp ? client.isp.replace(/_/g, " ") : "", getElement("user-location").innerHTML = client.location.city + ", " + client.location.country, curLocation = client.servers[0].location.city + ", " + client.servers[0].location.country, locationsStr = curLocation, locationsMap[curLocation] = !0, i = 1; i < Math.min(client.servers.length, 3); ++i) curLocation = client.servers[i].location.city + ", " + client.servers[i].location.country, locationsMap[curLocation] || (locationsStr += "  |  " + curLocation, locationsMap[curLocation] = !0); | |
getElement("server-locations").innerHTML = locationsStr | |
}, | |
showConfig = function () { | |
logger.logEvent("SettingsPresented"), renderConfig(config), hideElement(detailsElem), showElement(configContainerElem) | |
}, | |
cancelConfig = function () { | |
hideElement(configContainerElem), config.showAdvanced === !0 || "true" === config.showAdvanced ? showMoreDetails() : hideMoreDetails() | |
}, | |
applyConfig = function () { | |
var currentConfig = getTestSettings(tester), | |
shouldPersist = getElement(TEST_CONFIG.shouldPersist.elem).checked; | |
for (var name in TEST_CONFIG) { | |
var testerValue = getElement(TEST_CONFIG[name].elem); | |
testerValue && (config[name] = "boolean" == typeof TEST_CONFIG[name].default ? testerValue.checked : testerValue.value, shouldPersist && canUseLocalStorage && (window.localStorage[name] = String(config[name]))) | |
} | |
try { | |
applyTesterConfig(config), hideElement(configContainerElem), config.showAdvanced === !0 || "true" === config.showAdvanced ? showMoreDetails() : hideMoreDetails(), restartTest() | |
} catch (e) { | |
alert(e.message), renderConfig(currentConfig) | |
} | |
}, | |
resetConfig = function () { | |
var defaultConfig = {}; | |
for (var name in TEST_CONFIG) defaultConfig[name] = TEST_CONFIG[name].default, canUseLocalStorage && delete window.localStorage[name]; | |
applyTesterConfig(defaultConfig), config = getTestSettings(tester), renderConfig(config) | |
}, | |
renderConfig = function (config) { | |
for (var name in TEST_CONFIG) { | |
var testerValue = getElement(TEST_CONFIG[name].elem); | |
"boolean" == typeof TEST_CONFIG[name].default ? testerValue.checked = config[name] === !0 || "true" === config[name] : testerValue.value = config[name] | |
} | |
}, | |
getTestSettings = function (tester) { | |
var config = {}, | |
testerConfig = tester.config(); | |
if (canUseLocalStorage) | |
for (var name in TEST_CONFIG) { | |
var testerValue; | |
TEST_CONFIG[name].testerFunc && (testerValue = TEST_CONFIG[name].testerFunc(testerConfig)), config[name] = window.localStorage[name] || testerValue || TEST_CONFIG[name].default | |
} | |
return config | |
}, | |
applyTesterConfig = function (config) { | |
var testerConfig = {}; | |
return testerConfig.connections = { | |
min: parseInt(config.minConnections), | |
max: parseInt(config.maxConnections) | |
}, testerConfig.duration = { | |
min: parseInt(config.minDuration), | |
max: parseInt(config.maxDuration) | |
}, tester.setConfig(testerConfig), testerConfig | |
}, | |
that = { | |
onProgress: function (result) { | |
var updateMetric = function (testType) { | |
var speedElemId, speedUnitId, speedData, speed, speedElem, speedUnitsElem, bytesElemId, bytesElem, convertFunc; | |
"download" == testType ? (speedElemId = "speed-value", bytesElemId = "down-mb-value", speedUnitId = "speed-units", convertFunc = convertSpeed) : "upload" == testType ? (speedElemId = "upload-value", bytesElemId = "up-mb-value", speedUnitId = "upload-units", convertFunc = convertSpeed) : "latency" == testType ? (speedElemId = "latency-value", speedUnitId = "latency-units", convertFunc = convertLatency) : "bufferbloat" == testType && (speedElemId = "bufferbloat-value", speedUnitId = "bufferbloat-units", convertFunc = convertLatency), speedElem = getElement(speedElemId), speedUnitsElem = getElement(speedUnitId), bytesElemId && (bytesElem = getElement(bytesElemId), bytesElem.innerHTML = convertToMB(result)), speedData = convertFunc(result), speed = speedData.speed + speedData.units, speedElem && (speedElem.innerHTML = speedData.speed), speedUnitsElem && (speedUnitsElem.innerHTML = speedData.units) | |
}; | |
updateMetric(result.testType), "download" === result.testType && updateMetric("bufferbloat"), "true" !== config.measureUploadLatency && config.measureUploadLatency !== !0 || "upload" !== result.testType || updateMetric("bufferbloat") | |
}, | |
onComplete: function (result) { | |
var className, speedElemId, speedUnitId, speedLabelId, speedElem, speedUnitsElem, speedLabelElem, unstableResultsElem, testErrorElem, testType = result.testType, | |
speedProgressIndicator = getElement("speed-progress-indicator"), | |
speedProgressIndicatorIcon = getElement("speed-progress-indicator-icon"), | |
showAfterTestActions = !1; | |
if (downloadResult = result, "download" == testType ? (speedElemId = "speed-value", speedUnitId = "speed-units", speedLabelId = "speed-value", showAfterTestActions = !0, convertFunc = convertSpeed) : "upload" == testType ? (speedElemId = "upload-value", speedUnitId = "upload-units", speedLabelId = "upload-label", convertFunc = convertSpeed) : "latency" == testType ? (speedElemId = "latency-value", speedUnitId = "latency-units", speedLabelId = "latency-label", convertFunc = convertLatency) : "bufferbloat" == testType && (speedElemId = "bufferbloat-value", speedUnitId = "bufferbloat-units", speedLabelId = "bufferbloat-label", convertFunc = convertLatency), speedElem = getElement(speedElemId), speedUnitsElem = getElement(speedUnitId), speedLabelElem = getElement(speedLabelId), "success" === result.result ? (that.onProgress(result), result.stable ? className = "succeeded" : (className = "failed", unstableResultsElem = getElement("unstable-results-msg"), showElement(unstableResultsElem))) : "stop" !== result.result ? (testErrorElem = getElement("error-results-msg"), showElement(testErrorElem), className = "failed") : (className = "stopped", showAfterTestActions = !1), showAfterTestActions && setupAfterTestActions(), ("download" === testType && !showingMoreDetails || "upload" === testType || "stop" === result.result) && (removeClass(speedProgressIndicator, "in-progress"), removeClass(speedProgressIndicatorIcon, "oc-icon-pause"), addClass(speedProgressIndicatorIcon, "oc-icon-refresh"), addClass(speedProgressIndicator, className), utils.removeEventListener(testAgainBtn, "click", pause), utils.addEventListener(testAgainBtn, "click", restartTest)), addClass(speedElem, className), addClass(speedUnitsElem, className), addClass(speedLabelElem, className), logger.flush(), "download" === testType && "success" === result.result) { | |
var speedData = convertSpeed(result); | |
fbShareBtn = getElement("share-on-facebook-link"), twShareBtn = getElement("share-on-twitter-link"), fbShareEventListener && utils.removeEventListener(fbShareBtn, "click", fbShareEventListener), twShareEventListener && utils.removeEventListener(fbShareBtn, "click", twShareEventListener), fbShareEventListener = function () { | |
var location, targetUrl, facebookUrl, hostname, protocol, messageContentElem = getElement("share-msg"), | |
shareDescription = messageContentElem.getAttribute("my-speed") + speedData.speed + speedData.units + ". " + messageContentElem.getAttribute("yours-speed"), | |
shareTitle = messageContentElem.getAttribute("share-on-fb"); | |
location = window.location, hostname = location.hostname, protocol = location.protocol, "http:" !== location.protocol && "https:" !== location.protocol && (hostname = "fast.com", protocol = "https:"), targetUrl = protocol + "//" + hostname + "/" + curLang + "/share/" + speedData.speed + speedData.units + ".html", facebookUrl = "https://www.facebook.com/sharer/sharer.php?u=" + encodeURI(targetUrl) + "&title=" + encodeURI("FAST speed test") + "&description=" + encodeURI(shareDescription), logger.logEvent("FacebookShare", { | |
url: targetUrl | |
}), popupCenter(facebookUrl, shareTitle, 400, 400) | |
}, twShareEventListener = function () { | |
var location, targetUrl, twitterUrl, hostname, protocol, messageContentElem = getElement("share-msg"), | |
shareDescription = messageContentElem.getAttribute("my-speed") + " " + speedData.speed + speedData.units + ". " + messageContentElem.getAttribute("yours-speed"), | |
shareTitle = messageContentElem.getAttribute("share-on-tw"); | |
location = window.location, hostname = location.hostname, protocol = location.protocol, "http:" !== location.protocol && "https:" !== location.protocol && (hostname = "fast.com", protocol = "https:"), targetUrl = protocol + "//" + hostname + "/" + curLang + "/share/" + speedData.speed + speedData.units + ".html", twitterUrl = "https://twitter.com/intent/tweet?url=" + encodeURI(targetUrl) + "&text=" + encodeURI(shareDescription), logger.logEvent("TwitterShare", { | |
url: targetUrl | |
}), popupCenter(twitterUrl, shareTitle, 400, 400) | |
}, utils.addEventListener(fbShareBtn, "click", fbShareEventListener), utils.addEventListener(twShareBtn, "click", twShareEventListener); | |
var infoElem = getElement("test-info-container"), | |
showInfo = function (uploadResult) { | |
tester.off(events.END, showInfo), "stop" !== uploadResult.result && (addClass(infoElem, "succeeded"), addClass(detailsElem, "succeeded")) | |
}, | |
doUpload = function (latencyResult) { | |
tester.off(events.END, doUpload), "stop" !== latencyResult.result && (addClass(latencyContainer, "succeeded"), !downloadResult || void 0 !== downloadResult.latency && null !== downloadResult.latency || (downloadResult.latency = downloadResult.latency || { | |
value: "NA" | |
}), that.onComplete({ | |
testType: "bufferbloat", | |
stable: !0, | |
value: downloadResult.latency.value, | |
result: "success" | |
}), tester.on(events.END, showInfo), tester.upload()) | |
}; | |
tester.on(events.END, doUpload), tester.latency() | |
} | |
}, | |
setupEvents: function () { | |
var testConfigBtn, cancelConfigBtn, applyConfigBtn, resetConfigBtn; | |
config = getTestSettings(tester); | |
try { | |
applyTesterConfig(config) | |
} catch (e) { | |
resetConfig() | |
} | |
renderConfig(config), pauseElem = getElement("speed-progress-indicator"), fbShareBtn = getElement("share-on-facebook-link"), twShareBtn = getElement("share-on-twitter-link"), testAgainBtn = getElement("speed-progress-indicator"), showMoreDetailsBtn = getElement("show-more-details-link"), cancelConfigBtn = getElement("cancel-config"), applyConfigBtn = getElement("apply-config"), resetConfigBtn = getElement("reset-config"), testConfigBtn = getElement("settings-link"), resultsExplanationElem = getElement("test-context-container"), configContainerElem = getElement("test-config-container"), detailsElem = getElement("extra-details-container"), latencyContainer = getElement("latency-container"), setupHelp(), setupPause(), setupLanguageControls(), utils.addEventListener(showMoreDetailsBtn, "click", showMoreDetails), utils.addEventListener(testConfigBtn, "click", showConfig), utils.addEventListener(cancelConfigBtn, "click", cancelConfig), utils.addEventListener(applyConfigBtn, "click", applyConfig), utils.addEventListener(resetConfigBtn, "click", resetConfig), tester.on(events.URL_REQUEST_END, updateClientInfo), (config.showAdvanced === !0 || "true" === config.showAdvanced) && showMoreDetails() | |
} | |
}; | |
return that | |
}; | |
module.exports = ui | |
}), require.define("/app/viewport-units-buggyfill.js", function (require, module, exports) { | |
! function (root, factory) { | |
"use strict"; | |
"function" == typeof define && define.amd ? define([], factory) : "object" == typeof exports ? module.exports = factory() : root.viewportUnitsBuggyfill = factory() | |
}(this, function () { | |
"use strict"; | |
function debounce(func, wait) { | |
var timeout; | |
return function () { | |
var context = this, | |
args = arguments, | |
callback = function () { | |
func.apply(context, args) | |
}; | |
clearTimeout(timeout), timeout = setTimeout(callback, wait) | |
} | |
} | |
function initialize(initOptions) { | |
if (!initialized) { | |
if (options = initOptions || {}, options.isMobileSafari = isMobileSafari, options.isBadStockAndroid = isBadStockAndroid, !isMobileSafari && !isBadStockAndroid && !isOperaMini) return window.console, window.console, { | |
init: function () {} | |
}; | |
initialized = !0, styleNode = document.createElement("style"), styleNode.id = "patched-viewport", document.head.appendChild(styleNode); | |
var _refresh = debounce(refresh, options.refreshDebounceWait || 100); | |
window.addEventListener("orientationchange", _refresh, !0), window.addEventListener("pageshow", _refresh, !0), refresh() | |
} | |
} | |
function updateStyles() { | |
styleNode.textContent = getReplacedViewportUnits(), styleNode.parentNode.appendChild(styleNode) | |
} | |
function refresh() { | |
initialized && (findProperties(), setTimeout(function () { | |
updateStyles() | |
}, 1)) | |
} | |
function processStylesheet(ss) { | |
try { | |
if (!ss.cssRules) return | |
} catch (e) { | |
if ("SecurityError" !== e.name) throw e; | |
return | |
} | |
for (var rules = [], i = 0; i < ss.cssRules.length; i++) { | |
var rule = ss.cssRules[i]; | |
rules.push(rule) | |
} | |
return rules | |
} | |
function findProperties() { | |
return declarations = [], forEach.call(document.styleSheets, function (sheet) { | |
var cssRules = processStylesheet(sheet); | |
cssRules && "patched-viewport" !== sheet.ownerNode.id && (sheet.media && sheet.media.mediaText && window.matchMedia && !window.matchMedia(sheet.media.mediaText).matches || forEach.call(cssRules, findDeclarations)) | |
}), declarations | |
} | |
function findDeclarations(rule) { | |
if (7 === rule.type) { | |
var value; | |
try { | |
value = rule.cssText | |
} catch (e) { | |
return | |
} | |
return viewportUnitExpression.lastIndex = 0, void(viewportUnitExpression.test(value) && declarations.push([rule, null, value])) | |
} | |
if (!rule.style) { | |
if (!rule.cssRules) return; | |
return void forEach.call(rule.cssRules, function (_rule) { | |
findDeclarations(_rule) | |
}) | |
} | |
forEach.call(rule.style, function (name) { | |
var value = rule.style.getPropertyValue(name); | |
rule.style.getPropertyPriority(name) && (value += " !important"), viewportUnitExpression.lastIndex = 0, viewportUnitExpression.test(value) && declarations.push([rule, name, value]) | |
}) | |
} | |
function getReplacedViewportUnits() { | |
dimensions = getViewport(); | |
var open, close, css = [], | |
buffer = []; | |
return declarations.forEach(function (item) { | |
var _item = overwriteDeclaration.apply(null, item), | |
_open = _item.selector.length ? _item.selector.join(" {\n") + " {\n" : "", | |
_close = new Array(_item.selector.length + 1).join("\n}"); | |
return _open && _open === open ? (_open && !open && (open = _open, close = _close), void buffer.push(_item.content)) : (buffer.length && (css.push(open + buffer.join("\n") + close), buffer.length = 0), void(_open ? (open = _open, close = _close, buffer.push(_item.content)) : (css.push(_item.content), open = null, close = null))) | |
}), buffer.length && css.push(open + buffer.join("\n") + close), isOperaMini && css.push("* { content: normal !important; }"), css.join("\n\n") | |
} | |
function overwriteDeclaration(rule, name, value) { | |
var _value, _selectors = []; | |
_value = value.replace(viewportUnitExpression, replaceValues), name && (_selectors.push(rule.selectorText), _value = name + ": " + _value + ";"); | |
for (var _rule = rule.parentRule; _rule;) _selectors.unshift("@media " + _rule.media.mediaText), _rule = _rule.parentRule; | |
return { | |
selector: _selectors, | |
content: _value | |
} | |
} | |
function replaceValues(match, number, unit) { | |
var _base = dimensions[unit], | |
_number = parseFloat(number) / 100; | |
return _number * _base + "px" | |
} | |
function getViewport() { | |
var vh = window.innerHeight, | |
vw = window.innerWidth; | |
return { | |
vh: vh, | |
vw: vw, | |
vmax: Math.max(vw, vh), | |
vmin: Math.min(vw, vh) | |
} | |
} | |
var options, dimensions, declarations, styleNode, initialized = !1, | |
userAgent = window.navigator.userAgent, | |
viewportUnitExpression = /([+-]?[0-9.]+)(vh|vw|vmin|vmax)/g, | |
forEach = [].forEach, | |
isOperaMini = userAgent.indexOf("Opera Mini") > -1, | |
isMobileSafari = /(iPhone|iPod|iPad).+AppleWebKit/i.test(userAgent) && function () { | |
var iOSversion = userAgent.match(/OS (\d)/); | |
return iOSversion && iOSversion.length > 1 && parseInt(iOSversion[1]) < 10 | |
}(), | |
isBadStockAndroid = function () { | |
var isAndroid = userAgent.indexOf(" Android ") > -1; | |
if (!isAndroid) return !1; | |
var isStockAndroid = userAgent.indexOf("Version/") > -1; | |
if (!isStockAndroid) return !1; | |
var versionNumber = parseFloat((userAgent.match("Android ([0-9.]+)") || [])[1]); | |
return 4.4 >= versionNumber | |
}(); | |
return { | |
version: "0.6.0", | |
findProperties: findProperties, | |
getCss: getReplacedViewportUnits, | |
init: initialize, | |
refresh: refresh | |
} | |
}) | |
}), require.define("/app/browser_test.js", function (require) { | |
function startTest() { | |
tester.download() | |
} | |
function flushLogger(force) { | |
!force && tester && tester.isRunning() || logger.flush() | |
} | |
var aggregator, stopper, testerFactory, tester, utils, events, requester, logging, logger, ui, apiEndpoint, loggingEndpoint; | |
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { | |
var l, errorData; | |
errorData = { | |
message: errorMsg, | |
url: url, | |
line: lineNumber, | |
column: column | |
}, errorObj && (errorData.error = { | |
name: errorObj.name, | |
stack: errorObj.stack, | |
data: errorObj | |
}), logger && (l = logger.getLogger(), l.logEvent("ExceptionOccurred", errorData), flushLogger(!0)) | |
}, aggregator = require("./aggregator/stableMovingAverage")(5), stopper = require("./stopper/stableDeltaMeasurementsStopper")({ | |
minDuration: 7, | |
maxDuration: 30, | |
stabilityDelta: 2, | |
minStableMeasurements: 6, | |
measureLatency: !1 | |
}), testerFactory = require("./tester"), utils = require("./utils"), events = require("./event").events, requester = require("./requester/xhr"), logging = require("./logger"), apiEndpoint = "api.fast.com/netflix/speedtest/v2", loggingEndpoint = "https://ichnaea-web.netflix.com/cl2", window.console || (console = { | |
log: utils.dummy | |
}), utils.polyfillObjectKeys(), tester = testerFactory(requester, { | |
collectAfterComplete: !1, | |
duration: { | |
min: 5, | |
max: 30 | |
}, | |
connections: { | |
min: 1, | |
max: 8 | |
}, | |
maxAttempts: 10, | |
aggregator: aggregator, | |
measureLatency: !0, | |
getTestOcasParams: { | |
https: !0, | |
endpoint: apiEndpoint, | |
token: "YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm", | |
urlCount: 5 | |
}, | |
stopper: stopper, | |
progressFrequencyMs: 150 | |
}), logger = logging(tester, { | |
url: loggingEndpoint | |
}); | |
var testLogger = logger.getLogger(); | |
if (testLogger.logEvent("PageVisit"), flushLogger(!0), setInterval(flushLogger, 2e3), window.location.pathname.indexOf("/share/") >= 0) { | |
var l = testLogger, | |
data = { | |
referrer: document.referrer, | |
url: window.location.href | |
}; | |
l.logEvent("ShareLinkClicked", data), flushLogger(!0), setTimeout(function () { | |
window.location.href = location.protocol + "//" + location.hostname | |
}, 5) | |
} | |
ui = require("./ui")(tester, testLogger), tester.on(events.START, function () { | |
tester.on(events.END, ui.onComplete).on(events.PROGRESS, ui.onProgress) | |
}).on(events.END, function () { | |
tester.off(events.PROGRESS, ui.onProgress).off(events.END, ui.onComplete) | |
}), logger.startLogging(), ui.setupEvents(), startTest(), require("./viewport-units-buggyfill").init() | |
}), require("/app/browser_test.js") | |
}(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment