@aicore/core-analytics-client-lib 1.0.2 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,20 +19,15 @@ events for [Core-Analytics-Server](https://github.com/aicore/Core-Analytics-Serv
19
19
 
20
20
  # Usage
21
21
 
22
- ## Initialize the session
22
+ ## Load the Library
23
23
  Embed the script in your HTML file :
24
24
  ```html
25
- <html lang="en">
26
- <script type="module">
27
- // For production use cases, use url: https://unpkg.com/aicore/core-analytics-client-lib/dist/analytics.min.js
28
- // The below url is for development purposes only.
29
- import {initSession} from "https://unpkg.com/aicore/core-analytics-client-lib/src/analytics.js";
30
- initSession("accountID", "appName");
31
- </script>
32
- </html>
25
+ <script src="https://unpkg.com/@aicore/core-analytics-client-lib/src/analytics.js"></script>
33
26
  ```
27
+ This will create a global `analytics` variable which can be used to access the analytics APIs.
34
28
 
35
- initSession(): Initialize the analytics session. It takes the following parameters:
29
+ ## Initialize the analytics session.
30
+ Call `analytics.initSession()` after loading the library. It takes the following parameters:
36
31
 
37
32
  * `accountID`: Your analytics account id as configured in the server or core.ai analytics
38
33
  * `appName`: The app name to log the events against. Eg: "phoenixCode"
@@ -40,37 +35,44 @@ initSession(): Initialize the analytics session. It takes the following paramete
40
35
  * `granularitySec` (_Optional_): The smallest time period under which the events can be distinguished. Multiple
41
36
  events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means
42
37
  that any events that happen within 3 seconds cannot be distinguished in ordering.
43
- * `postBaseURLInit` Optional: Provide your own analytics server address if you self-hosted the server
38
+ * `analyticsURL` (_Optional_): Provide your own analytics server address if you self-hosted the server
39
+ * `debug` (_Optional_): set to true if you want to see detailed debug logs.
44
40
 
41
+ ### usageExample
45
42
  ```javascript
43
+ // Init with default values.
44
+ analytics.initSession("accountID", "appName");
45
+
46
46
  // Example for custom initSession where the analytics aggregated data
47
47
  // is posted to custom server https://localhost:3000 every 600 secs
48
48
  // with a granularity(resolution) of 5 seconds.
49
+ analytics.initSession("accountID", "appName", "https://localhost:3000", 600, 5);
49
50
 
50
- initSession("accountID", "appName", 600, 5, "https://localhost:3000");
51
+ // To initSession in debug mode set debug arg in init to true:
52
+ analytics.initSession("accountID", "appName", "https://localhost:3000", 600, 5, true);
51
53
  ```
52
54
 
53
55
  ## Raising analytics events
54
- Once `initSession` is called, we can now start logging analytics events by calling `analyticsEvent` API.
56
+ Once `initSession` is called, we can now start logging analytics events by calling `analytics.event` API.
55
57
  The API registers an analytics event. The events will be aggregated and send to the analytics server periodically.
56
58
 
57
59
  ```javascript
58
60
  // analyticsEvent(eventType, eventCategory, subCategory, eventCount, eventValue);
59
61
 
60
62
  // Eg: event without counts and values
61
- analyticsEvent("platform", "os", "linux");
63
+ analytics.event("platform", "os", "linux");
62
64
 
63
65
  // Eg: event with count, here it logs that html file is opened 100 times
64
- analyticsEvent("file", "opened", "html", 100);
66
+ analytics.event("file", "opened", "html", 100);
65
67
 
66
68
  // Eg: event with count and value, here it logs that the startup time is 250 milliseconds.
67
69
  // Note that the value is unitless from analytics perspective. unit is deduced from subCategory name
68
- analyticsEvent("platform", "performance", "startupTimeMs", 1, 250);
70
+ analytics.event("platform", "performance", "startupTimeMs", 1, 250);
69
71
 
70
72
  // Eg: event with fractional value.
71
- analyticsEvent("platform", "CPU", "utilization", 1, .45);
73
+ analytics.event("platform", "CPU", "utilization", 1, .45);
72
74
  // Eg. Here we register that the system has 8 cores with each core having 2300MHz frequency.
73
- analyticsEvent("platform", "CPU", "coreCountsAndFrequencyMhz", 8, 2300);
75
+ analytics.event("platform", "CPU", "coreCountsAndFrequencyMhz", 8, 2300);
74
76
  ```
75
77
  ### API parameters
76
78
  * `eventType` - A string, required
@@ -1,3 +1,4 @@
1
- let accountID,appName,userID,sessionID,postIntervalSeconds,granularitySec,postURL;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;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,granularitySec:granularitySec,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 t=localStorage.getItem(USERID_LOCAL_STORAGE_KEY);return t||(t=crypto.randomUUID(),localStorage.setItem(USERID_LOCAL_STORAGE_KEY,t)),t}function _getOrCreateSessionID(){let t=sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY);return t||(t=Math.random().toString(36).substr(2,10),sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY,t)),t}function _setupIDs(){userID=_getOrCreateUserID(),sessionID=_getOrCreateSessionID()}function _retryPost(t){t.backoffCount=(t.backoffCount||0)+1,console.log(`Failed to call core analytics server. Will retry in ${DEFAULT_RETRY_TIME_IN_SECONDS*t.backoffCount}s: `),setTimeout(()=>{_postCurrentAnalyticsEvent(t)},1e3*DEFAULT_RETRY_TIME_IN_SECONDS*t.backoffCount)}function _postCurrentAnalyticsEvent(e){var t;e||(e=currentAnalyticsEvent,currentAnalyticsEvent=_createAnalyticsEvent()),0!==e.numEventsTotal&&((t=JSON.stringify(e)).length>POST_LARGE_DATA_THRESHOLD_BYTES&&console.warn(`Analytics event generated is very large at greater than ${t.length}B. This
2
- typically means that you may be sending too many value events? .`),window.fetch(postURL,{method:"POST",headers:{"Content-Type":"application/json"},body:t}).then(t=>{200!==t.status&&(400!==t.status?_retryPost(e):console.error("Bad Request, this is most likely a problem with the library, update to latest version."))}).catch(t=>{console.error(t),_retryPost(e)}))}function _setupTimers(){granularityTimer&&(clearInterval(granularityTimer),granularityTimer=null),granularityTimer=setInterval(()=>{currentQuantisedTime+=granularitySec},1e3*granularitySec),postTimer&&(clearInterval(postTimer),postTimer=null),postTimer=setInterval(_postCurrentAnalyticsEvent,1e3*postIntervalSeconds)}function initSession(t,e,n,r,a){if(!t||!e)throw new Error("accountID and appName must exist for init");accountID=t,appName=e,postIntervalSeconds=n||DEFAULT_POST_INTERVAL_SECONDS,granularitySec=r||DEFAULT_GRANULARITY_IN_SECONDS,postURL=(a||DEFAULT_BASE_URL)+"/ingest",_setupIDs(),currentAnalyticsEvent=_createAnalyticsEvent(),_setupTimers()}function _ensureAnalyticsEventExists(t,e,n){let r=currentAnalyticsEvent.events;r[t]=r[t]||{},r[t][e]=r[t][e]||{},r[t][e][n]=r[t][e][n]||{time:[],valueCount:[]}}function _validateEvent(t,e,n,r,a){if(_validateCurrentState(),!t||!e||!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(e,n,r,a,s,i){let o=currentAnalyticsEvent.events;var t="number"==typeof o[n][r][a].valueCount[e];if(t&&0===i)o[n][r][a].valueCount[e]+=s;else if(t&&0!==i){let t={};t[i]=s,t[0]=o[n][r][a].valueCount[e],o[n][r][a].valueCount[e]=t}else if(!t){let t=o[n][r][a].valueCount[e];t[i]=(t[i]||0)+s}currentAnalyticsEvent.numEventsTotal+=1}function analyticsEvent(e,n,r,a=1,s=0){_validateEvent(e,n,r,a,s),_ensureAnalyticsEventExists(e,n,r);let i=currentAnalyticsEvent.events;var t=i[e][n][r].time;if((0<t.length?t[t.length-1]:null)===currentQuantisedTime)_updateExistingAnalyticsEvent(i[e][n][r].valueCount.length-1,e,n,r,a,s);else{if(i[e][n][r].time.push(currentQuantisedTime),0===s)i[e][n][r].valueCount.push(a);else{let t={};t[s]=a,i[e][n][r].valueCount.push(t)}currentAnalyticsEvent.numEventsTotal+=1}}export{initSession,getCurrentAnalyticsEvent,analyticsEvent};
1
+ 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
2
+ 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}
3
+ 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();
3
4
  //# sourceMappingURL=analytics.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"src/analytics.js","sources":["src/analytics.js"],"names":["analytics","init","let","accountID","appName","userID","sessionID","postIntervalSeconds","granularitySec","analyticsURL","postURL","serverConfig","DEFAULT_GRANULARITY_IN_SECONDS","DEFAULT_RETRY_TIME_IN_SECONDS","DEFAULT_POST_INTERVAL_SECONDS","USERID_LOCAL_STORAGE_KEY","POST_LARGE_DATA_THRESHOLD_BYTES","currentAnalyticsEvent","IS_NODE_ENV","window","DEFAULT_BASE_URL","granularityTimer","postTimer","currentQuantisedTime","disabled","debugMode","debugLog","args","console","log","Error","_createAnalyticsEvent","schemaVersion","uuid","unixTimestampUTC","Date","numEventsTotal","events","_validateCurrentState","_setupIDs","localUserID","localStorage","getItem","crypto","randomUUID","setItem","_getOrCreateUserID","localSessionID","sessionStorage","Math","random","toString","substr","_getOrCreateSessionID","_retryPost","eventToSend","backoffCount","setTimeout","_postCurrentAnalyticsEvent","textToSend","_resetGranularityTimer","JSON","stringify","length","warn","fetch","method","headers","Content-Type","body","then","res","status","error","catch","debugError","disable","clearInterval","setInterval","_setupTimers","async","_initFromRemoteConfig","postIntervalSecondsInit","granularitySecInit","Promise","resolve","reject","configURL","serverResponse","json","err","initSession","accountIDInit","appNameInit","analyticsURLInit","debug","replace","getCurrentAnalyticsEvent","parse","event","eventType","eventCategory","subCategory","eventCount","eventValue","_validateEvent","category","count","value","_ensureAnalyticsEventExists","time","valueCount","timeArray","modificationIndex","_updateExistingAnalyticsEvent","index","newValue","storedValueIsCount","newValueCount","storedValueObject","push","getAppConfig"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicore/core-analytics-client-lib",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "Analytics client library for https://github.com/aicore/Core-Analytics-Server",
5
5
  "main": "dist/analytics.min.js",
6
6
  "type": "module",
@@ -19,17 +19,23 @@
19
19
  "lint": "eslint --quiet src test",
20
20
  "lint:fix": "eslint --quiet --fix src test",
21
21
  "prepare": "husky install",
22
- "test": "echo please open test/index.html in browser to run tests",
23
- "test:unit": "echo please open test/index.html in browser to run tests",
22
+ "test": "echo please run - npm run serve - and then open test/unit-test.html in browser to run tests",
23
+ "test:unit": "npm run test",
24
24
  "test:integ": "echo integration tests are disabled in this repo",
25
- "cover": "echo please open test/index.html in browser to run tests",
26
- "cover:unit": "echo please open test/index.html in browser to run tests",
27
- "cover:integ": "echo please open test/index.html in browser to run tests",
28
- "build": "",
29
- "patchVersion": "npm --no-git-tag-version version patch",
25
+ "cover": "echo no coverage for now",
26
+ "cover:unit": "echo no coverage for now",
27
+ "cover:integ": "echo no coverage for now",
28
+ "build": "npm run minify",
30
29
  "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'\"",
31
- "release": "npm run patchVersion && npm run minify"
30
+ "bumpPatchVersion": "npm --no-git-tag-version version patch",
31
+ "bumpPatchVersionWithGitTag": "npm version patch",
32
+ "release": "npm run minify && npm run bumpPatchVersionWithGitTag",
33
+ "serve": "http-server . -p 8000 -c-1"
32
34
  },
35
+ "files": [
36
+ "src",
37
+ "dist"
38
+ ],
33
39
  "repository": {
34
40
  "type": "git",
35
41
  "url": "git+https://github.com/aicore/core-analytics-client-lib.git"
@@ -41,13 +47,14 @@
41
47
  },
42
48
  "homepage": "https://github.com/aicore/core-analytics-client-lib#readme",
43
49
  "devDependencies": {
44
- "@commitlint/cli": "16.1.0",
45
- "@commitlint/config-conventional": "16.0.0",
46
- "c8": "7.11.0",
50
+ "@commitlint/cli": "16.3.0",
51
+ "@commitlint/config-conventional": "16.2.4",
52
+ "c8": "7.11.2",
47
53
  "chai": "4.3.6",
48
- "eslint": "8.8.0",
54
+ "eslint": "8.15.0",
49
55
  "husky": "7.0.4",
50
- "mocha": "9.2.0",
51
- "uglify-js": "3.15.1"
56
+ "mocha": "9.2.2",
57
+ "uglify-js": "3.15.5",
58
+ "http-server": "14.1.0"
52
59
  }
53
60
  }
@@ -0,0 +1,330 @@
1
+ // GNU AGPL-3.0 License Copyright (c) 2021 - present core.ai . All rights reserved.
2
+
3
+ // jshint ignore: start
4
+ /*global localStorage, sessionStorage, crypto*/
5
+
6
+ var analytics = {};
7
+
8
+ function init() {
9
+ let accountID, appName, userID, sessionID, postIntervalSeconds, granularitySec, analyticsURL, postURL, serverConfig;
10
+ const DEFAULT_GRANULARITY_IN_SECONDS = 3;
11
+ const DEFAULT_RETRY_TIME_IN_SECONDS = 30;
12
+ const DEFAULT_POST_INTERVAL_SECONDS = 600; // 10 minutes
13
+ const USERID_LOCAL_STORAGE_KEY = 'aicore.analytics.userID';
14
+ const POST_LARGE_DATA_THRESHOLD_BYTES = 10000;
15
+ let currentAnalyticsEvent = null;
16
+ const IS_NODE_ENV = (typeof window === 'undefined');
17
+ let DEFAULT_BASE_URL = "https://analytics.core.ai";
18
+
19
+ let granularityTimer;
20
+ let postTimer;
21
+ let currentQuantisedTime = 0;
22
+ let disabled = false;
23
+ let debugMode = false;
24
+
25
+ function debugLog(...args) {
26
+ if(!debugMode){
27
+ return;
28
+ }
29
+ console.log(...args);
30
+ }
31
+
32
+ function debugError(...args) {
33
+ if(!debugMode){
34
+ return;
35
+ }
36
+ console.error(...args);
37
+ }
38
+
39
+
40
+ if(IS_NODE_ENV){
41
+ throw new Error("Node environment is not currently supported");
42
+ }
43
+
44
+ function _createAnalyticsEvent() {
45
+ return {
46
+ schemaVersion: 1,
47
+ accountID: accountID,
48
+ appName: appName,
49
+ uuid: userID,
50
+ sessionID: sessionID,
51
+ unixTimestampUTC: +new Date(),
52
+ numEventsTotal: 0,
53
+ events: {}
54
+ };
55
+ }
56
+
57
+ function _validateCurrentState() {
58
+ if(!currentAnalyticsEvent){
59
+ throw new Error("Please call initSession before using any analytics event");
60
+ }
61
+ }
62
+
63
+ function getCurrentAnalyticsEvent() {
64
+ _validateCurrentState();
65
+ // return a clone
66
+ return JSON.parse(JSON.stringify(currentAnalyticsEvent));
67
+ }
68
+
69
+ function _getOrCreateUserID() {
70
+ let localUserID = localStorage.getItem(USERID_LOCAL_STORAGE_KEY);
71
+ if(!localUserID){
72
+ localUserID = crypto.randomUUID();
73
+ localStorage.setItem(USERID_LOCAL_STORAGE_KEY, localUserID);
74
+ }
75
+ return localUserID;
76
+ }
77
+
78
+ function _getOrCreateSessionID() {
79
+ let localSessionID = sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY);
80
+ if(!localSessionID){
81
+ localSessionID = Math.random().toString(36).substr(2, 10);
82
+ sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY, localSessionID);
83
+ }
84
+ return localSessionID;
85
+ }
86
+
87
+ function _setupIDs() {
88
+ userID = _getOrCreateUserID();
89
+ sessionID = _getOrCreateSessionID();
90
+ }
91
+
92
+ function _retryPost(eventToSend) {
93
+ eventToSend.backoffCount = (eventToSend.backoffCount || 0) + 1;
94
+ debugLog(`Failed to call core analytics server. Will retry in ${
95
+ DEFAULT_RETRY_TIME_IN_SECONDS * eventToSend.backoffCount}s: `);
96
+ setTimeout(()=>{
97
+ _postCurrentAnalyticsEvent(eventToSend);
98
+ }, DEFAULT_RETRY_TIME_IN_SECONDS * 1000 * eventToSend.backoffCount);
99
+ }
100
+
101
+ function _postCurrentAnalyticsEvent(eventToSend) {
102
+ if(disabled){
103
+ return;
104
+ }
105
+ if(!eventToSend){
106
+ eventToSend = currentAnalyticsEvent;
107
+ currentQuantisedTime = 0;
108
+ _resetGranularityTimer();
109
+ currentAnalyticsEvent = _createAnalyticsEvent();
110
+ }
111
+ if(eventToSend.numEventsTotal === 0 ){
112
+ return;
113
+ }
114
+ let textToSend = JSON.stringify(eventToSend);
115
+ if(textToSend.length > POST_LARGE_DATA_THRESHOLD_BYTES){
116
+ console.warn(`Analytics event generated is very large at greater than ${textToSend.length}B. This
117
+ typically means that you may be sending too many value events? .`);
118
+ }
119
+ debugLog("Sending Analytics data of length: ", textToSend.length, "B");
120
+ window.fetch(postURL, {
121
+ method: "POST",
122
+ headers: {'Content-Type': 'application/json'},
123
+ body: textToSend
124
+ }).then(res=>{
125
+ if(res.status === 200){
126
+ return;
127
+ }
128
+ if(res.status !== 400){ // we don't retry bad requests
129
+ _retryPost(eventToSend);
130
+ } else {
131
+ console.error("Analytics client: " +
132
+ "Bad Request, this is most likely a problem with the library, update to latest version.");
133
+ }
134
+ }).catch(res => {
135
+ debugError(res);
136
+ _retryPost(eventToSend);
137
+ });
138
+ }
139
+
140
+ function _resetGranularityTimer(disable) {
141
+ if(granularityTimer){
142
+ clearInterval(granularityTimer);
143
+ granularityTimer = null;
144
+ }
145
+ if(disable){
146
+ return;
147
+ }
148
+ granularityTimer = setInterval(()=>{
149
+ currentQuantisedTime = currentQuantisedTime + granularitySec;
150
+ }, granularitySec*1000);
151
+ }
152
+
153
+ function _setupTimers(disable) {
154
+ _resetGranularityTimer(disable);
155
+ if(postTimer){
156
+ clearInterval(postTimer);
157
+ postTimer = null;
158
+ }
159
+ if(disable){
160
+ return;
161
+ }
162
+ postTimer = setInterval(_postCurrentAnalyticsEvent, postIntervalSeconds*1000);
163
+ }
164
+
165
+ async function _getServerConfig() {
166
+ return new Promise((resolve, reject)=>{
167
+ let configURL = analyticsURL + `/getAppConfig?accountID=${accountID}&appName=${appName}`;
168
+ window.fetch(configURL).then(async res=>{
169
+ switch (res.status) {
170
+ case 200:
171
+ let serverResponse = await res.json();
172
+ resolve(serverResponse);
173
+ return;
174
+ case 400:
175
+ reject("Bad Request, check library version compatible?", res);
176
+ break;
177
+ default:
178
+ reject("analytics client: Could not update from remote config. Continuing with defaults.", res);
179
+ }
180
+ }).catch(err => {
181
+ reject("analytics client: Could not update from remote config. Continuing with defaults.", err);
182
+ });
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Returns the analytics config for the app
188
+ * @returns {Object}
189
+ */
190
+ function getAppConfig() {
191
+ return {
192
+ accountID, appName, disabled,
193
+ uuid: userID, sessionID,
194
+ postIntervalSeconds, granularitySec, analyticsURL, serverConfig
195
+ };
196
+ }
197
+
198
+ async function _initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit) {
199
+ serverConfig = await _getServerConfig();
200
+ if(serverConfig !== {}){
201
+ // User init overrides takes precedence over server overrides
202
+ postIntervalSeconds = postIntervalSecondsInit ||
203
+ serverConfig["postIntervalSecondsInit"] || DEFAULT_POST_INTERVAL_SECONDS;
204
+ granularitySec = granularitySecInit || serverConfig["granularitySecInit"] || DEFAULT_GRANULARITY_IN_SECONDS;
205
+ // For URLs, the server suggested URL takes precedence over user init values
206
+ analyticsURL = serverConfig["analyticsURLInit"] || analyticsURL || DEFAULT_BASE_URL;
207
+ disabled = serverConfig["disabled"] === true;
208
+ _setupTimers(disabled);
209
+ debugLog(`Init analytics Config from remote. disabled: ${disabled}
210
+ postIntervalSeconds:${postIntervalSeconds}, granularitySec: ${granularitySec} ,URL: ${analyticsURL}`);
211
+ if(disabled){
212
+ console.warn(`Core Analytics is disabled from the server for app: ${accountID}:${appName}`);
213
+ }
214
+ }
215
+ }
216
+
217
+ function _stripTrailingSlash(url) {
218
+ return url.replace(/\/$/, "");
219
+ }
220
+
221
+ /**
222
+ * Initialize the analytics session
223
+ * @param accountIDInit Your analytics account id as configured in the server or core.ai analytics
224
+ * @param appNameInit The app name to log the events against.
225
+ * @param analyticsURLInit Optional: Provide your own analytics server address if you self-hosted the server
226
+ * @param postIntervalSecondsInit Optional: This defines the interval between sending analytics events to the server.
227
+ * Default is 10 minutes
228
+ * @param granularitySecInit Optional: The smallest time period under which the events can be distinguished. Multiple
229
+ * events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means
230
+ * that any events that happen within 3 seconds cannot be distinguished in ordering.
231
+ * @param debug set to true if you want to see detailed debug logs.
232
+ */
233
+ function initSession(accountIDInit, appNameInit, analyticsURLInit, postIntervalSecondsInit, granularitySecInit, debug) {
234
+ if(!accountIDInit || !appNameInit){
235
+ throw new Error("accountID and appName must exist for init");
236
+ }
237
+ analyticsURL = analyticsURLInit? _stripTrailingSlash(analyticsURLInit) : DEFAULT_BASE_URL;
238
+ accountID = accountIDInit;
239
+ appName = appNameInit;
240
+ debugMode = debug || false;
241
+ postIntervalSeconds = postIntervalSecondsInit || DEFAULT_POST_INTERVAL_SECONDS;
242
+ granularitySec = granularitySecInit || DEFAULT_GRANULARITY_IN_SECONDS;
243
+ postURL = analyticsURL + "/ingest";
244
+ _setupIDs();
245
+ currentAnalyticsEvent = _createAnalyticsEvent();
246
+ _setupTimers();
247
+ _initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit);
248
+ }
249
+
250
+ function _ensureAnalyticsEventExists(eventType, category, subCategory) {
251
+ let events = currentAnalyticsEvent.events;
252
+ events[eventType] = events[eventType] || {};
253
+ events[eventType][category] = events[eventType][category] || {};
254
+ events[eventType][category][subCategory] = events[eventType][category][subCategory] || {
255
+ time: [], // quantised time
256
+ valueCount: [] // value and count array, If a single value, then it is count, else object {"val1":count1, ...}
257
+ };
258
+ }
259
+
260
+ function _validateEvent(eventType, category, subCategory, count, value) {
261
+ _validateCurrentState();
262
+ if(!eventType || !category || !subCategory){
263
+ throw new Error("missing eventType or category or subCategory");
264
+ }
265
+ if(typeof(count)!== 'number' || count <0){
266
+ throw new Error("invalid count");
267
+ }
268
+ if(typeof(value)!== 'number'){
269
+ throw new Error("invalid value");
270
+ }
271
+ }
272
+
273
+ function _updateExistingAnalyticsEvent(index, eventType, category, subCategory, count, newValue) {
274
+ let events = currentAnalyticsEvent.events;
275
+ const storedValueIsCount = typeof(events[eventType][category][subCategory]["valueCount"][index]) === 'number';
276
+ if(storedValueIsCount && newValue === 0){
277
+ events[eventType][category][subCategory]["valueCount"][index] += count;
278
+ } else if(storedValueIsCount && newValue !== 0){
279
+ let newValueCount = {};
280
+ newValueCount[newValue] = count;
281
+ newValueCount[0] = events[eventType][category][subCategory]["valueCount"][index];
282
+ events[eventType][category][subCategory]["valueCount"][index] = newValueCount;
283
+ } else if(!storedValueIsCount){
284
+ let storedValueObject = events[eventType][category][subCategory]["valueCount"][index];
285
+ storedValueObject[newValue] = (storedValueObject[newValue] || 0) + count;
286
+ }
287
+ currentAnalyticsEvent.numEventsTotal += 1;
288
+ }
289
+
290
+ /**
291
+ * Register an analytics event. The events will be aggregated and send to the analytics server periodically.
292
+ * @param eventType - String, required
293
+ * @param eventCategory - String, required
294
+ * @param subCategory - String, required
295
+ * @param eventCount (Optional) : A non-negative number indicating the number of times the event (or an event with a
296
+ * particular value if a value is specified) happened. defaults to 1.
297
+ * @param eventValue (Optional) : A number value associated with the event. defaults to 0
298
+ */
299
+ function event(eventType, eventCategory, subCategory, eventCount=1, eventValue=0) {
300
+ if(disabled){
301
+ return;
302
+ }
303
+ _validateEvent(eventType, eventCategory, subCategory, eventCount, eventValue);
304
+ _ensureAnalyticsEventExists(eventType, eventCategory, subCategory);
305
+ let events = currentAnalyticsEvent.events;
306
+ let timeArray = events[eventType][eventCategory][subCategory]["time"];
307
+ let lastTime = timeArray.length>0? timeArray[timeArray.length-1] : null;
308
+ if(lastTime !== currentQuantisedTime){
309
+ events[eventType][eventCategory][subCategory]["time"].push(currentQuantisedTime);
310
+ if(eventValue===0){
311
+ events[eventType][eventCategory][subCategory]["valueCount"].push(eventCount);
312
+ } else {
313
+ let valueCount = {};
314
+ valueCount[eventValue] = eventCount;
315
+ events[eventType][eventCategory][subCategory]["valueCount"].push(valueCount);
316
+ }
317
+ currentAnalyticsEvent.numEventsTotal += 1;
318
+ return;
319
+ }
320
+ let modificationIndex = events[eventType][eventCategory][subCategory]["valueCount"].length -1;
321
+ _updateExistingAnalyticsEvent(modificationIndex, eventType, eventCategory, subCategory, eventCount, eventValue);
322
+ }
323
+
324
+ analytics.initSession = initSession;
325
+ analytics.getCurrentAnalyticsEvent = getCurrentAnalyticsEvent;
326
+ analytics.event = event;
327
+ analytics.getAppConfig = getAppConfig;
328
+ }
329
+
330
+ init();
@@ -0,0 +1,91 @@
1
+ # Client schema
2
+ The `analytics client js lib` client periodically sends data to the analytics backend as JSON aggregated over a time period. The JSON format is as follows:
3
+
4
+ schema:
5
+ ```json
6
+ {
7
+ "schemaVersion" : 1,
8
+ "appName" : "app1",
9
+ "uuid": "u1",
10
+ "sessionID": "s1",
11
+ "unixTimestampUTC" : 1643043376,
12
+ "numEventsTotal": 8,
13
+ "events":{
14
+ "eventType":{
15
+ "category":{
16
+ "subCategory": {
17
+ "time": [0,2],
18
+ "valueCount": [
19
+ 1,
20
+ [{
21
+ "1": 5,
22
+ "3": 2
23
+ }]
24
+ ]
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+
31
+ ```
32
+ ## Fileds for schema version:1
33
+ 1. `schemaVersion` : The version number for schema followed by the client. Currently, only 1 schema exists.
34
+ 1. `appName`: The application name for which the event is being raised.
35
+ 2. `uuid`: The unique id for the user. or the string `default` if there is no UUID/anonymous analytics is used from client.
36
+ 3. `sessionID`: The unique session-id autogenerated by the client lib for each session. Will be reset for every session.
37
+ 5. `unixTimestampUTC`: The start Unix time from which the client time is being recorded.
38
+ 5. `numEventsTotal`: The sum total of all events raised from the client. This aggregates all count values `c`
39
+ 6. `events` : A map of events that took place from `unixTimestampUTC`. The keys are nested in order `eventType` > `category` > `subCategory`
40
+ * Within a subcategory, `time` array specifies the `time drift` in seconds from the `unixTimestampUTC` for which the current event is raised.
41
+ * `valueCount` array specifies the `values` and/or related counts. If the array
42
+ item is a number, then it signifies count. If it is an object, then
43
+ it indicates values and the corresponding counts.
44
+
45
+ ## sample analytics event
46
+ Schema
47
+ ```json
48
+ {
49
+ "schemaVersion": 1,
50
+ "appName": "brackets",
51
+ "uuid": "u1",
52
+ "sessionID": "s1",
53
+ "unixTimestampUTC": 1643043376,
54
+ "numEventsTotal": 5,
55
+ "events": {
56
+ "platform": {
57
+ "os": {
58
+ "win": {
59
+ "time": [0],
60
+ "valueCount": [1]
61
+ }
62
+ },
63
+ "performance": {
64
+ "fileOpenTimeMs": {
65
+ "time": [0, 2, 5],
66
+ "valueCount": [
67
+ [{
68
+ "121": 1
69
+ }],
70
+ [{
71
+ "100": 1,
72
+ "300": 1
73
+ }],
74
+ [{
75
+ "100": 5
76
+ }]
77
+ ]
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ ```
85
+ The above event raises two event types :
86
+ 1. (platform, os, win)
87
+ 2. (platform, performance, fileOpenTimeMs)
88
+ * at time `1643043376 + 0`, a value of `121` is registered and count is 1.
89
+ * at time `1643043376 + 3000`, two values `[100,300]` are registered with a count each of 1.
90
+ * at time `1643043376 + 6000`, a value of `500` is registered with a count of 5.
91
+