@zoho_apptics/apptics-js-sdk 1.0.2 → 2.0.0-beta.1
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/CHANGELOG.md +193 -0
- package/LICENSE +13 -0
- package/README.md +846 -11
- package/dist/apptics.esm.min.js +3 -0
- package/dist/apptics.esm.min.js.map +1 -0
- package/dist/apptics.min.js +3 -0
- package/dist/apptics.min.js.map +1 -0
- package/dist/chunks/lifecycle-VYr43KcB.min.js +3 -0
- package/dist/chunks/lifecycle-VYr43KcB.min.js.map +1 -0
- package/dist/cjs/auto-init.d.ts +8 -0
- package/dist/cjs/auto-init.js +32 -0
- package/dist/cjs/core/attempts.d.ts +28 -0
- package/dist/cjs/core/attempts.js +113 -0
- package/dist/cjs/core/batch.d.ts +32 -0
- package/dist/cjs/core/batch.js +116 -0
- package/dist/cjs/core/callsite.d.ts +34 -0
- package/dist/cjs/core/callsite.js +115 -0
- package/dist/cjs/core/consent.d.ts +27 -0
- package/dist/cjs/core/consent.js +132 -0
- package/dist/cjs/core/constants.d.ts +237 -0
- package/dist/cjs/core/constants.js +315 -0
- package/dist/cjs/core/db.d.ts +48 -0
- package/dist/cjs/core/db.js +467 -0
- package/dist/cjs/core/debug.d.ts +22 -0
- package/dist/cjs/core/debug.js +88 -0
- package/dist/cjs/core/env.d.ts +28 -0
- package/dist/cjs/core/env.js +267 -0
- package/dist/cjs/core/framework-id.d.ts +1 -0
- package/dist/cjs/core/framework-id.js +9 -0
- package/dist/cjs/core/guard.d.ts +11 -0
- package/dist/cjs/core/guard.js +86 -0
- package/dist/cjs/core/hooks.d.ts +39 -0
- package/dist/cjs/core/hooks.js +22 -0
- package/dist/cjs/core/locks.d.ts +9 -0
- package/dist/cjs/core/locks.js +22 -0
- package/dist/cjs/core/logger.d.ts +15 -0
- package/dist/cjs/core/logger.js +50 -0
- package/dist/cjs/core/state.d.ts +70 -0
- package/dist/cjs/core/state.js +78 -0
- package/dist/cjs/core/storage.d.ts +61 -0
- package/dist/cjs/core/storage.js +327 -0
- package/dist/cjs/core/stylesheet.d.ts +8 -0
- package/dist/cjs/core/stylesheet.js +29 -0
- package/dist/cjs/core/transport.d.ts +85 -0
- package/dist/cjs/core/transport.js +456 -0
- package/dist/cjs/core/utils.d.ts +26 -0
- package/dist/cjs/core/utils.js +175 -0
- package/dist/cjs/core/validate.d.ts +18 -0
- package/dist/cjs/core/validate.js +233 -0
- package/dist/cjs/device/appversion.d.ts +15 -0
- package/dist/cjs/device/appversion.js +170 -0
- package/dist/cjs/device/index.d.ts +34 -0
- package/dist/cjs/device/index.js +472 -0
- package/dist/cjs/engagement/api-rules.d.ts +49 -0
- package/dist/cjs/engagement/api-rules.js +206 -0
- package/dist/cjs/engagement/auto-api.d.ts +7 -0
- package/dist/cjs/engagement/auto-api.js +200 -0
- package/dist/cjs/engagement/auto-screen.d.ts +18 -0
- package/dist/cjs/engagement/auto-screen.js +305 -0
- package/dist/cjs/engagement/defined-events.d.ts +23 -0
- package/dist/cjs/engagement/defined-events.js +58 -0
- package/dist/cjs/engagement/send.d.ts +6 -0
- package/dist/cjs/engagement/send.js +104 -0
- package/dist/cjs/engagement/session.d.ts +51 -0
- package/dist/cjs/engagement/session.js +463 -0
- package/dist/cjs/engagement/track.d.ts +56 -0
- package/dist/cjs/engagement/track.js +375 -0
- package/dist/cjs/errors/send.d.ts +6 -0
- package/dist/cjs/errors/send.js +73 -0
- package/dist/cjs/errors/track.d.ts +4 -0
- package/dist/cjs/errors/track.js +167 -0
- package/dist/cjs/feedback/controls.d.ts +37 -0
- package/dist/cjs/feedback/controls.js +316 -0
- package/dist/cjs/feedback/index.d.ts +9 -0
- package/dist/cjs/feedback/index.js +56 -0
- package/dist/cjs/feedback/logs.d.ts +45 -0
- package/dist/cjs/feedback/logs.js +241 -0
- package/dist/cjs/feedback/quartz.d.ts +18 -0
- package/dist/cjs/feedback/quartz.js +105 -0
- package/dist/cjs/feedback/send.d.ts +28 -0
- package/dist/cjs/feedback/send.js +203 -0
- package/dist/cjs/feedback/styles.d.ts +10 -0
- package/dist/cjs/feedback/styles.js +8 -0
- package/dist/cjs/feedback/template.d.ts +2 -0
- package/dist/cjs/feedback/template.js +162 -0
- package/dist/cjs/feedback/ui.d.ts +63 -0
- package/dist/cjs/feedback/ui.js +709 -0
- package/dist/cjs/heatmap/send.d.ts +13 -0
- package/dist/cjs/heatmap/send.js +82 -0
- package/dist/cjs/heatmap/track.d.ts +4 -0
- package/dist/cjs/heatmap/track.js +448 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.js +162 -0
- package/dist/cjs/lifecycle.d.ts +21 -0
- package/dist/cjs/lifecycle.js +734 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/remoteconfig/country.d.ts +4 -0
- package/dist/cjs/remoteconfig/country.js +73 -0
- package/dist/cjs/remoteconfig/custom.d.ts +10 -0
- package/dist/cjs/remoteconfig/custom.js +58 -0
- package/dist/cjs/remoteconfig/evaluate.d.ts +24 -0
- package/dist/cjs/remoteconfig/evaluate.js +184 -0
- package/dist/cjs/remoteconfig/fetch.d.ts +24 -0
- package/dist/cjs/remoteconfig/fetch.js +200 -0
- package/dist/cjs/remoteconfig/index.d.ts +15 -0
- package/dist/cjs/remoteconfig/index.js +116 -0
- package/dist/cjs/remoteconfig/store.d.ts +18 -0
- package/dist/cjs/remoteconfig/store.js +98 -0
- package/dist/cjs/remoteconfig/tz-countries.d.ts +8 -0
- package/dist/cjs/remoteconfig/tz-countries.js +13 -0
- package/dist/cjs/remotelog/gate.d.ts +2 -0
- package/dist/cjs/remotelog/gate.js +28 -0
- package/dist/cjs/remotelog/index.d.ts +8 -0
- package/dist/cjs/remotelog/logger.d.ts +22 -0
- package/dist/cjs/remotelog/logger.js +111 -0
- package/dist/cjs/remotelog/send.d.ts +13 -0
- package/dist/cjs/remotelog/send.js +153 -0
- package/dist/cjs/remotelogger/index.d.ts +12 -0
- package/dist/cjs/remotelogger/index.js +18 -0
- package/dist/cjs/scheduler.d.ts +8 -0
- package/dist/cjs/scheduler.js +78 -0
- package/dist/cjs/types.d.ts +385 -0
- package/dist/cjs/user/index.d.ts +33 -0
- package/dist/cjs/user/index.js +183 -0
- package/dist/esm/auto-init.d.ts +8 -0
- package/dist/esm/auto-init.js +29 -0
- package/dist/esm/core/attempts.d.ts +28 -0
- package/dist/esm/core/attempts.js +106 -0
- package/dist/esm/core/batch.d.ts +32 -0
- package/dist/esm/core/batch.js +109 -0
- package/dist/esm/core/callsite.d.ts +34 -0
- package/dist/esm/core/callsite.js +110 -0
- package/dist/esm/core/consent.d.ts +27 -0
- package/dist/esm/core/consent.js +122 -0
- package/dist/esm/core/constants.d.ts +237 -0
- package/dist/esm/core/constants.js +292 -0
- package/dist/esm/core/db.d.ts +48 -0
- package/dist/esm/core/db.js +453 -0
- package/dist/esm/core/debug.d.ts +22 -0
- package/dist/esm/core/debug.js +81 -0
- package/dist/esm/core/env.d.ts +28 -0
- package/dist/esm/core/env.js +251 -0
- package/dist/esm/core/framework-id.d.ts +1 -0
- package/dist/esm/core/framework-id.js +7 -0
- package/dist/esm/core/guard.d.ts +11 -0
- package/dist/esm/core/guard.js +83 -0
- package/dist/esm/core/hooks.d.ts +39 -0
- package/dist/esm/core/hooks.js +20 -0
- package/dist/esm/core/locks.d.ts +9 -0
- package/dist/esm/core/locks.js +20 -0
- package/dist/esm/core/logger.d.ts +15 -0
- package/dist/esm/core/logger.js +46 -0
- package/dist/esm/core/state.d.ts +70 -0
- package/dist/esm/core/state.js +73 -0
- package/dist/esm/core/storage.d.ts +61 -0
- package/dist/esm/core/storage.js +314 -0
- package/dist/esm/core/stylesheet.d.ts +8 -0
- package/dist/esm/core/stylesheet.js +27 -0
- package/dist/esm/core/transport.d.ts +85 -0
- package/dist/esm/core/transport.js +439 -0
- package/dist/esm/core/utils.d.ts +26 -0
- package/dist/esm/core/utils.js +161 -0
- package/dist/esm/core/validate.d.ts +18 -0
- package/dist/esm/core/validate.js +226 -0
- package/dist/esm/device/appversion.d.ts +15 -0
- package/dist/esm/device/appversion.js +164 -0
- package/dist/esm/device/index.d.ts +34 -0
- package/dist/esm/device/index.js +461 -0
- package/dist/esm/engagement/api-rules.d.ts +49 -0
- package/dist/esm/engagement/api-rules.js +199 -0
- package/dist/esm/engagement/auto-api.d.ts +7 -0
- package/dist/esm/engagement/auto-api.js +198 -0
- package/dist/esm/engagement/auto-screen.d.ts +18 -0
- package/dist/esm/engagement/auto-screen.js +301 -0
- package/dist/esm/engagement/defined-events.d.ts +23 -0
- package/dist/esm/engagement/defined-events.js +55 -0
- package/dist/esm/engagement/send.d.ts +6 -0
- package/dist/esm/engagement/send.js +101 -0
- package/dist/esm/engagement/session.d.ts +51 -0
- package/dist/esm/engagement/session.js +448 -0
- package/dist/esm/engagement/track.d.ts +56 -0
- package/dist/esm/engagement/track.js +365 -0
- package/dist/esm/errors/send.d.ts +6 -0
- package/dist/esm/errors/send.js +70 -0
- package/dist/esm/errors/track.d.ts +4 -0
- package/dist/esm/errors/track.js +163 -0
- package/dist/esm/feedback/controls.d.ts +37 -0
- package/dist/esm/feedback/controls.js +309 -0
- package/dist/esm/feedback/index.d.ts +9 -0
- package/dist/esm/feedback/index.js +51 -0
- package/dist/esm/feedback/logs.d.ts +45 -0
- package/dist/esm/feedback/logs.js +227 -0
- package/dist/esm/feedback/quartz.d.ts +18 -0
- package/dist/esm/feedback/quartz.js +99 -0
- package/dist/esm/feedback/send.d.ts +28 -0
- package/dist/esm/feedback/send.js +198 -0
- package/dist/esm/feedback/styles.d.ts +10 -0
- package/dist/esm/feedback/styles.js +4 -0
- package/dist/esm/feedback/template.d.ts +2 -0
- package/dist/esm/feedback/template.js +159 -0
- package/dist/esm/feedback/ui.d.ts +63 -0
- package/dist/esm/feedback/ui.js +703 -0
- package/dist/esm/heatmap/send.d.ts +13 -0
- package/dist/esm/heatmap/send.js +79 -0
- package/dist/esm/heatmap/track.d.ts +4 -0
- package/dist/esm/heatmap/track.js +445 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +150 -0
- package/dist/esm/lifecycle.d.ts +21 -0
- package/dist/esm/lifecycle.js +729 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/remoteconfig/country.d.ts +4 -0
- package/dist/esm/remoteconfig/country.js +70 -0
- package/dist/esm/remoteconfig/custom.d.ts +10 -0
- package/dist/esm/remoteconfig/custom.js +54 -0
- package/dist/esm/remoteconfig/evaluate.d.ts +24 -0
- package/dist/esm/remoteconfig/evaluate.js +179 -0
- package/dist/esm/remoteconfig/fetch.d.ts +24 -0
- package/dist/esm/remoteconfig/fetch.js +196 -0
- package/dist/esm/remoteconfig/index.d.ts +15 -0
- package/dist/esm/remoteconfig/index.js +111 -0
- package/dist/esm/remoteconfig/store.d.ts +18 -0
- package/dist/esm/remoteconfig/store.js +91 -0
- package/dist/esm/remoteconfig/tz-countries.d.ts +8 -0
- package/dist/esm/remoteconfig/tz-countries.js +11 -0
- package/dist/esm/remotelog/gate.d.ts +2 -0
- package/dist/esm/remotelog/gate.js +26 -0
- package/dist/esm/remotelog/index.d.ts +8 -0
- package/dist/esm/remotelog/logger.d.ts +22 -0
- package/dist/esm/remotelog/logger.js +108 -0
- package/dist/esm/remotelog/send.d.ts +13 -0
- package/dist/esm/remotelog/send.js +149 -0
- package/dist/esm/remotelogger/index.d.ts +12 -0
- package/dist/esm/remotelogger/index.js +13 -0
- package/dist/esm/scheduler.d.ts +8 -0
- package/dist/esm/scheduler.js +74 -0
- package/dist/esm/types.d.ts +385 -0
- package/dist/esm/user/index.d.ts +33 -0
- package/dist/esm/user/index.js +176 -0
- package/dist/feedback.esm.min.js +3 -0
- package/dist/feedback.esm.min.js.map +1 -0
- package/package.json +122 -7
- package/src/appticssdk.js +0 -1
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var constants = require('../core/constants.js');
|
|
5
|
+
var state = require('../core/state.js');
|
|
6
|
+
var logger = require('../core/logger.js');
|
|
7
|
+
var utils = require('../core/utils.js');
|
|
8
|
+
var storage = require('../core/storage.js');
|
|
9
|
+
var env = require('../core/env.js');
|
|
10
|
+
var consent = require('../core/consent.js');
|
|
11
|
+
var locks = require('../core/locks.js');
|
|
12
|
+
|
|
13
|
+
/** The most recently started item of `type` that is still open (endtime 0). */
|
|
14
|
+
function latestOpen(type, items) {
|
|
15
|
+
return items.filter(function (item) {
|
|
16
|
+
return item.type == type && item.endtime === 0;
|
|
17
|
+
}).sort(function (a, b) {
|
|
18
|
+
return b.starttime - a.starttime;
|
|
19
|
+
})[0];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Ends one screen view, by id: it gets its end time and, with it, a duration, and becomes sendable
|
|
23
|
+
* (an open screen, endtime 0, waits like an open session does). A view already ended keeps its end.
|
|
24
|
+
*/
|
|
25
|
+
function closeScreen(id, time) {
|
|
26
|
+
if (utils.isEmpty(id)) {
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
if (state._ZAP._current_screen && state._ZAP._current_screen.id === id && !state._ZAP._current_screen.endtime) {
|
|
30
|
+
state._ZAP._current_screen = Object.assign({}, state._ZAP._current_screen, { endtime: time });
|
|
31
|
+
}
|
|
32
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
33
|
+
var open = items.filter(function (item) {
|
|
34
|
+
return item.type == constants.AP_CONSTANTS.SCREEN && item.id === id && item.endtime === 0;
|
|
35
|
+
})[0];
|
|
36
|
+
if (open) {
|
|
37
|
+
return storage.engagement_transactions.update(Object.assign({}, open, { endtime: Math.max(time, open.starttime) }));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ends this tab's current screen view: the page is hidden or closing. By id, so a screen another
|
|
43
|
+
* tab has open is never closed from here.
|
|
44
|
+
*/
|
|
45
|
+
function updateScreenWithEndtime() {
|
|
46
|
+
var current = state._ZAP._current_screen;
|
|
47
|
+
return current && current.id ? closeScreen(current.id, utils.nowMs()) : Promise.resolve();
|
|
48
|
+
}
|
|
49
|
+
/** Ends the screen views a session left open, at `time`: the session itself has ended. */
|
|
50
|
+
function closeScreensOf(session, time) {
|
|
51
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
52
|
+
var open = items.filter(function (item) {
|
|
53
|
+
return item.type == constants.AP_CONSTANTS.SCREEN && item.endtime === 0 && item.sessionstarttime === session.starttime;
|
|
54
|
+
});
|
|
55
|
+
return open.reduce(function (done, item) {
|
|
56
|
+
return done.then(function () {
|
|
57
|
+
return storage.engagement_transactions.update(Object.assign({}, item, { endtime: Math.max(time, item.starttime) }));
|
|
58
|
+
});
|
|
59
|
+
}, Promise.resolve());
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Track the session.
|
|
64
|
+
* @param sessionData A set of properties belonging to the session.
|
|
65
|
+
*/
|
|
66
|
+
function trackSession(sessionData) {
|
|
67
|
+
if (!consent.canTrackEngagement('trackSession')) { // no i18n
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
if (sessionData && typeof sessionData !== 'object') { // no i18n
|
|
71
|
+
logger.logFailure(constants.AP_LOG_MODULE.SESSION, 'apptics.trackSession: sessionData param must be an object'); // no i18n
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
var written = storage.engagement_transactions.add(sessionData);
|
|
75
|
+
logger.Console.log('apptics.trackSession(' + JSON.stringify(sessionData || {}) + ')'); // no i18n
|
|
76
|
+
logger.Console.dir(sessionData);
|
|
77
|
+
return written;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* This tab's session while it is open, otherwise the newest open session in storage: the one the
|
|
81
|
+
* site's other tabs are on, which this tab joins rather than starting a second session beside it.
|
|
82
|
+
*/
|
|
83
|
+
function currentSession() {
|
|
84
|
+
var mine = tabSessionId();
|
|
85
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
86
|
+
if (mine) {
|
|
87
|
+
var ours = items.filter(function (item) {
|
|
88
|
+
return item.type == constants.AP_CONSTANTS.SESSION && item.id === mine && item.endtime === 0;
|
|
89
|
+
})[0];
|
|
90
|
+
if (ours) {
|
|
91
|
+
return ours;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return latestOpen(constants.AP_CONSTANTS.SESSION, items);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* This page's own id, among the tabs sharing a session. New on every page load, so a reload or a
|
|
99
|
+
* duplicated tab counts as a tab of its own rather than as the one it came from. Made on first use,
|
|
100
|
+
* not when the module loads: getNewUUID reads `screen`, which a server-side render doesn't have.
|
|
101
|
+
*/
|
|
102
|
+
var tabIdValue = ''; // no i18n
|
|
103
|
+
function tabId() {
|
|
104
|
+
if (tabIdValue === '') {
|
|
105
|
+
tabIdValue = utils.getNewUUID();
|
|
106
|
+
}
|
|
107
|
+
return tabIdValue;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The tabs of this site using a session. All of them share one session, which ends when the last
|
|
111
|
+
* of them closes (see endCurrentSession). Kept on the stored session, never sent: see
|
|
112
|
+
* sendEngagementData. A session stored by an older SDK has none, and counts as this tab's alone.
|
|
113
|
+
*/
|
|
114
|
+
function tabsOf(session) {
|
|
115
|
+
return Array.isArray(session.tabs) ? session.tabs.slice() : [];
|
|
116
|
+
}
|
|
117
|
+
function withThisTab(session) {
|
|
118
|
+
var tabs = tabsOf(session);
|
|
119
|
+
if (tabs.indexOf(tabId()) === -1) {
|
|
120
|
+
tabs.push(tabId());
|
|
121
|
+
}
|
|
122
|
+
return tabs;
|
|
123
|
+
}
|
|
124
|
+
/* ---------------- screen views left open by tabs that are gone ----------------
|
|
125
|
+
* A screen view is ended by the tab that opened it: by the next screen, or as the page hides or
|
|
126
|
+
* closes. A tab that reloads, closes or crashes before that write lands leaves its view open, and
|
|
127
|
+
* nothing ended it until the whole session did, which with tabs sharing one session can be hours.
|
|
128
|
+
*
|
|
129
|
+
* So each tab holds a Web Lock, `apptics-tab-<its id>`, for as long as it is open: the browser
|
|
130
|
+
* lets go of it the moment the tab is gone, however it went. A tab that starts, or comes back into
|
|
131
|
+
* view, ends every open view whose tab no longer holds its lock, at the time that tab was last seen
|
|
132
|
+
* active. Without the Web Locks API, views are left as before and end with their session. */
|
|
133
|
+
const TAB_LOCK_PREFIX = 'apptics-tab-'; // no i18n
|
|
134
|
+
/** A view this young may belong to a tab whose lock is not granted yet: left alone. */
|
|
135
|
+
const YOUNG_VIEW_MS = 5000;
|
|
136
|
+
function tabLocks() {
|
|
137
|
+
var locks = typeof navigator !== 'undefined' ? navigator.locks : undefined; // no i18n
|
|
138
|
+
return locks && typeof locks.request === 'function' && typeof locks.query === 'function' ? locks : undefined; // no i18n
|
|
139
|
+
}
|
|
140
|
+
var holdingTabLock = false;
|
|
141
|
+
/** Takes this tab's lock, held until the page is gone. Once per page. */
|
|
142
|
+
function holdTabLock() {
|
|
143
|
+
var locks = tabLocks();
|
|
144
|
+
if (!locks || holdingTabLock) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
holdingTabLock = true;
|
|
148
|
+
locks.request(TAB_LOCK_PREFIX + tabId(), function () {
|
|
149
|
+
return new Promise(function () { });
|
|
150
|
+
}).catch(function () {
|
|
151
|
+
holdingTabLock = false;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Ends the open screen views of tabs that are gone. Resolves right away without Web Locks. */
|
|
155
|
+
function closeScreensOfGoneTabs() {
|
|
156
|
+
var locks = tabLocks();
|
|
157
|
+
if (!locks) {
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
}
|
|
160
|
+
var mine = tabId();
|
|
161
|
+
return locks.query().then(function (state) {
|
|
162
|
+
var alive = {};
|
|
163
|
+
(state.held || []).concat(state.pending || []).forEach(function (lock) {
|
|
164
|
+
if (lock.name && lock.name.indexOf(TAB_LOCK_PREFIX) === 0) {
|
|
165
|
+
alive[lock.name.slice(TAB_LOCK_PREFIX.length)] = true;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
var now = utils.nowMs();
|
|
169
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
170
|
+
var gone = items.filter(function (item) {
|
|
171
|
+
return item.type == constants.AP_CONSTANTS.SCREEN && item.endtime === 0 && typeof item.tab === 'string' // no i18n
|
|
172
|
+
&& item.tab !== mine && !alive[item.tab] && now - item.starttime > YOUNG_VIEW_MS;
|
|
173
|
+
});
|
|
174
|
+
if (gone.length) {
|
|
175
|
+
logger.Console.log('closeScreensOfGoneTabs: ending ' + gone.length + ' screen view(s) left open by a tab that is gone'); // no i18n
|
|
176
|
+
}
|
|
177
|
+
return gone.reduce(function (done, item) {
|
|
178
|
+
// when that tab was last seen with the view open, not now: the time since is not time spent
|
|
179
|
+
var end = Math.max(item.starttime, Number(item.lastseen) || item.starttime);
|
|
180
|
+
return done.then(function () {
|
|
181
|
+
return storage.engagement_transactions.update(Object.assign({}, item, { endtime: end }));
|
|
182
|
+
});
|
|
183
|
+
}, Promise.resolve());
|
|
184
|
+
});
|
|
185
|
+
}).catch(function (err) {
|
|
186
|
+
logger.Console.log('closeScreensOfGoneTabs: ' + err); // no i18n
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** Notes on this tab's open view that the tab is still here, the end a gone tab's view is given. */
|
|
190
|
+
function markScreenSeen(time) {
|
|
191
|
+
var current = state._ZAP._current_screen;
|
|
192
|
+
if (!current || !current.id || current.endtime) {
|
|
193
|
+
return Promise.resolve();
|
|
194
|
+
}
|
|
195
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
196
|
+
var open = items.filter(function (item) {
|
|
197
|
+
return item.type == constants.AP_CONSTANTS.SCREEN && item.id === current.id && item.endtime === 0;
|
|
198
|
+
})[0];
|
|
199
|
+
return open ? storage.engagement_transactions.update(Object.assign({}, open, { lastseen: time })) : undefined;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Every change to the session runs under a lock shared by the site's tabs: each one reads the
|
|
204
|
+
* stored session, changes it and writes it back, and two tabs doing that at once would each write
|
|
205
|
+
* over the other's change.
|
|
206
|
+
*/
|
|
207
|
+
function withSessionLock(run) {
|
|
208
|
+
return locks.withLock('session', run); // no i18n
|
|
209
|
+
}
|
|
210
|
+
/** The id of this tab's session. sessionStorage is per tab and survives a reload. */
|
|
211
|
+
const TAB_KEY = 'apptics-session'; // no i18n
|
|
212
|
+
function tabSessionId() {
|
|
213
|
+
try {
|
|
214
|
+
return sessionStorage.getItem(TAB_KEY) || ''; // no i18n
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
return ''; // no sessionStorage (private mode, sandboxed frame): fall back to the store
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function rememberTabSession(id) {
|
|
221
|
+
try {
|
|
222
|
+
sessionStorage.setItem(TAB_KEY, id);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// the session still works, it just can't be recognised after a reload
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** True until the first session on this device has been recorded: device_info's hadsession. */
|
|
229
|
+
function isFirstEverSession() {
|
|
230
|
+
return storage.getKeyFromDeviceInfo(HAD_SESSION_KEY) !== true;
|
|
231
|
+
}
|
|
232
|
+
const HAD_SESSION_KEY = 'hadsession'; // no i18n
|
|
233
|
+
/**
|
|
234
|
+
* A session that has begun but is not stored yet.
|
|
235
|
+
*
|
|
236
|
+
* A visit shorter than `min_session_duration` and with nothing tracked in it is not a session worth
|
|
237
|
+
* reporting — a bounce, a preview, a crawler that runs scripts. So the session is held here for
|
|
238
|
+
* those first seconds and written only once it has earned it: when the timer fires, or the moment
|
|
239
|
+
* anything is tracked that needs a session to belong to, whichever comes first.
|
|
240
|
+
*
|
|
241
|
+
* `_ZAP._current_session` is set as soon as the session begins even though this is pending, so
|
|
242
|
+
* anything tracked in those seconds still reads the right `sessionstarttime`. Committing does not
|
|
243
|
+
* change `starttime`: it stays the moment the session began, so the wait is never counted as a gap.
|
|
244
|
+
*/
|
|
245
|
+
var pendingSession = null;
|
|
246
|
+
var pendingTimer = null;
|
|
247
|
+
function clearPendingTimer() {
|
|
248
|
+
if (pendingTimer !== null) {
|
|
249
|
+
clearTimeout(pendingTimer);
|
|
250
|
+
pendingTimer = null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Writes the waiting session, if there is one. Called by the timer and by anything that tracks an
|
|
255
|
+
* item, so an event in the first seconds takes its session with it.
|
|
256
|
+
*/
|
|
257
|
+
function commitPendingSession() {
|
|
258
|
+
if (!pendingSession) {
|
|
259
|
+
return Promise.resolve();
|
|
260
|
+
}
|
|
261
|
+
var session = pendingSession;
|
|
262
|
+
pendingSession = null;
|
|
263
|
+
clearPendingTimer();
|
|
264
|
+
// the flag is set here rather than at the start, so a dropped session does not consume it and
|
|
265
|
+
// leave the next real session reporting firstsession false
|
|
266
|
+
if (session.firstsession) {
|
|
267
|
+
storage.setDeviceInfoKey(HAD_SESSION_KEY, true);
|
|
268
|
+
}
|
|
269
|
+
return trackSession(session);
|
|
270
|
+
}
|
|
271
|
+
/** Forgets a session that never earned its place, on a tab closing inside the minimum. */
|
|
272
|
+
function dropPendingSession() {
|
|
273
|
+
if (!pendingSession) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
logger.Console.log('session: dropped, shorter than the minimum with nothing tracked in it'); // no i18n
|
|
277
|
+
pendingSession = null;
|
|
278
|
+
clearPendingTimer();
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
function startNewSession(time) {
|
|
282
|
+
state._ZAP._current_session = {
|
|
283
|
+
id: utils.getNewUUID(),
|
|
284
|
+
type: constants.AP_CONSTANTS.SESSION,
|
|
285
|
+
starttime: time,
|
|
286
|
+
endtime: 0,
|
|
287
|
+
edge: env.getConnectionType(),
|
|
288
|
+
orientation: env.getOrientation(),
|
|
289
|
+
firstsession: isFirstEverSession(),
|
|
290
|
+
networkstatus: env.getNetworkStatus(),
|
|
291
|
+
lastactivetime: time,
|
|
292
|
+
tabs: [tabId()],
|
|
293
|
+
};
|
|
294
|
+
// ram is optional in sessionWebSDK but checked against ram_rom ('4 GB'), which an empty
|
|
295
|
+
// string cannot match. navigator.deviceMemory is Chromium-only, so Firefox and Safari have
|
|
296
|
+
// nothing to put here - and a key that fails its regex does not lose the session, it loses
|
|
297
|
+
// the whole batch the session was sent in, permanently
|
|
298
|
+
var ram = env.getRam();
|
|
299
|
+
if (!utils.isEmpty(ram)) {
|
|
300
|
+
state._ZAP._current_session.ram = ram;
|
|
301
|
+
}
|
|
302
|
+
rememberTabSession(state._ZAP._current_session.id);
|
|
303
|
+
var minimum = Number(state.getConfig('min_session_duration'));
|
|
304
|
+
if (!(minimum > 0)) {
|
|
305
|
+
return commitNow(state._ZAP._current_session);
|
|
306
|
+
}
|
|
307
|
+
// held, not stored: see pendingSession above
|
|
308
|
+
pendingSession = state._ZAP._current_session;
|
|
309
|
+
clearPendingTimer();
|
|
310
|
+
pendingTimer = setTimeout(function () {
|
|
311
|
+
pendingTimer = null;
|
|
312
|
+
commitPendingSession();
|
|
313
|
+
}, minimum);
|
|
314
|
+
return Promise.resolve();
|
|
315
|
+
}
|
|
316
|
+
/** The un-deferred path, for a minimum of 0. */
|
|
317
|
+
function commitNow(session) {
|
|
318
|
+
if (session.firstsession) {
|
|
319
|
+
storage.setDeviceInfoKey(HAD_SESSION_KEY, true);
|
|
320
|
+
}
|
|
321
|
+
return trackSession(session);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* This tab is closing, or the page is being replaced. The session ends here when this is the last
|
|
325
|
+
* tab using it, so it can be sent; while another tab of the site is still on it, this tab only
|
|
326
|
+
* leaves it, and the last tab to close ends it. Nothing happens when it is already closed.
|
|
327
|
+
*/
|
|
328
|
+
function endCurrentSession() {
|
|
329
|
+
var time = utils.nowMs();
|
|
330
|
+
// nothing was tracked and the minimum was never reached: this visit is not reported at all
|
|
331
|
+
if (dropPendingSession()) {
|
|
332
|
+
return Promise.resolve();
|
|
333
|
+
}
|
|
334
|
+
return withSessionLock(function () {
|
|
335
|
+
return closeOrLeaveSession(time);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function closeOrLeaveSession(time) {
|
|
339
|
+
return currentSession().then(function (_currentSession) {
|
|
340
|
+
if (!_currentSession || _currentSession.endtime !== 0) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
var others = tabsOf(_currentSession).filter(function (tab) { return tab !== tabId(); });
|
|
344
|
+
if (others.length > 0) {
|
|
345
|
+
var left = Object.assign({}, _currentSession, { tabs: others, lastactivetime: time });
|
|
346
|
+
state._ZAP._current_session = left;
|
|
347
|
+
logger.Console.log('endCurrentSession: ' + others.length + ' other tab(s) still on this session, so it stays open'); // no i18n
|
|
348
|
+
return storage.engagement_transactions.update(left);
|
|
349
|
+
}
|
|
350
|
+
var closed = Object.assign({}, _currentSession, { endtime: time, lastactivetime: time, tabs: [] });
|
|
351
|
+
state._ZAP._current_session = closed;
|
|
352
|
+
logger.Console.log('endCurrentSession: closing the session opened at ' + closed.starttime); // no i18n
|
|
353
|
+
return storage.engagement_transactions.update(closed).then(function () {
|
|
354
|
+
return closeScreensOf(closed, time);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Closes sessions left open by a tab that was killed before it could close its own: anything
|
|
360
|
+
* idle for longer than inactiveduration. A live tab in another window keeps its session,
|
|
361
|
+
* because that one's lastactivetime is recent.
|
|
362
|
+
*/
|
|
363
|
+
function closeAbandonedSessions() {
|
|
364
|
+
var cutoff = utils.nowMs() - state._ZAP.inactiveduration * 60 * 1000;
|
|
365
|
+
var mine = tabSessionId();
|
|
366
|
+
return storage.engagement_transactions.all().then(function (items) {
|
|
367
|
+
var abandoned = items.filter(function (item) {
|
|
368
|
+
return item.type == constants.AP_CONSTANTS.SESSION && item.endtime === 0 &&
|
|
369
|
+
item.id !== mine && item.lastactivetime < cutoff;
|
|
370
|
+
});
|
|
371
|
+
if (!abandoned.length) {
|
|
372
|
+
return Promise.resolve();
|
|
373
|
+
}
|
|
374
|
+
logger.Console.log('closeAbandonedSessions: closing ' + abandoned.length + ' session(s) left open by a closed tab'); // no i18n
|
|
375
|
+
return abandoned.reduce(function (done, item) {
|
|
376
|
+
return done.then(function () {
|
|
377
|
+
return storage.engagement_transactions.update(Object.assign({}, item, { endtime: item.lastactivetime }));
|
|
378
|
+
}).then(function () {
|
|
379
|
+
// the screen the closed tab was on ends with its session
|
|
380
|
+
return closeScreensOf(item, item.lastactivetime);
|
|
381
|
+
});
|
|
382
|
+
}, Promise.resolve());
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function checkAndUpdateSession() {
|
|
386
|
+
if (!state._ZAP._token) {
|
|
387
|
+
logger.logFailure(constants.AP_LOG_MODULE.SESSION, 'checkAndUpdateSession: You must call Apptics.init() first'); // no i18n
|
|
388
|
+
return Promise.resolve();
|
|
389
|
+
}
|
|
390
|
+
if (env.is_apptics_disabled()) {
|
|
391
|
+
return Promise.resolve();
|
|
392
|
+
}
|
|
393
|
+
var time = utils.nowMs();
|
|
394
|
+
return withSessionLock(function () {
|
|
395
|
+
return settleSession(time);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function settleSession(time) {
|
|
399
|
+
// a tab that was killed before it could close its session: close it so it gets sent; and the
|
|
400
|
+
// screen views left open by tabs that are gone, so they end now rather than with the session
|
|
401
|
+
return closeAbandonedSessions().then(closeScreensOfGoneTabs).then(currentSession).then(function (_currentSession) {
|
|
402
|
+
// a closed session belongs to a tab that has gone: this tab starts its own
|
|
403
|
+
if (!_currentSession || _currentSession.endtime !== 0) {
|
|
404
|
+
return startNewSession(time);
|
|
405
|
+
}
|
|
406
|
+
state._ZAP._current_session = _currentSession;
|
|
407
|
+
// Check if the user is inactive for the past 30 minutes
|
|
408
|
+
if ((time - _currentSession.lastactivetime) > (state._ZAP.inactiveduration * 1000 * 60)) {
|
|
409
|
+
logger.Console.log('checkAndUpdateSession: User is inactive for the past ' + state._ZAP.inactiveduration + ' minutes so starting a new session'); // no i18n
|
|
410
|
+
// ends when the user was last active, so the idle gap isn't counted as session time
|
|
411
|
+
var _newSession = Object.assign({}, _currentSession, { endtime: _currentSession.lastactivetime });
|
|
412
|
+
logger.Console.log('checkAndUpdateSession: (' + JSON.stringify(_newSession) + ')'); // no i18n
|
|
413
|
+
logger.Console.dir(_newSession);
|
|
414
|
+
return storage.engagement_transactions.update(_newSession).then(function () {
|
|
415
|
+
// the screen left open over the idle gap ends with the session, when it was last used
|
|
416
|
+
return closeScreensOf(_newSession, _newSession.endtime);
|
|
417
|
+
}).then(function () {
|
|
418
|
+
return startNewSession(time);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
logger.Console.log('checkAndUpdateSession: User is back before ' + state._ZAP.inactiveduration + ' minutes so updating the lastactivitytime - ' + time); // no i18n
|
|
422
|
+
return touchSession();
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function updateLastActiveTimeOfSession() {
|
|
426
|
+
return withSessionLock(touchSession);
|
|
427
|
+
}
|
|
428
|
+
/** Marks the session active now, with this tab among its tabs. Callers hold the session lock. */
|
|
429
|
+
function touchSession() {
|
|
430
|
+
var time = utils.nowMs();
|
|
431
|
+
// a pending session is not in the store yet, so it is updated in place instead
|
|
432
|
+
if (pendingSession) {
|
|
433
|
+
pendingSession.lastactivetime = time;
|
|
434
|
+
state._ZAP._current_session = pendingSession;
|
|
435
|
+
return Promise.resolve();
|
|
436
|
+
}
|
|
437
|
+
return currentSession().then(function (_currentSession) {
|
|
438
|
+
if (!_currentSession) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
logger.Console.log('updateLastActiveTimeOfSession: Updating the lastactivitytime - ' + time); // no i18n
|
|
442
|
+
var _newSession = Object.assign({}, _currentSession, { lastactivetime: time, tabs: withThisTab(_currentSession) });
|
|
443
|
+
state._ZAP._current_session = _newSession;
|
|
444
|
+
return storage.engagement_transactions.update(_newSession);
|
|
445
|
+
}).then(function () {
|
|
446
|
+
return markScreenSeen(time);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
exports.checkAndUpdateSession = checkAndUpdateSession;
|
|
451
|
+
exports.closeAbandonedSessions = closeAbandonedSessions;
|
|
452
|
+
exports.closeScreen = closeScreen;
|
|
453
|
+
exports.closeScreensOfGoneTabs = closeScreensOfGoneTabs;
|
|
454
|
+
exports.commitPendingSession = commitPendingSession;
|
|
455
|
+
exports.currentSession = currentSession;
|
|
456
|
+
exports.dropPendingSession = dropPendingSession;
|
|
457
|
+
exports.endCurrentSession = endCurrentSession;
|
|
458
|
+
exports.holdTabLock = holdTabLock;
|
|
459
|
+
exports.startNewSession = startNewSession;
|
|
460
|
+
exports.tabId = tabId;
|
|
461
|
+
exports.trackSession = trackSession;
|
|
462
|
+
exports.updateLastActiveTimeOfSession = updateLastActiveTimeOfSession;
|
|
463
|
+
exports.updateScreenWithEndtime = updateScreenWithEndtime;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Dict, Properties } from '../types.js';
|
|
2
|
+
import { type DefinedEvent } from './defined-events.js';
|
|
3
|
+
/**
|
|
4
|
+
* Records an event: `trackEvent(name, group?, properties?)`, or a defined event with
|
|
5
|
+
* `trackEvent(DefinedEvents.AP_USER_LOGIN, properties?)`, which brings its own group.
|
|
6
|
+
*
|
|
7
|
+
* As on Android, an invalid name or group is not logged. A group left out is `default`, so the
|
|
8
|
+
* one-argument form keeps working; a group given but invalid is a mistake worth being told about,
|
|
9
|
+
* not one to file quietly under `default`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function trackEvent(eventName: string | DefinedEvent, groupName?: string | Properties, jsonObj?: Properties): void;
|
|
12
|
+
/** The app's own screen view. Automatic screen tracking calls recordScreen() instead. */
|
|
13
|
+
export declare function inScreen(screenName: string, jsonObj?: Properties): string;
|
|
14
|
+
/**
|
|
15
|
+
* The app's own screen view with a known id and start. The public inScreen takes the id and the
|
|
16
|
+
* time when the app calls it, so the id it hands back is the view's even when start-up is still
|
|
17
|
+
* running and this runs a moment later.
|
|
18
|
+
*/
|
|
19
|
+
export declare function inScreenAt(screenName: string, jsonObj: Properties | undefined, id: string, startedAt: number): void;
|
|
20
|
+
/** Ends the screen view inScreen() began, as Android's AppticsScreenTracker.outScreen(id). */
|
|
21
|
+
export declare function outScreenAt(id: string, endedAt: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Queues a screen view and makes it the current screen.
|
|
24
|
+
*
|
|
25
|
+
* The view is stored open (endtime 0) and closed when the next screen starts, when the page is
|
|
26
|
+
* hidden or closed, when its session ends, or by outScreen() — which is what gives it a duration.
|
|
27
|
+
* An open view is not sent until it is closed, as an open session is not.
|
|
28
|
+
*/
|
|
29
|
+
export declare function recordScreen(screenName: string, jsonObj?: Properties, id?: string, startedAt?: number, implicit?: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* The screen a heatmap record belongs to, making sure engagement/add has a view of it: the console
|
|
32
|
+
* places a record by its screen, and a screen that was never sent as a view leaves it nowhere.
|
|
33
|
+
*
|
|
34
|
+
* A screen the app or automatic screen tracking named is used as it is — that one already has its
|
|
35
|
+
* view. Otherwise (automatic tracking off and no inScreen() call) the page is named the way
|
|
36
|
+
* automatic tracking would name it, and a view is recorded for it: once per screen, a new one when
|
|
37
|
+
* the path changes, and given way as soon as anything else names the screen.
|
|
38
|
+
*/
|
|
39
|
+
export declare function screenForRecord(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Back in view after being hidden: the hide ended the screen view, so the screen the user is on
|
|
42
|
+
* starts a new one, as an Android activity coming back to the foreground does.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resumeScreen(): void;
|
|
45
|
+
export declare function trackApi(jsonObj?: Dict): void;
|
|
46
|
+
/**
|
|
47
|
+
* Begins timing one API call, as Android's AppticsApiTracker.startTrackApi(url, method): returns
|
|
48
|
+
* the id to hand to endTrackApi once the response is in. Records nothing by itself.
|
|
49
|
+
*/
|
|
50
|
+
export declare function startTrackApi(url: string, method?: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Finishes a call startTrackApi began and records it, timed from start to `endedAt`. The public
|
|
53
|
+
* wrapper takes `endedAt` when the app calls it, so a call ended while start-up is still running
|
|
54
|
+
* keeps its real duration even though it is recorded a moment later.
|
|
55
|
+
*/
|
|
56
|
+
export declare function endTrackApiAt(id: string, responseCode: number, responseMessage: string | undefined, endedAt: number): void;
|