@aicore/core-analytics-client-lib
Advanced tools
Comparing version 1.0.4 to 1.0.5
@@ -1,4 +0,4 @@ | ||
let accountID,appName,userID,sessionID,postIntervalSeconds,granularitySec,analyticsURL,postURL,serverConfig;const DEFAULT_GRANULARITY_IN_SECONDS=3,DEFAULT_RETRY_TIME_IN_SECONDS=30,DEFAULT_POST_INTERVAL_SECONDS=600,USERID_LOCAL_STORAGE_KEY="aicore.analytics.userID",POST_LARGE_DATA_THRESHOLD_BYTES=1e4;let currentAnalyticsEvent=null;const IS_NODE_ENV="undefined"==typeof window;let DEFAULT_BASE_URL="https://analytics.core.ai",granularityTimer,postTimer,currentQuantisedTime=0,disabled=!1,debugMode=!1;function debugLog(...e){debugMode&&console.log(...e)}function debugError(...e){debugMode&&console.error(...e)}if(IS_NODE_ENV)throw new Error("Node environment is not currently supported");function _createAnalyticsEvent(){return{schemaVersion:1,accountID:accountID,appName:appName,uuid:userID,sessionID:sessionID,unixTimestampUTC:+new Date,numEventsTotal:0,events:{}}}function _validateCurrentState(){if(!currentAnalyticsEvent)throw new Error("Please call initSession before using any analytics event")}function getCurrentAnalyticsEvent(){return _validateCurrentState(),JSON.parse(JSON.stringify(currentAnalyticsEvent))}function _getOrCreateUserID(){let e=localStorage.getItem(USERID_LOCAL_STORAGE_KEY);return e||(e=crypto.randomUUID(),localStorage.setItem(USERID_LOCAL_STORAGE_KEY,e)),e}function _getOrCreateSessionID(){let e=sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY);return e||(e=Math.random().toString(36).substr(2,10),sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY,e)),e}function _setupIDs(){userID=_getOrCreateUserID(),sessionID=_getOrCreateSessionID()}function _retryPost(e){e.backoffCount=(e.backoffCount||0)+1,debugLog(`Failed to call core analytics server. Will retry in ${DEFAULT_RETRY_TIME_IN_SECONDS*e.backoffCount}s: `),setTimeout(()=>{_postCurrentAnalyticsEvent(e)},1e3*DEFAULT_RETRY_TIME_IN_SECONDS*e.backoffCount)}function _postCurrentAnalyticsEvent(t){var e;disabled||(t||(t=currentAnalyticsEvent,currentQuantisedTime=0,_resetGranularityTimer(),currentAnalyticsEvent=_createAnalyticsEvent()),0!==t.numEventsTotal&&((e=JSON.stringify(t)).length>POST_LARGE_DATA_THRESHOLD_BYTES&&console.warn(`Analytics event generated is very large at greater than ${e.length}B. This | ||
typically means that you may be sending too many value events? .`),debugLog("Sending Analytics data of length: ",e.length,"B"),window.fetch(postURL,{method:"POST",headers:{"Content-Type":"application/json"},body:e}).then(e=>{200!==e.status&&(400!==e.status?_retryPost(t):console.error("Analytics client: Bad Request, this is most likely a problem with the library, update to latest version."))}).catch(e=>{debugError(e),_retryPost(t)})))}function _resetGranularityTimer(e){granularityTimer&&(clearInterval(granularityTimer),granularityTimer=null),e||(granularityTimer=setInterval(()=>{currentQuantisedTime+=granularitySec},1e3*granularitySec))}function _setupTimers(e){_resetGranularityTimer(e),postTimer&&(clearInterval(postTimer),postTimer=null),e||(postTimer=setInterval(_postCurrentAnalyticsEvent,1e3*postIntervalSeconds))}async function _getServerConfig(){return new Promise((n,r)=>{var e=analyticsURL+(`/getAppConfig?accountID=${accountID}&appName=`+appName);window.fetch(e).then(async e=>{switch(e.status){case 200:var t=await e.json();return void n(t);case 400:r("Bad Request, check library version compatible?",e);break;default:r("analytics client: Could not update from remote config. Continuing with defaults.",e)}}).catch(e=>{r("analytics client: Could not update from remote config. Continuing with defaults.",e)})})}function getAppConfig(){return{accountID:accountID,appName:appName,disabled:disabled,uuid:userID,sessionID:sessionID,postIntervalSeconds:postIntervalSeconds,granularitySec:granularitySec,analyticsURL:analyticsURL,serverConfig:serverConfig}}async function _initFromRemoteConfig(e,t){(serverConfig=await _getServerConfig())!=={}&&(postIntervalSeconds=e||serverConfig.postIntervalSecondsInit||DEFAULT_POST_INTERVAL_SECONDS,granularitySec=t||serverConfig.granularitySecInit||DEFAULT_GRANULARITY_IN_SECONDS,analyticsURL=serverConfig.analyticsURLInit||analyticsURL||DEFAULT_BASE_URL,_setupTimers(disabled=!0===serverConfig.disabled),debugLog(`Init analytics Config from remote. disabled: ${disabled} | ||
postIntervalSeconds:${postIntervalSeconds}, granularitySec: ${granularitySec} ,URL: `+analyticsURL),disabled&&console.warn(`Core Analytics is disabled from the server for app: ${accountID}:`+appName))}function _stripTrailingSlash(e){return e.replace(/\/$/,"")}function initSession(e,t,n,r,a,i){if(!e||!t)throw new Error("accountID and appName must exist for init");analyticsURL=n?_stripTrailingSlash(n):DEFAULT_BASE_URL,accountID=e,appName=t,debugMode=i||!1,postIntervalSeconds=r||DEFAULT_POST_INTERVAL_SECONDS,granularitySec=a||DEFAULT_GRANULARITY_IN_SECONDS,postURL=analyticsURL+"/ingest",_setupIDs(),currentAnalyticsEvent=_createAnalyticsEvent(),_setupTimers(),_initFromRemoteConfig(r,a)}function _ensureAnalyticsEventExists(e,t,n){let r=currentAnalyticsEvent.events;r[e]=r[e]||{},r[e][t]=r[e][t]||{},r[e][t][n]=r[e][t][n]||{time:[],valueCount:[]}}function _validateEvent(e,t,n,r,a){if(_validateCurrentState(),!e||!t||!n)throw new Error("missing eventType or category or subCategory");if("number"!=typeof r||r<0)throw new Error("invalid count");if("number"!=typeof a)throw new Error("invalid value")}function _updateExistingAnalyticsEvent(t,n,r,a,i,s){let o=currentAnalyticsEvent.events;var e="number"==typeof o[n][r][a].valueCount[t];if(e&&0===s)o[n][r][a].valueCount[t]+=i;else if(e&&0!==s){let e={};e[s]=i,e[0]=o[n][r][a].valueCount[t],o[n][r][a].valueCount[t]=e}else if(!e){let e=o[n][r][a].valueCount[t];e[s]=(e[s]||0)+i}currentAnalyticsEvent.numEventsTotal+=1}function analyticsEvent(n,r,a,i=1,s=0){if(!disabled){_validateEvent(n,r,a,i,s),_ensureAnalyticsEventExists(n,r,a);let t=currentAnalyticsEvent.events;var e=t[n][r][a].time;if((0<e.length?e[e.length-1]:null)!==currentQuantisedTime){if(t[n][r][a].time.push(currentQuantisedTime),0===s)t[n][r][a].valueCount.push(i);else{let e={};e[s]=i,t[n][r][a].valueCount.push(e)}currentAnalyticsEvent.numEventsTotal+=1}else _updateExistingAnalyticsEvent(t[n][r][a].valueCount.length-1,n,r,a,i,s)}}export{initSession,getCurrentAnalyticsEvent,analyticsEvent,getAppConfig}; | ||
var analytics={};function init(){let r,s,e,t,l,u,c,f,n;const v=3,a=30,y=600,o="aicore.analytics.userID",i=1e4;let d=null;var m="undefined"==typeof window;let g="https://analytics.core.ai",p,h,w=0,C=!1,I=!1;function b(...e){I&&console.log(...e)}if(m)throw new Error("Node environment is not currently supported");function S(){return{schemaVersion:1,accountID:r,appName:s,uuid:e,sessionID:t,unixTimestampUTC:+new Date,numEventsTotal:0,events:{}}}function E(){if(!d)throw new Error("Please call initSession before using any analytics event")}function T(){e=function(){let e=localStorage.getItem(o);return e||(e=crypto.randomUUID(),localStorage.setItem(o,e)),e}(),t=function(){let e=sessionStorage.getItem(o);return e||(e=Math.random().toString(36).substr(2,10),sessionStorage.setItem(o,e)),e}()}function D(e){e.backoffCount=(e.backoffCount||0)+1,b(`Failed to call core analytics server. Will retry in ${a*e.backoffCount}s: `),setTimeout(()=>{N(e)},1e3*a*e.backoffCount)}function N(t){var e;C||(t||(t=d,w=0,$(),d=S()),0!==t.numEventsTotal&&((e=JSON.stringify(t)).length>i&&console.warn(`Analytics event generated is very large at greater than ${e.length}B. This | ||
typically means that you may be sending too many value events? .`),b("Sending Analytics data of length: ",e.length,"B"),window.fetch(f,{method:"POST",headers:{"Content-Type":"application/json"},body:e}).then(e=>{200!==e.status&&(400!==e.status?D(t):console.error("Analytics client: Bad Request, this is most likely a problem with the library, update to latest version."))}).catch(e=>{e=[e],I&&console.error(...e),D(t)})))}function $(e){p&&(clearInterval(p),p=null),e||(p=setInterval(()=>{w+=u},1e3*u))}function k(e){$(e),h&&(clearInterval(h),h=null),e||(h=setInterval(N,1e3*l))}async function A(e,t){(n=await new Promise((n,a)=>{var e=c+(`/getAppConfig?accountID=${r}&appName=`+s);window.fetch(e).then(async e=>{switch(e.status){case 200:var t=await e.json();return void n(t);case 400:a("Bad Request, check library version compatible?",e);break;default:a("analytics client: Could not update from remote config. Continuing with defaults.",e)}}).catch(e=>{a("analytics client: Could not update from remote config. Continuing with defaults.",e)})}))!=={}&&(l=e||n.postIntervalSecondsInit||y,u=t||n.granularitySecInit||v,c=n.analyticsURLInit||c||g,k(C=!0===n.disabled),b(`Init analytics Config from remote. disabled: ${C} | ||
postIntervalSeconds:${l}, granularitySec: ${u} ,URL: `+c),C&&console.warn(`Core Analytics is disabled from the server for app: ${r}:`+s))}analytics.initSession=function(e,t,n,a,o,i){if(!e||!t)throw new Error("accountID and appName must exist for init");c=n?n.replace(/\/$/,""):g,r=e,s=t,I=i||!1,l=a||y,u=o||v,f=c+"/ingest",T(),d=S(),k(),A(a,o)},analytics.getCurrentAnalyticsEvent=function(){return E(),JSON.parse(JSON.stringify(d))},analytics.event=function(n,a,o,i=1,r=0){if(!C){var s=n,l=a,u=o,c=i,f=r;if(E(),!s||!l||!u)throw new Error("missing eventType or category or subCategory");if("number"!=typeof c||c<0)throw new Error("invalid count");if("number"!=typeof f)throw new Error("invalid value");{s=n;l=a;u=o;let e=d.events;e[s]=e[s]||{},e[s][l]=e[s][l]||{},e[s][l][u]=e[s][l][u]||{time:[],valueCount:[]}}let t=d.events;c=t[n][a][o].time;if((0<c.length?c[c.length-1]:null)===w){f=t[n][a][o].valueCount.length-1;{var s=f,l=n,u=a,c=o,f=i,v=r;let t=d.events;var e="number"==typeof t[l][u][c].valueCount[s];if(e&&0===v)t[l][u][c].valueCount[s]+=f;else if(e&&0!==v){let e={};e[v]=f,e[0]=t[l][u][c].valueCount[s],t[l][u][c].valueCount[s]=e}else if(!e){let e=t[l][u][c].valueCount[s];e[v]=(e[v]||0)+f}d.numEventsTotal+=1}}else{if(t[n][a][o].time.push(w),0===r)t[n][a][o].valueCount.push(i);else{let e={};e[r]=i,t[n][a][o].valueCount.push(e)}d.numEventsTotal+=1}}},analytics.getAppConfig=function(){return{accountID:r,appName:s,disabled:C,uuid:e,sessionID:t,postIntervalSeconds:l,granularitySec:u,analyticsURL:c,serverConfig:n}}}init(); | ||
//# sourceMappingURL=analytics.min.js.map |
{ | ||
"name": "@aicore/core-analytics-client-lib", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "Analytics client library for https://github.com/aicore/Core-Analytics-Server", | ||
@@ -22,13 +22,14 @@ "main": "dist/analytics.min.js", | ||
"prepare": "husky install", | ||
"test": "echo please open test/index.html in browser to run tests", | ||
"test:unit": "echo please open test/index.html in browser to run tests", | ||
"test": "echo please run - npm run serve - and then open test/unit-test.html in browser to run tests", | ||
"test:unit": "npm run test", | ||
"test:integ": "echo integration tests are disabled in this repo", | ||
"cover": "echo please open test/index.html in browser to run tests", | ||
"cover:unit": "echo please open test/index.html in browser to run tests", | ||
"cover:integ": "echo please open test/index.html in browser to run tests", | ||
"build": "", | ||
"cover": "echo no coverage for now", | ||
"cover:unit": "echo no coverage for now", | ||
"cover:integ": "echo no coverage for now", | ||
"build": "npm run minify", | ||
"minify": "echo creating minified package dist/analytics.min.js && mkdir -p dist && uglifyjs src/analytics.js --compress --mangle -o dist/analytics.min.js -c -m --source-map \"root='src/analytics.js',url='analytics.min.js.map'\"", | ||
"bumpPatchVersion": "npm --no-git-tag-version version patch", | ||
"bumpPatchVersionWithGitTag": "npm version patch", | ||
"release": "npm run minify && npm run bumpPatchVersionWithGitTag" | ||
"release": "npm run minify && npm run bumpPatchVersionWithGitTag", | ||
"serve": "http-server . -p 8000 -c-1" | ||
}, | ||
@@ -57,4 +58,5 @@ "files": [ | ||
"mocha": "9.2.2", | ||
"uglify-js": "3.15.5" | ||
"uglify-js": "3.15.5", | ||
"http-server": "14.1.0" | ||
} | ||
} |
@@ -22,16 +22,11 @@ # Core Analytics Client Lib - JS | ||
## Initialize the session | ||
## Load the Library | ||
Embed the script in your HTML file : | ||
```html | ||
<html lang="en"> | ||
<script type="module"> | ||
// For production use cases, use url: https://unpkg.com/@aicore/core-analytics-client-lib/dist/analytics.min.js | ||
// The below url is for development purposes only. | ||
import {initSession} from "https://unpkg.com/@aicore/core-analytics-client-lib/src/analytics.js"; | ||
initSession("accountID", "appName"); | ||
</script> | ||
</html> | ||
<script src="https://unpkg.com/@aicore/core-analytics-client-lib/src/analytics.js"></script> | ||
``` | ||
This will create a global `analytics` variable which can be used to access the analytics APIs. | ||
initSession(): Initialize the analytics session. It takes the following parameters: | ||
## Initialize the analytics session. | ||
Call `analytics.initSession()` after loading the library. It takes the following parameters: | ||
@@ -47,15 +42,18 @@ * `accountID`: Your analytics account id as configured in the server or core.ai analytics | ||
### usageExample | ||
```javascript | ||
// Init with default values. | ||
analytics.initSession("accountID", "appName"); | ||
// Example for custom initSession where the analytics aggregated data | ||
// is posted to custom server https://localhost:3000 every 600 secs | ||
// with a granularity(resolution) of 5 seconds. | ||
analytics.initSession("accountID", "appName", "https://localhost:3000", 600, 5); | ||
initSession("accountID", "appName", "https://localhost:3000", 600, 5); | ||
// To initSession in debug mode set debug arg in init to true: | ||
initSession("accountID", "appName", "https://localhost:3000", 600, 5, true); | ||
analytics.initSession("accountID", "appName", "https://localhost:3000", 600, 5, true); | ||
``` | ||
## Raising analytics events | ||
Once `initSession` is called, we can now start logging analytics events by calling `analyticsEvent` API. | ||
Once `initSession` is called, we can now start logging analytics events by calling `analytics.event` API. | ||
The API registers an analytics event. The events will be aggregated and send to the analytics server periodically. | ||
@@ -67,15 +65,15 @@ | ||
// Eg: event without counts and values | ||
analyticsEvent("platform", "os", "linux"); | ||
analytics.event("platform", "os", "linux"); | ||
// Eg: event with count, here it logs that html file is opened 100 times | ||
analyticsEvent("file", "opened", "html", 100); | ||
analytics.event("file", "opened", "html", 100); | ||
// Eg: event with count and value, here it logs that the startup time is 250 milliseconds. | ||
// Note that the value is unitless from analytics perspective. unit is deduced from subCategory name | ||
analyticsEvent("platform", "performance", "startupTimeMs", 1, 250); | ||
analytics.event("platform", "performance", "startupTimeMs", 1, 250); | ||
// Eg: event with fractional value. | ||
analyticsEvent("platform", "CPU", "utilization", 1, .45); | ||
analytics.event("platform", "CPU", "utilization", 1, .45); | ||
// Eg. Here we register that the system has 8 cores with each core having 2300MHz frequency. | ||
analyticsEvent("platform", "CPU", "coreCountsAndFrequencyMhz", 8, 2300); | ||
analytics.event("platform", "CPU", "coreCountsAndFrequencyMhz", 8, 2300); | ||
``` | ||
@@ -82,0 +80,0 @@ ### API parameters |
@@ -6,322 +6,326 @@ // GNU AGPL-3.0 License Copyright (c) 2021 - present core.ai . All rights reserved. | ||
let accountID, appName, userID, sessionID, postIntervalSeconds, granularitySec, analyticsURL, postURL, serverConfig; | ||
const DEFAULT_GRANULARITY_IN_SECONDS = 3; | ||
const DEFAULT_RETRY_TIME_IN_SECONDS = 30; | ||
const DEFAULT_POST_INTERVAL_SECONDS = 600; // 10 minutes | ||
const USERID_LOCAL_STORAGE_KEY = 'aicore.analytics.userID'; | ||
const POST_LARGE_DATA_THRESHOLD_BYTES = 10000; | ||
let currentAnalyticsEvent = null; | ||
const IS_NODE_ENV = (typeof window === 'undefined'); | ||
let DEFAULT_BASE_URL = "https://analytics.core.ai"; | ||
var analytics = {}; | ||
let granularityTimer; | ||
let postTimer; | ||
let currentQuantisedTime = 0; | ||
let disabled = false; | ||
let debugMode = false; | ||
function init() { | ||
let accountID, appName, userID, sessionID, postIntervalSeconds, granularitySec, analyticsURL, postURL, serverConfig; | ||
const DEFAULT_GRANULARITY_IN_SECONDS = 3; | ||
const DEFAULT_RETRY_TIME_IN_SECONDS = 30; | ||
const DEFAULT_POST_INTERVAL_SECONDS = 600; // 10 minutes | ||
const USERID_LOCAL_STORAGE_KEY = 'aicore.analytics.userID'; | ||
const POST_LARGE_DATA_THRESHOLD_BYTES = 10000; | ||
let currentAnalyticsEvent = null; | ||
const IS_NODE_ENV = (typeof window === 'undefined'); | ||
let DEFAULT_BASE_URL = "https://analytics.core.ai"; | ||
function debugLog(...args) { | ||
if(!debugMode){ | ||
return; | ||
let granularityTimer; | ||
let postTimer; | ||
let currentQuantisedTime = 0; | ||
let disabled = false; | ||
let debugMode = false; | ||
function debugLog(...args) { | ||
if(!debugMode){ | ||
return; | ||
} | ||
console.log(...args); | ||
} | ||
console.log(...args); | ||
} | ||
function debugError(...args) { | ||
if(!debugMode){ | ||
return; | ||
function debugError(...args) { | ||
if(!debugMode){ | ||
return; | ||
} | ||
console.error(...args); | ||
} | ||
console.error(...args); | ||
} | ||
if(IS_NODE_ENV){ | ||
throw new Error("Node environment is not currently supported"); | ||
} | ||
if(IS_NODE_ENV){ | ||
throw new Error("Node environment is not currently supported"); | ||
} | ||
function _createAnalyticsEvent() { | ||
return { | ||
schemaVersion: 1, | ||
accountID: accountID, | ||
appName: appName, | ||
uuid: userID, | ||
sessionID: sessionID, | ||
unixTimestampUTC: +new Date(), | ||
numEventsTotal: 0, | ||
events: {} | ||
}; | ||
} | ||
function _createAnalyticsEvent() { | ||
return { | ||
schemaVersion: 1, | ||
accountID: accountID, | ||
appName: appName, | ||
uuid: userID, | ||
sessionID: sessionID, | ||
unixTimestampUTC: +new Date(), | ||
numEventsTotal: 0, | ||
events: {} | ||
}; | ||
} | ||
function _validateCurrentState() { | ||
if(!currentAnalyticsEvent){ | ||
throw new Error("Please call initSession before using any analytics event"); | ||
function _validateCurrentState() { | ||
if(!currentAnalyticsEvent){ | ||
throw new Error("Please call initSession before using any analytics event"); | ||
} | ||
} | ||
} | ||
function getCurrentAnalyticsEvent() { | ||
_validateCurrentState(); | ||
// return a clone | ||
return JSON.parse(JSON.stringify(currentAnalyticsEvent)); | ||
} | ||
function getCurrentAnalyticsEvent() { | ||
_validateCurrentState(); | ||
// return a clone | ||
return JSON.parse(JSON.stringify(currentAnalyticsEvent)); | ||
} | ||
function _getOrCreateUserID() { | ||
let localUserID = localStorage.getItem(USERID_LOCAL_STORAGE_KEY); | ||
if(!localUserID){ | ||
localUserID = crypto.randomUUID(); | ||
localStorage.setItem(USERID_LOCAL_STORAGE_KEY, localUserID); | ||
function _getOrCreateUserID() { | ||
let localUserID = localStorage.getItem(USERID_LOCAL_STORAGE_KEY); | ||
if(!localUserID){ | ||
localUserID = crypto.randomUUID(); | ||
localStorage.setItem(USERID_LOCAL_STORAGE_KEY, localUserID); | ||
} | ||
return localUserID; | ||
} | ||
return localUserID; | ||
} | ||
function _getOrCreateSessionID() { | ||
let localSessionID = sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY); | ||
if(!localSessionID){ | ||
localSessionID = Math.random().toString(36).substr(2, 10); | ||
sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY, localSessionID); | ||
function _getOrCreateSessionID() { | ||
let localSessionID = sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY); | ||
if(!localSessionID){ | ||
localSessionID = Math.random().toString(36).substr(2, 10); | ||
sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY, localSessionID); | ||
} | ||
return localSessionID; | ||
} | ||
return localSessionID; | ||
} | ||
function _setupIDs() { | ||
userID = _getOrCreateUserID(); | ||
sessionID = _getOrCreateSessionID(); | ||
} | ||
function _setupIDs() { | ||
userID = _getOrCreateUserID(); | ||
sessionID = _getOrCreateSessionID(); | ||
} | ||
function _retryPost(eventToSend) { | ||
eventToSend.backoffCount = (eventToSend.backoffCount || 0) + 1; | ||
debugLog(`Failed to call core analytics server. Will retry in ${ | ||
DEFAULT_RETRY_TIME_IN_SECONDS * eventToSend.backoffCount}s: `); | ||
setTimeout(()=>{ | ||
_postCurrentAnalyticsEvent(eventToSend); | ||
}, DEFAULT_RETRY_TIME_IN_SECONDS * 1000 * eventToSend.backoffCount); | ||
} | ||
function _retryPost(eventToSend) { | ||
eventToSend.backoffCount = (eventToSend.backoffCount || 0) + 1; | ||
debugLog(`Failed to call core analytics server. Will retry in ${ | ||
DEFAULT_RETRY_TIME_IN_SECONDS * eventToSend.backoffCount}s: `); | ||
setTimeout(()=>{ | ||
_postCurrentAnalyticsEvent(eventToSend); | ||
}, DEFAULT_RETRY_TIME_IN_SECONDS * 1000 * eventToSend.backoffCount); | ||
} | ||
function _postCurrentAnalyticsEvent(eventToSend) { | ||
if(disabled){ | ||
return; | ||
} | ||
if(!eventToSend){ | ||
eventToSend = currentAnalyticsEvent; | ||
currentQuantisedTime = 0; | ||
_resetGranularityTimer(); | ||
currentAnalyticsEvent = _createAnalyticsEvent(); | ||
} | ||
if(eventToSend.numEventsTotal === 0 ){ | ||
return; | ||
} | ||
let textToSend = JSON.stringify(eventToSend); | ||
if(textToSend.length > POST_LARGE_DATA_THRESHOLD_BYTES){ | ||
console.warn(`Analytics event generated is very large at greater than ${textToSend.length}B. This | ||
function _postCurrentAnalyticsEvent(eventToSend) { | ||
if(disabled){ | ||
return; | ||
} | ||
if(!eventToSend){ | ||
eventToSend = currentAnalyticsEvent; | ||
currentQuantisedTime = 0; | ||
_resetGranularityTimer(); | ||
currentAnalyticsEvent = _createAnalyticsEvent(); | ||
} | ||
if(eventToSend.numEventsTotal === 0 ){ | ||
return; | ||
} | ||
let textToSend = JSON.stringify(eventToSend); | ||
if(textToSend.length > POST_LARGE_DATA_THRESHOLD_BYTES){ | ||
console.warn(`Analytics event generated is very large at greater than ${textToSend.length}B. This | ||
typically means that you may be sending too many value events? .`); | ||
} | ||
debugLog("Sending Analytics data of length: ", textToSend.length, "B"); | ||
window.fetch(postURL, { | ||
method: "POST", | ||
headers: {'Content-Type': 'application/json'}, | ||
body: textToSend | ||
}).then(res=>{ | ||
if(res.status === 200){ | ||
return; | ||
} | ||
if(res.status !== 400){ // we don't retry bad requests | ||
_retryPost(eventToSend); | ||
} else { | ||
console.error("Analytics client: " + | ||
"Bad Request, this is most likely a problem with the library, update to latest version."); | ||
} | ||
}).catch(res => { | ||
debugError(res); | ||
_retryPost(eventToSend); | ||
}); | ||
} | ||
debugLog("Sending Analytics data of length: ", textToSend.length, "B"); | ||
window.fetch(postURL, { | ||
method: "POST", | ||
headers: {'Content-Type': 'application/json'}, | ||
body: textToSend | ||
}).then(res=>{ | ||
if(res.status === 200){ | ||
function _resetGranularityTimer(disable) { | ||
if(granularityTimer){ | ||
clearInterval(granularityTimer); | ||
granularityTimer = null; | ||
} | ||
if(disable){ | ||
return; | ||
} | ||
if(res.status !== 400){ // we don't retry bad requests | ||
_retryPost(eventToSend); | ||
} else { | ||
console.error("Analytics client: " + | ||
"Bad Request, this is most likely a problem with the library, update to latest version."); | ||
} | ||
}).catch(res => { | ||
debugError(res); | ||
_retryPost(eventToSend); | ||
}); | ||
} | ||
function _resetGranularityTimer(disable) { | ||
if(granularityTimer){ | ||
clearInterval(granularityTimer); | ||
granularityTimer = null; | ||
granularityTimer = setInterval(()=>{ | ||
currentQuantisedTime = currentQuantisedTime + granularitySec; | ||
}, granularitySec*1000); | ||
} | ||
if(disable){ | ||
return; | ||
} | ||
granularityTimer = setInterval(()=>{ | ||
currentQuantisedTime = currentQuantisedTime + granularitySec; | ||
}, granularitySec*1000); | ||
} | ||
function _setupTimers(disable) { | ||
_resetGranularityTimer(disable); | ||
if(postTimer){ | ||
clearInterval(postTimer); | ||
postTimer = null; | ||
function _setupTimers(disable) { | ||
_resetGranularityTimer(disable); | ||
if(postTimer){ | ||
clearInterval(postTimer); | ||
postTimer = null; | ||
} | ||
if(disable){ | ||
return; | ||
} | ||
postTimer = setInterval(_postCurrentAnalyticsEvent, postIntervalSeconds*1000); | ||
} | ||
if(disable){ | ||
return; | ||
} | ||
postTimer = setInterval(_postCurrentAnalyticsEvent, postIntervalSeconds*1000); | ||
} | ||
async function _getServerConfig() { | ||
return new Promise((resolve, reject)=>{ | ||
let configURL = analyticsURL + `/getAppConfig?accountID=${accountID}&appName=${appName}`; | ||
window.fetch(configURL).then(async res=>{ | ||
switch (res.status) { | ||
case 200: | ||
let serverResponse = await res.json(); | ||
resolve(serverResponse); | ||
return; | ||
case 400: | ||
reject("Bad Request, check library version compatible?", res); | ||
break; | ||
default: | ||
reject("analytics client: Could not update from remote config. Continuing with defaults.", res); | ||
} | ||
}).catch(err => { | ||
reject("analytics client: Could not update from remote config. Continuing with defaults.", err); | ||
async function _getServerConfig() { | ||
return new Promise((resolve, reject)=>{ | ||
let configURL = analyticsURL + `/getAppConfig?accountID=${accountID}&appName=${appName}`; | ||
window.fetch(configURL).then(async res=>{ | ||
switch (res.status) { | ||
case 200: | ||
let serverResponse = await res.json(); | ||
resolve(serverResponse); | ||
return; | ||
case 400: | ||
reject("Bad Request, check library version compatible?", res); | ||
break; | ||
default: | ||
reject("analytics client: Could not update from remote config. Continuing with defaults.", res); | ||
} | ||
}).catch(err => { | ||
reject("analytics client: Could not update from remote config. Continuing with defaults.", err); | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
/** | ||
* Returns the analytics config for the app | ||
* @returns {Object} | ||
*/ | ||
function getAppConfig() { | ||
return { | ||
accountID, appName, disabled, | ||
uuid: userID, sessionID, | ||
postIntervalSeconds, granularitySec, analyticsURL, serverConfig | ||
}; | ||
} | ||
/** | ||
* Returns the analytics config for the app | ||
* @returns {Object} | ||
*/ | ||
function getAppConfig() { | ||
return { | ||
accountID, appName, disabled, | ||
uuid: userID, sessionID, | ||
postIntervalSeconds, granularitySec, analyticsURL, serverConfig | ||
}; | ||
} | ||
async function _initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit) { | ||
serverConfig = await _getServerConfig(); | ||
if(serverConfig !== {}){ | ||
// User init overrides takes precedence over server overrides | ||
postIntervalSeconds = postIntervalSecondsInit || | ||
serverConfig["postIntervalSecondsInit"] || DEFAULT_POST_INTERVAL_SECONDS; | ||
granularitySec = granularitySecInit || serverConfig["granularitySecInit"] || DEFAULT_GRANULARITY_IN_SECONDS; | ||
// For URLs, the server suggested URL takes precedence over user init values | ||
analyticsURL = serverConfig["analyticsURLInit"] || analyticsURL || DEFAULT_BASE_URL; | ||
disabled = serverConfig["disabled"] === true; | ||
_setupTimers(disabled); | ||
debugLog(`Init analytics Config from remote. disabled: ${disabled} | ||
async function _initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit) { | ||
serverConfig = await _getServerConfig(); | ||
if(serverConfig !== {}){ | ||
// User init overrides takes precedence over server overrides | ||
postIntervalSeconds = postIntervalSecondsInit || | ||
serverConfig["postIntervalSecondsInit"] || DEFAULT_POST_INTERVAL_SECONDS; | ||
granularitySec = granularitySecInit || serverConfig["granularitySecInit"] || DEFAULT_GRANULARITY_IN_SECONDS; | ||
// For URLs, the server suggested URL takes precedence over user init values | ||
analyticsURL = serverConfig["analyticsURLInit"] || analyticsURL || DEFAULT_BASE_URL; | ||
disabled = serverConfig["disabled"] === true; | ||
_setupTimers(disabled); | ||
debugLog(`Init analytics Config from remote. disabled: ${disabled} | ||
postIntervalSeconds:${postIntervalSeconds}, granularitySec: ${granularitySec} ,URL: ${analyticsURL}`); | ||
if(disabled){ | ||
console.warn(`Core Analytics is disabled from the server for app: ${accountID}:${appName}`); | ||
if(disabled){ | ||
console.warn(`Core Analytics is disabled from the server for app: ${accountID}:${appName}`); | ||
} | ||
} | ||
} | ||
} | ||
function _stripTrailingSlash(url) { | ||
return url.replace(/\/$/, ""); | ||
} | ||
function _stripTrailingSlash(url) { | ||
return url.replace(/\/$/, ""); | ||
} | ||
/** | ||
* Initialize the analytics session | ||
* @param accountIDInit Your analytics account id as configured in the server or core.ai analytics | ||
* @param appNameInit The app name to log the events against. | ||
* @param analyticsURLInit Optional: Provide your own analytics server address if you self-hosted the server | ||
* @param postIntervalSecondsInit Optional: This defines the interval between sending analytics events to the server. | ||
* Default is 10 minutes | ||
* @param granularitySecInit Optional: The smallest time period under which the events can be distinguished. Multiple | ||
* events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means | ||
* that any events that happen within 3 seconds cannot be distinguished in ordering. | ||
* @param debug set to true if you want to see detailed debug logs. | ||
*/ | ||
function initSession(accountIDInit, appNameInit, analyticsURLInit, postIntervalSecondsInit, granularitySecInit, debug) { | ||
if(!accountIDInit || !appNameInit){ | ||
throw new Error("accountID and appName must exist for init"); | ||
/** | ||
* Initialize the analytics session | ||
* @param accountIDInit Your analytics account id as configured in the server or core.ai analytics | ||
* @param appNameInit The app name to log the events against. | ||
* @param analyticsURLInit Optional: Provide your own analytics server address if you self-hosted the server | ||
* @param postIntervalSecondsInit Optional: This defines the interval between sending analytics events to the server. | ||
* Default is 10 minutes | ||
* @param granularitySecInit Optional: The smallest time period under which the events can be distinguished. Multiple | ||
* events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means | ||
* that any events that happen within 3 seconds cannot be distinguished in ordering. | ||
* @param debug set to true if you want to see detailed debug logs. | ||
*/ | ||
function initSession(accountIDInit, appNameInit, analyticsURLInit, postIntervalSecondsInit, granularitySecInit, debug) { | ||
if(!accountIDInit || !appNameInit){ | ||
throw new Error("accountID and appName must exist for init"); | ||
} | ||
analyticsURL = analyticsURLInit? _stripTrailingSlash(analyticsURLInit) : DEFAULT_BASE_URL; | ||
accountID = accountIDInit; | ||
appName = appNameInit; | ||
debugMode = debug || false; | ||
postIntervalSeconds = postIntervalSecondsInit || DEFAULT_POST_INTERVAL_SECONDS; | ||
granularitySec = granularitySecInit || DEFAULT_GRANULARITY_IN_SECONDS; | ||
postURL = analyticsURL + "/ingest"; | ||
_setupIDs(); | ||
currentAnalyticsEvent = _createAnalyticsEvent(); | ||
_setupTimers(); | ||
_initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit); | ||
} | ||
analyticsURL = analyticsURLInit? _stripTrailingSlash(analyticsURLInit) : DEFAULT_BASE_URL; | ||
accountID = accountIDInit; | ||
appName = appNameInit; | ||
debugMode = debug || false; | ||
postIntervalSeconds = postIntervalSecondsInit || DEFAULT_POST_INTERVAL_SECONDS; | ||
granularitySec = granularitySecInit || DEFAULT_GRANULARITY_IN_SECONDS; | ||
postURL = analyticsURL + "/ingest"; | ||
_setupIDs(); | ||
currentAnalyticsEvent = _createAnalyticsEvent(); | ||
_setupTimers(); | ||
_initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit); | ||
} | ||
function _ensureAnalyticsEventExists(eventType, category, subCategory) { | ||
let events = currentAnalyticsEvent.events; | ||
events[eventType] = events[eventType] || {}; | ||
events[eventType][category] = events[eventType][category] || {}; | ||
events[eventType][category][subCategory] = events[eventType][category][subCategory] || { | ||
time: [], // quantised time | ||
valueCount: [] // value and count array, If a single value, then it is count, else object {"val1":count1, ...} | ||
}; | ||
} | ||
function _validateEvent(eventType, category, subCategory, count, value) { | ||
_validateCurrentState(); | ||
if(!eventType || !category || !subCategory){ | ||
throw new Error("missing eventType or category or subCategory"); | ||
function _ensureAnalyticsEventExists(eventType, category, subCategory) { | ||
let events = currentAnalyticsEvent.events; | ||
events[eventType] = events[eventType] || {}; | ||
events[eventType][category] = events[eventType][category] || {}; | ||
events[eventType][category][subCategory] = events[eventType][category][subCategory] || { | ||
time: [], // quantised time | ||
valueCount: [] // value and count array, If a single value, then it is count, else object {"val1":count1, ...} | ||
}; | ||
} | ||
if(typeof(count)!== 'number' || count <0){ | ||
throw new Error("invalid count"); | ||
} | ||
if(typeof(value)!== 'number'){ | ||
throw new Error("invalid value"); | ||
} | ||
} | ||
function _updateExistingAnalyticsEvent(index, eventType, category, subCategory, count, newValue) { | ||
let events = currentAnalyticsEvent.events; | ||
const storedValueIsCount = typeof(events[eventType][category][subCategory]["valueCount"][index]) === 'number'; | ||
if(storedValueIsCount && newValue === 0){ | ||
events[eventType][category][subCategory]["valueCount"][index] += count; | ||
} else if(storedValueIsCount && newValue !== 0){ | ||
let newValueCount = {}; | ||
newValueCount[newValue] = count; | ||
newValueCount[0] = events[eventType][category][subCategory]["valueCount"][index]; | ||
events[eventType][category][subCategory]["valueCount"][index] = newValueCount; | ||
} else if(!storedValueIsCount){ | ||
let storedValueObject = events[eventType][category][subCategory]["valueCount"][index]; | ||
storedValueObject[newValue] = (storedValueObject[newValue] || 0) + count; | ||
function _validateEvent(eventType, category, subCategory, count, value) { | ||
_validateCurrentState(); | ||
if(!eventType || !category || !subCategory){ | ||
throw new Error("missing eventType or category or subCategory"); | ||
} | ||
if(typeof(count)!== 'number' || count <0){ | ||
throw new Error("invalid count"); | ||
} | ||
if(typeof(value)!== 'number'){ | ||
throw new Error("invalid value"); | ||
} | ||
} | ||
currentAnalyticsEvent.numEventsTotal += 1; | ||
} | ||
/** | ||
* Register an analytics event. The events will be aggregated and send to the analytics server periodically. | ||
* @param eventType - String, required | ||
* @param eventCategory - String, required | ||
* @param subCategory - String, required | ||
* @param eventCount (Optional) : A non-negative number indicating the number of times the event (or an event with a | ||
* particular value if a value is specified) happened. defaults to 1. | ||
* @param eventValue (Optional) : A number value associated with the event. defaults to 0 | ||
*/ | ||
function analyticsEvent(eventType, eventCategory, subCategory, eventCount=1, eventValue=0) { | ||
if(disabled){ | ||
return; | ||
} | ||
_validateEvent(eventType, eventCategory, subCategory, eventCount, eventValue); | ||
_ensureAnalyticsEventExists(eventType, eventCategory, subCategory); | ||
let events = currentAnalyticsEvent.events; | ||
let timeArray = events[eventType][eventCategory][subCategory]["time"]; | ||
let lastTime = timeArray.length>0? timeArray[timeArray.length-1] : null; | ||
if(lastTime !== currentQuantisedTime){ | ||
events[eventType][eventCategory][subCategory]["time"].push(currentQuantisedTime); | ||
if(eventValue===0){ | ||
events[eventType][eventCategory][subCategory]["valueCount"].push(eventCount); | ||
} else { | ||
let valueCount = {}; | ||
valueCount[eventValue] = eventCount; | ||
events[eventType][eventCategory][subCategory]["valueCount"].push(valueCount); | ||
function _updateExistingAnalyticsEvent(index, eventType, category, subCategory, count, newValue) { | ||
let events = currentAnalyticsEvent.events; | ||
const storedValueIsCount = typeof(events[eventType][category][subCategory]["valueCount"][index]) === 'number'; | ||
if(storedValueIsCount && newValue === 0){ | ||
events[eventType][category][subCategory]["valueCount"][index] += count; | ||
} else if(storedValueIsCount && newValue !== 0){ | ||
let newValueCount = {}; | ||
newValueCount[newValue] = count; | ||
newValueCount[0] = events[eventType][category][subCategory]["valueCount"][index]; | ||
events[eventType][category][subCategory]["valueCount"][index] = newValueCount; | ||
} else if(!storedValueIsCount){ | ||
let storedValueObject = events[eventType][category][subCategory]["valueCount"][index]; | ||
storedValueObject[newValue] = (storedValueObject[newValue] || 0) + count; | ||
} | ||
currentAnalyticsEvent.numEventsTotal += 1; | ||
return; | ||
} | ||
let modificationIndex = events[eventType][eventCategory][subCategory]["valueCount"].length -1; | ||
_updateExistingAnalyticsEvent(modificationIndex, eventType, eventCategory, subCategory, eventCount, eventValue); | ||
/** | ||
* Register an analytics event. The events will be aggregated and send to the analytics server periodically. | ||
* @param eventType - String, required | ||
* @param eventCategory - String, required | ||
* @param subCategory - String, required | ||
* @param eventCount (Optional) : A non-negative number indicating the number of times the event (or an event with a | ||
* particular value if a value is specified) happened. defaults to 1. | ||
* @param eventValue (Optional) : A number value associated with the event. defaults to 0 | ||
*/ | ||
function event(eventType, eventCategory, subCategory, eventCount=1, eventValue=0) { | ||
if(disabled){ | ||
return; | ||
} | ||
_validateEvent(eventType, eventCategory, subCategory, eventCount, eventValue); | ||
_ensureAnalyticsEventExists(eventType, eventCategory, subCategory); | ||
let events = currentAnalyticsEvent.events; | ||
let timeArray = events[eventType][eventCategory][subCategory]["time"]; | ||
let lastTime = timeArray.length>0? timeArray[timeArray.length-1] : null; | ||
if(lastTime !== currentQuantisedTime){ | ||
events[eventType][eventCategory][subCategory]["time"].push(currentQuantisedTime); | ||
if(eventValue===0){ | ||
events[eventType][eventCategory][subCategory]["valueCount"].push(eventCount); | ||
} else { | ||
let valueCount = {}; | ||
valueCount[eventValue] = eventCount; | ||
events[eventType][eventCategory][subCategory]["valueCount"].push(valueCount); | ||
} | ||
currentAnalyticsEvent.numEventsTotal += 1; | ||
return; | ||
} | ||
let modificationIndex = events[eventType][eventCategory][subCategory]["valueCount"].length -1; | ||
_updateExistingAnalyticsEvent(modificationIndex, eventType, eventCategory, subCategory, eventCount, eventValue); | ||
} | ||
analytics.initSession = initSession; | ||
analytics.getCurrentAnalyticsEvent = getCurrentAnalyticsEvent; | ||
analytics.event = event; | ||
analytics.getAppConfig = getAppConfig; | ||
} | ||
export { | ||
initSession, | ||
getCurrentAnalyticsEvent, | ||
analyticsEvent, | ||
getAppConfig | ||
}; | ||
init(); |
Sorry, the diff of this file is not supported yet
70402
9
317
141