clarity-decode
Version:
An analytics library that uses web page interactions to generate aggregated insights
770 lines (762 loc) • 32.4 kB
JavaScript
var version$1 = "0.8.59";
var version = version$1;
function decode$5(tokens) {
var time = tokens[0];
var event = tokens[1];
switch (event) {
case 25 /* Data.Event.Ping */:
var ping = { gap: tokens[2] };
return { time: time, event: event, data: ping };
case 35 /* Data.Event.Limit */:
var limit = { check: tokens[2] };
return { time: time, event: event, data: limit };
case 24 /* Data.Event.Custom */:
var custom = { key: tokens[2], value: tokens[3] };
return { time: time, event: event, data: custom };
case 3 /* Data.Event.Upgrade */:
var upgrade = { key: tokens[2] };
return { time: time, event: event, data: upgrade };
case 2 /* Data.Event.Upload */:
var upload = { sequence: tokens[2], attempts: tokens[3], status: tokens[4] };
return { time: time, event: event, data: upload };
case 0 /* Data.Event.Metric */:
var m = 2; // Start from 3rd index since first two are used for time & event
var metrics = {};
while (m < tokens.length) {
metrics[tokens[m++]] = tokens[m++];
}
return { time: time, event: event, data: metrics };
case 1 /* Data.Event.Dimension */:
var d = 2; // Start from 3rd index since first two are used for time & event
var dimensions = {};
while (d < tokens.length) {
dimensions[tokens[d++]] = tokens[d++];
}
return { time: time, event: event, data: dimensions };
case 36 /* Data.Event.Summary */:
var s = 2; // Start from 3rd index since first two are used for time & event
var summary = {};
while (s < tokens.length) {
var key = tokens[s++];
var values = tokens[s++];
summary[key] = [];
for (var i = 0; i < values.length - 1; i += 2) {
summary[key].push([values[i], values[i + 1]]);
}
}
return { time: time, event: event, data: summary };
case 4 /* Data.Event.Baseline */:
var tokenLength = tokens.length;
var baselineData = {
visible: tokens[2],
docWidth: tokens[3],
docHeight: tokens[4],
screenWidth: tokens[5],
screenHeight: tokens[6],
scrollX: tokens[7],
scrollY: tokens[8],
pointerX: tokens[9],
pointerY: tokens[10],
activityTime: tokens[11],
scrollTime: tokenLength > 12 ? tokens[12] : null,
pointerTime: tokenLength > 13 ? tokens[13] : null,
moveX: tokenLength > 14 ? tokens[14] : null,
moveY: tokenLength > 15 ? tokens[15] : null,
moveTime: tokenLength > 16 ? tokens[16] : null,
downX: tokenLength > 17 ? tokens[17] : null,
downY: tokenLength > 18 ? tokens[18] : null,
downTime: tokenLength > 19 ? tokens[19] : null,
upX: tokenLength > 20 ? tokens[20] : null,
upY: tokenLength > 21 ? tokens[21] : null,
upTime: tokenLength > 22 ? tokens[22] : null,
pointerPrevX: tokenLength > 23 ? tokens[23] : null,
pointerPrevY: tokenLength > 24 ? tokens[24] : null,
pointerPrevTime: tokenLength > 25 ? tokens[25] : null,
modules: tokenLength > 26 ? tokens[26] : null,
};
return { time: time, event: event, data: baselineData };
case 34 /* Data.Event.Variable */:
var v = 2; // Start from 3rd index since first two are used for time & event
var variables = {};
while (v < tokens.length) {
variables[tokens[v++]] = typeof tokens[v + 1] == "string" /* Constant.String */ ? [tokens[v++]] : tokens[v++];
}
return { time: time, event: event, data: variables };
case 40 /* Data.Event.Extract */:
var e = 2; // Start from 3rd index since first two are used for time & event
var extract = {};
while (e < tokens.length) {
// For backward compatibility from version 0.7.4
if (typeof (tokens[e + 1]) == "string" /* Constant.String */) {
extract[tokens[e++]] = tokens[e++];
}
else {
var key = tokens[e++];
var values = tokens[e++];
extract[key] = [];
for (var i = 0; i < values.length - 1; i += 2) {
extract[key][values[i]] = values[i + 1];
}
}
}
return { time: time, event: event, data: extract };
case 47 /* Data.Event.Consent */:
var consent = { source: tokens[2], ad_Storage: tokens[3], analytics_Storage: tokens[4] };
return { time: time, event: event, data: consent };
}
return null;
}
function envelope(tokens) {
return {
version: tokens[0],
sequence: tokens[1],
start: tokens[2],
duration: tokens[3],
projectId: tokens[4],
userId: tokens[5],
sessionId: tokens[6],
pageNum: tokens[7],
upload: tokens[8],
end: tokens[9],
applicationPlatform: tokens[10],
url: tokens[11]
};
}
function decode$4(tokens) {
var time = tokens[0];
var event = tokens[1];
switch (event) {
case 31 /* Data.Event.ScriptError */:
var scriptError = {
message: tokens[2],
line: tokens[3],
column: tokens[4],
stack: tokens[5],
source: tokens[6]
};
return { time: time, event: event, data: scriptError };
case 33 /* Data.Event.Log */:
var log = {
code: tokens[2],
name: tokens[3],
message: tokens[4],
stack: tokens[5],
severity: tokens[6]
};
return { time: time, event: event, data: log };
case 41 /* Data.Event.Fraud */:
var fraud = {
id: tokens[2],
target: tokens[3],
checksum: tokens[4]
};
return { time: time, event: event, data: fraud };
}
return null;
}
function decode$3(tokens) {
var time = tokens[0];
var event = tokens[1];
switch (event) {
case 13 /* Data.Event.MouseDown */:
case 14 /* Data.Event.MouseUp */:
case 12 /* Data.Event.MouseMove */:
case 15 /* Data.Event.MouseWheel */:
case 16 /* Data.Event.DoubleClick */:
case 17 /* Data.Event.TouchStart */:
case 20 /* Data.Event.TouchCancel */:
case 18 /* Data.Event.TouchEnd */:
case 19 /* Data.Event.TouchMove */:
var pointerData = {
target: tokens[2],
x: tokens[3],
y: tokens[4],
id: tokens[5],
};
return { time: time, event: event, data: pointerData };
case 9 /* Data.Event.Click */:
case 48 /* Data.Event.ContextMenu */:
var clickHashes = tokens[12].split("." /* Data.Constant.Dot */);
var clickData = {
target: tokens[2],
x: tokens[3],
y: tokens[4],
eX: tokens[5],
eY: tokens[6],
button: tokens[7],
reaction: tokens[8],
context: tokens[9],
text: tokens[10],
link: tokens[11],
hash: clickHashes[0],
hashBeta: clickHashes.length > 0 ? clickHashes[1] : null,
trust: tokens.length > 13 ? tokens[13] : 1 /* Data.BooleanFlag.True */,
isFullText: tokens.length > 14 ? tokens[14] : null,
w: tokens.length > 15 ? tokens[15] : 0,
h: tokens.length > 16 ? tokens[16] : 0,
tag: tokens.length > 17 ? tokens[17] : null,
class: tokens.length > 18 ? tokens[18] : null,
id: tokens.length > 19 ? tokens[19] : null,
source: tokens.length > 20 ? tokens[20] : null,
};
return { time: time, event: event, data: clickData };
case 38 /* Data.Event.Clipboard */:
var clipData = { target: tokens[2], action: tokens[3] };
return { time: time, event: event, data: clipData };
case 11 /* Data.Event.Resize */:
var resizeData = { width: tokens[2], height: tokens[3] };
return { time: time, event: event, data: resizeData };
case 27 /* Data.Event.Input */:
var inputData = { target: tokens[2], value: tokens[3], trust: tokens.length > 4 ? tokens[4] : 1 /* Data.BooleanFlag.True */ };
return { time: time, event: event, data: inputData };
case 21 /* Data.Event.Selection */:
var selectionData = {
start: tokens[2],
startOffset: tokens[3],
end: tokens[4],
endOffset: tokens[5]
};
return { time: time, event: event, data: selectionData };
case 42 /* Data.Event.Change */:
var changeData = {
target: tokens[2],
type: tokens[3],
value: tokens[4],
checksum: tokens[5]
};
return { time: time, event: event, data: changeData };
case 39 /* Data.Event.Submit */:
var submitData = {
target: tokens[2]
};
return { time: time, event: event, data: submitData };
case 10 /* Data.Event.Scroll */:
var scrollData = {
target: tokens[2],
x: tokens[3],
y: tokens[4],
top: tokens.length > 5 ? tokens[5] : null,
bottom: tokens.length > 6 ? tokens[6] : null,
trust: tokens.length > 7 ? tokens[7] : 1 /* Data.BooleanFlag.True */
};
return { time: time, event: event, data: scrollData };
case 22 /* Data.Event.Timeline */:
var timelineHashes = tokens[3].split("." /* Data.Constant.Dot */);
var timelineData = {
type: tokens[2],
hash: timelineHashes[0 /* Layout.Selector.Alpha */],
x: tokens[4],
y: tokens[5],
reaction: tokens[6],
context: tokens[7],
hashBeta: timelineHashes.length > 0 ? timelineHashes[1 /* Layout.Selector.Beta */] : null
};
return { time: time, event: event, data: timelineData };
case 28 /* Data.Event.Visibility */:
var visibleData = void 0;
if (typeof tokens[2] === "number") {
visibleData = { visible: tokens[2] };
}
else {
visibleData = { visible: tokens[2] === "visible" ? 1 /* Data.BooleanFlag.True */ : 0 /* Data.BooleanFlag.False */ };
}
return { time: time, event: event, data: visibleData };
case 50 /* Data.Event.Focus */:
var focusData = { focused: tokens[2] };
return { time: time, event: event, data: focusData };
case 26 /* Data.Event.Unload */:
var unloadData = { name: tokens[2], persisted: tokens[3] };
return { time: time, event: event, data: unloadData };
}
return null;
}
var AverageWordLength = 6;
var Space = " ";
function decode$2(tokens) {
var time = tokens[0];
var event = tokens[1];
switch (event) {
case 8 /* Data.Event.Document */:
var documentData = { width: tokens[2], height: tokens[3] };
return { time: time, event: event, data: documentData };
case 7 /* Data.Event.Region */:
var regionData = [];
// From 0.6.15 we send each reach update in an individual event. This allows us to include time with it.
// To keep it backward compatible (<= 0.6.14), we look for multiple regions in the same event. This works both with newer and older payloads.
// In future, we can update the logic to look deterministically for only 3 fields and remove the for loop.
var increment = void 0;
for (var i = 2; i < tokens.length; i += increment) {
var region = void 0;
if (typeof (tokens[i + 2]) == "number" /* Constant.Number */) {
region = {
id: tokens[i],
interaction: tokens[i + 1],
visibility: tokens[i + 2],
name: tokens[i + 3]
};
increment = 4;
}
else {
var state = tokens[i + 1];
region = {
id: tokens[i],
// For backward compatibility before 0.6.24 - where region states were sent as a single enum
// we convert the value into the two states tracked after 0.6.24
interaction: state >= 16 /* Interaction.None */ ? state : 16 /* Interaction.None */,
visibility: state <= 13 /* RegionVisibility.ScrolledToEnd */ ? state : 0 /* RegionVisibility.Rendered */,
name: tokens[i + 2]
};
increment = 3;
}
regionData.push(region);
}
return { time: time, event: event, data: regionData };
case 45 /* Data.Event.StyleSheetAdoption */:
var styleSheetAdoptionData = {
id: tokens[2],
operation: tokens[3],
newIds: tokens[4]
};
return { time: time, event: event, data: styleSheetAdoptionData };
case 46 /* Data.Event.StyleSheetUpdate */:
var styleSheetUpdateData = {
id: tokens[2],
operation: tokens[3],
cssRules: tokens[4]
};
return { time: time, event: event, data: styleSheetUpdateData };
case 44 /* Data.Event.Animation */:
var animationData = {
id: tokens[2],
operation: tokens[3],
keyFrames: tokens[4],
timing: tokens[5],
timeline: tokens[6],
targetId: tokens[7]
};
return { time: time, event: event, data: animationData };
case 5 /* Data.Event.Discover */:
case 6 /* Data.Event.Mutation */:
case 43 /* Data.Event.Snapshot */:
var lastType = null;
var node = [];
var tagIndex = 0;
var domData = [];
for (var i = 2; i < tokens.length; i++) {
var token = tokens[i];
var type = typeof (token);
switch (type) {
case "number":
if (type !== lastType && lastType !== null) {
domData.push(process(node, tagIndex));
node = [];
tagIndex = 0;
}
node.push(token);
tagIndex++;
break;
case "string":
node.push(token);
break;
case "object":
var subtoken = token[0];
var subtype = typeof (subtoken);
switch (subtype) {
case "number":
for (var _i = 0, _a = token; _i < _a.length; _i++) {
var t = _a[_i];
node.push(tokens.length > t ? tokens[t] : null);
}
break;
}
}
lastType = type;
}
// Process last node
domData.push(process(node, tagIndex));
return { time: time, event: event, data: domData };
case 51 /* Data.Event.CustomElement */:
var customElement = { name: tokens[2] };
return { time: time, event: event, data: customElement };
}
return null;
}
function process(node, tagIndex) {
// For backward compatibility, only extract the tag even if position is available as part of the tag name
// And, continue computing position in the visualization library from decoded payload.
var tag = node[tagIndex] ? node[tagIndex].split("~")[0] : node[tagIndex];
var output = {
id: Math.abs(node[0]),
parent: tagIndex > 1 ? node[1] : null,
previous: tagIndex > 2 ? node[2] : null,
tag: tag
};
var masked = node[0] < 0;
var hasAttribute = false;
var attributes = {};
var value = null;
for (var i = tagIndex + 1; i < node.length; i++) {
// Explicitly convert the token into a string value
var token = node[i].toString();
var keyIndex = token.indexOf("=");
var firstChar = token[0];
var lastChar = token[token.length - 1];
if (i === (node.length - 1) && output.tag === "STYLE") {
value = token;
}
else if (output.tag !== "*T" /* Layout.Constant.TextTag */ && lastChar === ">" && keyIndex === -1) ;
else if (output.tag !== "*T" /* Layout.Constant.TextTag */ && firstChar === "#" /* Layout.Constant.Hash */ && keyIndex === -1) {
var parts = token.substr(1).split("." /* Layout.Constant.Period */);
if (parts.length === 2) {
output.width = num(parts[0]) / 100 /* Data.Setting.BoxPrecision */;
output.height = num(parts[1]) / 100 /* Data.Setting.BoxPrecision */;
}
}
else if (output.tag !== "*T" /* Layout.Constant.TextTag */ && keyIndex > 0) {
hasAttribute = true;
var k = token.substr(0, keyIndex);
var v = token.substr(keyIndex + 1);
attributes[k] = v;
}
else if (output.tag === "*T" /* Layout.Constant.TextTag */) {
value = masked ? unmask(token) : token;
}
}
if (hasAttribute) {
output.attributes = attributes;
}
if (value) {
output.value = value;
}
return output;
}
function num(input) {
return input ? parseInt(input, 36) : null;
}
function unmask(value) {
var trimmed = value.trim();
if (trimmed.length > 0 && trimmed.indexOf(Space) === -1) {
var length_1 = num(trimmed);
if (length_1 > 0) {
var quotient = Math.floor(length_1 / AverageWordLength);
var remainder = length_1 % AverageWordLength;
var output = Array(remainder + 1).join("\u2022" /* Data.Constant.Mask */);
for (var i = 0; i < quotient; i++) {
output += (i === 0 && remainder === 0 ? "\u2022" /* Data.Constant.Mask */ : Space) + Array(AverageWordLength).join("\u2022" /* Data.Constant.Mask */);
}
return output;
}
}
return value;
}
function decode$1(tokens) {
var time = tokens[0];
var event = tokens[1];
switch (event) {
case 29 /* Data.Event.Navigation */:
var navigationData = {
fetchStart: tokens[2],
connectStart: tokens[3],
connectEnd: tokens[4],
requestStart: tokens[5],
responseStart: tokens[6],
responseEnd: tokens[7],
domInteractive: tokens[8],
domComplete: tokens[9],
loadEventStart: tokens[10],
loadEventEnd: tokens[11],
redirectCount: tokens[12],
size: tokens[13],
type: tokens[14],
protocol: tokens[15],
encodedSize: tokens[16],
decodedSize: tokens[17]
};
return { time: time, event: event, data: navigationData };
}
return null;
}
function decode(input) {
var json = JSON.parse(input);
var envelope$1 = envelope(json.e);
var timestamp = Date.now();
var payload = { timestamp: timestamp, envelope: envelope$1 };
// Sort encoded events by time to simplify summary computation
// It's possible for individual events to be out of order, dependent on how they are buffered on the client
// E.g. scroll events are queued internally before they are sent over the wire.
// By comparison, events like resize & click are sent out immediately.
var encoded = json.p ? json.a.concat(json.p) : json.a;
encoded = encoded.sort(function (a, b) { return a[0] - b[0]; });
// Check if the incoming version is compatible with the current running code
// We do an exact match for the major version and minor version.
// For patch, we are backward and forward compatible with up to version change.
var jsonVersion = parseVersion(payload.envelope.version);
var codeVersion = parseVersion(version);
if (jsonVersion.major !== codeVersion.major ||
Math.abs(jsonVersion.minor - codeVersion.minor) > 1) {
console.warn("Invalid version. Actual: ".concat(payload.envelope.version, " | Expected: ").concat(version, " (+/- 1) | ").concat(input.substr(0, 250)));
}
for (var _i = 0, encoded_1 = encoded; _i < encoded_1.length; _i++) {
var entry = encoded_1[_i];
switch (entry[1]) {
case 4 /* Data.Event.Baseline */:
if (payload.baseline === undefined) {
payload.baseline = [];
}
payload.baseline.push(decode$5(entry));
break;
case 25 /* Data.Event.Ping */:
if (payload.ping === undefined) {
payload.ping = [];
}
payload.ping.push(decode$5(entry));
break;
case 35 /* Data.Event.Limit */:
if (payload.limit === undefined) {
payload.limit = [];
}
payload.limit.push(decode$5(entry));
break;
case 3 /* Data.Event.Upgrade */:
if (payload.upgrade === undefined) {
payload.upgrade = [];
}
payload.upgrade.push(decode$5(entry));
break;
case 0 /* Data.Event.Metric */:
if (payload.metric === undefined) {
payload.metric = [];
}
var metric = decode$5(entry);
// It's not possible to accurately include the byte count of the payload within the same payload
// The value we get from clarity-js lags behind by a payload. To work around that,
// we increment the bytes from the incoming payload at decode time.
metric.data[2 /* Data.Metric.TotalBytes */] = input.length;
payload.metric.push(metric);
break;
case 1 /* Data.Event.Dimension */:
if (payload.dimension === undefined) {
payload.dimension = [];
}
payload.dimension.push(decode$5(entry));
break;
case 36 /* Data.Event.Summary */:
if (payload.summary === undefined) {
payload.summary = [];
}
payload.summary.push(decode$5(entry));
break;
case 24 /* Data.Event.Custom */:
if (payload.custom === undefined) {
payload.custom = [];
}
payload.custom.push(decode$5(entry));
break;
case 34 /* Data.Event.Variable */:
if (payload.variable === undefined) {
payload.variable = [];
}
payload.variable.push(decode$5(entry));
break;
case 2 /* Data.Event.Upload */:
if (payload.upload === undefined) {
payload.upload = [];
}
payload.upload.push(decode$5(entry));
break;
case 13 /* Data.Event.MouseDown */:
case 14 /* Data.Event.MouseUp */:
case 12 /* Data.Event.MouseMove */:
case 15 /* Data.Event.MouseWheel */:
case 16 /* Data.Event.DoubleClick */:
case 17 /* Data.Event.TouchStart */:
case 20 /* Data.Event.TouchCancel */:
case 18 /* Data.Event.TouchEnd */:
case 19 /* Data.Event.TouchMove */:
if (payload.pointer === undefined) {
payload.pointer = [];
}
payload.pointer.push(decode$3(entry));
break;
case 9 /* Data.Event.Click */:
if (payload.click === undefined) {
payload.click = [];
}
var clickEntry = decode$3(entry);
payload.click.push(clickEntry);
break;
case 48 /* Data.Event.ContextMenu */:
if (payload.contextMenu === undefined) {
payload.contextMenu = [];
}
var contextMenuEntry = decode$3(entry);
payload.contextMenu.push(contextMenuEntry);
break;
case 38 /* Data.Event.Clipboard */:
if (payload.clipboard === undefined) {
payload.clipboard = [];
}
var clipEntry = decode$3(entry);
payload.clipboard.push(clipEntry);
break;
case 10 /* Data.Event.Scroll */:
if (payload.scroll === undefined) {
payload.scroll = [];
}
payload.scroll.push(decode$3(entry));
break;
case 11 /* Data.Event.Resize */:
if (payload.resize === undefined) {
payload.resize = [];
}
payload.resize.push(decode$3(entry));
break;
case 21 /* Data.Event.Selection */:
if (payload.selection === undefined) {
payload.selection = [];
}
payload.selection.push(decode$3(entry));
break;
case 42 /* Data.Event.Change */:
if (payload.change === undefined) {
payload.change = [];
}
var changeEntry = decode$3(entry);
payload.change.push(changeEntry);
break;
case 39 /* Data.Event.Submit */:
if (payload.submit === undefined) {
payload.submit = [];
}
var submitEntry = decode$3(entry);
payload.submit.push(submitEntry);
break;
case 22 /* Data.Event.Timeline */:
if (payload.timeline === undefined) {
payload.timeline = [];
}
payload.timeline.push(decode$3(entry));
break;
case 27 /* Data.Event.Input */:
if (payload.input === undefined) {
payload.input = [];
}
payload.input.push(decode$3(entry));
break;
case 26 /* Data.Event.Unload */:
if (payload.unload === undefined) {
payload.unload = [];
}
payload.unload.push(decode$3(entry));
break;
case 28 /* Data.Event.Visibility */:
if (payload.visibility === undefined) {
payload.visibility = [];
}
payload.visibility.push(decode$3(entry));
break;
case 50 /* Data.Event.Focus */:
if (payload.focus === undefined) {
payload.focus = [];
}
payload.focus.push(decode$3(entry));
break;
case 37 /* Data.Event.Box */:
/* Deprecated - Intentionally, no-op. For backward compatibility. */
break;
case 7 /* Data.Event.Region */:
if (payload.region === undefined) {
payload.region = [];
}
payload.region.push(decode$2(entry));
break;
case 5 /* Data.Event.Discover */:
case 6 /* Data.Event.Mutation */:
case 43 /* Data.Event.Snapshot */:
case 45 /* Data.Event.StyleSheetAdoption */:
case 46 /* Data.Event.StyleSheetUpdate */:
case 44 /* Data.Event.Animation */:
if (payload.dom === undefined) {
payload.dom = [];
}
payload.dom.push(decode$2(entry));
break;
case 8 /* Data.Event.Document */:
if (payload.doc === undefined) {
payload.doc = [];
}
payload.doc.push(decode$2(entry));
break;
case 31 /* Data.Event.ScriptError */:
if (payload.script === undefined) {
payload.script = [];
}
payload.script.push(decode$4(entry));
break;
case 33 /* Data.Event.Log */:
if (payload.log === undefined) {
payload.log = [];
}
payload.log.push(decode$4(entry));
break;
case 41 /* Data.Event.Fraud */:
if (payload.fraud === undefined) {
payload.fraud = [];
}
payload.fraud.push(decode$4(entry));
break;
case 29 /* Data.Event.Navigation */:
if (payload.navigation === undefined) {
payload.navigation = [];
}
payload.navigation.push(decode$1(entry));
break;
case 30 /* Data.Event.Connection */:
case 32 /* Data.Event.ImageError */:
/* Deprecated - Intentionally, no-op. For backward compatibility. */
break;
case 40 /* Data.Event.Extract */:
if (payload.extract === undefined) {
payload.extract = [];
}
payload.extract.push(decode$5(entry));
break;
case 47 /* Data.Event.Consent */:
if (payload.consent === undefined) {
payload.consent = [];
}
payload.consent.push(decode$5(entry));
break;
case 51 /* Data.Event.CustomElement */:
if (payload.customElement === undefined) {
payload.customElement = [];
}
payload.customElement.push(decode$2(entry));
break;
default:
if (typeof entry[1] === "number" && entry[1] < 200) {
console.error("No handler for Event: ".concat(JSON.stringify(entry)));
}
break;
}
}
return payload;
}
function parseVersion(ver) {
var output = { major: 0, minor: 0, patch: 0, beta: 0 };
var parts = ver.split(".");
if (parts.length === 3) {
var subparts = parts[2].split("-b");
output.major = parseInt(parts[0], 10);
output.minor = parseInt(parts[1], 10);
if (subparts.length === 2) {
output.patch = parseInt(subparts[0], 10);
output.beta = parseInt(subparts[1], 10);
}
else {
output.patch = parseInt(parts[2], 10);
}
}
return output;
}
export { decode };