@zoho_apptics/apptics-js-sdk 1.0.2 → 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 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Dict, ServerResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sends queued heatmap records to heatmap/add.
|
|
4
|
+
*
|
|
5
|
+
* Shaped like the error and engagement senders, because the queue behaves the same way: records
|
|
6
|
+
* are stored first and sent on the flush timer, a failed send leaves them for the next one, and
|
|
7
|
+
* `forget` is used on page hide so the last batch survives the page going away.
|
|
8
|
+
*
|
|
9
|
+
* Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
|
|
10
|
+
* want events while declining to have every click's coordinates recorded.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sendHeatmapData(forget?: boolean): Promise<void>;
|
|
13
|
+
export declare function requestToSendHeatmaps(_payload: Dict, forget?: boolean): Promise<ServerResult>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var state = require('../core/state.js');
|
|
5
|
+
var logger = require('../core/logger.js');
|
|
6
|
+
var utils = require('../core/utils.js');
|
|
7
|
+
var storage = require('../core/storage.js');
|
|
8
|
+
var env = require('../core/env.js');
|
|
9
|
+
var consent = require('../core/consent.js');
|
|
10
|
+
var batch = require('../core/batch.js');
|
|
11
|
+
var transport = require('../core/transport.js');
|
|
12
|
+
var hooks = require('../core/hooks.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sends queued heatmap records to heatmap/add.
|
|
16
|
+
*
|
|
17
|
+
* Shaped like the error and engagement senders, because the queue behaves the same way: records
|
|
18
|
+
* are stored first and sent on the flush timer, a failed send leaves them for the next one, and
|
|
19
|
+
* `forget` is used on page hide so the last batch survives the page going away.
|
|
20
|
+
*
|
|
21
|
+
* Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
|
|
22
|
+
* want events while declining to have every click's coordinates recorded.
|
|
23
|
+
*/
|
|
24
|
+
function sendHeatmapData(forget) {
|
|
25
|
+
if (env.is_apptics_disabled() || !consent.shouldTrackOrSendData() || !state._ZAP._heatmapTracking) {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
if (navigator.onLine == false) {
|
|
29
|
+
logger.Console.warn('sendHeatmapData: No Network'); // no i18n
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
if (state._ZAP._sending) {
|
|
33
|
+
batch.defer(sendHeatmapData);
|
|
34
|
+
return Promise.resolve();
|
|
35
|
+
}
|
|
36
|
+
state._ZAP._sending = true;
|
|
37
|
+
batch.clearDefer();
|
|
38
|
+
return batch.withSendLock('heatmaps', function () {
|
|
39
|
+
return batch.freshItems(storage.heatmap_transactions, function () { return true; },
|
|
40
|
+
// aged by when the record happened, not when its session began: a tab left open for
|
|
41
|
+
// days would otherwise make every new record look older than the retention window
|
|
42
|
+
// and have it dropped unsent
|
|
43
|
+
function (item) { return (item.triggered_time || item.session_start_time); }).then(function (items) {
|
|
44
|
+
if (items.length == 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
var data = [];
|
|
48
|
+
var sent = [];
|
|
49
|
+
utils.cloneObject(items).forEach(function (item) {
|
|
50
|
+
sent.push({ ...item });
|
|
51
|
+
// `id` is the queue's own key, not something the endpoint declares
|
|
52
|
+
delete item.id;
|
|
53
|
+
data.push(item);
|
|
54
|
+
});
|
|
55
|
+
return batch.awaitUnlessClosing(requestToSendHeatmaps({ data: data }, forget).then(function () {
|
|
56
|
+
return storage.heatmap_transactions.remove(sent);
|
|
57
|
+
}, function (err) {
|
|
58
|
+
// the server will never accept this batch. Keeping it would block every later
|
|
59
|
+
// batch behind it for good, so it is dropped and said out loud
|
|
60
|
+
if (transport.isPermanentRefusal(err)) {
|
|
61
|
+
logger.warnAlways('apptics: the server refused these heatmap records, so they were dropped: ' + String(err && err.message)); // no i18n
|
|
62
|
+
return storage.heatmap_transactions.remove(sent);
|
|
63
|
+
}
|
|
64
|
+
return Promise.reject(err);
|
|
65
|
+
}), forget, 'sendHeatmapData'); // no i18n
|
|
66
|
+
});
|
|
67
|
+
}).catch(function (err) {
|
|
68
|
+
logger.Console.log('sendHeatmapData:' + err); // no i18n
|
|
69
|
+
}).then(function () {
|
|
70
|
+
state._ZAP._sending = false;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function requestToSendHeatmaps(_payload, forget) {
|
|
74
|
+
var label = 'requestToSendHeatmaps'; // no i18n
|
|
75
|
+
return batch.sendBatch('heatmap', label, _payload, forget); // no i18n
|
|
76
|
+
}
|
|
77
|
+
// the scheduler flushes through this, so importing the heatmap entry is what puts heatmap
|
|
78
|
+
// records on the flush timer at all
|
|
79
|
+
hooks.hooks.sendHeatmaps = sendHeatmapData;
|
|
80
|
+
|
|
81
|
+
exports.requestToSendHeatmaps = requestToSendHeatmaps;
|
|
82
|
+
exports.sendHeatmapData = sendHeatmapData;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Turns capture on or off. Listeners are passive, so they never delay a click or a scroll. */
|
|
2
|
+
export declare function enableHeatmap(status?: boolean): void;
|
|
3
|
+
/** Forgets what this visit recorded. reset() calls it; the cap starts again from zero. */
|
|
4
|
+
export declare function resetHeatmapSession(): void;
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var constants = require('../core/constants.js');
|
|
5
|
+
var state = require('../core/state.js');
|
|
6
|
+
var logger = require('../core/logger.js');
|
|
7
|
+
var utils = require('../core/utils.js');
|
|
8
|
+
var storage = require('../core/storage.js');
|
|
9
|
+
var session = require('../engagement/session.js');
|
|
10
|
+
var env = require('../core/env.js');
|
|
11
|
+
var consent = require('../core/consent.js');
|
|
12
|
+
var track = require('../engagement/track.js');
|
|
13
|
+
var hooks = require('../core/hooks.js');
|
|
14
|
+
var debug = require('../core/debug.js');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Where people click and how far they scroll.
|
|
18
|
+
*
|
|
19
|
+
* Two shapes of record, and they behave very differently. A click is one discrete thing and is
|
|
20
|
+
* recorded as it happens. Scrolling fires continuously — hundreds of events for one flick of a
|
|
21
|
+
* wheel — so it is coalesced into one record per gesture: a burst of scrolling that ends when the
|
|
22
|
+
* page has been still for a moment. Recording every scroll event would swamp the queue, the
|
|
23
|
+
* batch and the user's bandwidth, and would say nothing a single record per gesture does not.
|
|
24
|
+
*/
|
|
25
|
+
/** How long the page must be still before a scroll burst counts as one finished gesture. */
|
|
26
|
+
const SCROLL_IDLE_MS = 150;
|
|
27
|
+
/** Clicks closer than this in time and space count as one angry repetition. */
|
|
28
|
+
const RAGE_WINDOW_MS = 1000;
|
|
29
|
+
const RAGE_RADIUS_PX = 30;
|
|
30
|
+
const RAGE_COUNT = 3;
|
|
31
|
+
/** How long a click has to change something before it is called dead. */
|
|
32
|
+
const DEAD_CLICK_MS = 1500;
|
|
33
|
+
/** Longest element label we keep; enough to identify a control, short enough not to be content. */
|
|
34
|
+
const LABEL_MAX = 80;
|
|
35
|
+
/** heatMapObjectSDK caps screen, url_path and element_label at 500 characters. */
|
|
36
|
+
const FIELD_MAX = 500;
|
|
37
|
+
var installed = false;
|
|
38
|
+
var recorded = 0;
|
|
39
|
+
/** Recent clicks, for spotting a rage burst. */
|
|
40
|
+
var recentClicks = [];
|
|
41
|
+
var scrollStart = 0;
|
|
42
|
+
var scrollStartX = 0;
|
|
43
|
+
var scrollStartY = 0;
|
|
44
|
+
var scrollTimer = null;
|
|
45
|
+
/**
|
|
46
|
+
* Where the page was before the current gesture began.
|
|
47
|
+
*
|
|
48
|
+
* A scroll event fires *after* the position has already changed, so the first event of a burst
|
|
49
|
+
* reports somewhere the page has moved to, not where it started. Measuring from that loses the
|
|
50
|
+
* first increment of every gesture, and loses a single-event jump — an anchor link or a "back to
|
|
51
|
+
* top" button — completely, which is exactly the movement worth seeing on a scroll map.
|
|
52
|
+
*/
|
|
53
|
+
var lastX = 0;
|
|
54
|
+
var lastY = 0;
|
|
55
|
+
/** Whether a record may still be stored, or the visit has produced enough. */
|
|
56
|
+
function underCap() {
|
|
57
|
+
var cap = state.getConfig('heatmap_max_per_session');
|
|
58
|
+
if (typeof cap !== 'number' || cap <= 0) {
|
|
59
|
+
cap = constants.AP_CONFIG.heatmap_max_per_session;
|
|
60
|
+
}
|
|
61
|
+
if (recorded >= cap) {
|
|
62
|
+
if (recorded === cap) {
|
|
63
|
+
recorded++; // said once, not on every event for the rest of the visit
|
|
64
|
+
logger.Console.log('apptics.heatmap: reached ' + cap + ' records for this session, pausing capture'); // no i18n
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/** The page's full scrollable height, which is what a scroll depth is a fraction of. */
|
|
71
|
+
function contentHeight() {
|
|
72
|
+
var doc = document.documentElement;
|
|
73
|
+
var body = document.body;
|
|
74
|
+
return Math.max(doc ? doc.scrollHeight : 0, body ? body.scrollHeight : 0, doc ? doc.clientHeight : 0);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A 0-1 fraction, to three decimals.
|
|
78
|
+
*
|
|
79
|
+
* Clamped because the inputs can exceed each other: a page that has not laid out yet reports a
|
|
80
|
+
* content height of 0, and a rubber-band scroll puts the viewport bottom past the document end.
|
|
81
|
+
* Both produce a "fraction" far outside 0-1, which is meaningless on a heatmap.
|
|
82
|
+
*/
|
|
83
|
+
function fraction(part, whole) {
|
|
84
|
+
if (!whole || whole <= 0) {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
var value = part / whole;
|
|
88
|
+
if (value < 0) {
|
|
89
|
+
value = 0;
|
|
90
|
+
}
|
|
91
|
+
if (value > 1) {
|
|
92
|
+
value = 1;
|
|
93
|
+
}
|
|
94
|
+
return Math.round(value * 1000) / 1000;
|
|
95
|
+
}
|
|
96
|
+
function viewportWidth() {
|
|
97
|
+
return window.innerWidth || (document.documentElement ? document.documentElement.clientWidth : 0) || 1;
|
|
98
|
+
}
|
|
99
|
+
/** Whether the element, or something it sits inside, is meant to be clicked. */
|
|
100
|
+
function clickableAncestor(node) {
|
|
101
|
+
var depth = 0;
|
|
102
|
+
while (node && depth < 10) {
|
|
103
|
+
var tag = node.tagName ? node.tagName.toLowerCase() : ''; // no i18n
|
|
104
|
+
var role = node.getAttribute ? node.getAttribute('role') : null; // no i18n
|
|
105
|
+
if (tag === 'a' || tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea' // no i18n
|
|
106
|
+
|| tag === 'summary' || tag === 'label' // no i18n
|
|
107
|
+
|| role === 'button' || role === 'link' || role === 'tab' || role === 'menuitem' // no i18n
|
|
108
|
+
|| (node.hasAttribute && node.hasAttribute('onclick'))) { // no i18n
|
|
109
|
+
return node;
|
|
110
|
+
}
|
|
111
|
+
node = node.parentElement;
|
|
112
|
+
depth++;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A name for the element that was clicked.
|
|
118
|
+
*
|
|
119
|
+
* Attributes an author chose come first, because they are deliberate and stable. Visible text is
|
|
120
|
+
* the last resort and is skipped entirely when the user has opted out of PII: the text of a
|
|
121
|
+
* control can be a person's name, an email address or the contents of their cart.
|
|
122
|
+
*/
|
|
123
|
+
function labelFor(node) {
|
|
124
|
+
if (!node) {
|
|
125
|
+
return ''; // no i18n
|
|
126
|
+
}
|
|
127
|
+
var attributes = ['data-ap-label', 'apeventaction', 'aria-label', 'name', 'id', 'title']; // no i18n
|
|
128
|
+
for (var i = 0; i < attributes.length; i++) {
|
|
129
|
+
var value = node.getAttribute ? node.getAttribute(attributes[i]) : null;
|
|
130
|
+
if (!utils.isEmpty(value)) {
|
|
131
|
+
return String(value).slice(0, LABEL_MAX);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
var tag = node.tagName ? node.tagName.toLowerCase() : 'element'; // no i18n
|
|
135
|
+
if (consent.getTrackingStatus() !== constants.AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII) {
|
|
136
|
+
// without consent for PII the shape of the control is all we say
|
|
137
|
+
var role = node.getAttribute ? node.getAttribute('role') : null; // no i18n
|
|
138
|
+
return utils.isEmpty(role) ? tag : tag + '[' + role + ']'; // no i18n
|
|
139
|
+
}
|
|
140
|
+
var text = (node.textContent || '').replace(/\s+/g, ' ').trim(); // no i18n
|
|
141
|
+
return utils.isEmpty(text) ? tag : text.slice(0, LABEL_MAX);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A stable number for the element, so the console can group every click on one control together.
|
|
145
|
+
* Derived from its position in the tree rather than from its text, so it survives a label change
|
|
146
|
+
* and carries nothing a person typed.
|
|
147
|
+
*/
|
|
148
|
+
function triggeredId(node) {
|
|
149
|
+
if (!node) {
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
var path = ''; // no i18n
|
|
153
|
+
var current = node;
|
|
154
|
+
var depth = 0;
|
|
155
|
+
while (current && depth < 12) {
|
|
156
|
+
var parent = current.parentElement;
|
|
157
|
+
var index = 0;
|
|
158
|
+
if (parent) {
|
|
159
|
+
var children = parent.children;
|
|
160
|
+
for (var i = 0; i < children.length; i++) {
|
|
161
|
+
if (children[i] === current) {
|
|
162
|
+
index = i;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
path = '/' + (current.tagName || '?') + ':' + index + path; // no i18n
|
|
168
|
+
current = parent;
|
|
169
|
+
depth++;
|
|
170
|
+
}
|
|
171
|
+
// FNV-1a, kept positive: a short stable number, not a cryptographic digest
|
|
172
|
+
var hash = 2166136261;
|
|
173
|
+
for (var c = 0; c < path.length; c++) {
|
|
174
|
+
hash ^= path.charCodeAt(c);
|
|
175
|
+
hash = (hash * 16777619) >>> 0;
|
|
176
|
+
}
|
|
177
|
+
return hash;
|
|
178
|
+
}
|
|
179
|
+
/** Whether this click is part of a burst of repeated clicks in the same spot. */
|
|
180
|
+
function isRageClick(x, y, at) {
|
|
181
|
+
recentClicks = recentClicks.filter(function (click) { return at - click.at <= RAGE_WINDOW_MS; });
|
|
182
|
+
recentClicks.push({ at: at, x: x, y: y });
|
|
183
|
+
var near = recentClicks.filter(function (click) {
|
|
184
|
+
return Math.abs(click.x - x) <= RAGE_RADIUS_PX && Math.abs(click.y - y) <= RAGE_RADIUS_PX;
|
|
185
|
+
});
|
|
186
|
+
return near.length >= RAGE_COUNT;
|
|
187
|
+
}
|
|
188
|
+
/** The fields every record carries, whatever kind it is. */
|
|
189
|
+
function common() {
|
|
190
|
+
return {
|
|
191
|
+
id: utils.getNewUUID(),
|
|
192
|
+
session_start_time: state._ZAP._current_session.starttime || utils.nowMs(),
|
|
193
|
+
// without this the server has no way to order records within a session: session_start_time
|
|
194
|
+
// is the same for every one of them
|
|
195
|
+
triggered_time: utils.nowMs(),
|
|
196
|
+
// the screen inScreen() or automatic screen tracking last named, or else the page, named
|
|
197
|
+
// as automatic tracking would name it and recorded as a screen view so engagement/add has it
|
|
198
|
+
screen: String(track.screenForRecord()).slice(0, FIELD_MAX),
|
|
199
|
+
url_path: String(window.location.pathname).slice(0, FIELD_MAX),
|
|
200
|
+
screen_width: window.screen ? window.screen.width : 0,
|
|
201
|
+
screen_height: window.screen ? window.screen.height : 0,
|
|
202
|
+
content_height: contentHeight(),
|
|
203
|
+
orientation: env.getOrientation(),
|
|
204
|
+
network_status: env.getNetworkStatus(),
|
|
205
|
+
edge_type: state._ZAP._device_info.connectionType || env.getConnectionType(),
|
|
206
|
+
scroll_offset_x: Math.round(window.scrollX || window.pageXOffset || 0),
|
|
207
|
+
scroll_offset_y: Math.round(window.scrollY || window.pageYOffset || 0),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function store(record) {
|
|
211
|
+
recorded++;
|
|
212
|
+
debug.noteDebugActivity();
|
|
213
|
+
// a heatmap record is worth a session in the same way an event is
|
|
214
|
+
session.commitPendingSession();
|
|
215
|
+
storage.heatmap_transactions.add(record);
|
|
216
|
+
logger.Console.dir(record);
|
|
217
|
+
}
|
|
218
|
+
function onPointerDown(event) {
|
|
219
|
+
if (!consent.canTrackHeatmap('heatmap') || !underCap()) { // no i18n
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
var at = utils.nowMs();
|
|
224
|
+
var target = event.target;
|
|
225
|
+
var clickable = clickableAncestor(target);
|
|
226
|
+
var named = clickable || target;
|
|
227
|
+
var x = Math.round(event.clientX);
|
|
228
|
+
var y = Math.round(event.clientY);
|
|
229
|
+
var height = contentHeight() || 1;
|
|
230
|
+
var pointerType = event.pointerType;
|
|
231
|
+
var record = Object.assign(common(), {
|
|
232
|
+
// TODO: send 'buttonclick' for a clickable target once the server accepts it. Held at
|
|
233
|
+
// 'default' for now while heatmap/add is still refusing payloads; `is_clickable`
|
|
234
|
+
// already carries the same information, so nothing is lost meanwhile.
|
|
235
|
+
triggered_type: 'default', // no i18n
|
|
236
|
+
gesture_type: 'click', // no i18n
|
|
237
|
+
gesture_duration: 0,
|
|
238
|
+
input_type: utils.isEmpty(pointerType) ? 'mouse' : String(pointerType), // no i18n
|
|
239
|
+
element_label: labelFor(named),
|
|
240
|
+
triggered_id: triggeredId(named),
|
|
241
|
+
coordinate_x: x,
|
|
242
|
+
coordinate_y: y,
|
|
243
|
+
normalized_x: fraction(x, viewportWidth()),
|
|
244
|
+
normalized_y: fraction((window.scrollY || 0) + y, height),
|
|
245
|
+
velocity: 0,
|
|
246
|
+
is_clickable: clickable ? 1 : 0,
|
|
247
|
+
is_rage_click: isRageClick(x, y, at) ? 1 : 0,
|
|
248
|
+
is_dead_click: 0,
|
|
249
|
+
});
|
|
250
|
+
store(record);
|
|
251
|
+
watchForDeadClick(record, target);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
logger.logFailure(constants.AP_LOG_MODULE.HEATMAP, 'apptics.heatmap: click capture failed: ' + String(err)); // no i18n
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* A click on something that was not clickable and changed nothing is a click someone expected to
|
|
259
|
+
* work. Decided after the fact: the record is already stored, and is updated in place if nothing
|
|
260
|
+
* happened, so the click is never lost waiting to find out.
|
|
261
|
+
*/
|
|
262
|
+
function watchForDeadClick(record, target) {
|
|
263
|
+
if (record.is_clickable === 1 || typeof MutationObserver !== 'function') { // no i18n
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
var changed = false;
|
|
267
|
+
var url = window.location.href;
|
|
268
|
+
var observer = new MutationObserver(function () { changed = true; });
|
|
269
|
+
try {
|
|
270
|
+
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
setTimeout(function () {
|
|
276
|
+
observer.disconnect();
|
|
277
|
+
if (!changed && window.location.href === url) {
|
|
278
|
+
record.is_dead_click = 1;
|
|
279
|
+
storage.heatmap_transactions.update(record);
|
|
280
|
+
}
|
|
281
|
+
}, DEAD_CLICK_MS);
|
|
282
|
+
}
|
|
283
|
+
function onScroll() {
|
|
284
|
+
if (!consent.canTrackHeatmap('heatmap') || !underCap()) { // no i18n
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (scrollTimer === null) {
|
|
288
|
+
scrollStart = utils.nowMs();
|
|
289
|
+
scrollStartX = lastX;
|
|
290
|
+
scrollStartY = lastY;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
clearTimeout(scrollTimer);
|
|
294
|
+
}
|
|
295
|
+
scrollTimer = setTimeout(endScrollGesture, SCROLL_IDLE_MS);
|
|
296
|
+
}
|
|
297
|
+
/** One record for the whole burst, once the page has been still for SCROLL_IDLE_MS. */
|
|
298
|
+
function endScrollGesture() {
|
|
299
|
+
scrollTimer = null;
|
|
300
|
+
try {
|
|
301
|
+
var endX = Math.round(window.scrollX || 0);
|
|
302
|
+
var endY = Math.round(window.scrollY || 0);
|
|
303
|
+
var dx = endX - scrollStartX;
|
|
304
|
+
var dy = endY - scrollStartY;
|
|
305
|
+
lastX = endX;
|
|
306
|
+
lastY = endY;
|
|
307
|
+
if (dx === 0 && dy === 0) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
var duration = Math.max(1, utils.nowMs() - scrollStart);
|
|
311
|
+
var distance = Math.sqrt(dx * dx + dy * dy);
|
|
312
|
+
var direction = Math.abs(dy) >= Math.abs(dx)
|
|
313
|
+
? (dy > 0 ? 'down-scroll' : 'up-scroll') // no i18n
|
|
314
|
+
: (dx > 0 ? 'right-scroll' : 'left-scroll'); // no i18n
|
|
315
|
+
var height = contentHeight() || 1;
|
|
316
|
+
store(Object.assign(common(), {
|
|
317
|
+
// the server accepts only event|default|buttonclick here; what kind of movement it
|
|
318
|
+
// was belongs in gesture_type. input_type is left off rather than invented: a wheel
|
|
319
|
+
// carries no pointer type, and the intended vocabulary has no value for "unknown"
|
|
320
|
+
triggered_type: 'default', // no i18n
|
|
321
|
+
gesture_type: direction,
|
|
322
|
+
gesture_duration: Math.round(duration),
|
|
323
|
+
element_label: '', // no i18n
|
|
324
|
+
triggered_id: 0,
|
|
325
|
+
coordinate_x: endX,
|
|
326
|
+
coordinate_y: endY,
|
|
327
|
+
normalized_x: 0,
|
|
328
|
+
// how far down the page the gesture left them, which is what a scroll map reads
|
|
329
|
+
normalized_y: fraction(endY + (window.innerHeight || 0), height),
|
|
330
|
+
velocity: Math.round((distance / duration) * 1000),
|
|
331
|
+
is_clickable: 0,
|
|
332
|
+
is_rage_click: 0,
|
|
333
|
+
is_dead_click: 0,
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
logger.logFailure(constants.AP_LOG_MODULE.HEATMAP, 'apptics.heatmap: scroll capture failed: ' + String(err)); // no i18n
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** Turns capture on or off. Listeners are passive, so they never delay a click or a scroll. */
|
|
341
|
+
function enableHeatmap(status) {
|
|
342
|
+
var wanted = typeof status === 'boolean' ? status : true; // no i18n
|
|
343
|
+
if (wanted === installed) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (wanted) {
|
|
347
|
+
// where the page already is, so the first gesture is measured from here and not from 0
|
|
348
|
+
lastX = Math.round(window.scrollX || 0);
|
|
349
|
+
lastY = Math.round(window.scrollY || 0);
|
|
350
|
+
window.addEventListener('pointerdown', onPointerDown, { capture: true, passive: true }); // no i18n
|
|
351
|
+
window.addEventListener('scroll', onScroll, { capture: true, passive: true }); // no i18n
|
|
352
|
+
installed = true;
|
|
353
|
+
logger.Console.log('apptics.heatmap: capturing clicks and scrolls'); // no i18n
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
window.removeEventListener('pointerdown', onPointerDown, true); // no i18n
|
|
357
|
+
window.removeEventListener('scroll', onScroll, true); // no i18n
|
|
358
|
+
if (scrollTimer !== null) {
|
|
359
|
+
clearTimeout(scrollTimer);
|
|
360
|
+
scrollTimer = null;
|
|
361
|
+
}
|
|
362
|
+
installed = false;
|
|
363
|
+
logger.Console.log('apptics.heatmap: capture stopped'); // no i18n
|
|
364
|
+
}
|
|
365
|
+
/** Forgets what this visit recorded. reset() calls it; the cap starts again from zero. */
|
|
366
|
+
function resetHeatmapSession() {
|
|
367
|
+
recorded = 0;
|
|
368
|
+
recentClicks = [];
|
|
369
|
+
lastX = 0;
|
|
370
|
+
lastY = 0;
|
|
371
|
+
}
|
|
372
|
+
// init() and reset() drive capture through these, so neither has to import this module
|
|
373
|
+
hooks.hooks.enableHeatmap = enableHeatmap;
|
|
374
|
+
hooks.hooks.resetHeatmap = resetHeatmapSession;
|
|
375
|
+
|
|
376
|
+
exports.enableHeatmap = enableHeatmap;
|
|
377
|
+
exports.resetHeatmapSession = resetHeatmapSession;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AP_FEEDBACK_TYPE, AP_TRACKING_STATUS } from './core/constants.js';
|
|
2
|
+
import { init as initImpl, ready as readyImpl, reset as resetImpl } from './lifecycle.js';
|
|
3
|
+
import { enableApiTracking as enableApiTrackingImpl } from './engagement/auto-api.js';
|
|
4
|
+
import { DefinedEvents } from './engagement/defined-events.js';
|
|
5
|
+
import { ApiTrackingBuilder } from './engagement/api-rules.js';
|
|
6
|
+
import { enableGlobalErrorHandler as enableGlobalErrorHandlerImpl } from './errors/track.js';
|
|
7
|
+
import { getUser as getUserImpl, UserBuilder, users } from './user/index.js';
|
|
8
|
+
import { getTrackingStatus as getTrackingStatusImpl, setTrackingStatus as setTrackingStatusImpl } from './core/consent.js';
|
|
9
|
+
import { flush as flushImpl } from './scheduler.js';
|
|
10
|
+
import { addDiagnosticsInfo as addDiagnosticsInfoImpl, addFeedbackLog as addFeedbackLogImpl } from './feedback/logs.js';
|
|
11
|
+
import type { AppticsApi } from './types.js';
|
|
12
|
+
import { disableDebug as disableDebugImpl, enableDebug as enableDebugImpl } from './core/debug.js';
|
|
13
|
+
import { AppticsLogger } from './remotelog/logger.js';
|
|
14
|
+
export type { AppticsLogger as AppticsLoggerApi } from './remotelog/logger.js';
|
|
15
|
+
export type { AppticsApi, AppticsConfig, ErrorLike, FeedbackType, Properties, TrackingStatus, UserInfo, UserValidationResult, } from './types.js';
|
|
16
|
+
/** Android's Apptics: `Apptics.init(config)` starts the SDK. */
|
|
17
|
+
export declare const Apptics: {
|
|
18
|
+
/** Starts the SDK. The config object is required; the console snippet supplies it via auto-init. */
|
|
19
|
+
init: typeof initImpl;
|
|
20
|
+
};
|
|
21
|
+
export declare const ready: typeof readyImpl;
|
|
22
|
+
/** Android's AppticsEvents: `addEvent(name, group?, properties?)`, or `addEvent(DefinedEvents.X, properties?)`. */
|
|
23
|
+
export declare const AppticsEvents: {
|
|
24
|
+
addEvent: AppticsApi["AppticsEvents"]["addEvent"];
|
|
25
|
+
};
|
|
26
|
+
/** Apptics' defined events, as Android's DefinedEvents, to log with AppticsEvents.addEvent. */
|
|
27
|
+
export { DefinedEvents };
|
|
28
|
+
export type { DefinedEvent } from './engagement/defined-events.js';
|
|
29
|
+
/** Android's AppticsScreenTracker: inScreen(name) returns the view's id, outScreen(id) ends it. */
|
|
30
|
+
export declare const AppticsScreenTracker: AppticsApi['AppticsScreenTracker'];
|
|
31
|
+
/** Android's AppticsApiTracker: startTrackApi / endTrackApi, and configure for the rules. */
|
|
32
|
+
export declare const AppticsApiTracker: AppticsApi['AppticsApiTracker'];
|
|
33
|
+
export { ApiTrackingBuilder };
|
|
34
|
+
export type { ApiTrackingConfig } from './engagement/api-rules.js';
|
|
35
|
+
/** Turns automatic API tracking on or off; the `track_api` config flag is the usual way to set it. */
|
|
36
|
+
export declare const enableApiTracking: typeof enableApiTrackingImpl;
|
|
37
|
+
/** Reports an error the app caught, or an `error` / `unhandledrejection` event. */
|
|
38
|
+
export declare const AppticsTrackError: AppticsApi["AppticsTrackError"];
|
|
39
|
+
export declare const enableGlobalErrorHandler: typeof enableGlobalErrorHandlerImpl;
|
|
40
|
+
/** Registers the signed-in user; properties may be a plain object or UserBuilder.build(). */
|
|
41
|
+
export declare const AppticsSetUser: AppticsApi["AppticsSetUser"];
|
|
42
|
+
export declare const getUser: typeof getUserImpl;
|
|
43
|
+
export declare const setTrackingStatus: typeof setTrackingStatusImpl;
|
|
44
|
+
export declare const getTrackingStatus: typeof getTrackingStatusImpl;
|
|
45
|
+
export declare const flush: typeof flushImpl;
|
|
46
|
+
/** Records a line for the next feedback submission. Costs nothing until feedback is sent. */
|
|
47
|
+
export declare const addFeedbackLog: typeof addFeedbackLogImpl;
|
|
48
|
+
/** Records a diagnostics key/value, grouped under `heading`, for the next feedback submission. */
|
|
49
|
+
export declare const addDiagnosticsInfo: typeof addDiagnosticsInfoImpl;
|
|
50
|
+
/**
|
|
51
|
+
* Turns debug mode on; works before init(), and wins over `debug` in init().
|
|
52
|
+
*
|
|
53
|
+
* On, engagement, exception and log go to their `debug/` twins and the answers carry a request id
|
|
54
|
+
* and a link into the server's own logs. It switches itself off after an hour with nothing
|
|
55
|
+
* tracked, so a forgotten tab stops tracing on its own.
|
|
56
|
+
*/
|
|
57
|
+
export declare const enableDebug: typeof enableDebugImpl;
|
|
58
|
+
/** Turns debug mode off; works before init(), and wins over `debug` in init(). */
|
|
59
|
+
export declare const disableDebug: typeof disableDebugImpl;
|
|
60
|
+
export declare const reset: typeof resetImpl;
|
|
61
|
+
/**
|
|
62
|
+
* Android's AppticsLogger, shown on the console's Remote Logger page. Off until
|
|
63
|
+
* `AppticsLogger.enable()`; then `AppticsLogger.v / d / i / w / e(tag, message, ...details)`.
|
|
64
|
+
*/
|
|
65
|
+
export { AppticsLogger };
|
|
66
|
+
export { AP_FEEDBACK_TYPE, AP_TRACKING_STATUS, UserBuilder, users };
|
|
67
|
+
declare const apptics: AppticsApi;
|
|
68
|
+
export default apptics;
|