@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,729 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
import { REQUIRED_CONFIG, CONFIG_KEYS, AP_CONFIG, RETIRED_CONFIG_NAMES, DEFAULT_INACTIVE_MINUTES } from './core/constants.js';
|
|
3
|
+
import { _ZAP } from './core/state.js';
|
|
4
|
+
import { Console, warnAlways } from './core/logger.js';
|
|
5
|
+
import { isEmpty, isSuccess } from './core/utils.js';
|
|
6
|
+
import { storageReady, user_defaults, engagement_transactions, error_transactions, tracking_preference } from './core/storage.js';
|
|
7
|
+
import { hooks } from './core/hooks.js';
|
|
8
|
+
import { applyDebugConfig } from './core/debug.js';
|
|
9
|
+
import { getConnectionType, getOrientation } from './core/env.js';
|
|
10
|
+
import { restoreServerSettings, prepareDevice, updateDeviceWithApptics, refreshServerSettings, needsDevice, deviceReady } from './device/index.js';
|
|
11
|
+
import { clearAppVersion, registerAppVersion, appVersionFailedForNetwork } from './device/appversion.js';
|
|
12
|
+
import { holdTabLock, checkAndUpdateSession, updateLastActiveTimeOfSession, updateScreenWithEndtime, endCurrentSession } from './engagement/session.js';
|
|
13
|
+
import { enableApiTracking } from './engagement/auto-api.js';
|
|
14
|
+
import { routePath, enableScreenTracking } from './engagement/auto-screen.js';
|
|
15
|
+
import { resumeScreen, trackEvent } from './engagement/track.js';
|
|
16
|
+
import { trackError } from './errors/track.js';
|
|
17
|
+
import { startTimers, flush, stopTimers } from './scheduler.js';
|
|
18
|
+
import { sendStoredLogs, forgetStoredLogs } from './remotelog/send.js';
|
|
19
|
+
import { getUserInfo } from './user/index.js';
|
|
20
|
+
import { clearAttempts } from './core/attempts.js';
|
|
21
|
+
import { FRAMEWORK_ID } from './core/framework-id.js';
|
|
22
|
+
|
|
23
|
+
let listenersAttached = false;
|
|
24
|
+
/** The config init() was last called with; reset() starts again from it. */
|
|
25
|
+
let lastConfig = null;
|
|
26
|
+
/** Bumped by every init(); a start() from an earlier init() that was reset in between does nothing. */
|
|
27
|
+
let generation = 0;
|
|
28
|
+
function isBrowser() {
|
|
29
|
+
if (typeof window === 'undefined' || typeof document === 'undefined' || typeof Promise === 'undefined') { // no i18n
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return typeof indexedDB !== 'undefined' || !!window.localStorage; // no i18n
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return false; // storage access can throw in sandboxed frames
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The config as the SDK keeps it: base_url, auth_key and app_domain under their inside names
|
|
41
|
+
* (serviceUrl, zak, identifier; see CONFIG_KEYS), and appversion as text. An earlier name of a
|
|
42
|
+
* key is still read when the key itself is not given, with a warning naming the key to use.
|
|
43
|
+
* The base url loses any trailing slash, because request paths add their own.
|
|
44
|
+
*/
|
|
45
|
+
function normaliseConfig(config) {
|
|
46
|
+
var settings = Object.assign({}, config);
|
|
47
|
+
Object.keys(CONFIG_KEYS).forEach(function (key) {
|
|
48
|
+
var inside = CONFIG_KEYS[key];
|
|
49
|
+
if (!isEmpty(config[key])) {
|
|
50
|
+
settings[inside] = config[key];
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
delete settings[inside];
|
|
54
|
+
Object.keys(RETIRED_CONFIG_NAMES).forEach(function (old) {
|
|
55
|
+
if (RETIRED_CONFIG_NAMES[old] !== key || isEmpty(config[old])) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
warnAlways('Apptics.init: ' + old + ' is now ' + key + '; rename it in your config'); // no i18n
|
|
59
|
+
if (isEmpty(settings[inside])) {
|
|
60
|
+
settings[inside] = config[old];
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
if (!isEmpty(settings.serviceUrl)) {
|
|
65
|
+
settings.serviceUrl = String(settings.serviceUrl).replace(/\/+$/, ''); // no i18n
|
|
66
|
+
}
|
|
67
|
+
settings.appversion = appVersionText(settings.appversion);
|
|
68
|
+
return settings;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The version as the text it is stored and sent as: a string trimmed, a finite number as its digits.
|
|
72
|
+
* Anything else — blank, NaN, an object — is '', which init() reports as missing.
|
|
73
|
+
*/
|
|
74
|
+
function appVersionText(value) {
|
|
75
|
+
if (typeof value === 'number') { // no i18n
|
|
76
|
+
return isFinite(value) ? String(value) : ''; // no i18n
|
|
77
|
+
}
|
|
78
|
+
return typeof value === 'string' ? value.trim() : ''; // no i18n
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The placeholders the console's snippets and the docs show for appversion. Starting with one
|
|
82
|
+
* would register a version that was never shipped, and every visit after it would report under it.
|
|
83
|
+
*/
|
|
84
|
+
const APPVERSION_PLACEHOLDERS = ['YOUR_APP_VERSION', 'YOUR_BUILD_OR_APP_VERSION', 'YOUR_APPVERSION']; // no i18n
|
|
85
|
+
function isAppVersionPlaceholder(version) {
|
|
86
|
+
return APPVERSION_PLACEHOLDERS.indexOf(version.toUpperCase()) !== -1;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The framework id this SDK reports to serviceUrl. The id built into the package is the one
|
|
90
|
+
* sdk-apptics.zoho.com issued at publish time, and localzoho servers don't know it: any
|
|
91
|
+
* localzoho serviceUrl reports 5970 instead, the id apptics.localzoho.com issued for 2.0.0-beta
|
|
92
|
+
* (Browser), so the same package also runs there.
|
|
93
|
+
*/
|
|
94
|
+
const LOCALZOHO_FRAMEWORK_ID = 5970;
|
|
95
|
+
function frameworkIdFor(serviceUrl) {
|
|
96
|
+
var host = serviceUrl;
|
|
97
|
+
try {
|
|
98
|
+
host = new URL(serviceUrl).hostname;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// not a url: judge the text as it is
|
|
102
|
+
}
|
|
103
|
+
return host.indexOf('localzoho') !== -1 ? LOCALZOHO_FRAMEWORK_ID : FRAMEWORK_ID; // no i18n
|
|
104
|
+
}
|
|
105
|
+
/** Which app this browser's stored data belongs to, before app/addversion has named it. */
|
|
106
|
+
function appKey(settings) {
|
|
107
|
+
return settings.serviceUrl + '|' + settings.identifier; // no i18n
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The app version stored data belongs to, read before app/addversion replaces it: what tells a
|
|
111
|
+
* new release of the app from a reload. '' on a first visit.
|
|
112
|
+
*/
|
|
113
|
+
var previousAppVersion = ''; // no i18n
|
|
114
|
+
/**
|
|
115
|
+
* Data stored for another app on this origin is discarded before anything else reads it,
|
|
116
|
+
* including the stored app/addversion answer that would otherwise hand out that app's ids.
|
|
117
|
+
*/
|
|
118
|
+
function discardOtherAppData(settings) {
|
|
119
|
+
// which app it is, and which version, are in app_version_info (serviceUrl, identifier and
|
|
120
|
+
// appversion): no keys of their own. 2.0.0-beta's appkey and appversion are read this once
|
|
121
|
+
var info = user_defaults.item('app_version_info') || {}; // no i18n
|
|
122
|
+
var stored = !isEmpty(info.serviceUrl) && !isEmpty(info.identifier)
|
|
123
|
+
? appKey(info)
|
|
124
|
+
: user_defaults.item('appkey'); // no i18n
|
|
125
|
+
previousAppVersion = String((!isEmpty(info.appversion) ? info.appversion : user_defaults.item('appversion')) || ''); // no i18n
|
|
126
|
+
user_defaults.add('appkey', undefined); // no i18n
|
|
127
|
+
user_defaults.add('appversion', undefined); // no i18n
|
|
128
|
+
if (!isEmpty(stored) && stored !== appKey(settings)) {
|
|
129
|
+
Console.log('init: stored data belongs to another app, resetting'); // no i18n
|
|
130
|
+
clearStorage();
|
|
131
|
+
previousAppVersion = ''; // no i18n
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Timing an app may tune in init(). Each has a range: a value outside it is ignored with a
|
|
136
|
+
* warning rather than applied, since a 10 ms flush interval would hammer the server.
|
|
137
|
+
*/
|
|
138
|
+
const TUNABLE = Object.freeze([
|
|
139
|
+
{ key: 'batch_flush_interval', min: 1000, max: 3600000 }, // no i18n
|
|
140
|
+
{ key: 'getupdates_interval', min: 10000, max: 86400000 }, // no i18n
|
|
141
|
+
{ key: 'batch_size', min: 1, max: 500 }, // no i18n
|
|
142
|
+
{ key: 'batch_request_timeout', min: 1, max: 30 }, // no i18n
|
|
143
|
+
{ key: 'min_session_duration', min: 0, max: 60000 }, // no i18n
|
|
144
|
+
{ key: 'heatmap_max_per_session', min: 0, max: 10000 }, // no i18n
|
|
145
|
+
]);
|
|
146
|
+
/** Reads the tunable timings out of the config. Anything missing goes back to its default. */
|
|
147
|
+
function applyIntervals(config) {
|
|
148
|
+
TUNABLE.forEach(function (rule) {
|
|
149
|
+
var value = config[rule.key];
|
|
150
|
+
// every init() decides these on its own, so a value from an earlier one never lingers
|
|
151
|
+
_ZAP._config[rule.key] = AP_CONFIG[rule.key];
|
|
152
|
+
if (isEmpty(value)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
var num = Number(value);
|
|
156
|
+
if (!isFinite(num) || num < rule.min || num > rule.max) {
|
|
157
|
+
warnAlways('Apptics.init: ' + rule.key + ' must be between ' + rule.min + ' and ' + rule.max + ', keeping ' + _ZAP._config[rule.key]); // no i18n
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
_ZAP._config[rule.key] = num;
|
|
161
|
+
Console.log('Apptics.init: ' + rule.key + ' set to ' + num); // no i18n
|
|
162
|
+
});
|
|
163
|
+
// inactiveduration lives on state, not in _config
|
|
164
|
+
_ZAP.inactiveduration = DEFAULT_INACTIVE_MINUTES;
|
|
165
|
+
if (!isEmpty(config.inactiveduration)) {
|
|
166
|
+
var minutes = Number(config.inactiveduration);
|
|
167
|
+
if (!isFinite(minutes) || minutes < 1 || minutes > 1440) {
|
|
168
|
+
warnAlways('Apptics.init: inactiveduration must be between 1 and 1440 minutes, keeping ' + _ZAP.inactiveduration); // no i18n
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
_ZAP.inactiveduration = minutes;
|
|
172
|
+
Console.log('Apptics.init: inactiveduration set to ' + minutes); // no i18n
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function clearStorage() {
|
|
177
|
+
clearAttempts();
|
|
178
|
+
user_defaults.clear();
|
|
179
|
+
engagement_transactions.clear();
|
|
180
|
+
error_transactions.clear();
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Starts the SDK. The config object is required: pass it directly, or let the console snippet
|
|
184
|
+
* supply it (auto-init reads window.appticssettings._defaultoptions and calls init with it).
|
|
185
|
+
* Returns true when the config has base_url, auth_key, app_domain and appversion and start-up has
|
|
186
|
+
* begun (see ready()), false when it was already running, the config is missing or incomplete, or
|
|
187
|
+
* there is no browser. Start-up calls app/addversion with those four, which answers with aaid, the
|
|
188
|
+
* portal and project ids and the rest; then device/add registers the browser, as in 1.x.
|
|
189
|
+
*/
|
|
190
|
+
function init(config) {
|
|
191
|
+
if (!config || typeof config !== 'object') { // no i18n
|
|
192
|
+
warnAlways('Apptics.init: a config object is required, e.g. Apptics.init({ base_url, auth_key, app_domain, appversion })'); // no i18n
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (_ZAP._initialised) {
|
|
196
|
+
Console.warn('Apptics.init: already initialised; call Apptics.reset() to start again'); // no i18n
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (!isBrowser()) {
|
|
200
|
+
warnAlways('Apptics.init: skipped, no browser with storage and Promise support'); // no i18n
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
var settings = normaliseConfig(config);
|
|
204
|
+
var missing = REQUIRED_CONFIG.filter(function (key) {
|
|
205
|
+
return isEmpty(settings[CONFIG_KEYS[key] || key]);
|
|
206
|
+
});
|
|
207
|
+
if (missing.length > 0) {
|
|
208
|
+
warnAlways('Apptics.init: missing required config: ' + missing.join(', ')); // no i18n
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (isAppVersionPlaceholder(settings.appversion)) {
|
|
212
|
+
warnAlways('Apptics.init: appversion is still the placeholder "' + settings.appversion // no i18n
|
|
213
|
+
+ '"; replace it with your app\'s version or build number, e.g. \'2.4.0\''); // no i18n
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
_ZAP._defaultoptions = settings;
|
|
217
|
+
// every request goes to serviceUrl; there is no other host to learn about
|
|
218
|
+
_ZAP._defaultoptions.base_domain = settings.serviceUrl;
|
|
219
|
+
// decided per init(), so a reset() or a page on another host never keeps the last one
|
|
220
|
+
_ZAP._frameworkID = frameworkIdFor(settings.serviceUrl);
|
|
221
|
+
// marks the SDK as started for the checks that need it; app/addversion replaces it with aaid
|
|
222
|
+
_ZAP._token = settings.identifier;
|
|
223
|
+
// console_log: the SDK's own activity in the browser console
|
|
224
|
+
if (!isEmpty(config.console_log)) {
|
|
225
|
+
_ZAP._config.debug = !!config.console_log;
|
|
226
|
+
}
|
|
227
|
+
if (typeof config.ignore_do_not_track === 'boolean') { // no i18n
|
|
228
|
+
_ZAP._config.ignore_do_not_track = config.ignore_do_not_track;
|
|
229
|
+
}
|
|
230
|
+
// debug: debug mode, the debug endpoints. Same payloads, but the answer says where to trace the
|
|
231
|
+
// request. debug_api is its earlier name and still works; debug wins when both are given
|
|
232
|
+
_ZAP._config.debug_api = typeof config.debug === 'boolean' ? config.debug // no i18n
|
|
233
|
+
: typeof config.debug_api === 'boolean' ? config.debug_api : AP_CONFIG.debug_api; // no i18n
|
|
234
|
+
// starts (or clears) the idle hour that closes debug mode on its own
|
|
235
|
+
// an enableDebug() or disableDebug() call the app already made wins over the config
|
|
236
|
+
applyDebugConfig(_ZAP._config.debug_api);
|
|
237
|
+
// what to call this device in the debug stream; see devicename in core/transport.ts
|
|
238
|
+
_ZAP._config.device_name = typeof config.device_name === 'string' ? config.device_name : AP_CONFIG.device_name; // no i18n
|
|
239
|
+
// the SDK no longer reports its own failures to the Remote Logger page: only the lines an app
|
|
240
|
+
// logs with AppticsLogger go there
|
|
241
|
+
if (config.remote_log !== undefined) {
|
|
242
|
+
warnAlways('Apptics.init: remote_log is no longer read; the Remote Logger page shows the lines your app logs with AppticsLogger'); // no i18n
|
|
243
|
+
}
|
|
244
|
+
// AppticsLogger lines go to the Remote Logger page only, never to the browser console
|
|
245
|
+
if (config.show_logs !== undefined) {
|
|
246
|
+
warnAlways('Apptics.init: show_logs is no longer read; AppticsLogger lines are not printed to the browser console'); // no i18n
|
|
247
|
+
}
|
|
248
|
+
// automatic api tracking: the app's own requests, wrapped. Which hosts it records is
|
|
249
|
+
// configureApiTracking's to say; the two keys that once did it are refused out loud rather
|
|
250
|
+
// than quietly ignored, since an app relying on them would otherwise record hosts it meant not to
|
|
251
|
+
_ZAP._config.track_api = AP_CONFIG.track_api;
|
|
252
|
+
['track_api_hosts', 'track_api_exclude_hosts'].forEach(function (key) {
|
|
253
|
+
if (config[key] !== undefined) {
|
|
254
|
+
warnAlways('Apptics.init: ' + key + ' is no longer supported; use AppticsApi.configure(new AppticsApi.ApiTrackingBuilder().' // no i18n
|
|
255
|
+
+ (key === 'track_api_hosts' ? 'allowOnlyDomains' : 'ignoreDomains') + '(...).build())'); // no i18n
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
// screen recording is set up in the console now, and arrives with getsdkconfig
|
|
259
|
+
['DC', 'quartz_workspace_key', 'quartz_department_key'].forEach(function (key) {
|
|
260
|
+
if (config[key] !== undefined) {
|
|
261
|
+
warnAlways('Apptics.init: ' + key + ' is no longer read; screen recording in the feedback form is set up from the Apptics console'); // no i18n
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// sessions are what everything else is recorded in; an app that wants nothing tracked uses
|
|
265
|
+
// AppticsSettings.setTrackingStatus instead
|
|
266
|
+
if (config.track_session !== undefined) {
|
|
267
|
+
warnAlways('Apptics.init: track_session is no longer read; sessions are always recorded. To stop tracking, use AppticsSettings.setTrackingStatus(AppticsSettings.AP_TRACKING_STATUS.NO_TRACKING)'); // no i18n
|
|
268
|
+
}
|
|
269
|
+
if (typeof config.track_api === 'boolean') { // no i18n
|
|
270
|
+
_ZAP._config.track_api = config.track_api;
|
|
271
|
+
}
|
|
272
|
+
enableApiTracking(_ZAP._config.track_api);
|
|
273
|
+
// heatmap capture: switched through the hook heatmap/track.ts sets
|
|
274
|
+
_ZAP._config.track_heatmap = typeof config.track_heatmap === 'boolean' // no i18n
|
|
275
|
+
? config.track_heatmap
|
|
276
|
+
: AP_CONFIG.track_heatmap;
|
|
277
|
+
hooks.trackHeatmap(_ZAP._config.track_heatmap);
|
|
278
|
+
// automatic screen tracking, on unless the config says false: switched on by start(), once a
|
|
279
|
+
// screen view can be stored
|
|
280
|
+
_ZAP._config.track_screen = typeof config.track_screen === 'boolean' ? config.track_screen : AP_CONFIG.track_screen; // no i18n
|
|
281
|
+
_ZAP._config.screen_routes = screenRouteRules(config.screen_routes);
|
|
282
|
+
// a theme in the config wins; without one, what AppticsFeedback.setFeedbackTheme set stays
|
|
283
|
+
if (config.feedback_theme === 'light' || config.feedback_theme === 'dark' || config.feedback_theme === 'auto') { // no i18n
|
|
284
|
+
_ZAP._config.feedback_theme = config.feedback_theme;
|
|
285
|
+
}
|
|
286
|
+
applyIntervals(config);
|
|
287
|
+
lastConfig = config;
|
|
288
|
+
_ZAP._initialised = true;
|
|
289
|
+
_ZAP._ready = false;
|
|
290
|
+
_ZAP._pending = [];
|
|
291
|
+
var myGeneration = ++generation;
|
|
292
|
+
_ZAP._readyPromise = storageReady().then(function (backend) {
|
|
293
|
+
if (myGeneration !== generation) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
if (!backend) {
|
|
297
|
+
warnAlways('Apptics.init: no usable storage (IndexedDB or localStorage), not starting'); // no i18n
|
|
298
|
+
_ZAP._initialised = false;
|
|
299
|
+
_ZAP._token = ''; // no i18n
|
|
300
|
+
_ZAP._pending = [];
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
discardOtherAppData(_ZAP._defaultoptions);
|
|
304
|
+
// app/addversion is the first call and everything waits on it: it is what turns the four
|
|
305
|
+
// values init() was given into aaid, the portal and project ids every url carries, and
|
|
306
|
+
// appreleaseversionid, which metaWebSDKV2 refuses a payload without
|
|
307
|
+
return registerAppVersion().then(function (registered) {
|
|
308
|
+
if (myGeneration !== generation) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
if (!registered && appVersionFailedForNetwork()) {
|
|
312
|
+
// a first visit with no network: start once it is back, keeping what the app
|
|
313
|
+
// tracks until then queued, rather than giving up for the rest of the page
|
|
314
|
+
warnAlways('Apptics.init: offline, so app/addversion is tried again when the browser is back online; calls made until then are kept'); // no i18n
|
|
315
|
+
return registerWhenOnline(myGeneration).then(function (ok) {
|
|
316
|
+
if (myGeneration !== generation) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (!ok) {
|
|
320
|
+
_ZAP._initialised = false;
|
|
321
|
+
_ZAP._token = ''; // no i18n
|
|
322
|
+
_ZAP._pending = [];
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return start(config, myGeneration);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (!registered) {
|
|
329
|
+
warnAlways('Apptics.init: app/addversion did not return the app ids, not starting'); // no i18n
|
|
330
|
+
_ZAP._initialised = false;
|
|
331
|
+
_ZAP._token = ''; // no i18n
|
|
332
|
+
_ZAP._pending = [];
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return start(config, myGeneration);
|
|
336
|
+
});
|
|
337
|
+
}).catch(function (err) {
|
|
338
|
+
Console.error('Apptics.init: start-up failed', err); // no i18n
|
|
339
|
+
return false;
|
|
340
|
+
});
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
/** How often a start-up waiting on the network tries app/addversion again, besides on `online`. */
|
|
344
|
+
const OFFLINE_RETRY_MS = 60 * 1000;
|
|
345
|
+
/**
|
|
346
|
+
* app/addversion again once the network is back: on the `online` event, and every minute for a
|
|
347
|
+
* network that came back without one. Resolves true once registered, false when the server
|
|
348
|
+
* refuses it or a reset()/new init() took over.
|
|
349
|
+
*/
|
|
350
|
+
function registerWhenOnline(myGeneration) {
|
|
351
|
+
return new Promise(function (resolve) {
|
|
352
|
+
var timer = null;
|
|
353
|
+
var trying = false;
|
|
354
|
+
function done(ok) {
|
|
355
|
+
window.removeEventListener('online', attempt); // no i18n
|
|
356
|
+
if (timer !== null)
|
|
357
|
+
clearInterval(timer);
|
|
358
|
+
resolve(ok);
|
|
359
|
+
}
|
|
360
|
+
function attempt() {
|
|
361
|
+
if (myGeneration !== generation) {
|
|
362
|
+
done(false);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (trying || navigator.onLine === false) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
trying = true;
|
|
369
|
+
registerAppVersion().then(function (registered) {
|
|
370
|
+
trying = false;
|
|
371
|
+
if (registered) {
|
|
372
|
+
done(true);
|
|
373
|
+
}
|
|
374
|
+
else if (!appVersionFailedForNetwork()) {
|
|
375
|
+
warnAlways('Apptics.init: app/addversion did not return the app ids, not starting'); // no i18n
|
|
376
|
+
done(false);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
window.addEventListener('online', attempt); // no i18n
|
|
381
|
+
timer = setInterval(attempt, OFFLINE_RETRY_MS);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* screen_routes as init() keeps it: each entry checked, an exact path cleaned up, a regex given as
|
|
386
|
+
* a string compiled. An entry that cannot be used is left out, and said, rather than matching
|
|
387
|
+
* something its author did not mean.
|
|
388
|
+
*/
|
|
389
|
+
function screenRouteRules(routes) {
|
|
390
|
+
if (!Array.isArray(routes)) {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
var out = [];
|
|
394
|
+
routes.forEach(function (route) {
|
|
395
|
+
if (typeof route === 'string') { // no i18n
|
|
396
|
+
if (route.trim() !== '') { // no i18n
|
|
397
|
+
out.push({ kind: 'pattern', pattern: route.trim() }); // no i18n
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
var rule = route && typeof route === 'object' ? route : null; // no i18n
|
|
402
|
+
var name = rule && typeof rule.name === 'string' ? rule.name.trim() : ''; // no i18n
|
|
403
|
+
if (rule && name !== '' && typeof rule.equals === 'string' && rule.equals.trim() !== '') { // no i18n
|
|
404
|
+
out.push({ kind: 'equals', path: routePath(rule.equals.trim()), name: name }); // no i18n
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (rule && name !== '' && rule.regex !== undefined) {
|
|
408
|
+
var regex = null;
|
|
409
|
+
if (rule.regex instanceof RegExp) {
|
|
410
|
+
regex = rule.regex;
|
|
411
|
+
}
|
|
412
|
+
else if (typeof rule.regex === 'string' && rule.regex !== '') { // no i18n
|
|
413
|
+
try {
|
|
414
|
+
regex = new RegExp(rule.regex);
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
regex = null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (regex) {
|
|
421
|
+
out.push({ kind: 'regex', regex: regex, name: name }); // no i18n
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
warnAlways('Apptics.init: screen_routes entry ' + safeJson(route) + ' is left out: give a route pattern, { equals, name } or { regex, name }'); // no i18n
|
|
426
|
+
});
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
/** An entry as the warning shows it: a RegExp as its source, which JSON.stringify would hide. */
|
|
430
|
+
function safeJson(value) {
|
|
431
|
+
try {
|
|
432
|
+
return JSON.stringify(value, function (_key, one) { return one instanceof RegExp ? String(one) : one; }) || String(value);
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
return String(value);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/** Resolves true once start-up has finished, false if init() refused or storage is unusable. */
|
|
439
|
+
function ready() {
|
|
440
|
+
return _ZAP._readyPromise || Promise.resolve(false);
|
|
441
|
+
}
|
|
442
|
+
function start(config, myGeneration) {
|
|
443
|
+
// this tab's lock, before any screen view: it is how other tabs tell this one is still open
|
|
444
|
+
holdTabLock();
|
|
445
|
+
// app/addversion named the app; data stored for another one was discarded before it ran
|
|
446
|
+
_ZAP._token = String(_ZAP._defaultoptions.aaid);
|
|
447
|
+
// last known server settings (getupdates) apply before anything is tracked
|
|
448
|
+
restoreServerSettings();
|
|
449
|
+
// a new release of the app, against the version the stored data was for (see
|
|
450
|
+
// discardOtherAppData): a first visit has none, and is no change
|
|
451
|
+
var appVersionChanged = previousAppVersion !== '' && previousAppVersion != _ZAP._defaultoptions.appversion; // no i18n
|
|
452
|
+
_ZAP._ignoreDnt = _ZAP._config.ignore_do_not_track;
|
|
453
|
+
// the visitor's own stored choice, not getTrackingStatus(): that answers NO_TRACKING while Do
|
|
454
|
+
// Not Track is on, and storing it made one such visit switch tracking off for good
|
|
455
|
+
tracking_preference.update_status(tracking_preference.opt_in_status());
|
|
456
|
+
// the uuid and device info, and a registration on a first visit, under a lock shared with the
|
|
457
|
+
// site's other tabs (see prepareDevice)
|
|
458
|
+
return prepareDevice().then(function () {
|
|
459
|
+
if (myGeneration !== generation) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
return startTracking(appVersionChanged, myGeneration);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/** The rest of start-up, once the device is set up. */
|
|
466
|
+
function startTracking(appVersionChanged, myGeneration) {
|
|
467
|
+
// no getsdkconfig here, straight after app/addversion: it is asked for once device/add has
|
|
468
|
+
// succeeded (registerDeviceWithApptics), then every getupdates_interval. A returning visit
|
|
469
|
+
// starts from the settings saved by the last one (restoreServerSettings, in start())
|
|
470
|
+
if (appVersionChanged) {
|
|
471
|
+
_ZAP._device_info.appversionid = ''; // no i18n
|
|
472
|
+
updateDeviceWithApptics(_ZAP._device_info).then(function (result) {
|
|
473
|
+
if (isSuccess(result)) {
|
|
474
|
+
Console.log('init: appversion updated to ' + _ZAP._defaultoptions.appversion); // no i18n
|
|
475
|
+
}
|
|
476
|
+
}).catch(function (err) {
|
|
477
|
+
Console.log('init: error updating appversion: ' + err); // no i18n
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// attempt to resolve connection type
|
|
481
|
+
_ZAP._device_info.connectionType = getConnectionType();
|
|
482
|
+
attachListeners();
|
|
483
|
+
_ZAP._properties.orientation = getOrientation();
|
|
484
|
+
getUserInfo();
|
|
485
|
+
return checkAndUpdateSession().then(function () {
|
|
486
|
+
if (myGeneration !== generation) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
startTimers();
|
|
490
|
+
sendStoredLogs();
|
|
491
|
+
_ZAP._ready = true;
|
|
492
|
+
// replay calls made while storage was loading, in order
|
|
493
|
+
var pending = _ZAP._pending;
|
|
494
|
+
_ZAP._pending = [];
|
|
495
|
+
pending.forEach(function (call) {
|
|
496
|
+
try {
|
|
497
|
+
call();
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
Console.error('apptics: buffered call failed', err); // no i18n
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
// after the replay, so a inScreen() the app made during start-up keeps its place, and
|
|
504
|
+
// the first automatic view is the page as it is now
|
|
505
|
+
enableScreenTracking(_ZAP._config.track_screen);
|
|
506
|
+
// a no-op unless the remote-config entry was imported, which sets the hook
|
|
507
|
+
hooks.remoteConfigStarted();
|
|
508
|
+
window.appticsReadyStatus = true;
|
|
509
|
+
if (typeof window.appticsReady == 'function') { // no i18n
|
|
510
|
+
window.appticsReady();
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
/** Clears stored identity and queues, then starts again from the snippet. */
|
|
516
|
+
function reset() {
|
|
517
|
+
generation++;
|
|
518
|
+
stopTimers();
|
|
519
|
+
// the wrappers come off with everything else; the init() below puts them back if the config
|
|
520
|
+
// asked for them, so a reset that has no config to restore leaves XHR and fetch untouched
|
|
521
|
+
enableApiTracking(false);
|
|
522
|
+
enableScreenTracking(false);
|
|
523
|
+
clearAppVersion();
|
|
524
|
+
hooks.trackHeatmap(false);
|
|
525
|
+
hooks.resetHeatmap();
|
|
526
|
+
hooks.resetRemoteConfig();
|
|
527
|
+
clearStorage();
|
|
528
|
+
_ZAP._initialised = false;
|
|
529
|
+
_ZAP._ready = false;
|
|
530
|
+
_ZAP._pending = [];
|
|
531
|
+
_ZAP._readyPromise = null;
|
|
532
|
+
_ZAP._device_info = {};
|
|
533
|
+
_ZAP._user_info = {};
|
|
534
|
+
_ZAP._current_screen = {};
|
|
535
|
+
_ZAP._current_session = {};
|
|
536
|
+
_ZAP._uuid = ''; // no i18n
|
|
537
|
+
_ZAP._apid = ''; // no i18n
|
|
538
|
+
_ZAP._mapid = ''; // no i18n
|
|
539
|
+
_ZAP._sending = false;
|
|
540
|
+
Console.log('Apptics.reset()'); // no i18n
|
|
541
|
+
// start again with the config init() was called with
|
|
542
|
+
if (lastConfig) {
|
|
543
|
+
init(lastConfig);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function isActive() {
|
|
547
|
+
return _ZAP._ready;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* A click-tracking attribute, in either spelling: `apeventaction`, or `data-apeventaction`, which
|
|
551
|
+
* is valid HTML and what React and TypeScript accept without declaring the attribute.
|
|
552
|
+
*/
|
|
553
|
+
function trackingAttribute(el, name) {
|
|
554
|
+
var value = el.getAttribute(name);
|
|
555
|
+
return value !== null && value !== '' ? value : el.getAttribute('data-' + name); // no i18n
|
|
556
|
+
}
|
|
557
|
+
/** `[name]` and `[data-name]`, empty values left out, for closest(). */
|
|
558
|
+
function trackingSelector(name) {
|
|
559
|
+
return '[' + name + ']:not([' + name + '=""]),[data-' + name + ']:not([data-' + name + '=""])'; // no i18n
|
|
560
|
+
}
|
|
561
|
+
// Declarative click tracking: <el apeventaction="name" apeventprops='{"k":"v"}'> inside <el apgroupaction="group">,
|
|
562
|
+
// or the same attributes with a data- prefix
|
|
563
|
+
function onDocumentClick(event) {
|
|
564
|
+
if (!window.appticsReadyStatus || !isActive()) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
var start = event.target;
|
|
568
|
+
if (start && typeof start.closest !== 'function') { // no i18n
|
|
569
|
+
start = start.parentElement;
|
|
570
|
+
}
|
|
571
|
+
if (!start) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
var targetElem = start.closest(trackingSelector('apeventaction')); // no i18n
|
|
575
|
+
if (!targetElem) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
var eventName = trackingAttribute(targetElem, 'apeventaction') || ''; // no i18n
|
|
579
|
+
var groupElem = targetElem.closest(trackingSelector('apgroupaction')); // no i18n
|
|
580
|
+
var groupName = (groupElem && trackingAttribute(groupElem, 'apgroupaction')) || AP_CONFIG.default_group_name; // no i18n
|
|
581
|
+
var eventProps = {};
|
|
582
|
+
var rawProps = trackingAttribute(targetElem, 'apeventprops'); // no i18n
|
|
583
|
+
if (!isEmpty(rawProps)) {
|
|
584
|
+
try {
|
|
585
|
+
eventProps = JSON.parse(rawProps);
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
Console.warn('apptics: invalid JSON in apeventprops for "' + eventName + '", sending no properties'); // no i18n
|
|
589
|
+
eventProps = {};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!isEmpty(eventName)) {
|
|
593
|
+
trackEvent(eventName, groupName, eventProps);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Page is in the background but still there: close the open screen and send what's queued.
|
|
598
|
+
* By keepalive, because hidden is often the last moment before the tab closes, and a send still
|
|
599
|
+
* waiting on its answer would hold the queue as pagehide tries to send it.
|
|
600
|
+
*/
|
|
601
|
+
function onPageHidden() {
|
|
602
|
+
if (!isActive())
|
|
603
|
+
return;
|
|
604
|
+
updateScreenWithEndtime();
|
|
605
|
+
flush(true);
|
|
606
|
+
Console.log('page hidden: closing the open screen and flushing'); // no i18n
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The tab is closing, or the page is being replaced or frozen into the back/forward cache.
|
|
610
|
+
* The session ends here, and everything queued goes out by keepalive, since a normal
|
|
611
|
+
* request would be cancelled with the page.
|
|
612
|
+
*/
|
|
613
|
+
function onPageClosing() {
|
|
614
|
+
if (!isActive())
|
|
615
|
+
return;
|
|
616
|
+
Console.log('page closing: ending the session and sending what is queued'); // no i18n
|
|
617
|
+
updateScreenWithEndtime();
|
|
618
|
+
forgetStoredLogs();
|
|
619
|
+
endCurrentSession().then(function () {
|
|
620
|
+
return flush(true);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
function attachListeners() {
|
|
624
|
+
if (listenersAttached) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
listenersAttached = true;
|
|
628
|
+
// listen for connection change events (only available in w3c implementation)
|
|
629
|
+
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
630
|
+
if (connection && connection.addEventListener) {
|
|
631
|
+
connection.addEventListener('change', function () {
|
|
632
|
+
_ZAP._device_info.connectionType = getConnectionType();
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
// Track when the window loses focus (user switches to another window or application)
|
|
636
|
+
window.addEventListener('blur', function () {
|
|
637
|
+
if (!isActive())
|
|
638
|
+
return;
|
|
639
|
+
Console.log('Window lost focus, user switched to another window or application'); // no i18n
|
|
640
|
+
updateLastActiveTimeOfSession();
|
|
641
|
+
});
|
|
642
|
+
// Track when the window gains focus (user switches back to the window)
|
|
643
|
+
window.addEventListener('focus', function () {
|
|
644
|
+
if (!isActive())
|
|
645
|
+
return;
|
|
646
|
+
Console.log('Window regained focus, user switched back to the window'); // no i18n
|
|
647
|
+
checkAndUpdateSession();
|
|
648
|
+
startTimers();
|
|
649
|
+
});
|
|
650
|
+
// IndexedDB writes are async, so unload work starts on pagehide rather than beforeunload
|
|
651
|
+
window.addEventListener('pagehide', onPageClosing); // no i18n
|
|
652
|
+
// back/forward cache: pagehide ended the session, so coming back starts a new one
|
|
653
|
+
window.addEventListener('pageshow', function (event) {
|
|
654
|
+
if (!isActive() || !event.persisted)
|
|
655
|
+
return;
|
|
656
|
+
Console.log('restored from the back/forward cache: starting a new session'); // no i18n
|
|
657
|
+
// the screen the page was left on starts a new view, in the new session
|
|
658
|
+
checkAndUpdateSession().then(resumeScreen);
|
|
659
|
+
startTimers();
|
|
660
|
+
});
|
|
661
|
+
document.addEventListener('visibilitychange', function () {
|
|
662
|
+
if (!isActive())
|
|
663
|
+
return;
|
|
664
|
+
if (document.visibilityState === 'hidden') { // no i18n
|
|
665
|
+
onPageHidden();
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
// back in view: the server's tracking flags may have changed while we were away
|
|
669
|
+
Console.log('page visible: refreshing the server settings'); // no i18n
|
|
670
|
+
// hiding ended the screen view; the screen the user is back on starts a new one, after the
|
|
671
|
+
// session is settled so the view belongs to the right one
|
|
672
|
+
checkAndUpdateSession().then(resumeScreen);
|
|
673
|
+
refreshServerSettings();
|
|
674
|
+
});
|
|
675
|
+
// if we are running in cordova use ondeviceready otherwise onload
|
|
676
|
+
window.addEventListener(window.cordova ? 'deviceready' : 'load', function () {
|
|
677
|
+
if (isActive())
|
|
678
|
+
checkAndUpdateSession();
|
|
679
|
+
}, { passive: true });
|
|
680
|
+
// Always send pending requests when the connection comes back online
|
|
681
|
+
window.addEventListener('online', function () {
|
|
682
|
+
if (!isActive())
|
|
683
|
+
return;
|
|
684
|
+
// a first visit that started offline has no device ids yet, and nothing can be sent
|
|
685
|
+
// until it has them: register before flushing, and flush once that has landed
|
|
686
|
+
if (needsDevice()) {
|
|
687
|
+
Console.log('back online: registering this device before sending'); // no i18n
|
|
688
|
+
// the same locked path as start-up, so two tabs coming back online register it once
|
|
689
|
+
prepareDevice().then(deviceReady).then(function () {
|
|
690
|
+
if (!isActive())
|
|
691
|
+
return;
|
|
692
|
+
flush();
|
|
693
|
+
// the lines logged while offline are waiting too, and log/add needs the same ids
|
|
694
|
+
sendStoredLogs();
|
|
695
|
+
});
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
flush();
|
|
699
|
+
sendStoredLogs();
|
|
700
|
+
}, { passive: true });
|
|
701
|
+
if (window.screen && window.screen.orientation && typeof window.screen.orientation.addEventListener === 'function') { // no i18n
|
|
702
|
+
window.screen.orientation.addEventListener('change', function () {
|
|
703
|
+
_ZAP._properties.orientation = getOrientation();
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
window.addEventListener('unhandledrejection', function (event) {
|
|
707
|
+
if (isActive() && _ZAP._isGlobalErrorTracking) {
|
|
708
|
+
trackError(event);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
// in the capture phase: a <script>, <img> or <link> that fails to load fires `error` on the
|
|
712
|
+
// element, and it does not bubble, so a bubbling listener never hears of it. One listener
|
|
713
|
+
// hears both kinds, the window's own errors included, so none is counted twice
|
|
714
|
+
window.addEventListener('error', function (event) {
|
|
715
|
+
if (!isActive() || !_ZAP._isGlobalErrorTracking) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
var target = event.target;
|
|
719
|
+
if (target && target !== window && typeof target.tagName === 'string') { // no i18n
|
|
720
|
+
// named by describeTarget: Event "error" on <script src="...">
|
|
721
|
+
trackError({ type: 'error', reason: event }); // no i18n
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
trackError(event);
|
|
725
|
+
}, true);
|
|
726
|
+
window.addEventListener('click', onDocumentClick, true); // no i18n
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export { init, ready, reset, screenRouteRules };
|