@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,299 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const AP_TRACKING_STATUS = Object.freeze({
|
|
5
|
+
USAGE_TRACKING_WITH_PII: 1,
|
|
6
|
+
USAGE_TRACKING_WITHOUT_PII: 4,
|
|
7
|
+
NO_TRACKING: -1,
|
|
8
|
+
});
|
|
9
|
+
const AP_FEEDBACK_TYPE = Object.freeze({
|
|
10
|
+
BUG: 0,
|
|
11
|
+
FEEDBACK: 1,
|
|
12
|
+
HELP: 2,
|
|
13
|
+
REPORT: 3,
|
|
14
|
+
});
|
|
15
|
+
const AP_CONSTANTS = Object.freeze({
|
|
16
|
+
SCREEN: 'screen', // no i18n
|
|
17
|
+
SESSION: 'session', // no i18n
|
|
18
|
+
EVENT: 'event', // no i18n
|
|
19
|
+
API: 'api', // no i18n
|
|
20
|
+
ERROR: 'error', // no i18n
|
|
21
|
+
SUCCESS: 'success', // no i18n
|
|
22
|
+
BROWSER: 'Browser', // no i18n
|
|
23
|
+
});
|
|
24
|
+
const DEFAULT_API_PATHS = Object.freeze({
|
|
25
|
+
// the first call: registers the build and answers with the ids every later call needs
|
|
26
|
+
addversion: 'app/addversion', // no i18n
|
|
27
|
+
regdevice: 'device/add', // no i18n
|
|
28
|
+
reguser: 'user/register', // no i18n
|
|
29
|
+
engagement: 'engagement/add', // no i18n
|
|
30
|
+
// the exception group is part of the path, and the proxy matches it with [a-z]+ — no
|
|
31
|
+
// underscore, so js_error cannot route. 'error' is in the accepted set and is what we send
|
|
32
|
+
error: 'exception/error', // no i18n
|
|
33
|
+
feedback: 'feedback/add', // no i18n
|
|
34
|
+
// the source of truth for which modules are on. v2 replaced the old getupdates answer: it is
|
|
35
|
+
// per-module flags keyed by id now, not loose booleans
|
|
36
|
+
sdkconfig: 'getsdkconfig', // no i18n
|
|
37
|
+
// still its own endpoint, and not a synonym for the one above: it takes a body naming the
|
|
38
|
+
// modules being asked about, and answers with their payloads - remote config among them
|
|
39
|
+
getupdates: 'getupdates', // no i18n
|
|
40
|
+
// v2 replaced console/addlog: one call now carries a batch of lines, not one line
|
|
41
|
+
consolelog: 'log/add', // no i18n
|
|
42
|
+
heatmap: 'heatmap/add', // no i18n
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Which API version each path lives under.
|
|
46
|
+
*
|
|
47
|
+
* Every path the SDK calls is defined under v2 in the proxy's security-sdk.xml, and four of them —
|
|
48
|
+
* app/addversion, getsdkconfig, heatmap/add and log/add — are defined there *only* under v2: there
|
|
49
|
+
* is no v1 url for them to fall back to, so sending v1 reaches nothing. It stays a per-path map
|
|
50
|
+
* rather than one shared prefix because v1 urls do still exist for the other five, so a path can
|
|
51
|
+
* be moved back on its own if one of them turns out not to be ready.
|
|
52
|
+
*/
|
|
53
|
+
const API_VERSIONS = Object.freeze({
|
|
54
|
+
addversion: 'v2', // no i18n
|
|
55
|
+
regdevice: 'v2', // no i18n
|
|
56
|
+
engagement: 'v2', // no i18n
|
|
57
|
+
error: 'v2', // no i18n
|
|
58
|
+
reguser: 'v2', // no i18n
|
|
59
|
+
feedback: 'v2', // no i18n
|
|
60
|
+
sdkconfig: 'v2', // no i18n
|
|
61
|
+
getupdates: 'v2', // no i18n
|
|
62
|
+
consolelog: 'v2', // no i18n
|
|
63
|
+
heatmap: 'v2', // no i18n
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* The SDK subsystem a remote log line is about, as console/addlog's `module` param.
|
|
67
|
+
* These are the server's own names, so they are sent exactly as written here.
|
|
68
|
+
*/
|
|
69
|
+
const AP_LOG_MODULE = Object.freeze({
|
|
70
|
+
SESSION: 'Session', // no i18n
|
|
71
|
+
SCREEN: 'Screen', // no i18n
|
|
72
|
+
EVENT: 'Event', // no i18n
|
|
73
|
+
API: 'API', // no i18n
|
|
74
|
+
HEATMAP: 'Heatmap', // no i18n
|
|
75
|
+
PROPERTIES: 'Properties', // no i18n
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* The modules getsdkconfig reports on, by the ids it keys them with. The SDK acts on the ones it
|
|
79
|
+
* has something to switch off; anything else the server sends is stored but unused.
|
|
80
|
+
*/
|
|
81
|
+
const SDK_MODULE = Object.freeze({
|
|
82
|
+
/**
|
|
83
|
+
* Remote config. It is the one module both answers report on: getupdates names it in
|
|
84
|
+
* `moduleids` and reports its flag back, and getsdkconfig reports the same flag as the
|
|
85
|
+
* authority. Where they disagree, getsdkconfig wins.
|
|
86
|
+
*/
|
|
87
|
+
REMOTE_CONFIG: '2', // no i18n
|
|
88
|
+
SESSION: '11', // no i18n
|
|
89
|
+
FEEDBACK: '12', // no i18n
|
|
90
|
+
CRASH: '13', // no i18n
|
|
91
|
+
REMOTE_LOGGER: '17', // no i18n
|
|
92
|
+
HEATMAP: '18', // no i18n
|
|
93
|
+
});
|
|
94
|
+
/** logSDK declares max-len="10000" on logmessage, so a line is trimmed to fit that. */
|
|
95
|
+
const CONSOLE_LOG_MAX_BODY = 10000;
|
|
96
|
+
/** logSDK caps `function` at 100 characters. */
|
|
97
|
+
const LOG_FUNCTION_MAX = 100;
|
|
98
|
+
/** logSDK caps `filename` at 100 too, which is why a call site carries a basename, not a url. */
|
|
99
|
+
const LOG_FILENAME_MAX = 100;
|
|
100
|
+
/**
|
|
101
|
+
* How much of `devicename` is sent. The url declares no max-len, only safe_string, but this is a
|
|
102
|
+
* label a person reads in a list: anything longer is a paragraph, not a name.
|
|
103
|
+
*/
|
|
104
|
+
const DEVICE_NAME_MAX = 100;
|
|
105
|
+
/**
|
|
106
|
+
* The `level` every line goes out with. Everything the remote logger carries is a failure inside
|
|
107
|
+
* the SDK — logFailure is its only caller — so the level never varies. 3 is what the v2 reference
|
|
108
|
+
* shows in its only example; the scale itself is not documented anywhere we can see.
|
|
109
|
+
*/
|
|
110
|
+
const REMOTE_LOG_LEVEL = 3;
|
|
111
|
+
/**
|
|
112
|
+
* How long debug mode survives with nothing tracked, before switching itself off.
|
|
113
|
+
*
|
|
114
|
+
* An hour is long enough to cover reading a payload, changing the app and reloading, and short
|
|
115
|
+
* enough that a tab left open overnight is not still sending every call to its debug twin.
|
|
116
|
+
*/
|
|
117
|
+
const DEBUG_IDLE_MS = 60 * 60 * 1000;
|
|
118
|
+
/** Idle minutes that end a session. Lives on state, not in _config, but defaults here. */
|
|
119
|
+
const DEFAULT_INACTIVE_MINUTES = 30;
|
|
120
|
+
/** Global defaults. `state._config` holds a mutable copy per init. */
|
|
121
|
+
const AP_CONFIG = Object.freeze({
|
|
122
|
+
// the version is per path (API_VERSIONS); this is only the part before it
|
|
123
|
+
endpoint: '/sdk/web/', // no i18n
|
|
124
|
+
method: 'POST', // no i18n
|
|
125
|
+
transport: 'XHR', // no i18n
|
|
126
|
+
track_link_timeout: 300,
|
|
127
|
+
cookie_expiration_days: 365,
|
|
128
|
+
storage_type: 'localStorage', // no i18n
|
|
129
|
+
enable_batch_requests: true,
|
|
130
|
+
batch_size: 50,
|
|
131
|
+
batch_flush_interval: 30000,
|
|
132
|
+
getupdates_interval: 120000, // 2 minutes in milliseconds
|
|
133
|
+
batch_request_timeout: 7,
|
|
134
|
+
auto_starts_batch: true,
|
|
135
|
+
mode: 1,
|
|
136
|
+
default_group_name: 'default', // no i18n
|
|
137
|
+
default_optin_status: AP_TRACKING_STATUS.USAGE_TRACKING_WITHOUT_PII,
|
|
138
|
+
enable_pageview_tracking: false,
|
|
139
|
+
save_referrer: true,
|
|
140
|
+
debug: false,
|
|
141
|
+
track_by_default: true,
|
|
142
|
+
ignore_do_not_track: false,
|
|
143
|
+
// the SDK's own failure reports to the Remote Logger page: off unless the app passes remote_log: true
|
|
144
|
+
remote_log: false,
|
|
145
|
+
/**
|
|
146
|
+
* Wrap XHR and fetch to record the app's API calls without it calling startTrackApi(). Off by
|
|
147
|
+
* default: the hostnames and paths of every request an app makes is a wider grant than events
|
|
148
|
+
* and screens, so the app opts in.
|
|
149
|
+
*/
|
|
150
|
+
track_api: false,
|
|
151
|
+
/**
|
|
152
|
+
* Record a screen view for the first page and every navigation after it, from the url. On by
|
|
153
|
+
* default; the app's own inScreen() during a navigation replaces that navigation's view.
|
|
154
|
+
*/
|
|
155
|
+
track_screen: true,
|
|
156
|
+
/**
|
|
157
|
+
* Print each app logger line to the browser console as well, as Android's showLogs prints to
|
|
158
|
+
* Logcat. Off by default; console_log is the SDK's own activity, this is the app's lines.
|
|
159
|
+
*/
|
|
160
|
+
show_logs: false,
|
|
161
|
+
/**
|
|
162
|
+
* Record sessions automatically. On by default; false is Android's
|
|
163
|
+
* automaticSessionsTrackingStatus = false: no sessions, while events and screens still are.
|
|
164
|
+
*/
|
|
165
|
+
track_session: true,
|
|
166
|
+
/**
|
|
167
|
+
* How long a visit must last before its session is stored, in milliseconds. A shorter visit with
|
|
168
|
+
* nothing tracked in it is dropped: a bounce or a crawler is not a session. Anything tracked
|
|
169
|
+
* inside the window writes the session immediately, keeping its original start time. 0 disables
|
|
170
|
+
* the wait and stores every session as it starts.
|
|
171
|
+
*/
|
|
172
|
+
min_session_duration: 5000,
|
|
173
|
+
/**
|
|
174
|
+
* Record where people click and how far they scroll, for the console's heatmaps. Off by default:
|
|
175
|
+
* coordinates and element labels are a wider grant than events and screens, so the app opts in.
|
|
176
|
+
*/
|
|
177
|
+
track_heatmap: false,
|
|
178
|
+
/**
|
|
179
|
+
* Send engagement, exception and log payloads to their `debug/` twins, which answer with a
|
|
180
|
+
* request id and a link to the server's own logs for that request. For tracing a payload that
|
|
181
|
+
* is not showing up in the console; off in normal use, and it does not change what is sent.
|
|
182
|
+
*/
|
|
183
|
+
debug_api: false,
|
|
184
|
+
/**
|
|
185
|
+
* What to call this device in the debug stream. Engagement, exception and log are the three
|
|
186
|
+
* urls that declare `devicename`, and it is what tells one tester's browser from another's
|
|
187
|
+
* when several are tracing the same build. Empty by default: it rides on every one of those
|
|
188
|
+
* calls, so an app that is not tracing should not pay for it. With `debug_api` on and nothing
|
|
189
|
+
* set here, a label is derived from the browser and OS instead, since an unnamed row in the
|
|
190
|
+
* debug stream is no use to whoever is reading it.
|
|
191
|
+
*/
|
|
192
|
+
device_name: '',
|
|
193
|
+
/**
|
|
194
|
+
* Most heatmap records one visit may produce. Clicks are cheap, but a page that scrolls a lot
|
|
195
|
+
* can produce them faster than they are sent, and an unbounded queue is a memory leak on a tab
|
|
196
|
+
* left open for a day. Reached, capture stops for the rest of the session.
|
|
197
|
+
*/
|
|
198
|
+
heatmap_max_per_session: 500,
|
|
199
|
+
});
|
|
200
|
+
/**
|
|
201
|
+
* Config keys `init()` refuses to start without (after the snippet aliases are resolved).
|
|
202
|
+
*
|
|
203
|
+
* `appversion` is here rather than defaulted on purpose. Every error, every device registration
|
|
204
|
+
* and every uploaded source map is only meaningful against the build it came from, and a default
|
|
205
|
+
* would answer that question wrongly and silently for every app that forgot to set it.
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* What init() must be given. Everything else the SDK needs — aaid, the portal and project ids that
|
|
209
|
+
* go in every url — comes back from app/addversion, which these four are enough to call.
|
|
210
|
+
*/
|
|
211
|
+
const REQUIRED_CONFIG = Object.freeze(['serviceUrl', 'zak', 'identifier', 'appversion']); // no i18n
|
|
212
|
+
/**
|
|
213
|
+
* Paths with a `debug/` twin. The debug endpoints take the same body and answer with a request id
|
|
214
|
+
* and a link to the server-side logs for that request, which is how a payload is traced end to
|
|
215
|
+
* end when the console does not show what was expected.
|
|
216
|
+
*/
|
|
217
|
+
const DEBUG_CAPABLE_PATHS = Object.freeze({
|
|
218
|
+
engagement: true, // no i18n
|
|
219
|
+
error: true, // no i18n
|
|
220
|
+
consolelog: true, // no i18n
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* Paths that declare `attempt`, `firstattempttime` and `lst`.
|
|
224
|
+
*
|
|
225
|
+
* Every url that carries data declares all three. app/addversion and getsdkconfig declare none:
|
|
226
|
+
* they are start-up calls whose answers the SDK needs, not payloads it is pushing, and sending
|
|
227
|
+
* an undeclared param is refused outright as EXTRA_PARAM_FOUND.
|
|
228
|
+
*/
|
|
229
|
+
const RETRY_CAPABLE_PATHS = Object.freeze({
|
|
230
|
+
regdevice: true, // no i18n
|
|
231
|
+
reguser: true, // no i18n
|
|
232
|
+
engagement: true, // no i18n
|
|
233
|
+
error: true, // no i18n
|
|
234
|
+
heatmap: true, // no i18n
|
|
235
|
+
consolelog: true, // no i18n
|
|
236
|
+
feedback: true, // no i18n
|
|
237
|
+
getupdates: true, // no i18n
|
|
238
|
+
});
|
|
239
|
+
/**
|
|
240
|
+
* Paths that live directly under the version, with no org and project segments. app/addversion is
|
|
241
|
+
* called before those ids are known, so it cannot carry them.
|
|
242
|
+
*/
|
|
243
|
+
const PATHS_WITHOUT_ORG = Object.freeze({
|
|
244
|
+
addversion: true, // no i18n
|
|
245
|
+
});
|
|
246
|
+
/** Console-snippet names accepted for the canonical keys above. */
|
|
247
|
+
const CONFIG_ALIASES = Object.freeze({
|
|
248
|
+
serviceUrl: 'base_domain', // no i18n
|
|
249
|
+
zak: 'token', // no i18n
|
|
250
|
+
});
|
|
251
|
+
// crawlers reference
|
|
252
|
+
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
|
|
253
|
+
const BLOCKED_WS = Object.freeze([
|
|
254
|
+
'ahrefsbot', // no i18n
|
|
255
|
+
'ahrefssiteaudit', // no i18n
|
|
256
|
+
'baiduspider', // no i18n
|
|
257
|
+
'bingbot', // no i18n
|
|
258
|
+
'bingpreview', // no i18n
|
|
259
|
+
'chrome-lighthouse', // no i18n
|
|
260
|
+
'facebookexternal', // no i18n
|
|
261
|
+
'petalbot', // no i18n
|
|
262
|
+
'pinterest', // no i18n
|
|
263
|
+
'screaming frog', // no i18n
|
|
264
|
+
'yahoo! slurp', // no i18n
|
|
265
|
+
'yandexbot', // no i18n
|
|
266
|
+
'adsbot-google', // no i18n
|
|
267
|
+
'apis-google', // no i18n
|
|
268
|
+
'duplexweb-google', // no i18n
|
|
269
|
+
'feedfetcher-google', // no i18n
|
|
270
|
+
'google favicon', // no i18n
|
|
271
|
+
'google web preview', // no i18n
|
|
272
|
+
'google-read-aloud', // no i18n
|
|
273
|
+
'googlebot', // no i18n
|
|
274
|
+
'googleweblight', // no i18n
|
|
275
|
+
'mediapartners-google', // no i18n
|
|
276
|
+
'storebot-google', // no i18n
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
exports.API_VERSIONS = API_VERSIONS;
|
|
280
|
+
exports.AP_CONFIG = AP_CONFIG;
|
|
281
|
+
exports.AP_CONSTANTS = AP_CONSTANTS;
|
|
282
|
+
exports.AP_FEEDBACK_TYPE = AP_FEEDBACK_TYPE;
|
|
283
|
+
exports.AP_LOG_MODULE = AP_LOG_MODULE;
|
|
284
|
+
exports.AP_TRACKING_STATUS = AP_TRACKING_STATUS;
|
|
285
|
+
exports.BLOCKED_WS = BLOCKED_WS;
|
|
286
|
+
exports.CONFIG_ALIASES = CONFIG_ALIASES;
|
|
287
|
+
exports.CONSOLE_LOG_MAX_BODY = CONSOLE_LOG_MAX_BODY;
|
|
288
|
+
exports.DEBUG_CAPABLE_PATHS = DEBUG_CAPABLE_PATHS;
|
|
289
|
+
exports.DEBUG_IDLE_MS = DEBUG_IDLE_MS;
|
|
290
|
+
exports.DEFAULT_API_PATHS = DEFAULT_API_PATHS;
|
|
291
|
+
exports.DEFAULT_INACTIVE_MINUTES = DEFAULT_INACTIVE_MINUTES;
|
|
292
|
+
exports.DEVICE_NAME_MAX = DEVICE_NAME_MAX;
|
|
293
|
+
exports.LOG_FILENAME_MAX = LOG_FILENAME_MAX;
|
|
294
|
+
exports.LOG_FUNCTION_MAX = LOG_FUNCTION_MAX;
|
|
295
|
+
exports.PATHS_WITHOUT_ORG = PATHS_WITHOUT_ORG;
|
|
296
|
+
exports.REMOTE_LOG_LEVEL = REMOTE_LOG_LEVEL;
|
|
297
|
+
exports.REQUIRED_CONFIG = REQUIRED_CONFIG;
|
|
298
|
+
exports.RETRY_CAPABLE_PATHS = RETRY_CAPABLE_PATHS;
|
|
299
|
+
exports.SDK_MODULE = SDK_MODULE;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Dict, QueueItem } from '../types.js';
|
|
2
|
+
export type QueueName = 'engagements' | 'errors' | 'logs' | 'consolelogs' | 'heatmaps';
|
|
3
|
+
/** One storage implementation. storage.ts talks only to this interface. */
|
|
4
|
+
export interface Backend {
|
|
5
|
+
readonly kind: 'indexeddb' | 'localstorage';
|
|
6
|
+
loadSettings(): Promise<Dict>;
|
|
7
|
+
putSetting(key: string, value: unknown): Promise<void>;
|
|
8
|
+
clearSettings(): Promise<void>;
|
|
9
|
+
/** Items in insertion order. */
|
|
10
|
+
queueAll(store: QueueName): Promise<QueueItem[]>;
|
|
11
|
+
/** Adds items, or replaces the item with the same id (keeping its position). */
|
|
12
|
+
queuePut(store: QueueName, items: QueueItem[]): Promise<void>;
|
|
13
|
+
queueDelete(store: QueueName, ids: string[]): Promise<void>;
|
|
14
|
+
queueClear(store: QueueName): Promise<void>;
|
|
15
|
+
/** One-transaction import of 1.x data; existing settings and items win. IndexedDB only. */
|
|
16
|
+
importAll?(settings: Dict, queues: Record<QueueName, QueueItem[]>): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare const DB_NAME = "apptics";
|
|
19
|
+
export declare const DB_VERSION = 3;
|
|
20
|
+
/** 1.x localStorage keys; also the layout of the localStorage backend. */
|
|
21
|
+
export declare const LEGACY_KEYS: Readonly<{
|
|
22
|
+
readonly settings: "apptics-device-info";
|
|
23
|
+
readonly engagements: "apptics-engagements-transactions";
|
|
24
|
+
readonly errors: "apptics-errors-transactions";
|
|
25
|
+
readonly logs: "apptics-log-transactions";
|
|
26
|
+
readonly consolelogs: "apptics-consolelog-transactions";
|
|
27
|
+
readonly heatmaps: "apptics-heatmap-transactions";
|
|
28
|
+
}>;
|
|
29
|
+
/** Shape guards shared by both backends and the 1.x migration. */
|
|
30
|
+
export declare function asList(value: unknown): QueueItem[];
|
|
31
|
+
export declare function asDict(value: unknown): Dict;
|
|
32
|
+
export declare function createLocalStorageBackend(): Backend;
|
|
33
|
+
/**
|
|
34
|
+
* Opens the database, and repairs it once if something else created an "apptics" database
|
|
35
|
+
* without our stores (then onupgradeneeded never fires and every read would fail).
|
|
36
|
+
*/
|
|
37
|
+
export declare function openIndexedDB(factory?: IDBFactory, timeoutMs?: number): Promise<IDBDatabase>;
|
|
38
|
+
export declare function createIndexedDBBackend(db: IDBDatabase): Backend;
|
|
39
|
+
/** IndexedDB when it opens, otherwise localStorage, otherwise null. Never rejects. */
|
|
40
|
+
export declare function openBackend(): Promise<Backend | null>;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var logger = require('./logger.js');
|
|
5
|
+
var utils = require('./utils.js');
|
|
6
|
+
|
|
7
|
+
const DB_NAME = 'apptics'; // no i18n
|
|
8
|
+
// 3 added the heatmaps store. The upgrade only creates stores that are missing, so an existing
|
|
9
|
+
// database keeps everything already queued in it.
|
|
10
|
+
const DB_VERSION = 3;
|
|
11
|
+
const OPEN_TIMEOUT_MS = 3000;
|
|
12
|
+
const QUEUES = ['engagements', 'errors', 'logs', 'consolelogs', 'heatmaps']; // no i18n
|
|
13
|
+
/** 1.x localStorage keys; also the layout of the localStorage backend. */
|
|
14
|
+
const LEGACY_KEYS = Object.freeze({
|
|
15
|
+
settings: 'apptics-device-info', // no i18n
|
|
16
|
+
engagements: 'apptics-engagements-transactions', // no i18n
|
|
17
|
+
errors: 'apptics-errors-transactions', // no i18n
|
|
18
|
+
logs: 'apptics-log-transactions', // no i18n
|
|
19
|
+
consolelogs: 'apptics-consolelog-transactions', // no i18n
|
|
20
|
+
// not a 1.x key: heatmaps did not exist then. It is here because this map is also the
|
|
21
|
+
// localStorage backend's layout, so the store needs a name there too
|
|
22
|
+
heatmaps: 'apptics-heatmap-transactions', // no i18n
|
|
23
|
+
});
|
|
24
|
+
/** Shape guards shared by both backends and the 1.x migration. */
|
|
25
|
+
function asList(value) {
|
|
26
|
+
return Array.isArray(value) ? value : [];
|
|
27
|
+
}
|
|
28
|
+
function asDict(value) {
|
|
29
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
30
|
+
}
|
|
31
|
+
/* ---------------- localStorage backend ---------------- */
|
|
32
|
+
function readJSON(key) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = localStorage.getItem(key);
|
|
35
|
+
return raw === null || raw === '' ? null : JSON.parse(raw); // no i18n
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
logger.Console.warn('apptics: could not read ' + key + ', treating it as empty'); // no i18n
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeJSON(key, value) {
|
|
43
|
+
try {
|
|
44
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.Console.warn('apptics: could not write ' + key + ': ' + String(err)); // no i18n
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function createLocalStorageBackend() {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'localstorage', // no i18n
|
|
53
|
+
loadSettings() {
|
|
54
|
+
return Promise.resolve(asDict(readJSON(LEGACY_KEYS.settings)));
|
|
55
|
+
},
|
|
56
|
+
putSetting(key, value) {
|
|
57
|
+
const all = asDict(readJSON(LEGACY_KEYS.settings));
|
|
58
|
+
all[key] = value;
|
|
59
|
+
writeJSON(LEGACY_KEYS.settings, all);
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
},
|
|
62
|
+
clearSettings() {
|
|
63
|
+
writeJSON(LEGACY_KEYS.settings, {});
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
},
|
|
66
|
+
queueAll(store) {
|
|
67
|
+
return Promise.resolve(asList(readJSON(LEGACY_KEYS[store])));
|
|
68
|
+
},
|
|
69
|
+
queuePut(store, items) {
|
|
70
|
+
const list = asList(readJSON(LEGACY_KEYS[store]));
|
|
71
|
+
items.forEach(function (item) {
|
|
72
|
+
const index = list.findIndex(function (existing) { return existing.id === item.id; });
|
|
73
|
+
if (index === -1) {
|
|
74
|
+
list.push(item);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
list[index] = item;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
writeJSON(LEGACY_KEYS[store], list);
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
},
|
|
83
|
+
queueDelete(store, ids) {
|
|
84
|
+
const remaining = asList(readJSON(LEGACY_KEYS[store])).filter(function (item) {
|
|
85
|
+
return ids.indexOf(item.id) === -1;
|
|
86
|
+
});
|
|
87
|
+
writeJSON(LEGACY_KEYS[store], remaining);
|
|
88
|
+
return Promise.resolve();
|
|
89
|
+
},
|
|
90
|
+
queueClear(store) {
|
|
91
|
+
writeJSON(LEGACY_KEYS[store], []);
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
let lastSeq = 0;
|
|
97
|
+
/**
|
|
98
|
+
* Strictly increasing, and time-based so tabs interleave in roughly the right order.
|
|
99
|
+
* Counting up from the last value rather than a counter that wraps keeps insertion order
|
|
100
|
+
* even when a chatty caller adds more than a thousand items inside one millisecond.
|
|
101
|
+
*/
|
|
102
|
+
function nextSeq() {
|
|
103
|
+
const base = utils.nowMs() * 1000;
|
|
104
|
+
lastSeq = base > lastSeq ? base : lastSeq + 1;
|
|
105
|
+
return lastSeq;
|
|
106
|
+
}
|
|
107
|
+
function request(req) {
|
|
108
|
+
return new Promise(function (resolve, reject) {
|
|
109
|
+
req.onsuccess = function () { resolve(req.result); };
|
|
110
|
+
req.onerror = function () { reject(req.error); };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function completed(tx) {
|
|
114
|
+
return new Promise(function (resolve, reject) {
|
|
115
|
+
tx.oncomplete = function () { resolve(); };
|
|
116
|
+
tx.onerror = function () { reject(tx.error); };
|
|
117
|
+
tx.onabort = function () { reject(tx.error); };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Puts `item` unless it exists (keepExisting) or replaces it keeping its seq. Runs inside `store`'s transaction. */
|
|
121
|
+
function putRecord(store, item, keepExisting) {
|
|
122
|
+
const id = String(item.id);
|
|
123
|
+
const lookup = store.get(id);
|
|
124
|
+
lookup.onsuccess = function () {
|
|
125
|
+
const existing = lookup.result;
|
|
126
|
+
if (existing && keepExisting) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
store.put({ id: id, seq: existing ? existing.seq : nextSeq(), type: item.type, item: item });
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Our stores, as created by onupgradeneeded. */
|
|
133
|
+
function hasAllStores(db) {
|
|
134
|
+
return db.objectStoreNames.contains('settings') && QUEUES.every(function (name) {
|
|
135
|
+
return db.objectStoreNames.contains(name);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function deleteDatabase(factory) {
|
|
139
|
+
return new Promise(function (resolve) {
|
|
140
|
+
var req = factory.deleteDatabase(DB_NAME);
|
|
141
|
+
req.onsuccess = function () { resolve(); };
|
|
142
|
+
req.onerror = function () { resolve(); };
|
|
143
|
+
req.onblocked = function () { resolve(); };
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Opens the database, and repairs it once if something else created an "apptics" database
|
|
148
|
+
* without our stores (then onupgradeneeded never fires and every read would fail).
|
|
149
|
+
*/
|
|
150
|
+
function openIndexedDB(factory = indexedDB, timeoutMs = OPEN_TIMEOUT_MS) {
|
|
151
|
+
return openOnce(factory, timeoutMs).then(function (db) {
|
|
152
|
+
if (hasAllStores(db)) {
|
|
153
|
+
return db;
|
|
154
|
+
}
|
|
155
|
+
logger.Console.warn('apptics: the "' + DB_NAME + '" database is missing its stores, recreating it'); // no i18n
|
|
156
|
+
db.close();
|
|
157
|
+
return deleteDatabase(factory).then(function () {
|
|
158
|
+
return openOnce(factory, timeoutMs);
|
|
159
|
+
}).then(function (fresh) {
|
|
160
|
+
if (!hasAllStores(fresh)) {
|
|
161
|
+
fresh.close();
|
|
162
|
+
throw new Error('indexedDB: stores could not be created'); // no i18n
|
|
163
|
+
}
|
|
164
|
+
return fresh;
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function openOnce(factory, timeoutMs) {
|
|
169
|
+
return new Promise(function (resolve, reject) {
|
|
170
|
+
let settled = false;
|
|
171
|
+
const fail = function (reason) {
|
|
172
|
+
if (!settled) {
|
|
173
|
+
settled = true;
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
reject(reason);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const timer = setTimeout(function () {
|
|
179
|
+
fail(new Error('indexedDB open timed out')); // no i18n
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
let req;
|
|
182
|
+
try {
|
|
183
|
+
req = factory.open(DB_NAME, DB_VERSION);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
fail(err);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
req.onupgradeneeded = function () {
|
|
190
|
+
const db = req.result;
|
|
191
|
+
if (!db.objectStoreNames.contains('settings')) { // no i18n
|
|
192
|
+
db.createObjectStore('settings'); // no i18n
|
|
193
|
+
}
|
|
194
|
+
QUEUES.forEach(function (name) {
|
|
195
|
+
if (!db.objectStoreNames.contains(name)) {
|
|
196
|
+
db.createObjectStore(name, { keyPath: 'id' }).createIndex('seq', 'seq'); // no i18n
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
req.onsuccess = function () {
|
|
201
|
+
if (settled) {
|
|
202
|
+
req.result.close(); // we already gave up (timeout or blocked)
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
settled = true;
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
// Another tab running a newer build needs to upgrade the schema, and cannot while this
|
|
208
|
+
// connection is open. Letting go is the only cooperative answer: holding on blocks that
|
|
209
|
+
// tab's upgrade until this one is closed, and it spends the whole time with no storage.
|
|
210
|
+
// This tab keeps working from memory and picks the new schema up on its next load.
|
|
211
|
+
req.result.onversionchange = function () {
|
|
212
|
+
logger.Console.warn('apptics: another tab is upgrading the database, closing this connection'); // no i18n
|
|
213
|
+
req.result.close();
|
|
214
|
+
};
|
|
215
|
+
resolve(req.result);
|
|
216
|
+
};
|
|
217
|
+
req.onerror = function () {
|
|
218
|
+
fail(req.error);
|
|
219
|
+
};
|
|
220
|
+
req.onblocked = function () {
|
|
221
|
+
// Another tab is holding an older version open. A build that sets onversionchange
|
|
222
|
+
// (above) lets go by itself, so this resolves on its own in a moment; the open is left
|
|
223
|
+
// to run until the timeout rather than abandoned, because giving up here would mean
|
|
224
|
+
// running with no storage for the rest of the visit.
|
|
225
|
+
logger.Console.warn('apptics: waiting for another tab to release the database'); // no i18n
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function createIndexedDBBackend(db) {
|
|
230
|
+
return {
|
|
231
|
+
kind: 'indexeddb', // no i18n
|
|
232
|
+
loadSettings() {
|
|
233
|
+
const store = db.transaction('settings', 'readonly').objectStore('settings'); // no i18n
|
|
234
|
+
return Promise.all([request(store.getAllKeys()), request(store.getAll())]).then(function ([keys, values]) {
|
|
235
|
+
const out = {};
|
|
236
|
+
keys.forEach(function (key, i) {
|
|
237
|
+
out[String(key)] = values[i];
|
|
238
|
+
});
|
|
239
|
+
return out;
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
putSetting(key, value) {
|
|
243
|
+
const tx = db.transaction('settings', 'readwrite'); // no i18n
|
|
244
|
+
tx.objectStore('settings').put(value, key); // no i18n
|
|
245
|
+
return completed(tx);
|
|
246
|
+
},
|
|
247
|
+
clearSettings() {
|
|
248
|
+
const tx = db.transaction('settings', 'readwrite'); // no i18n
|
|
249
|
+
tx.objectStore('settings').clear(); // no i18n
|
|
250
|
+
return completed(tx);
|
|
251
|
+
},
|
|
252
|
+
queueAll(store) {
|
|
253
|
+
const index = db.transaction(store, 'readonly').objectStore(store).index('seq'); // no i18n
|
|
254
|
+
return request(index.getAll()).then(function (records) {
|
|
255
|
+
return records.map(function (record) { return record.item; });
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
queuePut(store, items) {
|
|
259
|
+
const tx = db.transaction(store, 'readwrite'); // no i18n
|
|
260
|
+
const objectStore = tx.objectStore(store);
|
|
261
|
+
items.forEach(function (item) { putRecord(objectStore, item, false); });
|
|
262
|
+
return completed(tx);
|
|
263
|
+
},
|
|
264
|
+
queueDelete(store, ids) {
|
|
265
|
+
const tx = db.transaction(store, 'readwrite'); // no i18n
|
|
266
|
+
const objectStore = tx.objectStore(store);
|
|
267
|
+
ids.forEach(function (id) { objectStore.delete(id); });
|
|
268
|
+
return completed(tx);
|
|
269
|
+
},
|
|
270
|
+
queueClear(store) {
|
|
271
|
+
const tx = db.transaction(store, 'readwrite'); // no i18n
|
|
272
|
+
tx.objectStore(store).clear();
|
|
273
|
+
return completed(tx);
|
|
274
|
+
},
|
|
275
|
+
importAll(settings, queues) {
|
|
276
|
+
// scope derived from QUEUES, not repeated: the loop below opens every one of them,
|
|
277
|
+
// and a store left out of the scope throws NotFoundError the moment it is reached
|
|
278
|
+
const tx = db.transaction(['settings'].concat(QUEUES), 'readwrite'); // no i18n
|
|
279
|
+
const settingsStore = tx.objectStore('settings'); // no i18n
|
|
280
|
+
Object.keys(settings).forEach(function (key) {
|
|
281
|
+
const lookup = settingsStore.get(key);
|
|
282
|
+
lookup.onsuccess = function () {
|
|
283
|
+
if (lookup.result === undefined) {
|
|
284
|
+
settingsStore.put(settings[key], key);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
QUEUES.forEach(function (name) {
|
|
289
|
+
const objectStore = tx.objectStore(name);
|
|
290
|
+
queues[name].forEach(function (item) { putRecord(objectStore, item, true); });
|
|
291
|
+
});
|
|
292
|
+
return completed(tx);
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function localStorageUsable() {
|
|
297
|
+
try {
|
|
298
|
+
const probe = '__apptics_probe__'; // no i18n
|
|
299
|
+
localStorage.setItem(probe, '1');
|
|
300
|
+
localStorage.removeItem(probe);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** IndexedDB when it opens, otherwise localStorage, otherwise null. Never rejects. */
|
|
308
|
+
function openBackend() {
|
|
309
|
+
const fallback = function (reason) {
|
|
310
|
+
logger.Console.warn('apptics: IndexedDB unavailable (' + String(reason) + '), using localStorage'); // no i18n
|
|
311
|
+
return localStorageUsable() ? createLocalStorageBackend() : null;
|
|
312
|
+
};
|
|
313
|
+
let factory;
|
|
314
|
+
try {
|
|
315
|
+
factory = typeof indexedDB !== 'undefined' ? indexedDB : undefined; // no i18n
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
factory = undefined; // access can throw in sandboxed frames
|
|
319
|
+
}
|
|
320
|
+
if (!factory) {
|
|
321
|
+
return Promise.resolve(fallback('not supported')); // no i18n
|
|
322
|
+
}
|
|
323
|
+
return openIndexedDB(factory).then(createIndexedDBBackend, fallback);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
exports.DB_NAME = DB_NAME;
|
|
327
|
+
exports.DB_VERSION = DB_VERSION;
|
|
328
|
+
exports.LEGACY_KEYS = LEGACY_KEYS;
|
|
329
|
+
exports.asDict = asDict;
|
|
330
|
+
exports.asList = asList;
|
|
331
|
+
exports.createIndexedDBBackend = createIndexedDBBackend;
|
|
332
|
+
exports.createLocalStorageBackend = createLocalStorageBackend;
|
|
333
|
+
exports.openBackend = openBackend;
|
|
334
|
+
exports.openIndexedDB = openIndexedDB;
|