@esri/telemetry
Advanced tools
@@ -7,2 +7,12 @@ # Change Log | ||
## [1.1.0] - 06/05/2017 | ||
### Added | ||
* Support for tracking workflows | ||
### Changed | ||
* Send events to AWS as `workflow`, `pageView`, `other` | ||
### Fixed | ||
* All attributes are coerced to string before being sent to Amazon | ||
## [1.0.3] - 05/01/2017 | ||
@@ -26,4 +36,5 @@ ### Added | ||
[1.1.0]: https://github.com/arcgis/telemetry.js/compare/v1.1.0...v1.0.3 | ||
[1.0.3]: https://github.com/arcgis/telemetry.js/compare/v1.0.2...v1.0.3 | ||
[1.0.2]: https://github.com/arcgis/telemetry.js/compare/v1.0.2...v1.0.1 | ||
[1.0.1]: https://github.com/arcgis/telemetry.js/compare/v1.0.0...v1.0.1 |
{ | ||
"name": "@esri/telemetry", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "A JavaScript Implementation of the ArcGIS Telemetry Specification", | ||
@@ -5,0 +5,0 @@ "main": "dist/telemetry.js", |
@@ -700,3 +700,3 @@ define('telemetry', function () { 'use strict'; | ||
return { | ||
eventType: setType(event), | ||
eventType: event.eventType || 'other', | ||
timestamp: new Date().toISOString(), | ||
@@ -716,20 +716,11 @@ session: { | ||
function setType() { | ||
var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var type = event.workflow || event.process || event.category; | ||
if (type) { | ||
return type; | ||
} else if (event.error) { | ||
return 'error'; | ||
} else { | ||
return 'other'; | ||
} | ||
} | ||
function extractAttributes(event) { | ||
var attributes = _extends({}, event); | ||
delete attributes.workflow; | ||
METRICS.forEach(function (metric) { | ||
return delete attributes[metric]; | ||
}); | ||
Object.keys(attributes).forEach(function (attr) { | ||
attributes[attr] = attributes[attr] ? attributes[attr].toString() : 'null'; | ||
}); | ||
return attributes; | ||
@@ -759,7 +750,7 @@ } | ||
function createClientContext(client_id, app) { | ||
function createClientContext(clientId, app) { | ||
// eslint-disable-line | ||
return JSON.stringify({ | ||
client: { | ||
client_id: client_id, | ||
client_id: clientId, | ||
app_title: app.name, | ||
@@ -876,2 +867,3 @@ app_version_name: app.version || 'unknown' | ||
this.trackers = []; | ||
this.workflows = {}; | ||
if (options.amazon) { | ||
@@ -928,2 +920,59 @@ var amazon = new Amazon(options.amazon); | ||
} | ||
}, { | ||
key: 'startWorkflow', | ||
value: function startWorkflow(name) { | ||
this.workflows[name] = { | ||
name: name, | ||
start: Date.now(), | ||
steps: [] | ||
}; | ||
this._logWorkflow({ name: name, step: 'start' }); | ||
} | ||
}, { | ||
key: 'stepWorkflow', | ||
value: function stepWorkflow(name, step, details) { | ||
this._logWorkflow({ name: name, step: step, details: details }); | ||
} | ||
}, { | ||
key: 'endWorkflow', | ||
value: function endWorkflow(name) { | ||
this._logWorkflow({ name: name, step: 'finish' }); | ||
delete this.workflows[name]; | ||
} | ||
}, { | ||
key: 'cancelWorkflow', | ||
value: function cancelWorkflow(name) { | ||
this._logWorkflow({ name: name, step: 'cancel' }); | ||
delete this.workflows[name]; | ||
} | ||
}, { | ||
key: '_logWorkflow', | ||
value: function _logWorkflow(options) { | ||
/* | ||
const workflow = { | ||
name: 'add layer to map', | ||
step: 'start', | ||
details: 'some details about the step' | ||
} | ||
*/ | ||
options = preProcess(options); | ||
var workflow = this.workflows[options.name]; | ||
if (!workflow) { | ||
this.startWorkflow(options.name); | ||
} | ||
workflow.steps.push(options.step); | ||
workflow.duration = (Date.now() - workflow.start) / 1000; | ||
var track = { | ||
eventType: 'workflow', | ||
category: options.name, | ||
action: options.step, | ||
label: options.details, | ||
user: options.user, | ||
duration: workflow.duration | ||
}; | ||
this.logEvent(track); | ||
} | ||
}]); | ||
@@ -930,0 +979,0 @@ return Telemetry; |
@@ -1,2 +0,2 @@ | ||
define("telemetry",function(){"use strict";function e(e,t){var n=new XMLHttpRequest;n.addEventListener("load",function(){t(n.responseText)}),n.open(e.method,e.url),Object.keys(e.headers).forEach(function(t){n.setRequestHeader(t,e.headers[t])}),n.send(e.body)}function t(e,t){var r=window.localStorage.getItem(D);if(r&&(r=JSON.parse(r)),r&&Date.now()/1e3<r.Expiration)return t(r);n(e,function(e){window.localStorage.setItem(D,JSON.stringify(e)),t(e)})}function n(t,n){var i=j({},K);i.headers["X-Amz-Target"]="AWSCognitoIdentityService.GetId",i.body=JSON.stringify({IdentityPoolId:t}),e(i,function(e){r(JSON.parse(e),n)})}function r(t,n){var r=j({},K);r.headers["X-Amz-Target"]="AWSCognitoIdentityService.GetCredentialsForIdentity",r.body=JSON.stringify({IdentityId:t.IdentityId}),e(r,function(e){var t=JSON.parse(e);n(t.Credentials)})}function i(){return{session:o(),id:a()}}function o(){var e=void 0,t=void 0,n=window.localStorage.getItem(M);return n&&(n=JSON.parse(n),Date.now()<n.expiration&&(e=n)),e||(t=!0,e=s()),e.expiration=Date.now()+N,window.localStorage.setItem(M,JSON.stringify(e)),t&&(e.new=!0),e}function a(){var e=window.localStorage.getItem(R);return e||(e=c(),window.localStorage.setItem(R,e)),e}function s(){return{id:Math.floor(17592186044416*(1+Math.random())).toString(16),startTimestamp:(new Date).toISOString()}}function c(){return u()+u()+"-"+u()+"-"+u()+"-"+u()+"-"+u()+u()+u()}function u(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}function h(e,t){var n={host:t.uri.host,"content-type":e.config.defaultContentType,accept:e.config.defaultAcceptType,"x-amz-date":g(t.signDate)};t.request.method=t.request.method.toUpperCase(),t.request.body?t.payload=t.request.body:t.request.data&&e.payloadSerializer?t.payload=e.payloadSerializer(t.request.data):delete n["content-type"],t.request.headers=w(n,Object.keys(t.request.headers||{}).reduce(function(e,n){return e[n.toLowerCase()]=t.request.headers[n],e},{})),t.sortedHeaderKeys=Object.keys(t.request.headers).sort(),t.request.headers["content-type"]&&(t.request.headers["content-type"]=t.request.headers["content-type"].split(";")[0]),"object"===C(t.request.params)&&w(t.uri.queryParams,t.request.params)}function f(e,t){t.signedHeaders=t.sortedHeaderKeys.map(function(e){return e.toLowerCase()}).join(";"),t.canonicalRequest=String(t.request.method).toUpperCase()+"\n"+encodeURI(t.uri.path)+"\n"+Object.keys(t.uri.queryParams).sort().map(function(e){return encodeURIComponent(e)+"="+encodeURIComponent(t.uri.queryParams[e])}).join("&")+"\n"+t.sortedHeaderKeys.map(function(e){return e.toLocaleLowerCase()+":"+t.request.headers[e]}).join("\n")+"\n\n"+t.signedHeaders+"\n"+e.hasher.hash(t.payload?t.payload:"")}function l(e,t){t.credentialScope=[g(t.signDate,!0),e.config.region,e.config.service,"aws4_request"].join("/"),t.stringToSign="AWS4-HMAC-SHA256\n"+g(t.signDate)+"\n"+t.credentialScope+"\n"+e.hasher.hash(t.canonicalRequest)}function d(e,t){var n=e.hasher.hmac,r=n(n(n(n("AWS4"+e.config.secretAccessKey,g(t.signDate,!0),{hexOutput:!1}),e.config.region,{hexOutput:!1,textInput:!1}),e.config.service,{hexOutput:!1,textInput:!1}),"aws4_request",{hexOutput:!1,textInput:!1});t.signature=n(r,t.stringToSign,{textInput:!1})}function p(e,t){t.authorization="AWS4-HMAC-SHA256 Credential="+e.config.accessKeyId+"/"+t.credentialScope+", SignedHeaders="+t.signedHeaders+", Signature="+t.signature}function g(e,t){var n=e.toISOString().replace(/[:\-]|\.\d{3}/g,"").substr(0,17);return t?n.substr(0,8):n}function y(){return function(e){return JSON.stringify(e)}}function v(){function e(e){return/^\??(.*)$/.exec(e)[1].split("&").reduce(function(e,t){return t=/^(.+)=(.*)$/.exec(t),t&&(e[t[1]]=t[2]),e},{})}var t=document?document.createElement("a"):{};return function(n){return t.href=n,{protocol:t.protocol,host:t.host.replace(/^(.*):((80)|(443))$/,"$1"),path:("/"!==t.pathname.charAt(0)?"/":"")+t.pathname,queryParams:e(t.search)}}}function m(){return{hash:function(e,t){t=w({hexOutput:!0,textInput:!0},t);var n=L.SHA256(e);return t.hexOutput?n.toString(L.enc.Hex):n},hmac:function(e,t,n){n=w({hexOutput:!0,textInput:!0},n);var r=L.HmacSHA256(t,e,{asBytes:!0});return n.hexOutput?r.toString(L.enc.Hex):r}}}function w(e){return[].slice.call(arguments,1).forEach(function(t){t&&"object"===(void 0===t?"undefined":C(t))&&Object.keys(t).forEach(function(n){var r=t[n];void 0!==r&&(null!==r&&"object"===(void 0===r?"undefined":C(r))?(e[n]=Array.isArray(r)?[]:{},w(e[n],r)):e[n]=r)})}),e}function S(e,t){if(void 0===e||!e)throw new Error(t)}function b(e){var t=i().session;return{eventType:"pageView",timestamp:(new Date).toISOString(),session:{id:t.id,startTimestamp:t.startTimestamp},attributes:{referrer:document.referrer,hostname:window.location.hostname,path:window.location.pathname||e},metrics:{}}}function _(e){var t=i().session;return{eventType:A(e),timestamp:(new Date).toISOString(),session:{id:t.id,startTimestamp:t.startTimestamp},attributes:j({referrer:document.referrer,hostname:window.location.hostname,path:window.location.pathname},I(e)),metrics:x(e)}}function A(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.workflow||e.process||e.category;return t||(e.error?"error":"other")}function I(e){var t=j({},e);return U.forEach(function(e){return delete t[e]}),t}function x(e){var t={};return U.forEach(function(n){t[n]=e[n]}),t}function O(e,t){var n={region:"us-east-1",service:"mobileanalytics",accessKeyId:e.AccessKeyId,secretAccessKey:e.SecretKey,sessionToken:e.SessionToken};return new F(n).sign(t)}function k(e,t){return JSON.stringify({client:{client_id:e,app_title:t.name,app_version_name:t.version||"unknown"},services:{mobile_analytics:{app_id:t.id}}})}function E(n,r,o){var a=i();n=Array.isArray(n)?n:[n];var s=z(n);t(r,function(t){s.headers=O(t,s),s.headers["x-amz-Client-Context"]=k(a.id,o),e(s,function(e){e&&console.error(JSON.parse(e))})})}function z(e){return{url:arguments.length>1&&void 0!==arguments[1]?arguments[1]:W,method:"POST",body:JSON.stringify({events:e})}}function H(e){window.ga?window.ga(function(){e(window.ga.getAll())}):console.log(new Error("Google Analytics trackers not available"))}function q(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r={hitType:"event",eventCategory:e.category||"none",eventAction:e.action,eventLabel:e.label};return Object.keys(t).forEach(function(n){r["dimension"+t[n]]=e[n]}),Object.keys(n).forEach(function(t){r["metric"+n[t]]=e[t]}),r}function T(e){var t=j({},e);return t.user&&(t.user=G(t.user)),t}var C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},B=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},P=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),j=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},D="TELEMETRY_COGNITO_CREDENTIALS",K={method:"POST",url:"https://cognito-identity.us-east-1.amazonaws.com/",headers:{"Content-type":"application/x-amz-json-1.1"}},N=18e5,M="TELEMETRY_SESSION",R="TELEMETRY_CLIENT_ID",L=function(e,t){var n={},r=n.lib={},i=function(){},o=r.Base={extend:function(e){i.prototype=this;var t=new i;return e&&t.mixIn(e),t.hasOwnProperty("init")||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},a=r.WordArray=o.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=void 0!=t?t:4*e.length},toString:function(e){return(e||c).stringify(this)},concat:function(e){var t=this.words,n=e.words,r=this.sigBytes;if(e=e.sigBytes,this.clamp(),r%4)for(var i=0;i<e;i++)t[r+i>>>2]|=(n[i>>>2]>>>24-i%4*8&255)<<24-(r+i)%4*8;else if(n.length>65535)for(i=0;i<e;i+=4)t[r+i>>>2]=n[i>>>2];else t.push.apply(t,n);return this.sigBytes+=e,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=e.ceil(n/4)},clone:function(){var e=o.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var n=[],r=0;r<t;r+=4)n.push(4294967296*e.random()|0);return new a.init(n,t)}}),s=n.enc={},c=s.Hex={stringify:function(e){var t=e.words;e=e.sigBytes;for(var n=[],r=0;r<e;r++){var i=t[r>>>2]>>>24-r%4*8&255;n.push((i>>>4).toString(16)),n.push((15&i).toString(16))}return n.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r+=2)n[r>>>3]|=parseInt(e.substr(r,2),16)<<24-r%8*4;return new a.init(n,t/2)}},u=s.Latin1={stringify:function(e){var t=e.words;e=e.sigBytes;for(var n=[],r=0;r<e;r++)n.push(String.fromCharCode(t[r>>>2]>>>24-r%4*8&255));return n.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r++)n[r>>>2]|=(255&e.charCodeAt(r))<<24-r%4*8;return new a.init(n,t)}},h=s.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},f=r.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new a.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=h.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var n=this._data,r=n.words,i=n.sigBytes,o=this.blockSize,s=i/(4*o),s=t?e.ceil(s):e.max((0|s)-this._minBufferSize,0);if(t=s*o,i=e.min(4*t,i),t){for(var c=0;c<t;c+=o)this._doProcessBlock(r,c);c=r.splice(0,t),n.sigBytes-=i}return new a.init(c,i)},clone:function(){var e=o.clone.call(this);return e._data=this._data.clone(),e},_minBufferSize:0});r.Hasher=f.extend({cfg:o.extend(),init:function(e){this.cfg=this.cfg.extend(e),this.reset()},reset:function(){f.reset.call(this),this._doReset()},update:function(e){return this._append(e),this._process(),this},finalize:function(e){return e&&this._append(e),this._doFinalize()},blockSize:16,_createHelper:function(e){return function(t,n){return new e.init(n).finalize(t)}},_createHmacHelper:function(e){return function(t,n){return new l.HMAC.init(e,n).finalize(t)}}});var l=n.algo={};return n}(Math);!function(e){for(var t=L,n=t.lib,r=n.WordArray,i=n.Hasher,n=t.algo,o=[],a=[],s=function(e){return 4294967296*(e-(0|e))|0},c=2,u=0;u<64;){var h;e:{h=c;for(var f=e.sqrt(h),l=2;l<=f;l++)if(!(h%l)){h=!1;break e}h=!0}h&&(u<8&&(o[u]=s(e.pow(c,.5))),a[u]=s(e.pow(c,1/3)),u++),c++}var d=[],n=n.SHA256=i.extend({_doReset:function(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],o=n[2],s=n[3],c=n[4],u=n[5],h=n[6],f=n[7],l=0;l<64;l++){if(l<16)d[l]=0|e[t+l];else{var p=d[l-15],g=d[l-2];d[l]=((p<<25|p>>>7)^(p<<14|p>>>18)^p>>>3)+d[l-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+d[l-16]}p=f+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&u^~c&h)+a[l]+d[l],g=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&i^r&o^i&o),f=h,h=u,u=c,c=s+p|0,s=o,o=i,i=r,r=p+g|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+o|0,n[3]=n[3]+s|0,n[4]=n[4]+c|0,n[5]=n[5]+u|0,n[6]=n[6]+h|0,n[7]=n[7]+f|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return n[i>>>5]|=128<<24-i%32,n[14+(i+64>>>9<<4)]=e.floor(r/4294967296),n[15+(i+64>>>9<<4)]=r,t.sigBytes=4*n.length,this._process(),this._hash},clone:function(){var e=i.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=i._createHelper(n),t.HmacSHA256=i._createHmacHelper(n)}(Math),function(){var e=L,t=e.enc.Utf8;e.algo.HMAC=e.lib.Base.extend({init:function(e,n){e=this._hasher=new e.init,"string"==typeof n&&(n=t.parse(n));var r=e.blockSize,i=4*r;n.sigBytes>i&&(n=e.finalize(n)),n.clamp();for(var o=this._oKey=n.clone(),a=this._iKey=n.clone(),s=o.words,c=a.words,u=0;u<r;u++)s[u]^=1549556828,c[u]^=909522486;o.sigBytes=a.sigBytes=i,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var t=this._hasher;return e=t.finalize(e),t.reset(),t.finalize(this._oKey.clone().concat(e))}})}();var J={region:"eu-west-1",service:"execute-api",defaultContentType:"application/json",defaultAcceptType:"application/json",payloadSerializerFactory:y,uriParserFactory:v,hasherFactory:m},F=function(){function e(t){B(this,e),this.config=w({},J,t),this.payloadSerializer=this.config.payloadSerializer||this.config.payloadSerializerFactory(),this.uriParser=this.config.uriParserFactory(),this.hasher=this.config.hasherFactory(),S(this.config.accessKeyId,"AwsSigner requires AWS AccessKeyID"),S(this.config.secretAccessKey,"AwsSigner requires AWS SecretAccessKey")}return P(e,[{key:"sign",value:function(e,t){var n={request:w({},e),signDate:t||new Date,uri:this.uriParser(e.url)};return h(this,n),f(this,n),l(this,n),d(this,n),p(this,n),{Accept:n.request.headers.accept,Authorization:n.authorization,"Content-Type":n.request.headers["content-type"],"x-amz-date":n.request.headers["x-amz-date"],"x-amz-security-token":this.config.sessionToken||void 0}}}]),e}(),U=["size","duration"],W="https://mobileanalytics.us-east-1.amazonaws.com/2014-06-05/events",$=function(){function e(t){B(this,e),this.name="amazon",j(this,t),i().session.new&&this.logEvent({category:"_session.start"})}return P(e,[{key:"logPageView",value:function(e){E(b(e),this.userPoolID,this.app)}},{key:"logEvent",value:function(e){E(_(e),this.userPoolID,this.app)}}]),e}(),V=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};B(this,e),this.name="google",j(this,t)}return P(e,[{key:"logPageView",value:function(e){H(function(t){t.forEach(function(t){t.send("pageview",e||window.location.pathname)})})}},{key:"logEvent",value:function(e){var t=q(e,this.dimensions,this.metrics);H(function(e){e.forEach(function(e){e.send(t)})})}}]),e}(),G=function(e){return L.SHA256(e).toString(L.enc.Hex)};return function(){function e(t){if(B(this,e),this.trackers=[],t.amazon){var n=new $(t.amazon);this.trackers.push(n)}if(t.google){var r=new V(t.google);this.trackers.push(r)}this.trackers.length||console.error(new Error("No trackers configured"))}return P(e,[{key:"logPageView",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=T(t);return!this.trackers.length||this.disabled?(this.disabled||console.error(new Error("Page view was not logged because no trackers are configured.")),!1):(this.trackers.forEach(function(t){try{t.logPageView(e,n)}catch(e){console.error(t.name+" tracker failed to log page view.",e)}}),!0)}},{key:"logEvent",value:function(e){var t=T(e);return!this.trackers.length||this.disabled?(this.disabled||console.error(new Error("Event was not logged because no trackers are configured.")),!1):(this.trackers.forEach(function(e){try{e.logEvent(t)}catch(t){console.error(e.name+" tracker failed to log event",t)}}),!0)}}]),e}()}); | ||
define("telemetry",function(){"use strict";function e(e,t){var n=new XMLHttpRequest;n.addEventListener("load",function(){t(n.responseText)}),n.open(e.method,e.url),Object.keys(e.headers).forEach(function(t){n.setRequestHeader(t,e.headers[t])}),n.send(e.body)}function t(e,t){var r=window.localStorage.getItem(j);if(r&&(r=JSON.parse(r)),r&&Date.now()/1e3<r.Expiration)return t(r);n(e,function(e){window.localStorage.setItem(j,JSON.stringify(e)),t(e)})}function n(t,n){var i=P({},D);i.headers["X-Amz-Target"]="AWSCognitoIdentityService.GetId",i.body=JSON.stringify({IdentityPoolId:t}),e(i,function(e){r(JSON.parse(e),n)})}function r(t,n){var r=P({},D);r.headers["X-Amz-Target"]="AWSCognitoIdentityService.GetCredentialsForIdentity",r.body=JSON.stringify({IdentityId:t.IdentityId}),e(r,function(e){var t=JSON.parse(e);n(t.Credentials)})}function i(){return{session:o(),id:s()}}function o(){var e=void 0,t=void 0,n=window.localStorage.getItem(K);return n&&(n=JSON.parse(n),Date.now()<n.expiration&&(e=n)),e||(t=!0,e=a()),e.expiration=Date.now()+W,window.localStorage.setItem(K,JSON.stringify(e)),t&&(e.new=!0),e}function s(){var e=window.localStorage.getItem(N);return e||(e=c(),window.localStorage.setItem(N,e)),e}function a(){return{id:Math.floor(17592186044416*(1+Math.random())).toString(16),startTimestamp:(new Date).toISOString()}}function c(){return u()+u()+"-"+u()+"-"+u()+"-"+u()+"-"+u()+u()+u()}function u(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}function f(e,t){var n={host:t.uri.host,"content-type":e.config.defaultContentType,accept:e.config.defaultAcceptType,"x-amz-date":g(t.signDate)};t.request.method=t.request.method.toUpperCase(),t.request.body?t.payload=t.request.body:t.request.data&&e.payloadSerializer?t.payload=e.payloadSerializer(t.request.data):delete n["content-type"],t.request.headers=m(n,Object.keys(t.request.headers||{}).reduce(function(e,n){return e[n.toLowerCase()]=t.request.headers[n],e},{})),t.sortedHeaderKeys=Object.keys(t.request.headers).sort(),t.request.headers["content-type"]&&(t.request.headers["content-type"]=t.request.headers["content-type"].split(";")[0]),"object"===q(t.request.params)&&m(t.uri.queryParams,t.request.params)}function h(e,t){t.signedHeaders=t.sortedHeaderKeys.map(function(e){return e.toLowerCase()}).join(";"),t.canonicalRequest=String(t.request.method).toUpperCase()+"\n"+encodeURI(t.uri.path)+"\n"+Object.keys(t.uri.queryParams).sort().map(function(e){return encodeURIComponent(e)+"="+encodeURIComponent(t.uri.queryParams[e])}).join("&")+"\n"+t.sortedHeaderKeys.map(function(e){return e.toLocaleLowerCase()+":"+t.request.headers[e]}).join("\n")+"\n\n"+t.signedHeaders+"\n"+e.hasher.hash(t.payload?t.payload:"")}function l(e,t){t.credentialScope=[g(t.signDate,!0),e.config.region,e.config.service,"aws4_request"].join("/"),t.stringToSign="AWS4-HMAC-SHA256\n"+g(t.signDate)+"\n"+t.credentialScope+"\n"+e.hasher.hash(t.canonicalRequest)}function d(e,t){var n=e.hasher.hmac,r=n(n(n(n("AWS4"+e.config.secretAccessKey,g(t.signDate,!0),{hexOutput:!1}),e.config.region,{hexOutput:!1,textInput:!1}),e.config.service,{hexOutput:!1,textInput:!1}),"aws4_request",{hexOutput:!1,textInput:!1});t.signature=n(r,t.stringToSign,{textInput:!1})}function p(e,t){t.authorization="AWS4-HMAC-SHA256 Credential="+e.config.accessKeyId+"/"+t.credentialScope+", SignedHeaders="+t.signedHeaders+", Signature="+t.signature}function g(e,t){var n=e.toISOString().replace(/[:\-]|\.\d{3}/g,"").substr(0,17);return t?n.substr(0,8):n}function y(){return function(e){return JSON.stringify(e)}}function v(){function e(e){return/^\??(.*)$/.exec(e)[1].split("&").reduce(function(e,t){return t=/^(.+)=(.*)$/.exec(t),t&&(e[t[1]]=t[2]),e},{})}var t=document?document.createElement("a"):{};return function(n){return t.href=n,{protocol:t.protocol,host:t.host.replace(/^(.*):((80)|(443))$/,"$1"),path:("/"!==t.pathname.charAt(0)?"/":"")+t.pathname,queryParams:e(t.search)}}}function w(){return{hash:function(e,t){t=m({hexOutput:!0,textInput:!0},t);var n=M.SHA256(e);return t.hexOutput?n.toString(M.enc.Hex):n},hmac:function(e,t,n){n=m({hexOutput:!0,textInput:!0},n);var r=M.HmacSHA256(t,e,{asBytes:!0});return n.hexOutput?r.toString(M.enc.Hex):r}}}function m(e){return[].slice.call(arguments,1).forEach(function(t){t&&"object"===(void 0===t?"undefined":q(t))&&Object.keys(t).forEach(function(n){var r=t[n];void 0!==r&&(null!==r&&"object"===(void 0===r?"undefined":q(r))?(e[n]=Array.isArray(r)?[]:{},m(e[n],r)):e[n]=r)})}),e}function S(e,t){if(void 0===e||!e)throw new Error(t)}function k(e){var t=i().session;return{eventType:"pageView",timestamp:(new Date).toISOString(),session:{id:t.id,startTimestamp:t.startTimestamp},attributes:{referrer:document.referrer,hostname:window.location.hostname,path:window.location.pathname||e},metrics:{}}}function _(e){var t=i().session;return{eventType:e.eventType||"other",timestamp:(new Date).toISOString(),session:{id:t.id,startTimestamp:t.startTimestamp},attributes:P({referrer:document.referrer,hostname:window.location.hostname,path:window.location.pathname},b(e)),metrics:A(e)}}function b(e){var t=P({},e);return delete t.workflow,J.forEach(function(e){return delete t[e]}),Object.keys(t).forEach(function(e){t[e]=t[e]?t[e].toString():"null"}),t}function A(e){var t={};return J.forEach(function(n){t[n]=e[n]}),t}function I(e,t){var n={region:"us-east-1",service:"mobileanalytics",accessKeyId:e.AccessKeyId,secretAccessKey:e.SecretKey,sessionToken:e.SessionToken};return new L(n).sign(t)}function x(e,t){return JSON.stringify({client:{client_id:e,app_title:t.name,app_version_name:t.version||"unknown"},services:{mobile_analytics:{app_id:t.id}}})}function O(n,r,o){var s=i();n=Array.isArray(n)?n:[n];var a=E(n);t(r,function(t){a.headers=I(t,a),a.headers["x-amz-Client-Context"]=x(s.id,o),e(a,function(e){e&&console.error(JSON.parse(e))})})}function E(e){return{url:arguments.length>1&&void 0!==arguments[1]?arguments[1]:F,method:"POST",body:JSON.stringify({events:e})}}function z(e){window.ga?window.ga(function(){e(window.ga.getAll())}):console.log(new Error("Google Analytics trackers not available"))}function T(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r={hitType:"event",eventCategory:e.category||"none",eventAction:e.action,eventLabel:e.label};return Object.keys(t).forEach(function(n){r["dimension"+t[n]]=e[n]}),Object.keys(n).forEach(function(t){r["metric"+n[t]]=e[t]}),r}function H(e){var t=P({},e);return t.user&&(t.user=V(t.user)),t}var q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},C=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},B=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),P=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},j="TELEMETRY_COGNITO_CREDENTIALS",D={method:"POST",url:"https://cognito-identity.us-east-1.amazonaws.com/",headers:{"Content-type":"application/x-amz-json-1.1"}},W=18e5,K="TELEMETRY_SESSION",N="TELEMETRY_CLIENT_ID",M=function(e,t){var n={},r=n.lib={},i=function(){},o=r.Base={extend:function(e){i.prototype=this;var t=new i;return e&&t.mixIn(e),t.hasOwnProperty("init")||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},s=r.WordArray=o.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=void 0!=t?t:4*e.length},toString:function(e){return(e||c).stringify(this)},concat:function(e){var t=this.words,n=e.words,r=this.sigBytes;if(e=e.sigBytes,this.clamp(),r%4)for(var i=0;i<e;i++)t[r+i>>>2]|=(n[i>>>2]>>>24-i%4*8&255)<<24-(r+i)%4*8;else if(n.length>65535)for(i=0;i<e;i+=4)t[r+i>>>2]=n[i>>>2];else t.push.apply(t,n);return this.sigBytes+=e,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=e.ceil(n/4)},clone:function(){var e=o.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var n=[],r=0;r<t;r+=4)n.push(4294967296*e.random()|0);return new s.init(n,t)}}),a=n.enc={},c=a.Hex={stringify:function(e){var t=e.words;e=e.sigBytes;for(var n=[],r=0;r<e;r++){var i=t[r>>>2]>>>24-r%4*8&255;n.push((i>>>4).toString(16)),n.push((15&i).toString(16))}return n.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r+=2)n[r>>>3]|=parseInt(e.substr(r,2),16)<<24-r%8*4;return new s.init(n,t/2)}},u=a.Latin1={stringify:function(e){var t=e.words;e=e.sigBytes;for(var n=[],r=0;r<e;r++)n.push(String.fromCharCode(t[r>>>2]>>>24-r%4*8&255));return n.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r<t;r++)n[r>>>2]|=(255&e.charCodeAt(r))<<24-r%4*8;return new s.init(n,t)}},f=a.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},h=r.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new s.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=f.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var n=this._data,r=n.words,i=n.sigBytes,o=this.blockSize,a=i/(4*o),a=t?e.ceil(a):e.max((0|a)-this._minBufferSize,0);if(t=a*o,i=e.min(4*t,i),t){for(var c=0;c<t;c+=o)this._doProcessBlock(r,c);c=r.splice(0,t),n.sigBytes-=i}return new s.init(c,i)},clone:function(){var e=o.clone.call(this);return e._data=this._data.clone(),e},_minBufferSize:0});r.Hasher=h.extend({cfg:o.extend(),init:function(e){this.cfg=this.cfg.extend(e),this.reset()},reset:function(){h.reset.call(this),this._doReset()},update:function(e){return this._append(e),this._process(),this},finalize:function(e){return e&&this._append(e),this._doFinalize()},blockSize:16,_createHelper:function(e){return function(t,n){return new e.init(n).finalize(t)}},_createHmacHelper:function(e){return function(t,n){return new l.HMAC.init(e,n).finalize(t)}}});var l=n.algo={};return n}(Math);!function(e){for(var t=M,n=t.lib,r=n.WordArray,i=n.Hasher,n=t.algo,o=[],s=[],a=function(e){return 4294967296*(e-(0|e))|0},c=2,u=0;u<64;){var f;e:{f=c;for(var h=e.sqrt(f),l=2;l<=h;l++)if(!(f%l)){f=!1;break e}f=!0}f&&(u<8&&(o[u]=a(e.pow(c,.5))),s[u]=a(e.pow(c,1/3)),u++),c++}var d=[],n=n.SHA256=i.extend({_doReset:function(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],o=n[2],a=n[3],c=n[4],u=n[5],f=n[6],h=n[7],l=0;l<64;l++){if(l<16)d[l]=0|e[t+l];else{var p=d[l-15],g=d[l-2];d[l]=((p<<25|p>>>7)^(p<<14|p>>>18)^p>>>3)+d[l-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+d[l-16]}p=h+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&u^~c&f)+s[l]+d[l],g=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&i^r&o^i&o),h=f,f=u,u=c,c=a+p|0,a=o,o=i,i=r,r=p+g|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+o|0,n[3]=n[3]+a|0,n[4]=n[4]+c|0,n[5]=n[5]+u|0,n[6]=n[6]+f|0,n[7]=n[7]+h|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return n[i>>>5]|=128<<24-i%32,n[14+(i+64>>>9<<4)]=e.floor(r/4294967296),n[15+(i+64>>>9<<4)]=r,t.sigBytes=4*n.length,this._process(),this._hash},clone:function(){var e=i.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=i._createHelper(n),t.HmacSHA256=i._createHmacHelper(n)}(Math),function(){var e=M,t=e.enc.Utf8;e.algo.HMAC=e.lib.Base.extend({init:function(e,n){e=this._hasher=new e.init,"string"==typeof n&&(n=t.parse(n));var r=e.blockSize,i=4*r;n.sigBytes>i&&(n=e.finalize(n)),n.clamp();for(var o=this._oKey=n.clone(),s=this._iKey=n.clone(),a=o.words,c=s.words,u=0;u<r;u++)a[u]^=1549556828,c[u]^=909522486;o.sigBytes=s.sigBytes=i,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var t=this._hasher;return e=t.finalize(e),t.reset(),t.finalize(this._oKey.clone().concat(e))}})}();var R={region:"eu-west-1",service:"execute-api",defaultContentType:"application/json",defaultAcceptType:"application/json",payloadSerializerFactory:y,uriParserFactory:v,hasherFactory:w},L=function(){function e(t){C(this,e),this.config=m({},R,t),this.payloadSerializer=this.config.payloadSerializer||this.config.payloadSerializerFactory(),this.uriParser=this.config.uriParserFactory(),this.hasher=this.config.hasherFactory(),S(this.config.accessKeyId,"AwsSigner requires AWS AccessKeyID"),S(this.config.secretAccessKey,"AwsSigner requires AWS SecretAccessKey")}return B(e,[{key:"sign",value:function(e,t){var n={request:m({},e),signDate:t||new Date,uri:this.uriParser(e.url)};return f(this,n),h(this,n),l(this,n),d(this,n),p(this,n),{Accept:n.request.headers.accept,Authorization:n.authorization,"Content-Type":n.request.headers["content-type"],"x-amz-date":n.request.headers["x-amz-date"],"x-amz-security-token":this.config.sessionToken||void 0}}}]),e}(),J=["size","duration"],F="https://mobileanalytics.us-east-1.amazonaws.com/2014-06-05/events",U=function(){function e(t){C(this,e),this.name="amazon",P(this,t),i().session.new&&this.logEvent({category:"_session.start"})}return B(e,[{key:"logPageView",value:function(e){O(k(e),this.userPoolID,this.app)}},{key:"logEvent",value:function(e){O(_(e),this.userPoolID,this.app)}}]),e}(),$=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};C(this,e),this.name="google",P(this,t)}return B(e,[{key:"logPageView",value:function(e){z(function(t){t.forEach(function(t){t.send("pageview",e||window.location.pathname)})})}},{key:"logEvent",value:function(e){var t=T(e,this.dimensions,this.metrics);z(function(e){e.forEach(function(e){e.send(t)})})}}]),e}(),V=function(e){return M.SHA256(e).toString(M.enc.Hex)};return function(){function e(t){if(C(this,e),this.trackers=[],this.workflows={},t.amazon){var n=new U(t.amazon);this.trackers.push(n)}if(t.google){var r=new $(t.google);this.trackers.push(r)}this.trackers.length||console.error(new Error("No trackers configured"))}return B(e,[{key:"logPageView",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=H(t);return!this.trackers.length||this.disabled?(this.disabled||console.error(new Error("Page view was not logged because no trackers are configured.")),!1):(this.trackers.forEach(function(t){try{t.logPageView(e,n)}catch(e){console.error(t.name+" tracker failed to log page view.",e)}}),!0)}},{key:"logEvent",value:function(e){var t=H(e);return!this.trackers.length||this.disabled?(this.disabled||console.error(new Error("Event was not logged because no trackers are configured.")),!1):(this.trackers.forEach(function(e){try{e.logEvent(t)}catch(t){console.error(e.name+" tracker failed to log event",t)}}),!0)}},{key:"startWorkflow",value:function(e){this.workflows[e]={name:e,start:Date.now(),steps:[]},this._logWorkflow({name:e,step:"start"})}},{key:"stepWorkflow",value:function(e,t,n){this._logWorkflow({name:e,step:t,details:n})}},{key:"endWorkflow",value:function(e){this._logWorkflow({name:e,step:"finish"}),delete this.workflows[e]}},{key:"cancelWorkflow",value:function(e){this._logWorkflow({name:e,step:"cancel"}),delete this.workflows[e]}},{key:"_logWorkflow",value:function(e){e=H(e);var t=this.workflows[e.name];t||this.startWorkflow(e.name),t.steps.push(e.step),t.duration=(Date.now()-t.start)/1e3;var n={eventType:"workflow",category:e.name,action:e.step,label:e.details,user:e.user,duration:t.duration};this.logEvent(n)}}]),e}()}); | ||
//# sourceMappingURL=telemetry.amd.min.js.map |
{ | ||
"name": "@esri/telemetry", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "A JavaScript Implementation of the ArcGIS Telemetry Specification", | ||
@@ -5,0 +5,0 @@ "main": "dist/telemetry.js", |
@@ -9,4 +9,6 @@ # Telemetry.js | ||
## API | ||
# API | ||
### Direct tracking | ||
### `telemetry.logPageView(page)` | ||
@@ -41,2 +43,37 @@ | ||
## Workflows | ||
Workflows are meant to track a logical group of actions by a user from start to finish. | ||
First a workflow is created with `startWorkflow`. Then steps are added with `stepWorkflow`. Finally a workflow is either canceled with `cancelWorkflow` or completed successfully with `endWorkflow`. | ||
Workflows are tracked internally by `name` so this value must not change through the life of a workflow in order for steps and duration to be tracked properly. | ||
### `telemetry.startWorkflow(name)` | ||
```js | ||
telemetry.startWorkflow('add layer') | ||
``` | ||
### `telemetry.stepWorkflow(name, step, details)` | ||
```js | ||
telemetry.stepWorkflow('add layer', 'search', 'street trees') | ||
``` | ||
### `telemetry.cancelWorkflow(name, [details])` | ||
```js | ||
telemetry.cancelWorkflow('add layer') | ||
telemetry.cancelWorkflow('search', 'back to home') | ||
``` | ||
### `telemetry.endWorkflow(name, [details])` | ||
```js | ||
telemetry.endWorkflow('add layer') | ||
telemetry.endWorkflow('add layer', 'pasadena street trees') | ||
``` | ||
## How to use | ||
@@ -43,0 +80,0 @@ |
@@ -14,3 +14,3 @@ import { getCredentials } from './auth' | ||
const session = getUser().session | ||
if (session.new) this.logEvent({category: '_session.start'}) | ||
if (session.new) this.logEvent({ category: '_session.start' }) | ||
} | ||
@@ -50,3 +50,3 @@ | ||
return { | ||
eventType: setType(event), | ||
eventType: event.eventType || 'other', | ||
timestamp: new Date().toISOString(), | ||
@@ -67,16 +67,9 @@ session: { | ||
function setType (event = {}) { | ||
const type = event.workflow || event.process || event.category | ||
if (type) { | ||
return type | ||
} else if (event.error) { | ||
return 'error' | ||
} else { | ||
return 'other' | ||
} | ||
} | ||
function extractAttributes (event) { | ||
const attributes = Object.assign({}, event) | ||
delete attributes.workflow | ||
METRICS.forEach(metric => delete attributes[metric]) | ||
Object.keys(attributes).forEach(attr => { | ||
attributes[attr] = attributes[attr] ? attributes[attr].toString() : 'null' | ||
}) | ||
return attributes | ||
@@ -87,3 +80,5 @@ } | ||
const metrics = {} | ||
METRICS.forEach(metric => { metrics[metric] = event[metric] }) | ||
METRICS.forEach(metric => { | ||
metrics[metric] = event[metric] | ||
}) | ||
return metrics | ||
@@ -105,6 +100,7 @@ } | ||
function createClientContext (client_id, app) { // eslint-disable-line | ||
function createClientContext (clientId, app) { | ||
// eslint-disable-line | ||
return JSON.stringify({ | ||
client: { | ||
client_id, | ||
client_id: clientId, | ||
app_title: app.name, | ||
@@ -123,3 +119,3 @@ app_version_name: app.version || 'unknown' | ||
const user = getUser() | ||
events = Array.isArray(events) ? events : [ events ] | ||
events = Array.isArray(events) ? events : [events] | ||
const options = createTelemetryOptions(events) | ||
@@ -126,0 +122,0 @@ getCredentials(userPoolID, credentials => { |
@@ -8,2 +8,3 @@ import Amazon from './amazon' | ||
this.trackers = [] | ||
this.workflows = {} | ||
if (options.amazon) { | ||
@@ -55,2 +56,54 @@ const amazon = new Amazon(options.amazon) | ||
} | ||
startWorkflow (name) { | ||
this.workflows[name] = { | ||
name, | ||
start: Date.now(), | ||
steps: [] | ||
} | ||
this._logWorkflow({ name, step: 'start' }) | ||
} | ||
stepWorkflow (name, step, details) { | ||
this._logWorkflow({ name, step, details }) | ||
} | ||
endWorkflow (name) { | ||
this._logWorkflow({ name, step: 'finish' }) | ||
delete this.workflows[name] | ||
} | ||
cancelWorkflow (name) { | ||
this._logWorkflow({ name, step: 'cancel' }) | ||
delete this.workflows[name] | ||
} | ||
_logWorkflow (options) { | ||
/* | ||
const workflow = { | ||
name: 'add layer to map', | ||
step: 'start', | ||
details: 'some details about the step' | ||
} | ||
*/ | ||
options = preProcess(options) | ||
let workflow = this.workflows[options.name] | ||
if (!workflow) { | ||
this.startWorkflow(options.name) | ||
} | ||
workflow.steps.push(options.step) | ||
workflow.duration = (Date.now() - workflow.start) / 1000 | ||
const track = { | ||
eventType: 'workflow', | ||
category: options.name, | ||
action: options.step, | ||
label: options.details, | ||
user: options.user, | ||
duration: workflow.duration | ||
} | ||
this.logEvent(track) | ||
} | ||
} | ||
@@ -57,0 +110,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
464756
3.19%32
3.23%2822
5.46%175
26.81%