@zoho_apptics/apptics-js-sdk 1.0.1 → 2.0.0-beta
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 +87 -0
- package/LICENSE +13 -0
- package/README.md +747 -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/batch-BU_mTpm1.min.js +3 -0
- package/dist/chunks/batch-BU_mTpm1.min.js.map +1 -0
- package/dist/chunks/logs-Bea5zUW_.min.js +3 -0
- package/dist/chunks/logs-Bea5zUW_.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 +115 -0
- package/dist/cjs/core/callsite.d.ts +28 -0
- package/dist/cjs/core/callsite.js +93 -0
- package/dist/cjs/core/consent.d.ts +23 -0
- package/dist/cjs/core/consent.js +130 -0
- package/dist/cjs/core/constants.d.ts +212 -0
- package/dist/cjs/core/constants.js +299 -0
- package/dist/cjs/core/db.d.ts +40 -0
- package/dist/cjs/core/db.js +334 -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 +2 -0
- package/dist/cjs/core/guard.js +72 -0
- package/dist/cjs/core/hooks.d.ts +41 -0
- package/dist/cjs/core/hooks.js +23 -0
- package/dist/cjs/core/logger.d.ts +16 -0
- package/dist/cjs/core/logger.js +86 -0
- package/dist/cjs/core/state.d.ts +69 -0
- package/dist/cjs/core/state.js +78 -0
- package/dist/cjs/core/storage.d.ts +46 -0
- package/dist/cjs/core/storage.js +229 -0
- package/dist/cjs/core/transport.d.ts +83 -0
- package/dist/cjs/core/transport.js +441 -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 +20 -0
- package/dist/cjs/device/appversion.js +158 -0
- package/dist/cjs/device/index.d.ts +23 -0
- package/dist/cjs/device/index.js +372 -0
- package/dist/cjs/engagement/api-rules.d.ts +84 -0
- package/dist/cjs/engagement/api-rules.js +219 -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 +16 -0
- package/dist/cjs/engagement/auto-screen.js +241 -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 +99 -0
- package/dist/cjs/engagement/session.d.ts +42 -0
- package/dist/cjs/engagement/session.js +327 -0
- package/dist/cjs/engagement/track.d.ts +56 -0
- package/dist/cjs/engagement/track.js +350 -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 +127 -0
- package/dist/cjs/feedback/controls.d.ts +31 -0
- package/dist/cjs/feedback/controls.js +286 -0
- package/dist/cjs/feedback/index.d.ts +8 -0
- package/dist/cjs/feedback/index.js +37 -0
- package/dist/cjs/feedback/logs.d.ts +40 -0
- package/dist/cjs/feedback/logs.js +202 -0
- package/dist/cjs/feedback/send.d.ts +25 -0
- package/dist/cjs/feedback/send.js +178 -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 +55 -0
- package/dist/cjs/feedback/ui.js +631 -0
- package/dist/cjs/heatmap/index.d.ts +16 -0
- package/dist/cjs/heatmap/index.js +22 -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 +377 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +150 -0
- package/dist/cjs/lifecycle.d.ts +14 -0
- package/dist/cjs/lifecycle.js +538 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/remoteconfig/custom.d.ts +10 -0
- package/dist/cjs/remoteconfig/custom.js +78 -0
- package/dist/cjs/remoteconfig/evaluate.d.ts +24 -0
- package/dist/cjs/remoteconfig/evaluate.js +171 -0
- package/dist/cjs/remoteconfig/fetch.d.ts +24 -0
- package/dist/cjs/remoteconfig/fetch.js +214 -0
- package/dist/cjs/remoteconfig/index.d.ts +13 -0
- package/dist/cjs/remoteconfig/index.js +90 -0
- package/dist/cjs/remoteconfig/store.d.ts +18 -0
- package/dist/cjs/remoteconfig/store.js +98 -0
- package/dist/cjs/remotelog/gate.d.ts +11 -0
- package/dist/cjs/remotelog/gate.js +54 -0
- package/dist/cjs/remotelog/index.d.ts +25 -0
- package/dist/cjs/remotelog/index.js +55 -0
- package/dist/cjs/remotelog/logger.d.ts +28 -0
- package/dist/cjs/remotelog/logger.js +139 -0
- package/dist/cjs/remotelog/send.d.ts +13 -0
- package/dist/cjs/remotelog/send.js +153 -0
- package/dist/cjs/scheduler.d.ts +8 -0
- package/dist/cjs/scheduler.js +78 -0
- package/dist/cjs/types.d.ts +312 -0
- package/dist/cjs/user/index.d.ts +33 -0
- package/dist/cjs/user/index.js +178 -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 +108 -0
- package/dist/esm/core/callsite.d.ts +28 -0
- package/dist/esm/core/callsite.js +89 -0
- package/dist/esm/core/consent.d.ts +23 -0
- package/dist/esm/core/consent.js +120 -0
- package/dist/esm/core/constants.d.ts +212 -0
- package/dist/esm/core/constants.js +277 -0
- package/dist/esm/core/db.d.ts +40 -0
- package/dist/esm/core/db.js +324 -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 +2 -0
- package/dist/esm/core/guard.js +70 -0
- package/dist/esm/core/hooks.d.ts +41 -0
- package/dist/esm/core/hooks.js +21 -0
- package/dist/esm/core/logger.d.ts +16 -0
- package/dist/esm/core/logger.js +82 -0
- package/dist/esm/core/state.d.ts +69 -0
- package/dist/esm/core/state.js +73 -0
- package/dist/esm/core/storage.d.ts +46 -0
- package/dist/esm/core/storage.js +219 -0
- package/dist/esm/core/transport.d.ts +83 -0
- package/dist/esm/core/transport.js +425 -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 +20 -0
- package/dist/esm/device/appversion.js +153 -0
- package/dist/esm/device/index.d.ts +23 -0
- package/dist/esm/device/index.js +362 -0
- package/dist/esm/engagement/api-rules.d.ts +84 -0
- package/dist/esm/engagement/api-rules.js +211 -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 +16 -0
- package/dist/esm/engagement/auto-screen.js +238 -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 +96 -0
- package/dist/esm/engagement/session.d.ts +42 -0
- package/dist/esm/engagement/session.js +315 -0
- package/dist/esm/engagement/track.d.ts +56 -0
- package/dist/esm/engagement/track.js +340 -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 +123 -0
- package/dist/esm/feedback/controls.d.ts +31 -0
- package/dist/esm/feedback/controls.js +280 -0
- package/dist/esm/feedback/index.d.ts +8 -0
- package/dist/esm/feedback/index.js +30 -0
- package/dist/esm/feedback/logs.d.ts +40 -0
- package/dist/esm/feedback/logs.js +188 -0
- package/dist/esm/feedback/send.d.ts +25 -0
- package/dist/esm/feedback/send.js +175 -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 +55 -0
- package/dist/esm/feedback/ui.js +626 -0
- package/dist/esm/heatmap/index.d.ts +16 -0
- package/dist/esm/heatmap/index.js +17 -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 +374 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +122 -0
- package/dist/esm/lifecycle.d.ts +14 -0
- package/dist/esm/lifecycle.js +534 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/remoteconfig/custom.d.ts +10 -0
- package/dist/esm/remoteconfig/custom.js +74 -0
- package/dist/esm/remoteconfig/evaluate.d.ts +24 -0
- package/dist/esm/remoteconfig/evaluate.js +166 -0
- package/dist/esm/remoteconfig/fetch.d.ts +24 -0
- package/dist/esm/remoteconfig/fetch.js +210 -0
- package/dist/esm/remoteconfig/index.d.ts +13 -0
- package/dist/esm/remoteconfig/index.js +85 -0
- package/dist/esm/remoteconfig/store.d.ts +18 -0
- package/dist/esm/remoteconfig/store.js +91 -0
- package/dist/esm/remotelog/gate.d.ts +11 -0
- package/dist/esm/remotelog/gate.js +48 -0
- package/dist/esm/remotelog/index.d.ts +25 -0
- package/dist/esm/remotelog/index.js +51 -0
- package/dist/esm/remotelog/logger.d.ts +28 -0
- package/dist/esm/remotelog/logger.js +136 -0
- package/dist/esm/remotelog/send.d.ts +13 -0
- package/dist/esm/remotelog/send.js +149 -0
- package/dist/esm/scheduler.d.ts +8 -0
- package/dist/esm/scheduler.js +74 -0
- package/dist/esm/types.d.ts +312 -0
- package/dist/esm/user/index.d.ts +33 -0
- package/dist/esm/user/index.js +170 -0
- package/dist/feedback.esm.min.js +3 -0
- package/dist/feedback.esm.min.js.map +1 -0
- package/dist/heatmap.esm.min.js +3 -0
- package/dist/heatmap.esm.min.js.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +124 -7
- package/src/appticssdk.js +0 -3817
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
import { _ZAP, getConfig } from '../core/state.js';
|
|
3
|
+
import { Console } from '../core/logger.js';
|
|
4
|
+
import { nowMs, isEmpty } from '../core/utils.js';
|
|
5
|
+
import { trackApi } from './track.js';
|
|
6
|
+
import { hostAllowed, endpointIgnored } from './api-rules.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Automatic API tracking: wraps XMLHttpRequest and fetch so calls the app makes are recorded
|
|
10
|
+
* without it calling startTrackApi() / endTrackApi() itself.
|
|
11
|
+
*
|
|
12
|
+
* Two rules shape everything here. The wrappers must be invisible — return values, `this`, thrown
|
|
13
|
+
* errors and argument forms all pass straight through, because a mistake in this file breaks the
|
|
14
|
+
* host app's networking rather than just losing a metric. And the SDK's own traffic must never be
|
|
15
|
+
* recorded: engagement/add reports API calls, so tracking it would have each batch produce the
|
|
16
|
+
* items for the next one.
|
|
17
|
+
*
|
|
18
|
+
* Off unless `track_api` is set. Hostnames and paths of every request an app makes is a wider
|
|
19
|
+
* grant than events and screens, so it is the app's decision, not a default.
|
|
20
|
+
*/
|
|
21
|
+
var installed = false;
|
|
22
|
+
var originalOpen = null;
|
|
23
|
+
var originalSend = null;
|
|
24
|
+
var originalFetch = null;
|
|
25
|
+
/**
|
|
26
|
+
* Whether a request is the SDK's own, which is never reported: engagement/add carries API items, so
|
|
27
|
+
* recording it would have every batch produce the items for the next one, forever.
|
|
28
|
+
*
|
|
29
|
+
* Two checks, because one is not enough to bet a loop on. The host comparison is a prefix match, so
|
|
30
|
+
* it misses if the host is spelled differently in the request than in serviceUrl — a trailing
|
|
31
|
+
* slash is enough. The path check catches those, since every SDK call sits under the configured
|
|
32
|
+
* endpoint whatever host it goes to, including through a same-origin proxy.
|
|
33
|
+
*/
|
|
34
|
+
function isOwnRequest(url, pathname) {
|
|
35
|
+
var own = [_ZAP._defaultoptions.base_domain, _ZAP._defaultoptions.serviceUrl];
|
|
36
|
+
for (var i = 0; i < own.length; i++) {
|
|
37
|
+
if (!isEmpty(own[i]) && url.indexOf(own[i]) === 0) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
var endpoint = getConfig('endpoint');
|
|
42
|
+
return !isEmpty(endpoint) && pathname.indexOf(endpoint) === 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether one URL is ours to report: an http(s) call, not the SDK's own, and one the
|
|
46
|
+
* configureApiTracking rules let through. Anything unparseable is skipped rather than guessed at.
|
|
47
|
+
*/
|
|
48
|
+
function shouldTrack(rawUrl) {
|
|
49
|
+
if (isEmpty(rawUrl)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
var absolute;
|
|
53
|
+
try {
|
|
54
|
+
absolute = new URL(rawUrl, window.location.href);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (absolute.protocol !== 'https:' && absolute.protocol !== 'http:') { // no i18n
|
|
60
|
+
return false; // data:, blob:, chrome-extension: and friends
|
|
61
|
+
}
|
|
62
|
+
if (isOwnRequest(absolute.href, absolute.pathname)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// configureApiTracking's rules: checked here too so a call left out is not even timed. trackApi checks them again, for the
|
|
66
|
+
// calls an app reports itself
|
|
67
|
+
return hostAllowed(absolute.hostname) && !endpointIgnored(absolute.pathname);
|
|
68
|
+
}
|
|
69
|
+
/** One finished call. Never throws: a tracking failure must not surface in the app's call. */
|
|
70
|
+
function record(method, url, starttime, responsecode, responsemessage) {
|
|
71
|
+
try {
|
|
72
|
+
trackApi({
|
|
73
|
+
url: url,
|
|
74
|
+
method: method,
|
|
75
|
+
responsecode: responsecode,
|
|
76
|
+
responsemessage: responsemessage,
|
|
77
|
+
starttime: starttime,
|
|
78
|
+
endtime: nowMs(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
Console.log('auto api tracking: ' + String(err)); // no i18n
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The url of any of fetch's three first-argument forms. */
|
|
86
|
+
function fetchUrl(input) {
|
|
87
|
+
if (typeof input === 'string') { // no i18n
|
|
88
|
+
return input;
|
|
89
|
+
}
|
|
90
|
+
if (typeof Request !== 'undefined' && input instanceof Request) { // no i18n
|
|
91
|
+
return input.url;
|
|
92
|
+
}
|
|
93
|
+
return String(input);
|
|
94
|
+
}
|
|
95
|
+
function fetchMethod(input, init) {
|
|
96
|
+
if (init && init.method) {
|
|
97
|
+
return init.method;
|
|
98
|
+
}
|
|
99
|
+
if (typeof Request !== 'undefined' && input instanceof Request) { // no i18n
|
|
100
|
+
return input.method;
|
|
101
|
+
}
|
|
102
|
+
return 'GET'; // no i18n
|
|
103
|
+
}
|
|
104
|
+
function installXhr() {
|
|
105
|
+
if (typeof XMLHttpRequest !== 'function') { // no i18n
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
originalOpen = XMLHttpRequest.prototype.open;
|
|
109
|
+
originalSend = XMLHttpRequest.prototype.send;
|
|
110
|
+
XMLHttpRequest.prototype.open = function (method, url) {
|
|
111
|
+
try {
|
|
112
|
+
var href = String(url);
|
|
113
|
+
if (shouldTrack(href)) {
|
|
114
|
+
// starttime is set in send(): open() may happen long before the call goes out
|
|
115
|
+
this._ap_api = { method: String(method || 'GET'), url: href, starttime: 0 }; // no i18n
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this._ap_api = undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
this._ap_api = undefined;
|
|
123
|
+
}
|
|
124
|
+
return originalOpen.apply(this, arguments);
|
|
125
|
+
};
|
|
126
|
+
XMLHttpRequest.prototype.send = function () {
|
|
127
|
+
const tracked = this._ap_api;
|
|
128
|
+
if (tracked) {
|
|
129
|
+
tracked.starttime = nowMs();
|
|
130
|
+
// loadend fires for success, error, abort and timeout alike, so one listener covers
|
|
131
|
+
// every ending; status 0 is how a network failure arrives here
|
|
132
|
+
this.addEventListener('loadend', function () {
|
|
133
|
+
var message = this.status === 0 ? 'Network Error' : (this.statusText || '-'); // no i18n
|
|
134
|
+
record(tracked.method, tracked.url, tracked.starttime, this.status, message);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return originalSend.apply(this, arguments);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function installFetch() {
|
|
141
|
+
if (typeof fetch !== 'function') { // no i18n
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
originalFetch = fetch;
|
|
145
|
+
window.fetch = function (input, init) {
|
|
146
|
+
var url, method, starttime;
|
|
147
|
+
try {
|
|
148
|
+
url = fetchUrl(input);
|
|
149
|
+
method = fetchMethod(input, init);
|
|
150
|
+
starttime = nowMs();
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return originalFetch(input, init);
|
|
154
|
+
}
|
|
155
|
+
if (!shouldTrack(url)) {
|
|
156
|
+
return originalFetch(input, init);
|
|
157
|
+
}
|
|
158
|
+
return originalFetch(input, init).then(function (response) {
|
|
159
|
+
record(method, url, starttime, response.status, response.statusText || '-'); // no i18n
|
|
160
|
+
return response;
|
|
161
|
+
}, function (err) {
|
|
162
|
+
// a rejected fetch never reached a status; the rejection is re-thrown untouched
|
|
163
|
+
record(method, url, starttime, 0, String(err && err.message ? err.message : err) || 'Network Error'); // no i18n
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Starts or stops automatic tracking. Safe to call repeatedly: installing twice would report each
|
|
170
|
+
* call twice, so the second call is a no-op.
|
|
171
|
+
*/
|
|
172
|
+
function enableApiTracking(enabled) {
|
|
173
|
+
var on = typeof enabled === 'boolean' ? enabled : true; // no i18n
|
|
174
|
+
if (on === installed) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (on) {
|
|
178
|
+
installXhr();
|
|
179
|
+
installFetch();
|
|
180
|
+
installed = true;
|
|
181
|
+
Console.log('automatic api tracking: on'); // no i18n
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (originalOpen) {
|
|
185
|
+
XMLHttpRequest.prototype.open = originalOpen;
|
|
186
|
+
}
|
|
187
|
+
if (originalSend) {
|
|
188
|
+
XMLHttpRequest.prototype.send = originalSend;
|
|
189
|
+
}
|
|
190
|
+
if (originalFetch) {
|
|
191
|
+
window.fetch = originalFetch;
|
|
192
|
+
}
|
|
193
|
+
originalOpen = originalSend = originalFetch = null;
|
|
194
|
+
installed = false;
|
|
195
|
+
Console.log('automatic api tracking: off'); // no i18n
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export { enableApiTracking };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts or stops automatic screen tracking. Starting records the screen the page is on, unless
|
|
3
|
+
* the app has already called inScreen() for it. Safe to call repeatedly: wrapping history twice
|
|
4
|
+
* would check every navigation twice, so a second start does nothing. `wait` replaces SETTLE_MS,
|
|
5
|
+
* for tests.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enableScreenTracking(enabled?: boolean, wait?: number): void;
|
|
8
|
+
/**
|
|
9
|
+
* The name automatic tracking gives the page as it is now: the cleaned path. For records that
|
|
10
|
+
* need a screen when none has been
|
|
11
|
+
* tracked — heatmaps with `track_screen: false` and no inScreen() call, say. '' when the page
|
|
12
|
+
* has no name.
|
|
13
|
+
*/
|
|
14
|
+
export declare function automaticScreenName(): string;
|
|
15
|
+
/** Whether history is being watched right now. */
|
|
16
|
+
export declare function isScreenTrackingEnabled(): boolean;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
import { Console } from '../core/logger.js';
|
|
3
|
+
import { hooks } from '../core/hooks.js';
|
|
4
|
+
import { _ZAP } from '../core/state.js';
|
|
5
|
+
import { nowMs } from '../core/utils.js';
|
|
6
|
+
import { recordScreen } from './track.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Automatic screen tracking: a screen view for the page the SDK starts on, and one for every
|
|
10
|
+
* navigation after, without the app calling inScreen() from its router.
|
|
11
|
+
*
|
|
12
|
+
* Single-page routers move with history.pushState and replaceState, which fire no event, so those
|
|
13
|
+
* two are wrapped; back and forward arrive as popstate, and hash routers as hashchange. As with
|
|
14
|
+
* automatic API tracking, the wrappers must be invisible — arguments, `this` and the return value
|
|
15
|
+
* pass straight through — because a mistake here breaks the app's navigation, not just a metric.
|
|
16
|
+
*
|
|
17
|
+
* A router seldom makes one history call per move: it pushes the route, then replaces it to store
|
|
18
|
+
* its own state. Calls are therefore collected into one check a tick later, and a name equal to
|
|
19
|
+
* the last one recorded is not a new view. That also keeps a replaceState that only stores state
|
|
20
|
+
* from counting as a screen.
|
|
21
|
+
*
|
|
22
|
+
* An app may still name some screens itself. A inScreen() call during a navigation replaces
|
|
23
|
+
* the automatic view of it, so the navigation counts once, under the app's name. Router hooks
|
|
24
|
+
* often run after the navigation has been seen — React's effects, a Vue afterEach — so the
|
|
25
|
+
* automatic view waits SETTLE_MS before it is recorded, and a manual call in that time wins. The
|
|
26
|
+
* new path is the current screen from the moment the navigation is seen, so events fired while
|
|
27
|
+
* the page mounts are attributed to it, not to the page that was left. A page left again within
|
|
28
|
+
* the wait, such as a redirect, is not counted.
|
|
29
|
+
*
|
|
30
|
+
* On by default: most apps never call inScreen() themselves, and a screen-less session says
|
|
31
|
+
* little. An app that wants only its own calls sets `track_screen: false`.
|
|
32
|
+
*/
|
|
33
|
+
/** The most a screen name may be, and the characters it may hold (see validateScreenName). */
|
|
34
|
+
const NAME_MAX = 99;
|
|
35
|
+
const NAME_DISALLOWED = /[^\w\s.\-&/:]/g;
|
|
36
|
+
/** How long an automatic view waits for the app's own inScreen() of the same navigation. */
|
|
37
|
+
const SETTLE_MS = 300;
|
|
38
|
+
var installed = false;
|
|
39
|
+
/** The name last recorded, so a navigation that lands on the same screen is not a new view. */
|
|
40
|
+
var lastName = ''; // no i18n
|
|
41
|
+
var timer = null;
|
|
42
|
+
var settleMs = SETTLE_MS;
|
|
43
|
+
/** The automatic view waiting out SETTLE_MS, and its timer. */
|
|
44
|
+
var pendingName = ''; // no i18n
|
|
45
|
+
var settleTimer = null;
|
|
46
|
+
/** The app called inScreen() during the current navigation, so it is not recorded here. */
|
|
47
|
+
var claimed = false;
|
|
48
|
+
/** When the navigation waiting out SETTLE_MS happened: its view starts then, not when recorded. */
|
|
49
|
+
var pendingAt = 0;
|
|
50
|
+
/** The wrappers this module put on history, to tell them apart from one a library added later. */
|
|
51
|
+
var wrappedPush = null;
|
|
52
|
+
var wrappedReplace = null;
|
|
53
|
+
var originalPush = null;
|
|
54
|
+
var originalReplace = null;
|
|
55
|
+
/**
|
|
56
|
+
* The default name: the path, without the query string, which is where search terms, tokens and
|
|
57
|
+
* other values that are not a screen live. A hash route (`#/orders`, `#!/orders`) is the route,
|
|
58
|
+
* so it is joined onto the path; a plain `#anchor` is a place on the same screen and is left out.
|
|
59
|
+
*/
|
|
60
|
+
function nameFromUrl(url) {
|
|
61
|
+
var path = url.pathname;
|
|
62
|
+
var hash = url.hash;
|
|
63
|
+
if (/^#!?\//.test(hash)) {
|
|
64
|
+
var route = hash.replace(/^#!?/, '').split('?')[0]; // no i18n
|
|
65
|
+
path = path.replace(/\/+$/, '') + route; // no i18n
|
|
66
|
+
}
|
|
67
|
+
return path;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A name inScreen will accept. A path holds characters a screen name may not — percent
|
|
71
|
+
* encoding, `+`, `@` — and a refused name is dropped, so they are replaced rather than risked.
|
|
72
|
+
*/
|
|
73
|
+
function cleanName(raw) {
|
|
74
|
+
var name = raw;
|
|
75
|
+
try {
|
|
76
|
+
name = decodeURIComponent(raw);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// a malformed escape: clean it as it stands
|
|
80
|
+
}
|
|
81
|
+
return name.replace(NAME_DISALLOWED, '_').trim().slice(0, NAME_MAX); // no i18n
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The name for the page as it is now, from its url, or '' to record nothing. Never throws. An app
|
|
85
|
+
* that wants its own names calls inScreen() as the route changes, which replaces this view.
|
|
86
|
+
*/
|
|
87
|
+
function currentName() {
|
|
88
|
+
try {
|
|
89
|
+
return cleanName(nameFromUrl(new URL(window.location.href)));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return ''; // no i18n
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function check() {
|
|
96
|
+
timer = null;
|
|
97
|
+
if (!installed) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
var name = currentName();
|
|
101
|
+
if (name === '' || name === lastName) { // no i18n
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
lastName = name;
|
|
105
|
+
if (claimed) {
|
|
106
|
+
return; // the app has already recorded this navigation under its own name
|
|
107
|
+
}
|
|
108
|
+
// the current screen now, the view once SETTLE_MS has passed with no inScreen() of the app's.
|
|
109
|
+
// The open view keeps its id, so the view that follows still closes it, at the navigation
|
|
110
|
+
_ZAP._current_screen = Object.assign({}, _ZAP._current_screen, { screen: name });
|
|
111
|
+
pendingName = name;
|
|
112
|
+
pendingAt = nowMs();
|
|
113
|
+
settleTimer = setTimeout(settle, settleMs);
|
|
114
|
+
}
|
|
115
|
+
function settle() {
|
|
116
|
+
settleTimer = null;
|
|
117
|
+
var name = pendingName;
|
|
118
|
+
pendingName = ''; // no i18n
|
|
119
|
+
if (!installed || claimed || name === '') { // no i18n
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
recordScreen(name, undefined, undefined, pendingAt || undefined);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
Console.log('automatic screen tracking: ' + String(err)); // no i18n
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Drops an automatic view still waiting out SETTLE_MS. */
|
|
130
|
+
function cancelPending() {
|
|
131
|
+
if (settleTimer !== null) {
|
|
132
|
+
clearTimeout(settleTimer);
|
|
133
|
+
settleTimer = null;
|
|
134
|
+
}
|
|
135
|
+
pendingName = ''; // no i18n
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Collects the history calls and events of one navigation into a single check. The first call of
|
|
139
|
+
* a navigation starts it afresh: a inScreen() from before it belonged to the previous screen,
|
|
140
|
+
* and a view still waiting is a page left too soon to count.
|
|
141
|
+
*/
|
|
142
|
+
function navigated() {
|
|
143
|
+
if (timer === null) {
|
|
144
|
+
claimed = false;
|
|
145
|
+
cancelPending();
|
|
146
|
+
timer = setTimeout(check, 0);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** The app recorded a screen itself; the automatic view of this navigation gives way to it. */
|
|
150
|
+
function screenTrackedByApp() {
|
|
151
|
+
claimed = true;
|
|
152
|
+
cancelPending();
|
|
153
|
+
}
|
|
154
|
+
function installHistory() {
|
|
155
|
+
var push = history.pushState;
|
|
156
|
+
var replace = history.replaceState;
|
|
157
|
+
originalPush = push;
|
|
158
|
+
originalReplace = replace;
|
|
159
|
+
wrappedPush = function () {
|
|
160
|
+
var result = push.apply(this, arguments);
|
|
161
|
+
navigated();
|
|
162
|
+
return result;
|
|
163
|
+
};
|
|
164
|
+
wrappedReplace = function () {
|
|
165
|
+
var result = replace.apply(this, arguments);
|
|
166
|
+
navigated();
|
|
167
|
+
return result;
|
|
168
|
+
};
|
|
169
|
+
history.pushState = wrappedPush;
|
|
170
|
+
history.replaceState = wrappedReplace;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Puts history back. Only a wrapper still on top is taken off: if a library wrapped history after
|
|
174
|
+
* us, removing ours would remove theirs too. Ours then stays inside theirs, passing calls through
|
|
175
|
+
* and recording nothing, since `installed` is false.
|
|
176
|
+
*/
|
|
177
|
+
function restoreHistory() {
|
|
178
|
+
if (wrappedPush && history.pushState === wrappedPush && originalPush) {
|
|
179
|
+
history.pushState = originalPush;
|
|
180
|
+
}
|
|
181
|
+
if (wrappedReplace && history.replaceState === wrappedReplace && originalReplace) {
|
|
182
|
+
history.replaceState = originalReplace;
|
|
183
|
+
}
|
|
184
|
+
wrappedPush = wrappedReplace = originalPush = originalReplace = null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Starts or stops automatic screen tracking. Starting records the screen the page is on, unless
|
|
188
|
+
* the app has already called inScreen() for it. Safe to call repeatedly: wrapping history twice
|
|
189
|
+
* would check every navigation twice, so a second start does nothing. `wait` replaces SETTLE_MS,
|
|
190
|
+
* for tests.
|
|
191
|
+
*/
|
|
192
|
+
function enableScreenTracking(enabled, wait) {
|
|
193
|
+
var on = typeof enabled === 'boolean' ? enabled : true; // no i18n
|
|
194
|
+
if (on) {
|
|
195
|
+
settleMs = SETTLE_MS; // no i18n
|
|
196
|
+
}
|
|
197
|
+
if (on === installed) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (on) {
|
|
201
|
+
lastName = ''; // no i18n
|
|
202
|
+
installHistory();
|
|
203
|
+
window.addEventListener('popstate', navigated); // no i18n
|
|
204
|
+
window.addEventListener('hashchange', navigated); // no i18n
|
|
205
|
+
installed = true;
|
|
206
|
+
Console.log('automatic screen tracking: on'); // no i18n
|
|
207
|
+
// the page the SDK starts on. Not through navigated(): a inScreen() the app made during
|
|
208
|
+
// start-up was replayed just before this, and it is this screen's, so its claim stands
|
|
209
|
+
if (timer === null) {
|
|
210
|
+
timer = setTimeout(check, 0);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
restoreHistory();
|
|
215
|
+
window.removeEventListener('popstate', navigated); // no i18n
|
|
216
|
+
window.removeEventListener('hashchange', navigated); // no i18n
|
|
217
|
+
if (timer !== null) {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
timer = null;
|
|
220
|
+
}
|
|
221
|
+
cancelPending();
|
|
222
|
+
installed = false;
|
|
223
|
+
lastName = ''; // no i18n
|
|
224
|
+
claimed = false;
|
|
225
|
+
Console.log('automatic screen tracking: off'); // no i18n
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The name automatic tracking gives the page as it is now: the cleaned path. For records that
|
|
229
|
+
* need a screen when none has been
|
|
230
|
+
* tracked — heatmaps with `track_screen: false` and no inScreen() call, say. '' when the page
|
|
231
|
+
* has no name.
|
|
232
|
+
*/
|
|
233
|
+
function automaticScreenName() {
|
|
234
|
+
return currentName();
|
|
235
|
+
}
|
|
236
|
+
hooks.screenTrackedByApp = screenTrackedByApp;
|
|
237
|
+
|
|
238
|
+
export { automaticScreenName, enableScreenTracking };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apptics' defined events, as Android's DefinedEvents: ready-made names in reserved `AP_` groups,
|
|
3
|
+
* logged with trackEvent like any other event and never tracked automatically.
|
|
4
|
+
*
|
|
5
|
+
* trackEvent(DefinedEvents.AP_USER_LOGIN);
|
|
6
|
+
* trackEvent(DefinedEvents.AP_SEARCH, { query_length: 12 });
|
|
7
|
+
*
|
|
8
|
+
* They are the only events allowed the `ap_` prefix: an app's own event, group or property may
|
|
9
|
+
* not start with it. The list is Android's, so the console reads them the same from every platform;
|
|
10
|
+
* some (battery, OS update, uninstall) have no meaning in a browser and are simply never sent.
|
|
11
|
+
*/
|
|
12
|
+
/** One defined event: the name and the group it is sent under. */
|
|
13
|
+
export interface DefinedEvent {
|
|
14
|
+
readonly event: string;
|
|
15
|
+
readonly group: string;
|
|
16
|
+
}
|
|
17
|
+
/** Every defined event, by name: `DefinedEvents.AP_USER_LOGIN` and so on. */
|
|
18
|
+
export declare const DefinedEvents: Readonly<Record<string, DefinedEvent>>;
|
|
19
|
+
/**
|
|
20
|
+
* The defined event `value` is, or undefined. Only the entries of DefinedEvents count, compared by
|
|
21
|
+
* identity, so an object that merely looks like one cannot borrow the reserved prefix.
|
|
22
|
+
*/
|
|
23
|
+
export declare function asDefinedEvent(value: unknown): DefinedEvent | undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
/**
|
|
3
|
+
* Apptics' defined events, as Android's DefinedEvents: ready-made names in reserved `AP_` groups,
|
|
4
|
+
* logged with trackEvent like any other event and never tracked automatically.
|
|
5
|
+
*
|
|
6
|
+
* trackEvent(DefinedEvents.AP_USER_LOGIN);
|
|
7
|
+
* trackEvent(DefinedEvents.AP_SEARCH, { query_length: 12 });
|
|
8
|
+
*
|
|
9
|
+
* They are the only events allowed the `ap_` prefix: an app's own event, group or property may
|
|
10
|
+
* not start with it. The list is Android's, so the console reads them the same from every platform;
|
|
11
|
+
* some (battery, OS update, uninstall) have no meaning in a browser and are simply never sent.
|
|
12
|
+
*/
|
|
13
|
+
const GROUPS = {
|
|
14
|
+
AP_APP_LIFE_CYCLE: [
|
|
15
|
+
'AP_APP_INSTALL', 'AP_APP_UNINSTALL', 'AP_APP_UPDATE', 'AP_APP_CRACKED', 'AP_APP_OPEN', 'AP_APP_CLEAR_DATA', // no i18n
|
|
16
|
+
'AP_APP_FOREGROUND', 'AP_APP_BACKGROUND', 'AP_APP_OUT_OF_MEMORY', 'AP_APP_FIRST_OPEN', 'AP_APP_LAUNCHING', // no i18n
|
|
17
|
+
'AP_APP_RESIGN_ACTIVE', 'AP_APP_WILL_CONNECT', // no i18n
|
|
18
|
+
],
|
|
19
|
+
AP_USER_LIFE_CYCLE: ['AP_USER_SIGNUP', 'AP_USER_LOGIN', 'AP_USER_LOGOUT'], // no i18n
|
|
20
|
+
AP_APPLICATION: [
|
|
21
|
+
'AP_DEEP_LINK_OPEN', 'AP_DEEP_LINK_UPDATE', 'AP_DEEP_LINK_FIRST_OPEN', 'AP_IN_APP_PURCHASE', // no i18n
|
|
22
|
+
'AP_NOTIFICATION_RECEIVE', 'AP_NOTIFICATION_OPEN', 'AP_NOTIFICATION_DISMISS', 'AP_NOTIFICATION_FOREGROUND', // no i18n
|
|
23
|
+
'AP_SEARCH', 'AP_BATTERY_LOW', 'AP_BATTERY_FULL', 'AP_LOW_POWER_MODE_ON', 'AP_LOW_POWER_MODE_OFF', // no i18n
|
|
24
|
+
],
|
|
25
|
+
AP_OTHERS: [
|
|
26
|
+
'AP_NETWORK_REACHABILITY_CHANGE', 'AP_NETWORK_BANDWIDTH_CHANGE', 'AP_SWITCH_THEME_LIGHT', // no i18n
|
|
27
|
+
'AP_SWITCH_THEME_DARK', 'AP_SWITCH_THEME_CUSTOM', 'AP_SWITCH_ORIENTATION_LANDSCAPE', // no i18n
|
|
28
|
+
'AP_SWITCH_ORIENTATION_PORTRAIT', // no i18n
|
|
29
|
+
],
|
|
30
|
+
AP_OS: ['AP_OS_UPDATE', 'AP_OS_UNSUPPORTED'], // no i18n
|
|
31
|
+
};
|
|
32
|
+
function build() {
|
|
33
|
+
var out = {};
|
|
34
|
+
Object.keys(GROUPS).forEach(function (group) {
|
|
35
|
+
GROUPS[group].forEach(function (event) {
|
|
36
|
+
out[event] = Object.freeze({ event: event, group: group });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return Object.freeze(out);
|
|
40
|
+
}
|
|
41
|
+
/** Every defined event, by name: `DefinedEvents.AP_USER_LOGIN` and so on. */
|
|
42
|
+
const DefinedEvents = build();
|
|
43
|
+
/**
|
|
44
|
+
* The defined event `value` is, or undefined. Only the entries of DefinedEvents count, compared by
|
|
45
|
+
* identity, so an object that merely looks like one cannot borrow the reserved prefix.
|
|
46
|
+
*/
|
|
47
|
+
function asDefinedEvent(value) {
|
|
48
|
+
if (!value || typeof value !== 'object') { // no i18n
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
var named = value.event;
|
|
52
|
+
return typeof named === 'string' && DefinedEvents[named] === value ? value : undefined; // no i18n
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { DefinedEvents, asDefinedEvent };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Dict, ServerResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sends pending engagements to the Apptics API.
|
|
4
|
+
*/
|
|
5
|
+
export declare function sendEngagementData(forget?: boolean): Promise<void>;
|
|
6
|
+
export declare function requestToSendEngagements(_payload: Dict, forget?: boolean): Promise<ServerResult>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
import { AP_LOG_MODULE, AP_CONSTANTS } from '../core/constants.js';
|
|
3
|
+
import { _ZAP } from '../core/state.js';
|
|
4
|
+
import { Console, logFailure, warnAlways } from '../core/logger.js';
|
|
5
|
+
import { cloneObject, isEmptyObj } from '../core/utils.js';
|
|
6
|
+
import { engagement_transactions } from '../core/storage.js';
|
|
7
|
+
import { is_apptics_disabled } from '../core/env.js';
|
|
8
|
+
import { shouldTrackOrSendData } from '../core/consent.js';
|
|
9
|
+
import { defer, clearDefer, withSendLock, freshItems, awaitUnlessClosing, sendBatch } from '../core/batch.js';
|
|
10
|
+
import { isPermanentRefusal } from '../core/transport.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sends pending engagements to the Apptics API.
|
|
14
|
+
*/
|
|
15
|
+
function sendEngagementData(forget) {
|
|
16
|
+
// engagementtracking off (getupdates): queued items stay until tracking is back on or they expire
|
|
17
|
+
if (is_apptics_disabled() || !shouldTrackOrSendData() || !_ZAP._engagementTracking) {
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
if (navigator.onLine == false) {
|
|
21
|
+
Console.warn('sendEngagementData: No Network'); // no i18n
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
// if we're busy sending, retry in .5s
|
|
25
|
+
if (_ZAP._sending) {
|
|
26
|
+
defer(sendEngagementData);
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
_ZAP._sending = true;
|
|
30
|
+
clearDefer();
|
|
31
|
+
return withSendLock('engagements', function () {
|
|
32
|
+
return freshItems(engagement_transactions, function (item) { return item.endtime != 0; }, function (item) { return item.starttime; }).then(function (engagement_items) {
|
|
33
|
+
if (engagement_items.length == 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let sessionObj = [], screenObj = [], eventObj = [], apiObj = [];
|
|
37
|
+
var _payload = {};
|
|
38
|
+
var sent = [];
|
|
39
|
+
cloneObject(engagement_items).forEach(function (item) {
|
|
40
|
+
const itemWithId = { ...item };
|
|
41
|
+
if (item.type == AP_CONSTANTS.EVENT) {
|
|
42
|
+
eventObj.push(item);
|
|
43
|
+
}
|
|
44
|
+
else if (item.type == AP_CONSTANTS.SCREEN) {
|
|
45
|
+
screenObj.push(item);
|
|
46
|
+
}
|
|
47
|
+
else if (item.type == AP_CONSTANTS.SESSION) {
|
|
48
|
+
sessionObj.push(item);
|
|
49
|
+
}
|
|
50
|
+
else if (item.type == AP_CONSTANTS.API) {
|
|
51
|
+
apiObj.push(item);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
sent.push(itemWithId);
|
|
57
|
+
delete item.id;
|
|
58
|
+
delete item.type;
|
|
59
|
+
});
|
|
60
|
+
if (eventObj.length > 0)
|
|
61
|
+
_payload.events = eventObj;
|
|
62
|
+
if (screenObj.length > 0)
|
|
63
|
+
_payload.screens = screenObj;
|
|
64
|
+
if (sessionObj.length > 0)
|
|
65
|
+
_payload.sessions = sessionObj;
|
|
66
|
+
if (apiObj.length > 0)
|
|
67
|
+
_payload.apis = apiObj;
|
|
68
|
+
if (isEmptyObj(_payload)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
return awaitUnlessClosing(requestToSendEngagements(_payload, forget).then(function () {
|
|
72
|
+
return engagement_transactions.remove(sent);
|
|
73
|
+
}, function (err) {
|
|
74
|
+
// the server will never accept this batch. Keeping it would block every later
|
|
75
|
+
// batch behind it for good, so it is dropped and said out loud
|
|
76
|
+
if (isPermanentRefusal(err)) {
|
|
77
|
+
warnAlways('apptics: the server refused these engagement items, so they were dropped: ' + String(err && err.message)); // no i18n
|
|
78
|
+
return engagement_transactions.remove(sent);
|
|
79
|
+
}
|
|
80
|
+
return Promise.reject(err);
|
|
81
|
+
}), forget, 'sendEngagementData'); // no i18n
|
|
82
|
+
});
|
|
83
|
+
}).catch(function (err) {
|
|
84
|
+
logFailure(AP_LOG_MODULE.API, 'sendEngagementData: ' + err); // no i18n
|
|
85
|
+
}).then(function () {
|
|
86
|
+
// something went wrong or we finished: allow this method to be recalled
|
|
87
|
+
_ZAP._sending = false;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Function to send engagement data to server
|
|
91
|
+
function requestToSendEngagements(_payload, forget) {
|
|
92
|
+
var label = 'requestToSendEngagements'; // no i18n
|
|
93
|
+
return sendBatch('engagement', label, _payload, forget); // no i18n
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { requestToSendEngagements, sendEngagementData };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Dict, QueueItem } from '../types.js';
|
|
2
|
+
export declare function currentScreen(): Promise<QueueItem | undefined>;
|
|
3
|
+
/**
|
|
4
|
+
* Ends one screen view, by id: it gets its end time and, with it, a duration, and becomes sendable
|
|
5
|
+
* (an open screen, endtime 0, waits like an open session does). A view already ended keeps its end.
|
|
6
|
+
*/
|
|
7
|
+
export declare function closeScreen(id: string, time: number): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Ends this tab's current screen view: the page is hidden or closing. By id, so a screen another
|
|
10
|
+
* tab has open is never closed from here.
|
|
11
|
+
*/
|
|
12
|
+
export declare function updateScreenWithEndtime(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Track the session.
|
|
15
|
+
* @param sessionData A set of properties belonging to the session.
|
|
16
|
+
*/
|
|
17
|
+
export declare function trackSession(sessionData: Dict): Promise<void>;
|
|
18
|
+
/** This tab's session when it has one, otherwise the newest open session in storage. */
|
|
19
|
+
export declare function currentSession(): Promise<QueueItem | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Writes the waiting session, if there is one. Called by the timer and by anything that tracks an
|
|
22
|
+
* item, so an event in the first seconds takes its session with it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function commitPendingSession(): Promise<void>;
|
|
25
|
+
/** Forgets a session that never earned its place, on a tab closing inside the minimum. */
|
|
26
|
+
export declare function dropPendingSession(): boolean;
|
|
27
|
+
/** Whether a session is waiting to be written. Used by tests and by the page-closing path. */
|
|
28
|
+
export declare function hasPendingSession(): boolean;
|
|
29
|
+
export declare function startNewSession(time: number): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Closes this tab's session, so it can be sent: the tab is closing or the page is being replaced.
|
|
32
|
+
* Nothing happens when the session is already closed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function endCurrentSession(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Closes sessions left open by a tab that was killed before it could close its own: anything
|
|
37
|
+
* idle for longer than inactiveduration. A live tab in another window keeps its session,
|
|
38
|
+
* because that one's lastactivetime is recent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function closeAbandonedSessions(): Promise<void>;
|
|
41
|
+
export declare function checkAndUpdateSession(): Promise<void>;
|
|
42
|
+
export declare function updateLastActiveTimeOfSession(): Promise<void>;
|