@zoho_apptics/apptics-js-sdk 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/package.json +23 -0
  3. package/src/appticssdk.js +3817 -0
@@ -0,0 +1,3817 @@
1
+ /*$Id$*/
2
+ (function (window, document) {
3
+
4
+
5
+ var APConsole = console;
6
+ 'use strict'; // no i18n
7
+
8
+ // Console override
9
+ var Console = {
10
+ /** @type {function(...*)} */
11
+ log: function() {
12
+ if (_config.verbose && APConsole) {
13
+ try {
14
+ APConsole.log.apply(APConsole, arguments);
15
+ } catch (err) {
16
+ each(arguments, function(arg) {
17
+ APConsole.log(arg);
18
+ });
19
+ }
20
+ }
21
+ },
22
+ /** @type {function(...*)} */
23
+ warn: function() {
24
+ if (_config.verbose && APConsole) {
25
+ if (_config.verbose && APConsole) {
26
+ try {
27
+ APConsole.warn.apply(APConsole, arguments);
28
+ } catch (err) {
29
+ each(arguments, function(arg) {
30
+ APConsole.warn(arg);
31
+ });
32
+ }
33
+ }
34
+ }
35
+ },
36
+ /** @type {function(...*)} */
37
+ error: function() {
38
+ if (_config.verbose && APConsole) {
39
+ if (_config.verbose && APConsole) {
40
+ try {
41
+ APConsole.error.apply(APConsole, arguments);
42
+ } catch (err) {
43
+ each(arguments, function(arg) {
44
+ APConsole.error(arg);
45
+ });
46
+ }
47
+ }
48
+ }
49
+ },
50
+ /** @type {function(...*)} */
51
+ info: function() {
52
+ if (_config.verbose && APConsole) {
53
+ if (_config.verbose && APConsole) {
54
+ try {
55
+ APConsole.info.apply(APConsole, arguments);
56
+ } catch (err) {
57
+ each(arguments, function(arg) {
58
+ APConsole.info(arg);
59
+ });
60
+ }
61
+ }
62
+ }
63
+ },
64
+ /** @type {function(...*)} */
65
+ dir: function(arg) {
66
+ if (_config.verbose && APConsole) {
67
+ if (_config.verbose && APConsole) {
68
+ APConsole.dir(arg);
69
+ }
70
+ }
71
+ }
72
+ };
73
+
74
+ if (!window.localStorage) {
75
+ Console.warn('apptics: localStorage not supported'); // no i18n
76
+ return;
77
+ }
78
+
79
+ if (!window.Promise) {
80
+ Console.warn('apptics: Promise not supported (try adding a polyfill)'); // no i18n
81
+ return;
82
+ }
83
+
84
+ // Function to make an asynchronous request
85
+ function makeRequest(url, method, payload, headers) {
86
+ return new Promise((resolve, reject) => {
87
+ let xhr = new XMLHttpRequest();
88
+ xhr.open(method, url);
89
+ if (headers) {
90
+ Object.keys(headers).forEach((key) => {
91
+ xhr.setRequestHeader(key, headers[key]);
92
+ });
93
+ }
94
+ xhr.withCredentials = false;
95
+ xhr.onreadystatechange = function() {
96
+ if (xhr.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4
97
+ if (xhr.status == 200) {
98
+ resolve(xhr.responseText);
99
+ }
100
+ else {
101
+ reject(xhr.statusText);
102
+ }
103
+ }
104
+ };
105
+ xhr.onerror = function() {
106
+ reject('makeRequest: Network Error'); // no i18n
107
+ };
108
+ xhr.send(payload);
109
+
110
+ });
111
+ }
112
+
113
+ // Class to handle synchronous execution of requests
114
+ class RequestQueue {
115
+ constructor() {
116
+ this.queue = [];
117
+ this.currentRequest = Promise.resolve();
118
+ }
119
+
120
+ // Method to add a request to the queue
121
+ addRequest(url, method, payload, headers) {
122
+ const request = () => makeRequest(url, method, payload, headers);
123
+ this.queue.push(request);
124
+ return this;
125
+ }
126
+
127
+ // Method to process the queue
128
+ processQueue() {
129
+ this.currentRequest = this.queue.reduce((prev, curr) => prev.then(curr), Promise.resolve());
130
+ this.queue = [];
131
+ return this.currentRequest;
132
+ }
133
+ }
134
+
135
+ var _regDeviceSending = false;
136
+ var _regDeviceResolve = [];
137
+ var _regDeviceReject = [];
138
+
139
+ var _sending = false;
140
+ var _deferSendTimer = null;
141
+ var _breaker = {};
142
+
143
+ var _config = {};
144
+ var _defaultoptions = {};
145
+
146
+ // we add an internal _id to each event object in the format upoc-counter
147
+ // this makes it easier for us to remove successfully sent event from local db
148
+ var _internalEventIdCounter = 0;
149
+ var inactiveduration = 1;
150
+
151
+ // holds a copy of current request properties
152
+ var _token = '';
153
+ var _zak = '';
154
+ var _identifier = '';
155
+ var _zsoid = '';
156
+ var _projectID = '';
157
+ var _frameworkID = 5781;
158
+ var _fwid_local = 5573;
159
+ var _platform = 'Browser'; // no i18n
160
+ var _isanonrequired = true;
161
+ var _devicestatus = 0;
162
+ var _uuid = '';
163
+ var _apid = '';
164
+ var _mapid = '';
165
+ var _device_info = {};
166
+ var _user_info = {};
167
+ var _current_screen = {};
168
+ var _current_session = {};
169
+ var _properties = {};
170
+ var sendingInterval = null;
171
+ var timeoutId = null;
172
+ const requestQueue = new RequestQueue();
173
+ var _navigator = window.navigator;
174
+ var _userAgent = _navigator.userAgent;
175
+ var _ignoreDnt = false;
176
+ var _isGlobalErrorTracking = true;
177
+
178
+ /** @const */ var APPTICS_FRAMEWORK_VERSION = '1.3'; // no i18n
179
+ /** @const */ var APPTICS_INSTANCE_NAME = 'apptics'; // no i18n
180
+ /** @const */ var APPTICS_PAYLOAD_ENCODING_TYPE = 'base64'; // no i18n
181
+ /** @const */ var APPTICS_PAYLOAD_TYPE = 'json'; // no i18n
182
+
183
+ var DEFAULT_API_PATHS = {
184
+ 'regdevice': 'device/add', // no i18n
185
+ 'reguser': 'user/registerwithorg', // no i18n
186
+ 'engagement': 'engagement/add', // no i18n
187
+ 'error': 'exception/error', //no i18n
188
+ 'feedback': 'feedback/add' //no i18n
189
+ };
190
+
191
+ const AP_TRACKING_STATUS = Object.freeze({
192
+ USAGE_TRACKING_WITH_PII: 2,
193
+ USAGE_TRACKING_WITHOUT_PII: 5,
194
+ NO_TRACKING: -1
195
+ });
196
+
197
+ const AP_FEEDBACK_TYPE = Object.freeze({
198
+ BUG : 0,
199
+ FEEDBACK : 1,
200
+ HELP : 2,
201
+ REPORT : 3
202
+ });
203
+
204
+ /*
205
+ * Global settings
206
+ */
207
+ var AP_CONFIG = {
208
+ 'endpoint': '/sdk/web/v1/', // no i18n
209
+ 'method': 'POST', // no i18n
210
+ 'transport': 'XHR', // no i18n
211
+ 'mode': 1, // no i18n
212
+ 'default_group_name': 'default', // no i18n
213
+ 'default_optin_status': AP_TRACKING_STATUS.USAGE_TRACKING_WITHOUT_PII, // no i18n
214
+ 'enable_pageview_tracking': false, // no i18n
215
+ 'save_referrer': true, // no i18n
216
+ 'verbose': false, // no i18n
217
+ 'track_link_timeout': 300, // no i18n
218
+ 'cookie_expiration_days': 365, // no i18n
219
+ 'track_by_default': true, // no i18n
220
+ 'storage_type': 'localStorage', // no i18n
221
+ 'ignore_do_not_track': false, // no i18n
222
+ 'enable_batch_requests': true, // no i18n
223
+ 'batch_size': 50, // no i18n
224
+ 'batch_flush_interval': 30000, // no i18n
225
+ 'batch_request_timeout': 7, // no i18n
226
+ 'auto_starts_batch': true, // no i18n
227
+ 'debug': '',
228
+ 'base_domain': '',
229
+ 'identifier': '',
230
+ 'projectID': '',
231
+ 'token': '',
232
+ 'zsoid': '',
233
+ 'aaID': ''
234
+ };
235
+
236
+ var AP_RESERVED_CONFIG = [
237
+ 'endpoint', // no i18n
238
+ 'method', // no i18n
239
+ 'transport', // no i18n
240
+ 'track_link_timeout', // no i18n
241
+ 'cookie_expiration_days', // no i18n
242
+ 'storage_type', // no i18n
243
+ 'enable_batch_requests', // no i18n
244
+ 'batch_size', // no i18n
245
+ 'batch_request_timeout', // no i18n
246
+ 'auto_starts_batch' // no i18n
247
+ ];
248
+
249
+ // crawlers reference
250
+ // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
251
+ var BLOCKED_WS = [
252
+ 'ahrefsbot', // no i18n
253
+ 'ahrefssiteaudit', // no i18n
254
+ 'baiduspider', // no i18n
255
+ 'bingbot', // no i18n
256
+ 'bingpreview', // no i18n
257
+ 'chrome-lighthouse', // no i18n
258
+ 'facebookexternal', // no i18n
259
+ 'petalbot', // no i18n
260
+ 'pinterest', // no i18n
261
+ 'screaming frog', // no i18n
262
+ 'yahoo! slurp', // no i18n
263
+ 'yandexbot', // no i18n
264
+ 'adsbot-google', // no i18n
265
+ 'apis-google', // no i18n
266
+ 'duplexweb-google', // no i18n
267
+ 'feedfetcher-google', // no i18n
268
+ 'google favicon', // no i18n
269
+ 'google web preview', // no i18n
270
+ 'google-read-aloud', // no i18n
271
+ 'googlebot', // no i18n
272
+ 'googleweblight', // no i18n
273
+ 'mediapartners-google', // no i18n
274
+ 'storebot-google' // no i18n
275
+ ];
276
+
277
+ /**
278
+ * local storage helper for saving optin status for users.
279
+ */
280
+ var tracking_preference = {
281
+
282
+ _key: 'apptics-tracking-preference', // no i18n
283
+ _tracking_status: 'tracking_status', // no i18n
284
+
285
+ all: function () {
286
+ return JSON.parse(localStorage.getItem(tracking_preference._key) || JSON.stringify({}));
287
+ },
288
+ // returns a list of all tracking_preference or empty object.
289
+ opt_in_status: function () {
290
+ var status = tracking_preference.all();
291
+ return status[tracking_preference._tracking_status] || AP_TRACKING_STATUS.USAGE_TRACKING_WITHOUT_PII;
292
+ },
293
+ // opting in of tracking for a specific device and updates the preference.
294
+ opt_in_tracking_without_pii: function () {
295
+ var status = tracking_preference.all();
296
+ status[tracking_preference._tracking_status] = AP_TRACKING_STATUS.USAGE_TRACKING_WITHOUT_PII;
297
+ // save changes
298
+ localStorage.setItem(tracking_preference._key, JSON.stringify(status));
299
+ },
300
+ // opting in of tracking for a specific device and updates the preference.
301
+ opt_in_tracking_with_pii: function () {
302
+ var status = tracking_preference.all();
303
+ status[tracking_preference._tracking_status] = AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII;
304
+ // save changes
305
+ localStorage.setItem(tracking_preference._key, JSON.stringify(status));
306
+ },
307
+ // opting out of tracking for a specific device and updates the preference.
308
+ opt_out_tracking: function () {
309
+ var status = tracking_preference.all();
310
+ status[tracking_preference._tracking_status] = AP_TRACKING_STATUS.NO_TRACKING;
311
+ // save changes
312
+ localStorage.setItem(tracking_preference._key, JSON.stringify(status));
313
+ },
314
+ // opting out of tracking for a specific device and updates the preference.
315
+ update_status: function (tracking_status) {
316
+ var status = tracking_preference.all();
317
+ status[tracking_preference._tracking_status] = tracking_status;
318
+ // save changes
319
+ localStorage.setItem(tracking_preference._key, JSON.stringify(status));
320
+ },
321
+ // clears any pending tracking_preference.
322
+ clear: function () {
323
+ localStorage.setItem(tracking_preference._key, JSON.stringify({}));
324
+ }
325
+ };
326
+
327
+ /**
328
+ * local storage helper for saving engagement_transactions in order
329
+ */
330
+ var engagement_transactions = {
331
+
332
+ _key: 'apptics-engagements-transactions', // no i18n
333
+
334
+ // maximum size of payload allowed before we start sending to server
335
+ _maxSize: 1,
336
+
337
+ // returns a list of all engagement_transactions or empty array
338
+ all: function () {
339
+ return JSON.parse(localStorage.getItem(engagement_transactions._key) || JSON.stringify([]));
340
+ },
341
+ // returns a list of all engagement_transactions or empty array
342
+ payload: function () {
343
+ const targetSize = 1024 * 1024; // 1 MB in bytes
344
+
345
+ let dataSize = 0;
346
+ const result = [];
347
+ var existing = engagement_transactions.all().slice(0,getConfig('batch_size')); // no i18n
348
+ for (const item of existing) {
349
+ const size = sizeof(JSON.stringify(item));
350
+ if (dataSize + size > targetSize) {
351
+ break;
352
+ }
353
+ result.push(item);
354
+ dataSize += size;
355
+ }
356
+ return result;
357
+ },
358
+ // adds an item to the transaction log
359
+ add: function (data) {
360
+
361
+ // get existing engagement_transactions
362
+ var existing = engagement_transactions.all();
363
+
364
+ // increase event _id counter
365
+ _internalEventIdCounter++;
366
+
367
+ // add latest to end of stack
368
+ existing.push(data);
369
+
370
+ // save changes
371
+ localStorage.setItem(engagement_transactions._key, JSON.stringify(existing));
372
+ },
373
+
374
+ // adds an item to the transaction log
375
+ update: function (oldElement, newElement) {
376
+
377
+ // get existing engagement_transactions
378
+ var existing = engagement_transactions.all();
379
+
380
+ // var index = existing.indexOf(oldElement);
381
+ var index = existing.findIndex(function(object) {
382
+ return (object.type === oldElement.type && object.starttime === oldElement.starttime);
383
+ });
384
+
385
+ // add latest to end of stack
386
+ if (index !== -1) {
387
+ existing[index] = newElement;
388
+ }
389
+
390
+ // save changes
391
+ localStorage.setItem(engagement_transactions._key, JSON.stringify(existing));
392
+ },
393
+
394
+ // removes events from the transaction log
395
+ remove: function (itemsToRemove) {
396
+
397
+ // get array of ids to remove
398
+ var idsToRemove = (itemsToRemove || []).map(function(item) {
399
+ return item.id;
400
+ });
401
+
402
+ // go through existing engagement_transactions, removing items that contain a matching id
403
+ var remaining = engagement_transactions.all().filter(function(item) {
404
+ return idsToRemove.indexOf(item.id) === -1;
405
+ });
406
+
407
+ // save changes
408
+ localStorage.setItem(engagement_transactions._key, JSON.stringify(remaining));
409
+ },
410
+
411
+ // clears any pending engagement_transactions
412
+ clear: function () {
413
+ localStorage.setItem(engagement_transactions._key, JSON.stringify([]));
414
+ }
415
+ };
416
+
417
+ /**
418
+ * local storage helper for saving error_transactions in order
419
+ */
420
+ var error_transactions = {
421
+
422
+ _key: 'apptics-errors-transactions', // no i18n
423
+
424
+ // maximum number of items allowed before we start dropping first items
425
+ _maxLength: 100,
426
+
427
+ // returns a list of all error_transactions or empty array
428
+ all: function () {
429
+ return JSON.parse(localStorage.getItem(error_transactions._key) || JSON.stringify([]));
430
+ },
431
+ // returns a list of all error_transactions or empty array
432
+ payload: function () {
433
+ const targetSize = 1024 * 1024; // 1 MB in bytes
434
+
435
+ let dataSize = 0;
436
+ const result = [];
437
+ var existing = error_transactions.all().slice(0,getConfig('batch_size')); // no i18n
438
+ for (const item of existing) {
439
+ const size = sizeof(JSON.stringify(item));
440
+ if (dataSize + size > targetSize) {
441
+ break;
442
+ }
443
+ result.push(item);
444
+ dataSize += size;
445
+ }
446
+ return result;
447
+ },
448
+ // adds an item to the transaction log
449
+ add: function (data) {
450
+
451
+ // get existing error_transactions
452
+ var existing = error_transactions.all();
453
+
454
+ // increase event _id counter
455
+ _internalEventIdCounter++;
456
+
457
+ // add latest to end of stack
458
+ existing.push(data);
459
+
460
+ // save changes
461
+ localStorage.setItem(error_transactions._key, JSON.stringify(existing));
462
+ },
463
+
464
+ // adds an item to the transaction log
465
+ update: function (oldElement, newElement) {
466
+
467
+ // get existing error_transactions
468
+ var existing = error_transactions.all();
469
+
470
+ // var index = existing.indexOf(oldElement);
471
+ var index = existing.findIndex(function(object) {
472
+ return (object.type === oldElement.type && object.starttime === oldElement.starttime);
473
+ });
474
+
475
+ // add latest to end of stack
476
+ if (index !== -1) {
477
+ existing[index] = newElement;
478
+ }
479
+
480
+ // save changes
481
+ localStorage.setItem(error_transactions._key, JSON.stringify(existing));
482
+ },
483
+
484
+ // removes events from the transaction log
485
+ remove: function (itemsToRemove) {
486
+
487
+ // get array of ids to remove
488
+ var idsToRemove = (itemsToRemove || []).map(function(item) {
489
+ return item._id;
490
+ });
491
+
492
+ // go through existing error_transactions, removing items that contain a matching id
493
+ var remaining = error_transactions.all().filter(function(item) {
494
+ return idsToRemove.indexOf(item._id) === -1;
495
+ });
496
+
497
+ // save changes
498
+ localStorage.setItem(error_transactions._key, JSON.stringify(remaining));
499
+ },
500
+
501
+ // clears any pending error_transactions
502
+ clear: function () {
503
+ localStorage.setItem(error_transactions._key, JSON.stringify([]));
504
+ }
505
+ };
506
+
507
+
508
+ /**
509
+ * local storage helper for saving user_defaults in order
510
+ */
511
+ var user_defaults = {
512
+
513
+ _key: 'apptics-device-info', // no i18n
514
+
515
+ // maximum number of items allowed before we start dropping first items
516
+ _maxLength: 100,
517
+
518
+ // returns a list of all user_defaults or empty array
519
+ all: function () {
520
+ return JSON.parse(localStorage.getItem(user_defaults._key) || JSON.stringify({}));
521
+ },
522
+
523
+ item: function (key) {
524
+ // get existing user_defaults
525
+
526
+ var items = user_defaults.all();
527
+
528
+ return items[key];
529
+
530
+ },
531
+
532
+ // adds an item to the transaction log
533
+ add: function (key,value) {
534
+
535
+ // get existing user_defaults
536
+ var existing = user_defaults.all();
537
+
538
+ //add/update the value
539
+ existing[key]=value;
540
+
541
+ // // save changes
542
+ localStorage.setItem(user_defaults._key, JSON.stringify(existing));
543
+ },
544
+
545
+ // add or update an item to the devivce info transaction log
546
+ addorupdatedeviceinfo: function (key,value) {
547
+
548
+ var deviceinfokey = 'device_info';
549
+ // get existing user_defaults device info
550
+ var existing = user_defaults.item(deviceinfokey) || {};
551
+
552
+ //add/update the value
553
+ existing[key]=value;
554
+
555
+ // // save changes
556
+ user_defaults.add(deviceinfokey, existing); // no i18n
557
+ },
558
+
559
+ // removes events from the transaction log
560
+ remove: function (key) {
561
+
562
+ // get existing user_defaults
563
+ var existing = user_defaults.all();
564
+
565
+ //add/update the value
566
+ delete existing[key];
567
+
568
+ // // save changes
569
+ localStorage.setItem(user_defaults._key, JSON.stringify(existing));
570
+ },
571
+
572
+ // clears any pending user_defaults
573
+ clear: function () {
574
+ localStorage.setItem(user_defaults._key, JSON.stringify({}));
575
+ }
576
+ };
577
+
578
+ function isBlockedWS(ua) {
579
+ var i;
580
+ ua = ua.toLowerCase();
581
+ for (i = 0; i < BLOCKED_WS.length; i++) {
582
+ if (ua.indexOf(BLOCKED_WS[i]) !== -1) {
583
+ return true;
584
+ }
585
+ }
586
+ return false;
587
+ }
588
+
589
+ function is_apptics_disabled() {
590
+ return isBlockedWS(_userAgent);
591
+ }
592
+
593
+ function hasDoNotTrackEnabled() {
594
+ if (_ignoreDnt) {
595
+ return false;
596
+ }
597
+
598
+ var navigator = window.navigator || {};
599
+ var hasDntOn = false;
600
+
601
+ each([
602
+ navigator.doNotTrack, // default
603
+ navigator.msDoNotTrack, // MS
604
+ window.doNotTrack
605
+ ], function(dntValue) {
606
+ if (includes([true, 1, '1', 'yes'], dntValue)) {
607
+ hasDntOn = true;
608
+ setTrackingStatus(AP_TRACKING_STATUS.NO_TRACKING);
609
+ }
610
+ });
611
+ return hasDntOn;
612
+ }
613
+
614
+ function getConfig(key){
615
+ return _config[key];
616
+ }
617
+
618
+ function getHeaderParams(key){
619
+ if (key == 'zak'){ // no i18n
620
+ return _zak;
621
+ }
622
+ else if (key == 'uuid'){ // no i18n
623
+ return _uuid;
624
+ }
625
+ else if (key == 'apid'){ // no i18n
626
+ return _apid || getKeyFromDeviceInfo(key);
627
+ }
628
+ else if (key == 'mapid'){ // no i18n
629
+ return _mapid || getKeyFromDeviceInfo(key);
630
+ }
631
+ else{
632
+ return '';
633
+ }
634
+ }
635
+
636
+ function getParameters(path){
637
+ let returnVal = '';
638
+ if (path == 'regdevice'){ // no i18n
639
+ // Device
640
+ returnVal = '?identifier='+_identifier+'&platform='+_platform+'&appversion='+APPTICS_FRAMEWORK_VERSION+'&isanonrequired='+_isanonrequired+'&devicestatus='+_devicestatus; // no i18n
641
+ if(_isanonrequired && !isEmpty(_device_info.anonymousid)){
642
+ returnVal += '&anondeviceid='+_device_info.anonymousid; // no i18n
643
+ }
644
+ if(!_isanonrequired && !isEmpty(_device_info.deviceid)){
645
+ returnVal += '&deviceid='+_device_info.deviceid; // no i18n
646
+ }
647
+ return returnVal;
648
+ }
649
+ else{
650
+ if(isEmpty(_device_info.deviceid) || isEmpty(_device_info.anonymousid)){
651
+ checkAndUpdateDeviceInfo();
652
+ return returnVal;
653
+ }
654
+
655
+ if(!_isanonrequired && !isEmpty(_device_info.deviceid) || path == 'reguser'){ // no i18n
656
+ returnVal = '?deviceid='+_device_info.deviceid; // no i18n
657
+ if(opt_in_status() == AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII && path == 'reguser'){ // no i18n
658
+ if(_user_info.hasOwnProperty('userid') && !isEmpty(_user_info.userid)){ // no i18n
659
+ returnVal += '&userid='+ _user_info.userid; // no i18n
660
+ }
661
+ }
662
+ }
663
+ else if(_isanonrequired && !isEmpty(_device_info.anonymousid)){
664
+ returnVal = '?anondeviceid='+_device_info.anonymousid; // no i18n
665
+ }
666
+
667
+ if(path != 'reguser'){
668
+ if(opt_in_status() == AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII){
669
+ if(_user_info.hasOwnProperty('userid') && !isEmpty(_user_info.userid)){ // no i18n
670
+ returnVal += '&userid='+ _user_info.userid; // no i18n
671
+ }
672
+ if(_user_info.hasOwnProperty('groupid') && !isEmpty(_user_info.groupid)){ // no i18n
673
+ returnVal += '&customergroupid='+ _user_info.groupid; // no i18n
674
+ }
675
+ }
676
+ }
677
+ return returnVal;
678
+ }
679
+ }
680
+
681
+ function getUrlWithParams(path){
682
+ return _defaultoptions.base_domain + getConfig('endpoint') +_zsoid+'/'+_projectID+'/'+DEFAULT_API_PATHS[path]+getParameters(path); // no i18n
683
+ }
684
+
685
+ function validateEventOrGroup(str) {
686
+ if (!str || str === ''){
687
+ return false;
688
+ }
689
+ // Define the regular expression pattern
690
+ const pattern = /^[a-zA-Z][a-zA-Z0-9_]{0,99}$/;
691
+
692
+ // Test if the string matches the pattern
693
+ return pattern.test(str);
694
+ }
695
+
696
+ function validateJSON(jsonObj) {
697
+ if(isEmpty(jsonObj) || isEmptyObj(jsonObj)){
698
+ return {};
699
+ }
700
+ let maxKeys = 25;
701
+ let maxLength = 7666;
702
+ // Check if the number of keys in the JSON object exceeds the max limit
703
+ if (Object.keys(jsonObj).length >= maxKeys) {
704
+ // Number of keys exceeds the limit
705
+ Console.warn('validateJSON: Property limit exceeds, you can add only a maximum of 25 key-value pairs to the jsonObject'); // no i18n
706
+ return {'isvalid': false, 'message': {'code': 102}}; // no i18n
707
+ }
708
+
709
+ // Convert the JSON object to a string
710
+ const jsonString = JSON.stringify(jsonObj);
711
+
712
+ // Check if the string length exceeds the max limit
713
+ if (jsonString.length > maxLength) {
714
+ // String length exceeds the limit
715
+ Console.warn('validateJSON: Property limit exceeds, the overall length should be below 7666 characters'); // no i18n
716
+ return {'isvalid': false, 'message': {'code': 101}}; // no i18n
717
+ }
718
+
719
+ // If both checks pass, return true
720
+ return jsonKeyvalueValidation(jsonObj);
721
+ }
722
+
723
+ function jsonKeyvalueValidation(dictionary) {
724
+ let finalDict = {};
725
+
726
+ let dict = dictionary;
727
+
728
+ let result = {
729
+ isvalid: false,
730
+ message: {
731
+ code: [],
732
+ keys: [],
733
+ valuekeys: []
734
+ }
735
+ };
736
+
737
+ let pattern = /^[a-zA-Z][a-zA-Z0-9_]{0,49}$/;
738
+
739
+ for (let key in dict) {
740
+ let matches = key.match(pattern);
741
+ if (!matches) {
742
+ let trimmedKey = key.substring(0, 50);
743
+ result.message.keys.push(trimmedKey);
744
+ Console.warn('jsonKeyvalueValidation: Invalid property, the key \''+key+'\' does not comply with the requirements, please refer to our SDK guide.'); // no i18n
745
+ if (!result.message.code.includes(103)) {
746
+ result.message.code.push(103);
747
+ }
748
+ }
749
+
750
+ let value = dict[key];
751
+ let stringValue = JSON.stringify(value);
752
+ if (stringValue.length > 250) {
753
+ Console.warn('jsonKeyvalueValidation: Property limit exceeds, \''+key+'\' value length should be below 250'); // no i18n
754
+ result.message.valuekeys.push(key);
755
+ if (!result.message.code.includes(104)) {
756
+ result.message.code.push(104);
757
+ }
758
+ }
759
+ }
760
+
761
+ let areBothArraysEmpty = (result.message.keys.length === 0) && (result.message.valuekeys.length === 0);
762
+ if (areBothArraysEmpty) {
763
+ finalDict = checkForJsonRegex(dict);
764
+ } else {
765
+ finalDict = result;
766
+ }
767
+
768
+ return finalDict;
769
+ }
770
+
771
+ function checkForJsonRegex(inputDict) {
772
+
773
+ let FinalvalidDictionary = {};
774
+ let regex = /^[a-zA-Z][a-zA-Z0-9_]{0,49}$/;
775
+ let validData = {};
776
+ let validDataType = {};
777
+ let invalidKeys = [];
778
+ for (let key in inputDict) {
779
+ let matchCount = key.match(regex) ? key.match(regex).length : 0;
780
+ let dataType;
781
+
782
+ let obj = inputDict[key];
783
+
784
+ if (typeof obj === 'number') { // no i18n
785
+ dataType = 'Number'; // no i18n
786
+ if (obj === 1 || obj === 0) {
787
+ dataType = 'Boolean'; // no i18n
788
+ }
789
+ } else if (typeof obj === 'string') { // no i18n
790
+ dataType = 'String'; // no i18n
791
+ } else {
792
+ dataType = 'Unkown'; // no i18n
793
+ }
794
+
795
+ if (matchCount > 0) {
796
+ if (dataType === 'Unkown') { // no i18n
797
+ let stringRepresentation = JSON.stringify(obj);
798
+ validData[key] = stringRepresentation;
799
+ validDataType[key] = 'String'; // no i18n
800
+ } else {
801
+ validData[key] = obj;
802
+ validDataType[key] = dataType;
803
+ }
804
+ } else {
805
+ Console.warn('checkForJsonRegex: Invalid property, the key \''+key+'\' does not comply with the requirements, please refer to our SDK guide.'); // no i18n
806
+ invalidKeys.push(key);
807
+ }
808
+ }
809
+
810
+ let validResponse = {
811
+ data: validData,
812
+ datatype: validDataType,
813
+ isvalid: true
814
+ };
815
+
816
+ let invalidResponse = {
817
+ isvalid: false,
818
+ message: {
819
+ code: [103],
820
+ keys: invalidKeys
821
+ }
822
+ };
823
+
824
+ if (invalidKeys.length > 0) {
825
+ FinalvalidDictionary = invalidResponse;
826
+ } else {
827
+ if (Object.keys(validData).length > 0) {
828
+ FinalvalidDictionary = validResponse;
829
+ }
830
+ }
831
+
832
+ return FinalvalidDictionary;
833
+ }
834
+
835
+ function validateScreenName(str) {
836
+ if (!str || str === ''){
837
+ return false;
838
+ }
839
+ // Define the regular expression pattern
840
+ //^[a-zA-Z][\\w\\s]{1,100}$
841
+ const pattern = /^[\w\s.\-_&/:]{0,99}$/;
842
+
843
+ // Test if the string matches the pattern
844
+ return pattern.test(str);
845
+ }
846
+
847
+ /**
848
+ * Returns value of the given super property
849
+ * @param {string} propertyName - name of the property to retrieve
850
+ * @returns {any} value of property
851
+ */
852
+ function getProperty(propertyName) {
853
+ return _device_info[propertyName];
854
+ }
855
+
856
+ function getProperty() {
857
+ return _device_info;
858
+ }
859
+ /**
860
+ * Attempt to get the network connection type from w3c NetworkInformation or cordova-plugin-network-information
861
+ * @returns {string} connection type or empty
862
+ */
863
+ function getConnectionType() {
864
+ var connection = (navigator.connection || navigator.mozConnection || navigator.webkitConnection || {});
865
+
866
+ return connection.effectiveType || connection.type || '';
867
+ }
868
+
869
+ /**
870
+ * clear engagements and error transactions
871
+ * @returns {void}
872
+ */
873
+ function clear() {
874
+ engagement_transactions.clear();
875
+ error_transactions.clear();
876
+ Console.log('apptics.clear()'); // no i18n
877
+ }
878
+
879
+ /**
880
+ * clear current identity
881
+ * @returns {void}
882
+ */
883
+ function reset() {
884
+ user_defaults.clear();
885
+ engagement_transactions.clear();
886
+ error_transactions.clear();
887
+ tracking_preference.clear();
888
+ init(_token, _defaultoptions);
889
+ Console.log('apptics.reset()'); // no i18n
890
+ }
891
+
892
+ /**
893
+ * Track an event.
894
+ * @param {String} eventName The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
895
+ * @param {String} groupName The name of the group. This can be any group or module name that the event belongs - 'quickstart', 'dashboard', 'store-reviews', 'flow', 'funnel' ,'pricing', etc.
896
+ * @param {Object} [jsonObj] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
897
+ * @returns {void}
898
+ */
899
+ function trackEvent(eventName, groupName, jsonObj) {
900
+ if (!_token) {
901
+ Console.warn('apptics.trackEvent: You must call apptics.init() first'); // no i18n
902
+ return;
903
+ }
904
+
905
+ if (is_apptics_disabled()){
906
+ return;
907
+ }
908
+
909
+ if (!shouldTrackOrSendData()){
910
+ return;
911
+ }
912
+
913
+ if (!validateEventOrGroup(eventName)){
914
+ Console.warn('apptics.trackEvent: Invalid eventName'); // no i18n
915
+ return;
916
+ }
917
+
918
+ groupName = validateEventOrGroup(groupName) ? groupName : AP_CONFIG.default_group_name;
919
+ jsonObj = jsonObj || {};
920
+ var _jsonObj = validateJSON(jsonObj);
921
+
922
+ //setTimeout((()=>{
923
+ // epoch time in seconds
924
+ var time = Math.round((new Date()).getTime());
925
+ var eventData = {
926
+ id:getNewUUID(),
927
+ type:'event', // no i18n
928
+ group:groupName,
929
+ event: eventName,
930
+ starttime:time,
931
+ endtime:time,
932
+ edge:_device_info.connectionType || getConnectionType(),
933
+ networkstatus: getNetworkStatus(),
934
+ screen:_current_screen.screen || '',
935
+ sessionstarttime:_current_session.starttime || time,
936
+ customproperties: _jsonObj
937
+ };
938
+
939
+ // save the event
940
+ engagement_transactions.add(eventData);
941
+
942
+ Console.log('apptics.trackEvent(\'' + eventName + '\',\'' + groupName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
943
+ Console.dir(eventData);
944
+ //}), 500);
945
+ }
946
+
947
+ /**
948
+ * Track the screen.
949
+ * @param {String} screenName The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
950
+ * @param {Object} [data] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
951
+ * @returns {void}
952
+ */
953
+ function trackScreen(screenName, jsonObj) {
954
+
955
+ if (!_token) {
956
+ Console.warn('apptics.trackScreen: You must call apptics.init(token) first'); // no i18n
957
+ return;
958
+ }
959
+
960
+ if (is_apptics_disabled()){
961
+ return;
962
+ }
963
+
964
+ if (!shouldTrackOrSendData()){
965
+ return;
966
+ }
967
+
968
+ if (!validateScreenName(screenName)) {
969
+ Console.warn('apptics.trackScreen: Invalid screenName'); // no i18n
970
+ return;
971
+ }
972
+ jsonObj = jsonObj || {};
973
+ var _jsonObj = validateJSON(jsonObj);
974
+
975
+ //setTimeout((()=>{
976
+ // epoch time in seconds
977
+ var time = Math.round((new Date()).getTime());
978
+ var screenData = {
979
+ id:getNewUUID(),
980
+ type:'screen', // no i18n
981
+ screen: screenName,
982
+ starttime:time,
983
+ endtime:time, // need to check
984
+ edge:_device_info.connectionType || getConnectionType(),
985
+ networkstatus: getNetworkStatus(),
986
+ orientation:getOrientation(),
987
+ sessionstarttime:_current_session.starttime || time,
988
+ customproperties: _jsonObj
989
+ };
990
+
991
+ // save the screen
992
+ engagement_transactions.add(screenData);
993
+ _current_screen = screenData;
994
+
995
+ Console.log('apptics.trackScreen(\'' + screenName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
996
+ Console.dir(screenData);
997
+
998
+ //}), 500);
999
+ }
1000
+
1001
+ /**
1002
+ * Track an api.
1003
+ * * @param {Object} [data] A set of properties to include with the api you're sending. These describe the user who did the api or details about the event itself.
1004
+ * @returns {void}
1005
+ */
1006
+ function trackApi(jsonObj) {
1007
+ if (!_token) {
1008
+ Console.warn('apptics.trackApi: You must call apptics.init() first'); // no i18n
1009
+ return;
1010
+ }
1011
+
1012
+ if (is_apptics_disabled()){
1013
+ return;
1014
+ }
1015
+
1016
+ if (!shouldTrackOrSendData()){
1017
+ return;
1018
+ }
1019
+
1020
+ jsonObj = jsonObj || {};
1021
+ var _jsonObj = validateJSON(jsonObj);
1022
+
1023
+ //setTimeout((()=>{
1024
+ // epoch time in seconds
1025
+ var time = Math.round((new Date()).getTime());
1026
+ var apiData = {
1027
+ id:getNewUUID(),
1028
+ type:'api', // no i18n
1029
+ starttime:time,
1030
+ endtime:time,
1031
+ edge:_device_info.connectionType || getConnectionType(),
1032
+ networkstatus: getNetworkStatus(),
1033
+ screen:_current_screen.screen || '',
1034
+ sessionstarttime:_current_session.starttime || time,
1035
+ customproperties: _jsonObj
1036
+ };
1037
+ // save the api
1038
+ //engagement_transactions.add(apiData);
1039
+
1040
+ Console.log('apptics.trackApi(\'' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
1041
+ Console.dir(apiData);
1042
+ //}), 500);
1043
+ }
1044
+
1045
+ function currentScreen() {
1046
+
1047
+ // get all items waiting to be sent
1048
+ var items = engagement_transactions.all();
1049
+
1050
+ // if we have nothing to send, exit
1051
+ if (items.length === -1) {
1052
+ return null;
1053
+ }
1054
+
1055
+ // go through existing engagement_transactions, removing items that contain a matching id
1056
+ var screenData = items.filter(function(item) {
1057
+
1058
+ if (item.type == 'screen' && item.endtime === 0) {
1059
+ // Return the item
1060
+ return item;
1061
+ }
1062
+ });
1063
+
1064
+ screenData = screenData.sort(function (a, b){
1065
+ return b.starttime - a.starttime;
1066
+ });
1067
+
1068
+ var _screen = screenData[0];
1069
+
1070
+ if (_screen) {
1071
+ Console.log('currentScreen: (' + JSON.stringify(_screen || {}) + ')'); // no i18n
1072
+ }
1073
+ Console.dir(_screen);
1074
+
1075
+ return _screen;
1076
+ }
1077
+
1078
+ function updateScreenWithEndtime() {
1079
+ var time = Math.round((new Date()).getTime());
1080
+ var _currentScreen = currentScreen();
1081
+ if (_currentScreen && _currentScreen.endtime == 0){
1082
+ var _oldScreen = _currentScreen;
1083
+ var _newScreen = Object.assign({}, _oldScreen)
1084
+ _newScreen.endtime=time;
1085
+ engagement_transactions.update(_oldScreen,_newScreen);
1086
+ }
1087
+ }
1088
+
1089
+ /**
1090
+ * Track the session.
1091
+ * @param {Object} [sessionData] A set of properties belongs to the session.
1092
+ * @returns {void}
1093
+ */
1094
+ function trackSession(sessionData) {
1095
+
1096
+ if (!_token) {
1097
+ Console.warn('apptics.trackScreen: You must call apptics.init(token) first'); // no i18n
1098
+ return;
1099
+ }
1100
+
1101
+ if (is_apptics_disabled()){
1102
+ return;
1103
+ }
1104
+
1105
+ if (!shouldTrackOrSendData()){
1106
+ return;
1107
+ }
1108
+
1109
+
1110
+ if (sessionData && typeof sessionData !== 'object') {
1111
+ Console.warn('apptics.trackSession: sessionData param must be an object'); // no i18n
1112
+ return;
1113
+ }
1114
+
1115
+
1116
+ engagement_transactions.add(sessionData);
1117
+
1118
+ if (sessionData) {
1119
+ Console.log('apptics.trackSession(' + JSON.stringify(sessionData || {}) + ')'); // no i18n
1120
+ }
1121
+ Console.dir(sessionData);
1122
+
1123
+ }
1124
+
1125
+ function currentSession() {
1126
+
1127
+ // get all items waiting to be sent
1128
+ var items = engagement_transactions.all();
1129
+
1130
+ // if we have nothing to send, exit
1131
+ if (items.length === -1) {
1132
+ return null;
1133
+ }
1134
+
1135
+ // go through existing engagement_transactions, removing items that contain a matching id
1136
+ var sessionData = items.filter(function(item) {
1137
+
1138
+ if (item.type == 'session' && item.endtime === 0) {
1139
+ // Return the item
1140
+ return item;
1141
+ }
1142
+ });
1143
+
1144
+ sessionData = sessionData.sort(function (a, b){
1145
+ return b.starttime - a.starttime;
1146
+ });
1147
+
1148
+ var _session = sessionData[0];
1149
+
1150
+ if (_session) {
1151
+ Console.log('currentSession: (' + JSON.stringify(_session || {}) + ')'); // no i18n
1152
+ Console.dir(_session);
1153
+ }
1154
+
1155
+ return _session;
1156
+ }
1157
+
1158
+ function startNewSession(time){
1159
+ var storage = calculateLocalStorageSize();
1160
+ _current_session={
1161
+ id:getNewUUID(),
1162
+ type:'session', // no i18n
1163
+ starttime:time,
1164
+ endtime:0,
1165
+ edge:getConnectionType(),
1166
+ orientation:getOrientation(),
1167
+ firstsession:'false', // no i18n //need to check
1168
+ networkstatus:getNetworkStatus(),
1169
+ //startlocalstoragesize:storage.totalSizeKB, //need to check
1170
+ //endlocalstoragesize:storage.totalSizeKB, //need to check
1171
+ lastactivetime:time
1172
+ };
1173
+ trackSession(_current_session);
1174
+ }
1175
+
1176
+ /**
1177
+ * Track an error.
1178
+ * @param {Object} [error] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
1179
+ * @param {Object} [data] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
1180
+ * @returns {void}
1181
+ */
1182
+ function trackError(errorEvent, jsonObj) {
1183
+
1184
+ if (!_token) {
1185
+ Console.warn('apptics.trackScreen: You must call apptics.init(token) first'); // no i18n
1186
+ return;
1187
+ }
1188
+
1189
+ if (is_apptics_disabled()){
1190
+ return;
1191
+ }
1192
+
1193
+ if (!shouldTrackOrSendData()){
1194
+ return;
1195
+ }
1196
+
1197
+ if (isEmpty(errorEvent)) {
1198
+ Console.warn('apptics.trackError: Invalid error'); // no i18n
1199
+ return;
1200
+ }
1201
+
1202
+ var _jsonObj = validateJSON(jsonObj);
1203
+
1204
+ var stack = errorEvent.error?.stack || "";
1205
+ var methodName = extractMethodNameFromStackTrace(stack);
1206
+ var issuetype = errorEvent.type || "";
1207
+ var message = isEmpty(stack) ? errorEvent.toString() : errorEvent.message + ' at ' + methodName;
1208
+
1209
+ // epoch time in seconds
1210
+ var time = Math.round((new Date()).getTime());
1211
+ var errorData = {
1212
+ id:getNewUUID(),
1213
+ type:'error', // no i18n
1214
+ edge:_device_info.connectionType || getConnectionType(),
1215
+ networkstatus:getNetworkStatus(),
1216
+ happenedat:time,
1217
+ issuename:message,
1218
+ message:stack,
1219
+ screenname:_current_screen.screen || window.location.pathname,
1220
+ sessionstarttime:_current_session.starttime || time,
1221
+ customproperties: _jsonObj
1222
+ };
1223
+
1224
+ // save the error
1225
+ error_transactions.add(errorData);
1226
+
1227
+ Console.log('apptics.trackError(\'' + issuetype + '\',\'' + message + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
1228
+ Console.dir(errorData);
1229
+
1230
+ }
1231
+
1232
+ function enableGlobalErrorHandler(status){
1233
+ _isGlobalErrorTracking = typeof status === "boolean" ? status : true;
1234
+ }
1235
+
1236
+ /**
1237
+ * Feedback
1238
+ * @returns {void}
1239
+ */
1240
+
1241
+ function feedbackOpen(){
1242
+ feedback.init();
1243
+ }
1244
+
1245
+ function feedbackClose(){
1246
+ feedback.close();
1247
+ }
1248
+
1249
+ function sendFeedback(feedbackType, feedbackMessage, attachments){
1250
+ dataobj = {
1251
+ type : !isEmpty(feedbackType) ? feedbackType : 1,
1252
+ message : feedbackMessage || '',
1253
+ attachment : attachments || null,
1254
+ quartzid : ''
1255
+ }
1256
+ sendFeedbackData(dataobj);
1257
+ }
1258
+
1259
+ /**
1260
+ * setUser a user with a unique ID instead of a apptics randomly generated distinct_id.
1261
+ * If the method is never called, unique visitors will be identified by a UUID generated the first time they visit the site.
1262
+ * @param {String} [userid] A string that uniquely identifies a user.
1263
+ * * @param {String} [groupid] A string that uniquely identifies a user group.
1264
+ * @returns {void}
1265
+ */
1266
+ function setUser(userid, groupid, jsonObj) {
1267
+
1268
+ if (!_token) {
1269
+ Console.warn('apptics.setUser: You must call apptics.init(token) first'); // no i18n
1270
+ return;
1271
+ }
1272
+
1273
+ if (is_apptics_disabled()){
1274
+ return;
1275
+ }
1276
+
1277
+ if (!shouldTrackOrSendData()){
1278
+ return;
1279
+ }
1280
+
1281
+
1282
+ if (isEmpty(userid)) {
1283
+ Console.warn('apptics.setUser: Invalid userid'); // no i18n
1284
+ return;
1285
+ }
1286
+
1287
+ if(isEmpty(groupid)){
1288
+ Console.warn('apptics.setUser: Invalid groupid'); // no i18n
1289
+ return;
1290
+ }
1291
+
1292
+ jsonObj = jsonObj || {};
1293
+ let _jsonObj = validateJSON(jsonObj);
1294
+
1295
+ //setTimeout((()=>{
1296
+
1297
+ var user_info={
1298
+ mam: userid,
1299
+ customergroupid: groupid,
1300
+ appversionid: _device_info.appversionid || '', // no i18n
1301
+ status: 1,
1302
+ happendat: Math.round((new Date()).getTime())
1303
+ };
1304
+
1305
+ if(_jsonObj.hasOwnProperty('isvalid') || _jsonObj.isvalid == true){ // no i18n
1306
+ user_info.userproperties = _jsonObj;
1307
+ }
1308
+
1309
+ registerUserWithApptics(user_info).then(function (user_info) {
1310
+ Console.log('setUser: ' + JSON.stringify(user_info)); // no i18n
1311
+ }).catch(function (err){
1312
+ Console.log(err);
1313
+ });
1314
+
1315
+ //}), 500);
1316
+ }
1317
+
1318
+ function addOrUpdateUser(user_info) {
1319
+
1320
+ if (!shouldTrackOrSendData()){
1321
+ return;
1322
+ }
1323
+
1324
+ var users = user_defaults.item('users') || []; // no i18n
1325
+
1326
+ var index = users.findIndex(function(item) {
1327
+ return item.userid === user_info.userid;
1328
+ });
1329
+
1330
+ // If the object with the matching id is found, replace it with the new object
1331
+ if (index !== -1) {
1332
+ users[index]=user_info;
1333
+ }
1334
+ else{
1335
+ users = [user_info];
1336
+ }
1337
+
1338
+ user_defaults.add('users', users); // no i18n
1339
+
1340
+ }
1341
+
1342
+ function getUserInfo(id) {
1343
+
1344
+ if (!id || id.trim() === '') {
1345
+ Console.warn('getUserInfo: Invalid id'); // no i18n
1346
+ return;
1347
+ }
1348
+
1349
+ Console.log('getUserInfo: (\'' + id + '\')'); // no i18n
1350
+
1351
+ var users = user_defaults.item('users') || []; // no i18n
1352
+
1353
+ var index = users.findIndex(function(item) {
1354
+ return item.id === id;
1355
+ });
1356
+
1357
+ // If the object with the matching id is found, replace it with the new object
1358
+ if (index !== -1) {
1359
+ var user_info = users[index];
1360
+ return user_info;
1361
+ }
1362
+ else {
1363
+ Console.log('Object with the specified ID \''+id+'\' not found while updating the user info'); // no i18n
1364
+ return null;
1365
+ }
1366
+ }
1367
+
1368
+ /**
1369
+ * Register a set of super properties, which are included with all events
1370
+ * @param {object} data - JSON key/value pair
1371
+ * @returns {void}
1372
+ */
1373
+ function flush() {
1374
+
1375
+ if (!_token) {
1376
+ Console.warn('apptics.flush: You must call apptics.init(token) first'); // no i18n
1377
+ return;
1378
+ }
1379
+
1380
+ if (is_apptics_disabled()){
1381
+ return;
1382
+ }
1383
+
1384
+ if (!shouldTrackOrSendData()){
1385
+ return;
1386
+ }
1387
+ _sending = false;
1388
+ sendEngagementData();
1389
+ sendErrorData();
1390
+ }
1391
+
1392
+
1393
+ /**
1394
+ * Sends pending events to apptics API
1395
+ * @returns {void}
1396
+ */
1397
+ function sendEngagementData() {
1398
+
1399
+ if (is_apptics_disabled()){
1400
+ return;
1401
+ }
1402
+
1403
+ if (!shouldTrackOrSendData()){
1404
+ return;
1405
+ }
1406
+
1407
+ if (navigator.onLine == false){
1408
+ Console.warn('sendEngagementData: No Network'); // no i18n
1409
+ return;
1410
+ }
1411
+ // if we're busy sending
1412
+ if (_sending) {
1413
+ // clear defer timeout if it exists
1414
+ if (_deferSendTimer) {
1415
+ clearTimeout(_deferSendTimer);
1416
+ }
1417
+
1418
+ // set a new timer to recall send in .5s
1419
+ _deferSendTimer = setTimeout(sendEngagementData, 500);
1420
+
1421
+ // exit
1422
+ return;
1423
+ }
1424
+ // get all items waiting to be sent
1425
+ var engagement_items = engagement_transactions.payload().filter(function (item){
1426
+
1427
+ if (item.endtime != 0) {
1428
+ var todaytime = Math.round((new Date()).getTime());
1429
+ var batch_timeout = todaytime - (getConfig('batch_request_timeout') * 24 * 60 * 60 * 1000);
1430
+ if(item.starttime >= batch_timeout && item.starttime <= todaytime){
1431
+ // Return the item
1432
+ return item;
1433
+ }
1434
+ else{
1435
+ //Remove the item
1436
+ engagement_transactions.remove([item]);
1437
+ }
1438
+ }
1439
+ });
1440
+
1441
+ // if we have nothing to send, exit
1442
+ if (engagement_items.length == 0) {
1443
+ return;
1444
+ }
1445
+
1446
+ // otherwise, flag sending as started
1447
+ _sending = true;
1448
+
1449
+ // clear defer timeout if it exists
1450
+ if (_deferSendTimer) {
1451
+ clearTimeout(_deferSendTimer);
1452
+ _deferSendTimer = null;
1453
+ }
1454
+
1455
+ // convert each pending transaction into a request promise
1456
+ let sessionObj = [], screenObj = [], eventObj = [];
1457
+ var _payload = {},
1458
+ removepayloadids = [],
1459
+ removepayload = cloneObject(engagement_items).filter(function (item) {
1460
+
1461
+ const itemWithId = { ...item };
1462
+
1463
+ if(item.type == 'event'){ // no i18n
1464
+ eventObj.push(item);
1465
+ removepayloadids.push(itemWithId);
1466
+ }
1467
+ else if(item.type == 'screen'){ // no i18n
1468
+ screenObj.push(item);
1469
+ removepayloadids.push(itemWithId);
1470
+ }
1471
+ else if(item.type == 'session'){
1472
+ sessionObj.push(item);
1473
+ removepayloadids.push(itemWithId);
1474
+ }
1475
+
1476
+ delete item.id;
1477
+ delete item.type;
1478
+
1479
+ return item;
1480
+ });
1481
+
1482
+ eventObj.length > 0 ? _payload.events = eventObj : [];
1483
+ screenObj.length > 0 ? _payload.screens = screenObj : [];
1484
+ sessionObj.length > 0 ? _payload.sessions = sessionObj : [];
1485
+
1486
+ if (isEmptyObj(_payload)){
1487
+ return;
1488
+ }
1489
+
1490
+ requestToSendEngagements(_payload).then(function (result){
1491
+ if (result && result.result == 'success'){ // no i18n
1492
+ engagement_transactions.remove(removepayloadids);
1493
+ _sending = false;
1494
+ }
1495
+ }).catch(function (err){
1496
+ Console.log('sendEngagementData:' + err);
1497
+ // something went wrong, allow this method to be recalled
1498
+ _sending = false;
1499
+ });
1500
+ }
1501
+
1502
+ /**
1503
+ * Sends pending error to apptics API
1504
+ * @returns {void}
1505
+ */
1506
+ function sendErrorData() {
1507
+
1508
+ if (is_apptics_disabled()){
1509
+ return;
1510
+ }
1511
+
1512
+ if (!shouldTrackOrSendData()){
1513
+ return;
1514
+ }
1515
+
1516
+ if (navigator.onLine == false){
1517
+ Console.warn('sendErrorData: No Network'); // no i18n
1518
+ return;
1519
+ }
1520
+ // if we're busy sending
1521
+ if (_sending) {
1522
+ // clear defer timeout if it exists
1523
+ if (_deferSendTimer) {
1524
+ clearTimeout(_deferSendTimer);
1525
+ }
1526
+
1527
+ // set a new timer to recall send in .5s
1528
+ _deferSendTimer = setTimeout(sendErrorData, 500);
1529
+
1530
+ // exit
1531
+ return;
1532
+ }
1533
+ // get all items waiting to be sent
1534
+ var error_items = error_transactions.payload().filter(function (item){
1535
+ if (item.happenedat != 0) {
1536
+ var todaytime = Math.round((new Date()).getTime());
1537
+ var batch_timeout = todaytime - (getConfig('batch_request_timeout') * 24 * 60 * 60 * 1000);
1538
+ if(item.happenedat >= batch_timeout && item.happenedat <= todaytime){
1539
+ // Return the item
1540
+ return item;
1541
+ }
1542
+ else{
1543
+ // Remove the item
1544
+ error_transactions.remove([item]);
1545
+ }
1546
+ }
1547
+ });
1548
+
1549
+ // if we have nothing to send, exit
1550
+ if (error_items.length == 0) {
1551
+ return;
1552
+ }
1553
+
1554
+ // otherwise, flag sending as started
1555
+ _sending = true;
1556
+
1557
+ // clear defer timeout if it exists
1558
+ if (_deferSendTimer) {
1559
+ clearTimeout(_deferSendTimer);
1560
+ _deferSendTimer = null;
1561
+ }
1562
+
1563
+ // convert each pending transaction into a request promise
1564
+ let exceptiondetails = [];
1565
+ var _payload = {},
1566
+ removepayloadids = [],
1567
+ errorpayload = cloneObject(error_items).filter(function (item) {
1568
+
1569
+ const itemWithId = { ...item };
1570
+
1571
+ if(!item.hasOwnProperty('screenname')){
1572
+ item.screenname = '';
1573
+ }
1574
+ exceptiondetails.push(item);
1575
+ removepayloadids.push(itemWithId);
1576
+ delete item.id;
1577
+ delete item.type;
1578
+ return item;
1579
+ });
1580
+
1581
+ exceptiondetails.length > 0 ? _payload.exceptiondetails = exceptiondetails : [];
1582
+
1583
+ if (isEmptyObj(_payload)){
1584
+ return;
1585
+ }
1586
+ requestToSendErrors(_payload).then(function (result){
1587
+ if (result && result.result == 'success'){ // no i18n
1588
+ error_transactions.remove(removepayloadids);
1589
+ _sending = false;
1590
+ }
1591
+ }).catch(function (err){
1592
+ Console.log('sendErrorData:' + err);
1593
+ // something went wrong, allow this method to be recalled
1594
+ _sending = false;
1595
+ });
1596
+ }
1597
+
1598
+
1599
+ /**
1600
+ * Sends feedback to apptics API
1601
+ * @returns {void}
1602
+ */
1603
+ function sendFeedbackData(obj) {
1604
+ if (is_apptics_disabled()){
1605
+ return;
1606
+ }
1607
+
1608
+ // if (!shouldTrackOrSendData()){
1609
+ // return;
1610
+ // }
1611
+
1612
+ if (navigator.onLine == false){
1613
+ Console.warn('sendFeedbackData: No Network'); // no i18n
1614
+ return;
1615
+ }
1616
+ // if we're busy sending
1617
+ // if (_sending) {
1618
+ // // clear defer timeout if it exists
1619
+ // if (_deferSendTimer) {
1620
+ // clearTimeout(_deferSendTimer);
1621
+ // }
1622
+
1623
+ // // set a new timer to recall send in .5s
1624
+ // _deferSendTimer = setTimeout(sendFeedbackData, 500);
1625
+
1626
+ // // exit
1627
+ // return;
1628
+ // }
1629
+
1630
+ // // otherwise, flag sending as started
1631
+ // _sending = true;
1632
+
1633
+ // // clear defer timeout if it exists
1634
+ // if (_deferSendTimer) {
1635
+ // clearTimeout(_deferSendTimer);
1636
+ // _deferSendTimer = null;
1637
+ // }
1638
+
1639
+ // convert each pending transaction into a request promise
1640
+ var _payload = {};
1641
+ var _attachment = obj?.attachment || null;
1642
+ var _attachment_count = obj?.attachment?.length || 0;
1643
+ var time = Math.round((new Date()).getTime());
1644
+ var sendformdata = new FormData();
1645
+ var feedinfoObj = {
1646
+ networkstatus : getNetworkStatus(),
1647
+ orientation : getOrientation(),
1648
+ //battery : getBattery(),
1649
+ edge: getConnectionType(),
1650
+ ram : '',
1651
+ screenname : _current_screen.screenName || '',
1652
+ sessionstarttime : _current_session.starttime || time,
1653
+ report : obj.message,
1654
+ type : obj.type,
1655
+ source : 0,
1656
+ attachmentcount : _attachment_count || 0,
1657
+ logfilecount : 0,
1658
+ tag : '',
1659
+ happendat : time,
1660
+ quartzid : obj.quartzid
1661
+ };
1662
+ var feedinfoSendObj = {feedinfo: feedinfoObj, meta : getMetaObj()}
1663
+ sendformdata.append('feedinfo', JSON.stringify(feedinfoSendObj));
1664
+
1665
+ if(!isEmpty(_attachment) && _attachment_count > 0 && _attachment_count <= 5){
1666
+ // var modal = document.getElementById("zasdkFeedbackModal");
1667
+ // const inputElement = modal.querySelector('#zasdkFileInput');
1668
+ // const fileList = inputElement.files;
1669
+ let filecounter = 0;
1670
+ const fileList = _attachment;
1671
+
1672
+ for (let i = 0; i < fileList.length; i++) {
1673
+
1674
+ const reader = new FileReader();
1675
+ var file = fileList[i];
1676
+ reader.readAsArrayBuffer(file);
1677
+ reader.onload = function(event) {
1678
+ const fileContent = event.target.result;
1679
+ const newFileName = "image_" + (i+1);
1680
+
1681
+ const newFile = new File([fileContent], newFileName, { type: file.type });
1682
+
1683
+ sendformdata.append('attachment', newFile);
1684
+ filecounter++;
1685
+ if(filecounter === fileList.length){
1686
+ readyToSendFeedback(sendformdata)
1687
+
1688
+ }
1689
+ };
1690
+
1691
+ }
1692
+ }
1693
+ else{
1694
+ readyToSendFeedback(sendformdata)
1695
+ }
1696
+
1697
+ }
1698
+
1699
+ function readyToSendFeedback(data){
1700
+ requestToSendFeedback(data).then(function (result){
1701
+ if (result && result.result == 'success'){ // no i18n
1702
+ _sending = false;
1703
+
1704
+ feedback.handleStatusmsg(true, 'Feedback sent successfully'); // no i18n
1705
+ setTimeout(() => {
1706
+ feedback.close();
1707
+ }, 2000);
1708
+ //
1709
+ }
1710
+ else{
1711
+ feedback.handleStatusmsg(false, 'Something went wrong. Send feedback again'); // no i18n
1712
+ _sending = false;
1713
+ Console.log('sendFeedbackData:' + result);
1714
+ }
1715
+ }).catch(function (err){
1716
+ feedback.handleStatusmsg(false, 'Something went wrong. Send feedback again'); // no i18n
1717
+ _sending = false;
1718
+ Console.log('sendFeedbackData:' + err);
1719
+ });
1720
+ }
1721
+
1722
+ /* #region Helpers */
1723
+
1724
+ /**
1725
+ * Clones a JSON object
1726
+ * @param {object} obj - object to clone
1727
+ * @returns {object} cloned object
1728
+ */
1729
+ function cloneObject(obj) {
1730
+ return JSON.parse(JSON.stringify(obj));
1731
+ }
1732
+
1733
+ /**
1734
+ * Executes an array of function that return a promise in sequence
1735
+ * @param {Array} promises - array of functions that return a promise
1736
+ * @returns {Promise} executes .then if all resolve otherwise .catch
1737
+ */
1738
+ function promisesInSequence(promises) {
1739
+ var result = Promise.resolve();
1740
+
1741
+ promises.forEach(function (promise) {
1742
+ result = result.then(promise);
1743
+ });
1744
+
1745
+ return result;
1746
+ }
1747
+
1748
+ // Function to calculate the size of a JavaScript object in bytes
1749
+ function sizeof(obj) {
1750
+ let bytes = 0;
1751
+
1752
+ function sizeOf(obj) {
1753
+ if (obj !== null && typeof obj === 'object') { // no i18n
1754
+ for (const key in obj) {
1755
+ if (obj.hasOwnProperty(key)) {
1756
+ bytes += key.length * 2; // 2 bytes per character
1757
+ sizeOf(obj[key]);
1758
+ }
1759
+ }
1760
+ }
1761
+ else if (typeof obj === 'string') { // no i18n
1762
+ bytes += obj.length * 2; // 2 bytes per character
1763
+ }
1764
+ else if (typeof obj === 'number') { // no i18n
1765
+ bytes += 8; // 8 bytes for a number
1766
+ }
1767
+ else if (typeof obj === 'boolean') { // no i18n
1768
+ bytes += 4; // 4 bytes for a boolean
1769
+ }
1770
+ }
1771
+
1772
+ sizeOf(obj);
1773
+ return bytes;
1774
+ }
1775
+
1776
+ /**
1777
+ * Gets the NetworkStatus like online, offline etc
1778
+ * @returns {int} networkstatus
1779
+ */
1780
+ function getNetworkStatus(){
1781
+ return window.navigator.onLine ? 0 : -1;
1782
+ }
1783
+
1784
+ /**
1785
+ * Gets the Orientation like portrait, landscape etc
1786
+ * @returns {int} Orientation
1787
+ */
1788
+ function getOrientation() {
1789
+ var orientation = window.screen.orientation.type;
1790
+ if (includes(orientation, 'portrait-primary')) { // no i18n
1791
+ return 0;
1792
+ }
1793
+ if (includes(orientation, 'landscape-primary')) { // no i18n
1794
+ return 1;
1795
+ }
1796
+ if (includes(orientation, 'landscape-secondary')) { // no i18n
1797
+ return 2;
1798
+ }
1799
+ if (includes(orientation, 'portrait-secondary')) { // no i18n
1800
+ return 3;
1801
+ }
1802
+
1803
+ return -1;
1804
+ }
1805
+
1806
+ /**
1807
+ * Gets the device type iPad, iPhone etc
1808
+ * @returns {string} device name
1809
+ */
1810
+ function getDevice() {
1811
+
1812
+ var ua = navigator.userAgent;
1813
+
1814
+ if (/Windows/i.test(ua)) {
1815
+ return (/Phone/.test(ua) || /WPDesktop/.test(ua)) ? 'Windows Phone' : 'Windows'; // no i18n
1816
+ }
1817
+ if (/iPad/.test(ua)) {
1818
+ return 'iPad'; // no i18n
1819
+ }
1820
+ if (/iPod/.test(ua)) {
1821
+ return 'iPod Touch'; // no i18n
1822
+ }
1823
+ if (/iPhone/.test(ua)) {
1824
+ return 'iPhone'; // no i18n
1825
+ }
1826
+ if (/(BlackBerry|PlayBook|BB10)/i.test(ua)) {
1827
+ return 'BlackBerry'; // no i18n
1828
+ }
1829
+ if (/Android/.test(ua)) {
1830
+ return 'Android'; // no i18n
1831
+ }
1832
+ if (/Mac/i.test(ua)) {
1833
+ return 'Mac'; // no i18n
1834
+ }
1835
+ if (/Linux/.test(ua)) {
1836
+ return 'Linux'; // no i18n
1837
+ }
1838
+ if (/CrOS/.test(ua)) {
1839
+ return 'Chrome'; // no i18n
1840
+ }
1841
+ return '';
1842
+ }
1843
+
1844
+ /**
1845
+ * Gets the Operating System
1846
+ * @returns {string} os name
1847
+ */
1848
+ function getOS() {
1849
+
1850
+ var ua = navigator.userAgent;
1851
+
1852
+ if (/Windows/i.test(ua)) {
1853
+ return (/Phone/.test(ua) || /WPDesktop/.test(ua)) ? 'Windows Phone' : 'Windows'; // no i18n
1854
+ }
1855
+ if (/(iPhone|iPad|iPod)/.test(ua)) {
1856
+ return 'iOS'; // no i18n
1857
+ }
1858
+ if (/Android/.test(ua)) {
1859
+ return 'Android'; // no i18n
1860
+ }
1861
+ if (/(BlackBerry|PlayBook|BB10)/i.test(ua)) {
1862
+ return 'BlackBerry'; // no i18n
1863
+ }
1864
+ if (/Mac/i.test(ua)) {
1865
+ return 'Mac OS X'; // no i18n
1866
+ }
1867
+ if (/Linux/.test(ua)) {
1868
+ return 'Linux'; // no i18n
1869
+ }
1870
+ if (/CrOS/.test(ua)) {
1871
+ return 'Chrome OS'; // no i18n
1872
+ }
1873
+ return '';
1874
+ }
1875
+
1876
+ /**
1877
+ * This function detects which browser is running this script.
1878
+ * The order of the checks are important since many user agents
1879
+ * include key words used in later checks.
1880
+ * @returns {string} browser name
1881
+ */
1882
+ function getBrowser() {
1883
+
1884
+ var ua = navigator.userAgent;
1885
+ var vendor = navigator.vendor || ''; // vendor is undefined for at least IE9
1886
+ var opera = window.opera;
1887
+
1888
+ if (opera || includes(ua, ' OPR/')) { // no i18n
1889
+ return (includes(ua, 'Mini')) ? 'Opera Mini' : 'Opera'; // no i18n
1890
+ }
1891
+ if (/(BlackBerry|PlayBook|BB10)/i.test(ua)) {
1892
+ return 'BlackBerry'; // no i18n
1893
+ }
1894
+ if (includes(ua, 'IEMobile') || includes(ua, 'WPDesktop')) { // no i18n
1895
+ return 'Internet Explorer Mobile'; // no i18n
1896
+ }
1897
+ if (includes(ua, 'SamsungBrowser/')) { // no i18n
1898
+ return 'Samsung Internet'; // no i18n
1899
+ }
1900
+ if (includes(ua, 'Edge') || includes(ua, 'Edg/')) { // no i18n
1901
+ return 'Microsoft Edge'; // no i18n
1902
+ }
1903
+ if (includes(ua, 'FBIOS')) { // no i18n
1904
+ return 'Facebook Mobile'; // no i18n
1905
+ }
1906
+ if (includes(ua, 'Chrome')) { // no i18n
1907
+ if(typeof ulaa == 'object'){
1908
+ return 'Ulaa'; // no i18n
1909
+ }
1910
+ else if(window.navigator.brave != undefined && window.navigator.brave.isBrave.name == "isBrave"){
1911
+ return 'Brave'; // no i18n
1912
+ }
1913
+ else{
1914
+ return 'Chrome'; // no i18n
1915
+ }
1916
+ }
1917
+ if (includes(ua, 'CriOS')) { // no i18n
1918
+ return 'Chrome iOS'; // no i18n
1919
+ }
1920
+ if (includes(ua, 'UCWEB') || includes(ua, 'UCBrowser')) { // no i18n
1921
+ return 'UC Browser'; // no i18n
1922
+ }
1923
+ if (includes(ua, 'FxiOS')) { // no i18n
1924
+ return 'Firefox iOS'; // no i18n
1925
+ }
1926
+ if (includes(vendor, 'Apple')) { // no i18n
1927
+ return (includes(ua, 'Mobile')) ? 'Mobile Safari' : 'Safari'; // no i18n
1928
+ }
1929
+ if (includes(ua, 'Android')) { // no i18n
1930
+ return 'Android Mobile'; // no i18n
1931
+ }
1932
+ if (includes(ua, 'Konqueror')) { // no i18n
1933
+ return 'Konqueror'; // no i18n
1934
+ }
1935
+ if (includes(ua, 'Firefox')) { // no i18n
1936
+ return 'Firefox'; // no i18n
1937
+ }
1938
+ if (includes(ua, 'MSIE') || includes(ua, 'Trident/')) { // no i18n
1939
+ return 'Internet Explorer'; // no i18n
1940
+ }
1941
+ if (includes(ua, 'Gecko')) { // no i18n
1942
+ return 'Mozilla'; // no i18n
1943
+ }
1944
+ return '';
1945
+ }
1946
+
1947
+ /**
1948
+ * This function detects which browser version is running this script,
1949
+ * parsing major and minor version (e.g., 42.1). User agent strings from:
1950
+ * http://www.useragentstring.com/pages/useragentstring.php
1951
+ * @returns {number} browser version
1952
+ */
1953
+ function getBrowserVersion() {
1954
+
1955
+ var browser = getBrowser();
1956
+ var ua = navigator.userAgent;
1957
+
1958
+ var versionRegexs = {
1959
+ 'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/, // no i18n
1960
+ 'Microsoft Edge': /Edg\/(\d+(\.\d+)?)/, // no i18n
1961
+ 'Chrome': /Chrome\/(\d+(\.\d+)?)/,
1962
+ 'Brave': /Chrome\/(\d+(\.\d+)?)/,
1963
+ 'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/, // no i18n
1964
+ 'UC Browser': /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/, // no i18n
1965
+ 'Safari': /Version\/(\d+(\.\d+)?)/,
1966
+ 'Mobile Safari': /Version\/(\d+(\.\d+)?)/, // no i18n
1967
+ 'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/,
1968
+ 'Firefox': /Firefox\/(\d+(\.\d+)?)/,
1969
+ 'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/, // no i18n
1970
+ 'Konqueror': /Konqueror:(\d+(\.\d+)?)/,
1971
+ 'BlackBerry': /BlackBerry (\d+(\.\d+)?)/,
1972
+ 'Android Mobile': /android\s(\d+(\.\d+)?)/, // no i18n
1973
+ 'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/, // no i18n
1974
+ 'Mozilla': /rv:(\d+(\.\d+)?)/
1975
+ };
1976
+ // Check for Ulaa browser version
1977
+ if (browser === 'Ulaa' && typeof ulaa === 'object') {
1978
+ return ulaa.getVersion();
1979
+ }
1980
+
1981
+ var regex = versionRegexs[browser];
1982
+ if (regex === undefined) {
1983
+ return '';
1984
+ }
1985
+ var matches = ua.match(regex);
1986
+ if (!matches) {
1987
+ return '';
1988
+ }
1989
+ return parseFloat(matches[matches.length - 2]);
1990
+ }
1991
+
1992
+ /**
1993
+ * Gets the referring domain
1994
+ * @returns {string} domain or empty string
1995
+ */
1996
+ function getReferringURL() {
1997
+ var split = String(window.location.href || '').split(window.location.pathname);
1998
+ if(split.length >= 2){
1999
+ return split[1].length > 500 ? split[1].substring(0,500) : split[1];
2000
+ }
2001
+ return '';
2002
+ }
2003
+
2004
+ /**
2005
+ * Checks if a value exists within a string
2006
+ * @param {string} str - value to search
2007
+ * @param {string} needle - value to find
2008
+ * @returns {boolean} true if found, otherwise false
2009
+ */
2010
+ function includes(str, needle) {
2011
+ return str.indexOf(needle) !== -1;
2012
+ }
2013
+
2014
+ /**
2015
+ * @param {*=} obj
2016
+ * @param {function(...*)=} iterator
2017
+ * @param {Object=} context
2018
+ */
2019
+ function each(obj, iterator, context) {
2020
+ if (obj === null || obj === undefined) {
2021
+ return;
2022
+ }
2023
+ if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
2024
+ obj.forEach(iterator, context);
2025
+ }
2026
+ else if (obj.length === +obj.length) {
2027
+ for (var i = 0, l = obj.length; i < l; i++) {
2028
+ if (i in obj && iterator.call(context, obj[i], i, obj) === _breaker) {
2029
+ return;
2030
+ }
2031
+ }
2032
+ }
2033
+ else {
2034
+ for (var key in obj) {
2035
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
2036
+ if (iterator.call(context, obj[key], key, obj) === _breaker) {
2037
+ return;
2038
+ }
2039
+ }
2040
+ }
2041
+ }
2042
+ };
2043
+
2044
+
2045
+ /**
2046
+ * Executes a HTTP GET request within a promise
2047
+ * @param {string} url - url to get
2048
+ * @returns {Promise} executes .then if successful otherwise .catch
2049
+ */
2050
+ function httpGet(url) {
2051
+
2052
+ return new Promise(function (resolve, reject) {
2053
+
2054
+ var xhr = new XMLHttpRequest();
2055
+
2056
+ xhr.open('GET', url); // no i18n
2057
+ xhr.withCredentials = false;
2058
+ xhr.onreadystatechange = function () {
2059
+ if (xhr.readyState === 4) {
2060
+ if (xhr.status === 200) {
2061
+ resolve({
2062
+ url: url,
2063
+ status: 200,
2064
+ body: xhr.responseText || ''
2065
+ });
2066
+ }
2067
+ else {
2068
+ reject({
2069
+ url: url,
2070
+ status: xhr.status,
2071
+ body: xhr.responseText || ''
2072
+ });
2073
+ }
2074
+ }
2075
+ };
2076
+
2077
+ xhr.send();
2078
+ });
2079
+ }
2080
+
2081
+ /**
2082
+ * Generates a new distinct_id (code lifted from apptics js)
2083
+ * @returns {string} new UUID (example 16ee796360f641-0900a3aecd5282-3963720f-13c680-16ee7963610a3a)
2084
+ */
2085
+ function getNewUUID() {
2086
+
2087
+ // Time/ticks information
2088
+ // 1*new Date() is a cross browser version of Date.now()
2089
+ var T = function () {
2090
+ var d = 1 * new Date(),
2091
+ i = 0;
2092
+
2093
+ // this while loop figures how many browser ticks go by
2094
+ // before 1*new Date() returns a new number, ie the amount
2095
+ // of ticks that go by per millisecond
2096
+ while (d == 1 * new Date()) {
2097
+ i++;
2098
+ }
2099
+
2100
+ return d.toString(16) + i.toString(16);
2101
+ };
2102
+
2103
+ // Math.Random entropy
2104
+ var R = function () {
2105
+ return Math.random().toString(16).replace('.', '');
2106
+ };
2107
+
2108
+ // User agent entropy
2109
+ // This function takes the user agent string, and then xors
2110
+ // together each sequence of 8 bytes. This produces a final
2111
+ // sequence of 8 bytes which it returns as hex.
2112
+ var UA = function () {
2113
+ var ua = navigator.userAgent,
2114
+ i, ch, buffer = [],
2115
+ ret = 0;
2116
+
2117
+ function xor(result, byte_array) {
2118
+ var j, tmp = 0;
2119
+ for (j = 0; j < byte_array.length; j++) {
2120
+ tmp |= (buffer[j] << j * 8);
2121
+ }
2122
+ return result ^ tmp;
2123
+ }
2124
+
2125
+ for (i = 0; i < ua.length; i++) {
2126
+ ch = ua.charCodeAt(i);
2127
+ buffer.unshift(ch & 0xFF);
2128
+ if (buffer.length >= 4) {
2129
+ ret = xor(ret, buffer);
2130
+ buffer = [];
2131
+ }
2132
+ }
2133
+
2134
+ if (buffer.length > 0) {
2135
+ ret = xor(ret, buffer);
2136
+ }
2137
+
2138
+ return ret.toString(16);
2139
+ };
2140
+
2141
+ var se = (screen.height * screen.width).toString(16);
2142
+ return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
2143
+ }
2144
+
2145
+ /**
2146
+ * Base 64 encodes a string using btoa
2147
+ * @param {string} str - string to encode
2148
+ * @returns {string} containing padded base64 value
2149
+ */
2150
+ function base64Encode(str) {
2151
+ return window.btoa(unescape(encodeURIComponent(str)));
2152
+ }
2153
+
2154
+ function getTimeZone(){
2155
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
2156
+ }
2157
+
2158
+ function getKeyFromDeviceInfo(key){
2159
+ var returnVal = null; // no i18n
2160
+ var current_device_info = user_defaults.item('device_info'); // no i18n
2161
+ if(!isEmpty(current_device_info) && !isEmpty(current_device_info[key])){
2162
+ returnVal = current_device_info[key];
2163
+ }
2164
+ return returnVal;
2165
+ }
2166
+
2167
+ function calculateLocalStorageSize() {
2168
+ var totalSize = 0;
2169
+
2170
+ for (var i = 0; i < localStorage.length; i++) {
2171
+ var key = localStorage.key(i);
2172
+ var value = localStorage.getItem(key);
2173
+ ;
2174
+ totalSize += sizeof(key) + sizeof(value);
2175
+ }
2176
+
2177
+ // Convert to KB or MB for better readability
2178
+ var totalSizeKB = totalSize / 1024;
2179
+ var totalSizeMB = totalSizeKB / 1024;
2180
+
2181
+ return {
2182
+ totalSize: totalSize, // Total size in bytes
2183
+ totalSizeKB: totalSizeKB.toFixed(2) + ' KB', // no i18n
2184
+ totalSizeMB: totalSizeMB.toFixed(2) + ' MB' // no i18n
2185
+ };
2186
+ }
2187
+
2188
+ /* #endregion */
2189
+
2190
+ // no operation interface, exposes method that do nothing
2191
+ var mutedInterface = {
2192
+ trackEvent: function (eventName, groupName, data) {
2193
+ Console.log('apptics.trackEvent(\'' + eventName + '\',\'' + groupName || _config.default_group_name + '\',' + JSON.stringify(data || {}) + ')'); // no i18n
2194
+ },
2195
+ trackScreen: function (screenName, data) {
2196
+ Console.log('apptics.trackScreen(\'' + screenName + '\',' + JSON.stringify(data || {}) + ')'); // no i18n
2197
+ },
2198
+ enableGlobalErrorHandler: function(status){
2199
+ Console.log('apptics.enableGlobalErrorHandler(\'' + status + ')'); // no i18n
2200
+ },
2201
+ trackError: function (error, data) {
2202
+ Console.log('apptics.trackError(\'' + error + '\',' + JSON.stringify(data || {}) + ')'); // no i18n
2203
+ },
2204
+ feedbackOpen: function () {
2205
+ Console.log('apptics.feedbackOpen()'); // no i18n
2206
+ },
2207
+ feedbackClose: function () {
2208
+ Console.log('apptics.feedbackClose()'); // no i18n
2209
+ },
2210
+ sendFeedback: function(){
2211
+ Console.log('apptics.sendFeedback()'); // no i18n
2212
+ },
2213
+ setUser: function (userid, groupid, data) {
2214
+ Console.log('apptics.setUser(\'' + userid + '\',\'' + groupid + '\',' + JSON.stringify(data || {}) + ')'); // no i18n
2215
+ },
2216
+ getTrackingStatus: function () {
2217
+ Console.log('apptics.getTrackingStatus()'); // no i18n
2218
+ },
2219
+ setTrackingStatus: function (trackingid) {
2220
+ Console.log('apptics.setTrackingStatus(\'' + trackingid + '\')'); // no i18n
2221
+ },
2222
+ flush: function() {
2223
+ Console.log('apptics.flush()'); // no i18n
2224
+ },
2225
+ reset: function () {
2226
+ Console.log('apptics.reset()'); // no i18n
2227
+ },
2228
+ mute: mute,
2229
+ unmute: unmute,
2230
+ muted: true,
2231
+ AP_TRACKING_STATUS,
2232
+ AP_FEEDBACK_TYPE
2233
+ };
2234
+
2235
+ // operational interface, exposes methods that talk to apptics
2236
+ var unmutedInterface = {
2237
+ trackEvent: trackEvent,
2238
+ trackScreen: trackScreen,
2239
+ enableGlobalErrorHandler: enableGlobalErrorHandler,
2240
+ trackError: trackError,
2241
+ feedbackOpen: feedbackOpen,
2242
+ feedbackClose: feedbackClose,
2243
+ sendFeedback: sendFeedback,
2244
+ setUser: setUser,
2245
+ setTrackingStatus: setTrackingStatus,
2246
+ getTrackingStatus: getTrackingStatus,
2247
+ flush: flush,
2248
+ reset: reset,
2249
+ clear: clear,
2250
+ AP_TRACKING_STATUS,
2251
+ AP_FEEDBACK_TYPE
2252
+ };
2253
+ /**
2254
+ * Mutes apptics by overriding public methods with empty functions
2255
+ * @returns {void}
2256
+ */
2257
+ function mute() {
2258
+ _config.verbose = false;
2259
+ apptics.muted = true;
2260
+ }
2261
+
2262
+ /**
2263
+ * Restores apptics function after a call to apptics.mute allowing data to be sent to apptics
2264
+ * @returns {void}
2265
+ */
2266
+ function unmute() {
2267
+ _config.verbose = true;
2268
+ apptics.muted = false;
2269
+ }
2270
+ // expose apptics methods by default
2271
+ window.apptics = unmutedInterface;
2272
+
2273
+
2274
+ _defaultoptions = window.appticssettings._defaultoptions;
2275
+ _token = window.appticssettings._defaultoptions.aaID;
2276
+ if(!isEmpty(_token) && !isEmpty(_defaultoptions)){
2277
+ init(_token, _defaultoptions);
2278
+ }
2279
+
2280
+
2281
+ /**
2282
+ * Sets up in memory properties to be sent with each request
2283
+ * @param {string} token - apptics token
2284
+ * @param {object} opts - options { debug: true }
2285
+ * @returns {void}
2286
+ */
2287
+ function init(token, opts) {
2288
+ opts = opts || {};
2289
+
2290
+ _config = AP_CONFIG;
2291
+
2292
+ each(opts, function( value, key ) {
2293
+ if (_config.hasOwnProperty(key)){
2294
+ if (!AP_RESERVED_CONFIG.includes(key)){
2295
+ _config[key]=value;
2296
+ }
2297
+ else{
2298
+ Console.warn('apptics.init: Reserved key \''+key+'\' cannot be overridden'); // no i18n
2299
+ }
2300
+ }
2301
+ else{
2302
+ Console.warn('apptics.init: unsupported key \''+key+'\''); // no i18n
2303
+ }
2304
+ });
2305
+
2306
+ if (token && token !== '') {
2307
+ _token = token;
2308
+ _frameworkID = _defaultoptions.base_domain.includes('local') ? _fwid_local : _frameworkID;
2309
+ //check if localstorage _token is empty or fw_id is empty
2310
+ var stored_token = user_defaults.item('token'); // no i18n
2311
+ var stored_fwid = user_defaults.item('fw_id'); // no i18n
2312
+ if (isEmpty(stored_token) || isEmpty(stored_fwid)){
2313
+ user_defaults.add('token', _token); // no i18n
2314
+ user_defaults.add('fw_id', _frameworkID); // no i18n
2315
+ stored_token = user_defaults.item('token'); // no i18n
2316
+ stored_fwid = user_defaults.item('fw_id'); // no i18n
2317
+ }
2318
+ if(stored_token != _token || stored_fwid != _frameworkID){
2319
+ reset();
2320
+ return;
2321
+ }
2322
+
2323
+ }
2324
+
2325
+ if (opts.token && opts.token !== '') {
2326
+ _zak = opts.token;
2327
+ }
2328
+
2329
+ if (opts.zsoid && opts.zsoid !== '') {
2330
+ _zsoid = opts.zsoid;
2331
+ }
2332
+
2333
+ if (opts.projectID && opts.projectID !== '') {
2334
+ _projectID = opts.projectID;
2335
+ }
2336
+
2337
+ if (opts.identifier && opts.identifier !== '') {
2338
+ _identifier = opts.identifier;
2339
+ }
2340
+
2341
+
2342
+ if (opts.base_domain && opts.base_domain !== '') {
2343
+ _base_domain = opts.base_domain;
2344
+ }
2345
+
2346
+ if (_token === '') {
2347
+ Console.warn('apptics.init: invalid token'); // no i18n
2348
+ return;
2349
+ }
2350
+
2351
+ _isanonrequired = true;
2352
+ _ignoreDnt = _config.ignore_do_not_track;
2353
+
2354
+ _devicestatus = _config.default_optin_status;
2355
+ tracking_preference.update_status(_devicestatus);
2356
+
2357
+ //check if uuid is empty
2358
+ var uuid = user_defaults.item('uuid'); // no i18n
2359
+ if (uuid == null || uuid == ''){
2360
+ uuid = getNewUUID();
2361
+ user_defaults.add('uuid',uuid); // no i18n
2362
+ }
2363
+
2364
+ _uuid = uuid;
2365
+
2366
+ checkAndUpdateDeviceInfo();
2367
+
2368
+ // attempt to resolve connection type
2369
+ _device_info.connectionType = getConnectionType();
2370
+
2371
+ // listen for connection change events (only available in w3c implementation)
2372
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2373
+
2374
+ if (connection && connection.addEventListener) {
2375
+ // update connection when it changes
2376
+ connection.addEventListener('change', function () { // no i18n
2377
+ _device_info.connectionType = getConnectionType();
2378
+ });
2379
+ }
2380
+ _properties.orientation = getOrientation();
2381
+
2382
+ checkAndUpdateSession(); // need to check
2383
+
2384
+
2385
+
2386
+ window.appticsReadyStatus = true;
2387
+ if(typeof window.appticsReady == 'function'){
2388
+ window.appticsReady();
2389
+ }
2390
+
2391
+ scheduleToSendDataToServer();
2392
+
2393
+ }
2394
+
2395
+ function opt_in_tracking_without_pii(){
2396
+ let should_update = false;
2397
+ if (tracking_preference.opt_in_status() != AP_TRACKING_STATUS.USAGE_TRACKING_WITHOUT_PII){
2398
+ should_update = true;
2399
+ }
2400
+ tracking_preference.opt_in_tracking_without_pii();
2401
+ if (should_update){
2402
+ // update device info
2403
+ _devicestatus = tracking_preference.opt_in_status();
2404
+ updateDeviceWithApptics(_device_info);
2405
+ }
2406
+ }
2407
+
2408
+ function opt_in_tracking_with_pii(){
2409
+ let should_update = false;
2410
+ if (tracking_preference.opt_in_status() != AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII){
2411
+ should_update = true;
2412
+ }
2413
+ tracking_preference.opt_in_tracking_with_pii();
2414
+ if (should_update){
2415
+ // update device info
2416
+ _devicestatus = tracking_preference.opt_in_status();
2417
+ updateDeviceWithApptics(_device_info);
2418
+ }
2419
+ }
2420
+
2421
+ function opt_out_tracking(){
2422
+ let should_update = false;
2423
+ if (tracking_preference.opt_in_status() != AP_TRACKING_STATUS.NO_TRACKING){
2424
+ should_update = true;
2425
+ }
2426
+ tracking_preference.opt_out_tracking();
2427
+ if (should_update){
2428
+ // update device info
2429
+ _devicestatus = tracking_preference.opt_in_status();
2430
+ updateDeviceWithApptics(_device_info);
2431
+ }
2432
+ }
2433
+
2434
+ function opt_in_status(){
2435
+ if (hasDoNotTrackEnabled()){
2436
+ Console.warn('opt_in_status: It seems "Do Not Track" is enabled in this browser. To ignore the "Do Not Track" browser setting, init Apptics with the config "ignore_do_not_track: true"'); // no i18n
2437
+ return AP_TRACKING_STATUS.NO_TRACKING;
2438
+ }
2439
+ var opt_in_status = tracking_preference.opt_in_status();
2440
+ if (opt_in_status == -1){
2441
+ Console.warn('opt_in_status: You are opted out of Apptics tracking. This will prevent the our SDK from sending no data to our server.'); // no i18n
2442
+ }
2443
+ return opt_in_status;
2444
+ }
2445
+
2446
+ function shouldTrackOrSendData(){
2447
+ var optInStatus = opt_in_status();
2448
+ return (optInStatus > -1);
2449
+ }
2450
+
2451
+ function clear_opt_in_status(){
2452
+ tracking_preference.clear();
2453
+ }
2454
+
2455
+ function setTrackingStatus(trackingState){
2456
+ if(isEmpty(trackingState)){
2457
+ Console.log('setTrackingStatus: trackingStatus is Empty'); // no i18n
2458
+ return;
2459
+ }
2460
+
2461
+ let should_update = false;
2462
+ if (tracking_preference.opt_in_status() != trackingState){
2463
+ should_update = true;
2464
+ }
2465
+ if (should_update){
2466
+ tracking_preference.update_status(trackingState);
2467
+ _devicestatus = tracking_preference.opt_in_status();
2468
+ _isanonrequired = tracking_preference.opt_in_status() == AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII ? false : _isanonrequired;
2469
+ if(tracking_preference.opt_in_status() != AP_TRACKING_STATUS.NO_TRACKING){
2470
+ updateDeviceWithApptics(_device_info);
2471
+ }
2472
+ }
2473
+ }
2474
+
2475
+ function getTrackingStatus(){
2476
+ return opt_in_status();
2477
+ }
2478
+
2479
+ function scheduleToSendDataToServer(){
2480
+ // Set up periodic sending of events
2481
+ sendingInterval = setInterval((()=>{
2482
+ Console.log('scheduleToSendDataToServer: Scheduling to send engagement data to the server after sometime'); // no i18n
2483
+ sendEngagementData();
2484
+ sendErrorData();
2485
+ }), getConfig('batch_flush_interval')); // no i18n
2486
+ // Send events every 60 seconds (adjust as needed)
2487
+ }
2488
+
2489
+ // Function to stop periodic sending (if needed)
2490
+ function checkAndScheduleToSendData() {
2491
+ if (!_token) {
2492
+ Console.warn('checkAndScheduleToSendData: You must call apptics.init(token) first'); // no i18n
2493
+ return;
2494
+ }
2495
+ if (sendingInterval == null){
2496
+ //clearInterval(sendingInterval);
2497
+ scheduleToSendDataToServer();
2498
+ }
2499
+ }
2500
+
2501
+ function isEmpty( val ){
2502
+ // Check if val is empty
2503
+ if( val === undefined || val === null || val === "" )
2504
+ {
2505
+ return true;
2506
+ }
2507
+ else
2508
+ {
2509
+ return false;
2510
+ }
2511
+ }
2512
+
2513
+ function isEmptyObj(obj){
2514
+ return (
2515
+ obj &&
2516
+ Object.keys(obj).length === 0 &&
2517
+ obj.constructor === Object
2518
+ );
2519
+ }
2520
+
2521
+ function compareTwoObjects(_newmap, _oldmap) {
2522
+ // Check if the number of keys is the same.
2523
+ if (Object.keys(_newmap).length !== Object.keys(_oldmap).length) {
2524
+ return false;
2525
+ }
2526
+
2527
+ // Check if keys are the same
2528
+ for (let key in _newmap) {
2529
+ if (!_oldmap.hasOwnProperty(key)) {
2530
+ return false;
2531
+ }
2532
+ }
2533
+
2534
+ // Check if objects associated with each key are the same
2535
+ for (let key in _newmap) {
2536
+ if (!objectsAreEqual(_newmap[key], _oldmap[key])) {
2537
+ return false;
2538
+ }
2539
+ }
2540
+
2541
+ // Hash maps have the same keys and objects
2542
+ return true;
2543
+ }
2544
+
2545
+ function objectsAreEqual(obj1, obj2) {
2546
+ // Compare the objects' properties
2547
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
2548
+ }
2549
+
2550
+ function checkAndUpdateSession(){
2551
+
2552
+ if (!_token) {
2553
+ Console.warn('checkAndUpdateSession: You must call apptics.init(token) first'); // no i18n
2554
+ return;
2555
+ }
2556
+
2557
+ if (is_apptics_disabled()){
2558
+ return;
2559
+ }
2560
+
2561
+ if (!shouldTrackOrSendData()){
2562
+ return;
2563
+ }
2564
+
2565
+ var time = Math.round((new Date()).getTime());
2566
+ var _currentSession = currentSession();
2567
+ if (_currentSession){
2568
+ // Check if the user is inactive for the past 30 minutes
2569
+ _current_session = _currentSession;
2570
+ if ((time - _currentSession.lastactivetime) > (inactiveduration * 1000 * 60)){
2571
+ Console.log('checkAndUpdateSession: User is inactive for the past '+inactiveduration+' minutes so starting a new session'); // no i18n
2572
+ var _oldSession = _currentSession;
2573
+ var _newSession = Object.assign({}, _oldSession)
2574
+ _newSession.endtime=time;
2575
+ var storage = calculateLocalStorageSize();
2576
+ //_newSession.endlocalstoragesize=storage.totalSizeKB; // need to check
2577
+
2578
+ if (_newSession) {
2579
+ Console.log('checkAndUpdateSession: (' + JSON.stringify(_newSession || {}) + ')'); // no i18n
2580
+ }
2581
+ Console.dir(_newSession);
2582
+
2583
+ engagement_transactions.update(_oldSession,_newSession);
2584
+ startNewSession(time);
2585
+
2586
+ }
2587
+ else{
2588
+ Console.log('checkAndUpdateSession: User is back before '+inactiveduration+' minutes so updating the lastactivitytime - ' + time); // no i18n
2589
+ var _oldSession = _currentSession;
2590
+ var _newSession = Object.assign({}, _oldSession)
2591
+ _newSession.lastactivetime=time;
2592
+ _current_session=_newSession;
2593
+ engagement_transactions.update(_oldSession,_newSession);
2594
+
2595
+ }
2596
+ }
2597
+ else{
2598
+ startNewSession(time);
2599
+ }
2600
+ }
2601
+
2602
+ function updateLastActiveTimeOfSession(){
2603
+ clearTimeout(timeoutId);
2604
+ var time = Math.round((new Date()).getTime());
2605
+ var _currentSession = currentSession();
2606
+ if (_currentSession){
2607
+ Console.log('updateLastActiveTimeOfSession: Updating the lastactivitytime - ' + time); // no i18n
2608
+ var _oldSession = _currentSession;
2609
+ var _newSession = Object.assign({}, _oldSession)
2610
+ _newSession.lastactivetime=time;
2611
+ _current_session=_newSession;
2612
+ engagement_transactions.update(_oldSession,_newSession);
2613
+ }
2614
+ }
2615
+
2616
+ // Schedule the method to execute after 3 seconds
2617
+ function scheduleToCheckAndUpdateSession() {
2618
+
2619
+ if (!_token) {
2620
+ Console.warn('scheduleToCheckAndUpdateSession: You must call apptics.init(token) first'); // no i18n
2621
+ return;
2622
+ }
2623
+
2624
+ if (is_apptics_disabled()){
2625
+ return;
2626
+ }
2627
+
2628
+ if (!shouldTrackOrSendData()){
2629
+ return;
2630
+ }
2631
+
2632
+ // Clear any previously scheduled executions
2633
+ clearTimeout(timeoutId);
2634
+
2635
+ // Schedule the method to execute after 3 seconds
2636
+ timeoutId = setTimeout(checkAndUpdateSession, 10);
2637
+ }
2638
+
2639
+ function cloneMapExcludingKeys(originalObj, excludeKeys) {
2640
+ const clonedObj = {};
2641
+ for (let key in originalObj) {
2642
+ if (!excludeKeys.includes(key)) {
2643
+ clonedObj[key] = originalObj[key];
2644
+ }
2645
+ }
2646
+ return clonedObj;
2647
+ }
2648
+
2649
+ function getMetaObj(){
2650
+ var returnObj = {
2651
+ frameworkid: getKeyFromDeviceInfo('frameworkid'), // no i18n
2652
+ aaid: getKeyFromDeviceInfo('aaid'), // no i18n
2653
+ apid: getKeyFromDeviceInfo('apid'), // no i18n
2654
+ appversionid: getKeyFromDeviceInfo('appversionid'), // no i18n
2655
+ osversionid: getKeyFromDeviceInfo('osversionid'), // no i18n
2656
+ timezoneid: getKeyFromDeviceInfo('timezoneid'), // no i18n
2657
+ platformid: getKeyFromDeviceInfo('platformid'), // no i18n
2658
+ devicetypeid: getKeyFromDeviceInfo('devicetypeid') // no i18n
2659
+ }
2660
+ return returnObj;
2661
+ }
2662
+
2663
+ function checkAndUpdateDeviceInfo(){
2664
+ // params -> https://help.apptics.com/hc/en-us/articles/115004613766-Default-Properties-Collected-by-apptics
2665
+ _device_info = {
2666
+ osversion: getOS(),
2667
+ browser: getBrowser(),
2668
+ browserversion: String(getBrowserVersion()),
2669
+ donottrackenabled: hasDoNotTrackEnabled() ? 1 : 0, // no i18n
2670
+ devicetype: getDevice(),
2671
+ screenheight: screen.height,
2672
+ screenwidth: screen.width,
2673
+ referrer: getReferringURL(),
2674
+ connectionType: getConnectionType(),
2675
+ referringdomain: window.location.hostname || '', // no i18n
2676
+ deviceid: getKeyFromDeviceInfo('deviceid'), // no i18n
2677
+ anonymousid: getKeyFromDeviceInfo('anonymousid'), // no i18n
2678
+ frameworkid: getKeyFromDeviceInfo('frameworkid'), // no i18n
2679
+ mapid: getKeyFromDeviceInfo('mapid'), // no i18n
2680
+ aaid: getKeyFromDeviceInfo('aaid'), // no i18n
2681
+ apid: getKeyFromDeviceInfo('apid'), // no i18n
2682
+ appversionid: getKeyFromDeviceInfo('appversionid'), // no i18n
2683
+ platformid: getKeyFromDeviceInfo('platformid'), // no i18n
2684
+ devicetypeid: getKeyFromDeviceInfo('devicetypeid'), // no i18n
2685
+ osversionid: getKeyFromDeviceInfo('osversionid'), // no i18n
2686
+ timezoneid: getKeyFromDeviceInfo('timezoneid'), // no i18n
2687
+ timezone:getTimeZone()
2688
+ };
2689
+
2690
+ user_defaults.add('device_info', _device_info); // no i18n
2691
+
2692
+ addOrUpdateDeviceID(_device_info);
2693
+
2694
+ }
2695
+
2696
+ //
2697
+ function addOrUpdateDeviceID(_device_info){
2698
+ var _old_device_info = user_defaults.item('device_info'); // no i18n
2699
+ _frameworkID = _defaultoptions.base_domain.includes('local') ? _fwid_local : _frameworkID;
2700
+ // Compare the old and new device infos and update the server if anything changed in the new.
2701
+ if (_device_info.deviceid == null || _device_info.frameworkid == null || _device_info.anonymousid == null){
2702
+ _device_info.frameworkid = _frameworkID;
2703
+ Console.log('addOrUpdateDeviceID: Register the device'); // no i18n
2704
+ registerDeviceWithApptics().then(function (result_device_info){
2705
+ Console.log('addOrUpdateDeviceID: ' + JSON.stringify(result_device_info.data)); // no i18n
2706
+ Console.dir(result_device_info.data);
2707
+ }).catch(function (err){
2708
+ Console.log(err);
2709
+ });
2710
+ }
2711
+ else if (!compareTwoObjects(_device_info, _old_device_info)){
2712
+ // Both the info are not the same, so update device info
2713
+ updateDeviceWithApptics(_device_info);
2714
+ Console.log('addOrUpdateDeviceID: Device info changed, so updating new changes'); // no i18n
2715
+ Console.dir(_device_info);
2716
+ }
2717
+ }
2718
+
2719
+ // Function to register the device
2720
+ function registerDeviceWithApptics() {
2721
+ if (is_apptics_disabled()){
2722
+ return;
2723
+ }
2724
+
2725
+ if (!_token) {
2726
+ Console.warn('registerDeviceWithApptics: You must call apptics.init(token) first'); // no i18n
2727
+ return new Promise((resolve, reject) => {
2728
+ reject('registerDeviceWithApptics: You must call apptics.init(token) first');
2729
+ });
2730
+ }
2731
+
2732
+ if (navigator.onLine == false){
2733
+ Console.warn('registerDeviceWithApptics: No Network'); // no i18n
2734
+ return new Promise((resolve, reject) => {
2735
+ reject('registerDeviceWithApptics: No Network');
2736
+ });
2737
+ }
2738
+ if (_device_info.deviceid && _device_info.anonymousid){
2739
+ return new Promise((resolve, reject) => {
2740
+ resolve(_device_info);
2741
+ });
2742
+ }
2743
+ else{
2744
+ // Perform device registration here (e.g., make an API call)
2745
+ return new Promise((resolve, reject) => {
2746
+
2747
+ if (_regDeviceSending == false){
2748
+ _regDeviceSending = true;
2749
+ let device_info = cloneMapExcludingKeys(_device_info, ['deviceid', 'anonymousid', 'aaid', 'apid', 'mapid', 'devicetypeid', 'osversionid', 'platformid', 'appversionid', 'timezoneid', 'connectionType']); // no i18n
2750
+ var payload = JSON.stringify(device_info);
2751
+ var headers = {zak:getHeaderParams('zak'), uuid:getHeaderParams('uuid')}; // no i18n
2752
+
2753
+ requestQueue.addRequest(getUrlWithParams('regdevice'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
2754
+ var _result = JSON.parse(response);
2755
+ if (_result && _result.result == 'success'){ // no i18n
2756
+ var data = _result.data;
2757
+ _device_info.anonymousid = data.anonymousid;
2758
+ _device_info.deviceid = data.deviceid;
2759
+ _device_info.aaid = data.aaid;
2760
+ _device_info.apid = data.apid;
2761
+ _device_info.mapid = data.mapid;
2762
+
2763
+ _apid = data.apid;
2764
+ _mapid = data.mapid;
2765
+
2766
+ _device_info.appversionid = data.appversionid;
2767
+ _device_info.osversionid = data.osversionid;
2768
+ _device_info.timezoneid = data.timezoneid;
2769
+ _device_info.platformid = data.platformid;
2770
+ _device_info.devicetypeid = data.devicetypeid;
2771
+ //need to write function to update device info
2772
+
2773
+ // Update the new device info into the localstorage
2774
+ user_defaults.add('device_info', _device_info); // no i18n
2775
+
2776
+
2777
+ _regDeviceResolve.forEach(function (_resolve) {
2778
+ _resolve(_result);
2779
+ });
2780
+ resolve(_result);
2781
+ }
2782
+ else{
2783
+ _regDeviceReject.forEach(function (_reject) {
2784
+ _reject('registerDeviceWithApptics: '+ _result.result);
2785
+ });
2786
+ reject('registerDeviceWithApptics: '+ _result.result);
2787
+ }
2788
+ _regDeviceSending = false;
2789
+ }).catch(err => {
2790
+ _regDeviceReject.forEach(function (_reject) {
2791
+ _reject('registerDeviceWithApptics: '+ err);
2792
+ });
2793
+ reject('registerDeviceWithApptics: '+ err);
2794
+ _regDeviceSending = false;
2795
+ });
2796
+ }
2797
+ else{
2798
+ _regDeviceSending = false;
2799
+ _regDeviceResolve.push(resolve);
2800
+ _regDeviceReject.push(reject);
2801
+ }
2802
+ });
2803
+ }
2804
+ }
2805
+
2806
+
2807
+ // Function to update the device
2808
+ function updateDeviceWithApptics(_device_info) {
2809
+
2810
+ if (is_apptics_disabled()){
2811
+ return;
2812
+ }
2813
+
2814
+ if (navigator.onLine == false){
2815
+ Console.warn('updateDeviceWithApptics: No Network'); // no i18n
2816
+ return new Promise((resolve, reject) => {
2817
+ reject('updateDeviceWithApptics: No Network');
2818
+ });
2819
+ }
2820
+
2821
+ // Perform device registration here (e.g., make an API call)
2822
+ return new Promise((resolve, reject) => {
2823
+
2824
+ let device_info_exclude = cloneMapExcludingKeys(_device_info, ['deviceid', 'anonymousid', 'aaid', 'apid', 'mapid', 'appversionid', 'devicetypeid', 'osversionid', 'platformid', 'timezoneid', 'connectionType']); // no i18n
2825
+ var payload = JSON.stringify(device_info_exclude);
2826
+ var headers = {zak:getHeaderParams('zak'), uuid:getHeaderParams('uuid'), apid:getHeaderParams('apid'), mapid:getHeaderParams('mapid')}; // no i18n
2827
+
2828
+ requestQueue.addRequest(getUrlWithParams('regdevice'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
2829
+ var _result = JSON.parse(response);
2830
+ if (_result && _result.result == 'success'){ // no i18n
2831
+ var data = _result.data;
2832
+ _device_info.deviceid = data.deviceid;
2833
+ if(data.hasOwnProperty('anonymousid') && !isEmpty(data.anonymousid)){
2834
+ _device_info.anonymousid = data.anonymousid;
2835
+ }
2836
+
2837
+ _device_info.aaid = data.aaid;
2838
+ _device_info.apid = data.apid;
2839
+ _device_info.mapid = data.mapid;
2840
+
2841
+ _apid = data.apid;
2842
+ _mapid = data.mapid;
2843
+
2844
+ _device_info.appversionid = data.appversionid;
2845
+ _device_info.osversionid = data.osversionid;
2846
+ _device_info.timezoneid = data.timezoneid;
2847
+ _device_info.platformid = data.platformid;
2848
+ _device_info.devicetypeid = data.devicetypeid;
2849
+
2850
+ //need to write function to update device info
2851
+
2852
+ user_defaults.add('device_info', _device_info); // no i18n
2853
+ resolve(_device_info);
2854
+ }
2855
+ else{
2856
+ reject('updateDeviceWithApptics: '+ _result.result);
2857
+ }
2858
+
2859
+ }).catch(err => {
2860
+ reject('updateDeviceWithApptics: '+ err);
2861
+ });
2862
+ });
2863
+ }
2864
+
2865
+ // Function to register the device
2866
+ function registerUserWithApptics(user_info) {
2867
+
2868
+ if (is_apptics_disabled()){
2869
+ return;
2870
+ }
2871
+
2872
+ if (!shouldTrackOrSendData()){
2873
+ return;
2874
+ }
2875
+
2876
+ if (navigator.onLine == false){
2877
+ Console.warn('registerUserWithApptics: No Network'); // no i18n
2878
+ return new Promise((resolve, reject) => {
2879
+ reject('registerUserWithApptics: No Network');
2880
+ });
2881
+
2882
+ }
2883
+ if(isEmptyObj(user_info)){
2884
+ return;
2885
+ }
2886
+
2887
+ if (isEmpty(getHeaderParams('apid')) && isEmpty(getHeaderParams('mapid'))){
2888
+ checkAndUpdateDeviceInfo();
2889
+ return new Promise((resolve, reject) => {
2890
+ reject('registerUserWithApptics: You must call apptics.init() first');
2891
+ });
2892
+ }
2893
+
2894
+ // Perform user registration here (e.g., make an API call)
2895
+ return new Promise((resolve, reject) => {
2896
+
2897
+ let payload_user_info = cloneMapExcludingKeys(user_info, ['userid', 'groupid', 'happendat']); // no i18n
2898
+ var payload = JSON.stringify(payload_user_info);
2899
+
2900
+ var headers = {apid:getHeaderParams('apid'), mapid:getHeaderParams('mapid')}; // no i18n
2901
+
2902
+ requestQueue.addRequest(getUrlWithParams('reguser'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
2903
+ var _result = JSON.parse(response);
2904
+ if (_result && _result.result == 'success'){ // no i18n
2905
+ var data = _result.data;
2906
+
2907
+ user_info.userid = data.userid;
2908
+ user_info.groupid = data.customergroupid;
2909
+ _user_info = user_info;
2910
+ setTrackingStatus(AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII);
2911
+ addOrUpdateUser(user_info);
2912
+ resolve(data);
2913
+ }
2914
+ else{
2915
+ reject('registerUserWithApptics: '+ _result.result);
2916
+ }
2917
+
2918
+ }).catch(err => {
2919
+ reject('registerUserWithApptics: '+ err);
2920
+ });
2921
+ });
2922
+
2923
+ }
2924
+
2925
+ // Function to send engagement data to server
2926
+ function requestToSendEngagements(_payload) {
2927
+ if (is_apptics_disabled()){
2928
+ return;
2929
+ }
2930
+
2931
+ if (!shouldTrackOrSendData()){
2932
+ return;
2933
+ }
2934
+
2935
+ if (isEmptyObj(_payload)){
2936
+ return;
2937
+ }
2938
+
2939
+ if (isEmpty(getHeaderParams('apid')) && isEmpty(getHeaderParams('mapid'))){
2940
+ checkAndUpdateDeviceInfo();
2941
+ return new Promise((resolve, reject) => {
2942
+ reject('requestToSendEngagements: You must call apptics.init() first');
2943
+ });
2944
+ }
2945
+
2946
+ if(!_payload.hasOwnProperty('meta')){ // no i18n
2947
+ _payload.meta = getMetaObj();
2948
+ }
2949
+
2950
+ let dataSize = sizeof(_payload);
2951
+ Console.log('requestToSendEngagements: Engagement Payload size : ' + (dataSize / 1024)); // no i18n
2952
+
2953
+ return new Promise((resolve, reject) => {
2954
+ // Perform to send engagement data here (e.g., make an API call)
2955
+ var payload = JSON.stringify(_payload);
2956
+ var headers = {apid:getHeaderParams('apid'), mapid:getHeaderParams('mapid')}; // no i18n
2957
+
2958
+ requestQueue.addRequest(getUrlWithParams('engagement'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
2959
+ var _result = JSON.parse(response);
2960
+ if (_result && _result.result == 'success'){ // no i18n
2961
+ var data = _result;
2962
+ resolve(data);
2963
+ }
2964
+ else{
2965
+ reject('requestToSendEngagements: '+ _result.result);
2966
+ }
2967
+
2968
+ }).catch(err => {
2969
+ reject('requestToSendEngagements: '+ err);
2970
+ });
2971
+ });
2972
+
2973
+ }
2974
+
2975
+ // Function to send error data to server
2976
+ function requestToSendErrors(_payload) {
2977
+ if (is_apptics_disabled()){
2978
+ return;
2979
+ }
2980
+
2981
+ if (!shouldTrackOrSendData()){
2982
+ return;
2983
+ }
2984
+
2985
+ if (isEmptyObj(_payload)){
2986
+ return;
2987
+ }
2988
+
2989
+ if (isEmpty(getHeaderParams('apid')) && isEmpty(getHeaderParams('mapid'))){
2990
+ checkAndUpdateDeviceInfo();
2991
+ return new Promise((resolve, reject) => {
2992
+ reject('requestToSendErrors: You must call apptics.init() first');
2993
+ });
2994
+ }
2995
+
2996
+ if(!_payload.hasOwnProperty('meta')){ // no i18n
2997
+ _payload.meta = getMetaObj();
2998
+ }
2999
+
3000
+ let dataSize = sizeof(_payload);
3001
+ Console.log('requestToSendErrors: Error Payload size : ' + (dataSize / 1024)); // no i18n
3002
+
3003
+ return new Promise((resolve, reject) => {
3004
+ // Perform to send error data here (e.g., make an API call)
3005
+ var payload = JSON.stringify(_payload);
3006
+ var headers = {apid:getHeaderParams('apid'), mapid:getHeaderParams('mapid')}; // no i18n
3007
+
3008
+ requestQueue.addRequest(getUrlWithParams('error'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
3009
+ var _result = JSON.parse(response);
3010
+ if (_result && _result.result == 'success'){ // no i18n
3011
+ var data = _result;
3012
+ resolve(data);
3013
+ }
3014
+ else{
3015
+ reject('requestToSendErrors: '+ _result.result);
3016
+ }
3017
+
3018
+ }).catch(err => {
3019
+ reject('requestToSendErrors: '+ err);
3020
+ });
3021
+ });
3022
+
3023
+ }
3024
+
3025
+ // Function to send feedback data to server
3026
+ function requestToSendFeedback(_payload) {
3027
+ if (is_apptics_disabled()){
3028
+ return;
3029
+ }
3030
+
3031
+ if (isEmptyObj(_payload)){
3032
+ return;
3033
+ }
3034
+
3035
+ if (isEmpty(getHeaderParams('apid')) && isEmpty(getHeaderParams('mapid'))){
3036
+ checkAndUpdateDeviceInfo();
3037
+ return new Promise((resolve, reject) => {
3038
+ reject('requestToFeedback: You must call apptics.init() first');
3039
+ });
3040
+ }
3041
+
3042
+ let dataSize = sizeof(_payload);
3043
+ Console.log('requestToFeedback: Feedback Payload size : ' + (dataSize / 1024)); // no i18n
3044
+
3045
+ return new Promise((resolve, reject) => {
3046
+ // Perform to send feedback data here (e.g., make an API call)
3047
+ var payload = _payload;
3048
+ var headers = {apid:getHeaderParams('apid'), mapid:getHeaderParams('mapid')}; // no i18n
3049
+
3050
+ requestQueue.addRequest(getUrlWithParams('feedback'), getConfig('method'), payload, headers).processQueue().then(response => { // no i18n
3051
+ var _result = JSON.parse(response);
3052
+ if (_result && _result.result == 'success'){ // no i18n
3053
+ var data = _result;
3054
+ resolve(data);
3055
+ }
3056
+ else{
3057
+ reject('requestToFeedback: '+ _result.result);
3058
+ }
3059
+
3060
+ }).catch(err => {
3061
+ reject('requestToFeedback: '+ err);
3062
+ });
3063
+ });
3064
+
3065
+ }
3066
+
3067
+ function extractMethodNameFromStackTrace(stackTrace) {
3068
+ let methodName = '';
3069
+ if(isEmpty(stackTrace)){
3070
+ return methodName;
3071
+ }
3072
+ const lines = stackTrace.split('\n'); // no i18n
3073
+ if (lines.length < 2) {
3074
+ return methodName; // Not enough information in the stack trace
3075
+ }
3076
+ // The method name is typically included in the second line
3077
+ const methodNamePattern1 = /at (\S+) \(/;
3078
+ const methodNamePattern2 = /^(\w+)@/;
3079
+
3080
+ for (let line of lines){
3081
+
3082
+ const match1 = line.match(methodNamePattern1);
3083
+ const match2 = line.match(methodNamePattern2);
3084
+ // For browsers like Chrome, Ula
3085
+ if (match1 && match1[1]) {
3086
+ // Return the method name
3087
+ const urlRegex = /\((.*)\)/;
3088
+ // Extract the URL from the error string
3089
+ const urlMatch = urlRegex.exec(line);
3090
+ // Extracted URL
3091
+ const url = urlMatch ? urlMatch[1] : 'Unknown URL'; // no i18n
3092
+ methodName = match1[1];
3093
+ fileNameAndLine = url.split('/').pop(); // no i18n
3094
+ methodName = methodName;// + '@'+ fileNameAndLine;
3095
+ break;
3096
+ }
3097
+
3098
+ // For browsers like Safari
3099
+ if (match2 && match2[1]) {
3100
+
3101
+ // Return the method name
3102
+ methodName = match2[1];
3103
+ fileNameAndLine = line.split('@')[1].split('/').pop(); // no i18n
3104
+ methodName = methodName;// + '@'+ fileNameAndLine; // no i18n
3105
+ break;
3106
+ }
3107
+ }
3108
+
3109
+ return methodName;
3110
+ }
3111
+
3112
+ // Function to format duration conditionally
3113
+ function formatDuration(ms) {
3114
+ if (ms < 1000) { // Less than 1 second, show in milliseconds
3115
+ return `${ms.toFixed(2)} ms`;
3116
+ }
3117
+ const seconds = (ms / 1000).toFixed(2); // If seconds are meaningful
3118
+ if (ms < 60000) { // Less than 1 minute, show in seconds
3119
+ return `${seconds} s`;
3120
+ }
3121
+ const minutes = Math.floor(ms / 60000);
3122
+ const remainingSeconds = ((ms % 60000) / 1000).toFixed(2);
3123
+ if (minutes < 60) { // Less than 1 hour, show in minutes
3124
+ return `${minutes} m ${remainingSeconds} s`;
3125
+ }
3126
+ const hours = Math.floor(ms / (1000 * 60 * 60));
3127
+ return `${hours} h ${minutes % 60} m`; // Show in hours and minutes
3128
+ }
3129
+
3130
+ // Function to format size conditionally
3131
+ function formatSize(bytes) {
3132
+ if (bytes < 1024) { // Less than 1 KB
3133
+ return `${bytes} B`;
3134
+ }
3135
+ const kb = (bytes / 1024).toFixed(2); // Size in KB
3136
+ if (bytes < 1024 * 1024) { // Less than 1 MB
3137
+ return `${kb} KB`;
3138
+ }
3139
+ const mb = (bytes / (1024 * 1024)).toFixed(2); // Size in MB
3140
+ return `${mb} MB`;
3141
+ }
3142
+
3143
+
3144
+ // Track when the window loses focus (user switches to another window or application)
3145
+ window.addEventListener('blur', function() { // no i18n
3146
+ // Window lost focus, user switched to another window or application
3147
+ Console.log('Window lost focus, user switched to another window or application'); // no i18n
3148
+ updateLastActiveTimeOfSession();
3149
+ });
3150
+
3151
+ // Track when the window gains focus (user switches back to the window)
3152
+ window.addEventListener('focus', function() { // no i18n
3153
+ // Window regained focus, user switched back to the window
3154
+ Console.log('Window regained focus, user switched back to the window'); // no i18n
3155
+ scheduleToCheckAndUpdateSession();
3156
+ checkAndScheduleToSendData();
3157
+ });
3158
+
3159
+ window.addEventListener('beforeunload', function() { // no i18n
3160
+ // Send an analytics event before the user leaves the page
3161
+ updateScreenWithEndtime();
3162
+ Console.log('update lastactivetime upon page unload'); // no i18n
3163
+ });
3164
+
3165
+ // if we are running in cordova use ondeviceready otherwise onload
3166
+ window.addEventListener((window.cordova) ? 'deviceready' : 'load', checkAndUpdateSession, { passive: true }); // no i18n
3167
+
3168
+ // Always send pending request when the connection comes back online
3169
+ window.addEventListener('online', scheduleToSendDataToServer, { passive: true }); // no i18n
3170
+
3171
+ window.screen.orientation.addEventListener('change', function() { // no i18n
3172
+ _properties.orientation = getOrientation();
3173
+ });
3174
+
3175
+ window.addEventListener("unhandledrejection", function(event) { // no i18n
3176
+ if(_isGlobalErrorTracking){
3177
+ trackError(event);
3178
+ }
3179
+ });
3180
+
3181
+ window.addEventListener('error', function(event) { // no i18n
3182
+ if(_isGlobalErrorTracking){
3183
+ trackError(event);
3184
+ }
3185
+ });
3186
+
3187
+
3188
+ //Feedback UI
3189
+ var feedback = {
3190
+ feedbackUIHtml : `<div id="zasdkFeedbackconatiner" class="zasdk-modal-content">
3191
+ <div class="zasdk-header">
3192
+ <h2 class="zasdk-title">Share feedback</h2>
3193
+ <span id="appticsfeedbackclose" class="zasdk-close"><svg viewBox="0 0 24 24" style=" width: 20px; "><path d="M19.92,19.08c.23.23.23.61,0,.85-.12.12-.27.18-.42.18s-.31-.06-.42-.18l-7.08-7.08-7.08,7.08c-.12.12-.27.18-.42.18s-.31-.06-.42-.18c-.23-.23-.23-.61,0-.85l7.08-7.08-7.08-7.08c-.23-.23-.23-.61,0-.85s.61-.23.85,0l7.08,7.08,7.08-7.08c.23-.23.61-.23.85,0s.23.61,0,.85l-7.08,7.08,7.08,7.08Z"/></svg></span>
3194
+ </div>
3195
+ <div class="zasdk-feedback-types" data-type="">
3196
+ <div class="zasdk-feedback-type zasdk-selected" data-type="bug">
3197
+ <div class="zasdk-iconcnt zasdkfeedback__icon-report">
3198
+ <i class="zasdkfeedback__icon"><svg viewBox="0 0 24 24"><path d="M15.6,6.58c-.13-.37-.3-.7-.5-.97l1.23-1.18c.24-.23.25-.61.02-.85-.23-.24-.61-.25-.85-.02l-1.26,1.21c-.66-.45-1.37-.62-1.79-.68-.25-.04-.51-.04-.76-.02-.83.08-1.45.35-1.93.7l-1.22-1.2c-.24-.23-.62-.23-.85,0-.23.24-.23.62,0,.85l1.21,1.19c-.24.34-.41.68-.51.96-2.02,1.37-3.27,3.82-3.27,6.44,0,4.18,3.08,7.58,6.87,7.58s6.87-3.4,6.87-7.58c0-2.62-1.25-5.06-3.26-6.44ZM11.82,5.27c.15-.02.31-.01.47.01.69.1,1.86.48,2.26,1.9-.17.17-.51.44-1.14.65-1.08.36-2.31.28-3.36-.23-.24-.11-.44-.23-.6-.36,0-.01,0-.03.01-.04.18-.65.73-1.77,2.36-1.93ZM12,19.4c-3.12,0-5.67-2.86-5.67-6.38,0-1.98.82-3.82,2.19-5.02.26.25.59.48,1.01.68.78.38,1.65.57,2.5.57.6,0,1.19-.09,1.76-.28.88-.29,1.39-.7,1.67-1,1.38,1.2,2.21,3.06,2.21,5.05,0,3.52-2.54,6.38-5.67,6.38ZM9.12,9.86c-.92,0-1.67.75-1.67,1.67s.75,1.67,1.67,1.67,1.67-.75,1.67-1.67-.75-1.67-1.67-1.67ZM9.12,11.99c-.26,0-.47-.21-.47-.47s.21-.47.47-.47.47.21.47.47-.21.47-.47.47ZM14.88,9.86c-.92,0-1.67.75-1.67,1.67s.75,1.67,1.67,1.67,1.67-.75,1.67-1.67-.75-1.67-1.67-1.67ZM14.88,11.99c-.26,0-.47-.21-.47-.47s.21-.47.47-.47.47.21.47.47-.21.47-.47.47ZM12,14.49c-.92,0-1.67.75-1.67,1.67s.75,1.67,1.67,1.67,1.67-.75,1.67-1.67-.75-1.67-1.67-1.67ZM12,16.62c-.26,0-.47-.21-.47-.47s.21-.47.47-.47.47.21.47.47-.21.47-.47.47Z"/></svg></i>
3199
+ </div>
3200
+ <div>Bug</div>
3201
+ </div>
3202
+ <div class="zasdk-feedback-type" data-type="feedback">
3203
+ <div class="zasdk-iconcnt zasdkfeedback__icon-reqest">
3204
+ <i class="zasdkfeedback__icon"><svg viewBox="0 0 24 24"><path d="M6.69,9.25h10.81c.41,0,.75-.34.75-.75s-.34-.75-.75-.75H6.69c-.41,0-.75.34-.75.75s.34.75.75.75Z"/><path d="M6.69,13.07h8.11c.41,0,.75-.34.75-.75s-.34-.75-.75-.75H6.69c-.41,0-.75.34-.75.75s.34.75.75.75Z"/><path d="M20.72,5.17c0-.68-.55-1.23-1.23-1.23H4.48c-.68,0-1.23.55-1.23,1.23v11.84c0,.68.55,1.23,1.23,1.23h13.49l1.25,1.51c.17.2.41.31.67.31.1,0,.2-.02.3-.05.34-.13.57-.46.56-.83l-.03-14.01ZM19.22,17.39l-.17-.2c-.24-.29-.58-.45-.95-.45H4.75V5.44h14.47v11.95Z"/></svg></i>
3205
+ </div>
3206
+ <div>Feedback</div>
3207
+ </div>
3208
+ <div class="zasdk-feedback-type" data-type="help">
3209
+ <div class="zasdk-iconcnt zasdkfeedback__icon-help">
3210
+ <i class="zasdkfeedback__icon"><svg viewBox="0 0 24 24"><path d="M12.45,16.47c-.33,0-.6-.27-.6-.6v-2.7c0-.43.25-.82.64-.99.95-.42,3.15-1.62,3.15-3.51,0-1.6-1.01-3.03-2.51-3.55-.95-.33-1.97-.28-2.87.16-.91.43-1.59,1.2-1.92,2.14-.11.31-.45.48-.77.37-.31-.11-.48-.45-.37-.77.44-1.25,1.34-2.25,2.54-2.83,1.2-.57,2.54-.65,3.79-.21,1.99.7,3.32,2.58,3.31,4.69,0,2.46-2.34,3.91-3.79,4.57v2.63c0,.33-.27.6-.6.6ZM13.21,19.1c0-.66-.54-1.2-1.2-1.2h-.03c-.66,0-1.19.54-1.19,1.2s.55,1.2,1.21,1.2,1.2-.54,1.2-1.2Z"/></svg></i>
3211
+ </div>
3212
+ <div>Help</div>
3213
+ </div>
3214
+ <div class="zasdk-feedback-type" data-type="report">
3215
+ <div class="zasdk-iconcnt zasdkfeedback__icon-appreciate">
3216
+ <i class="zasdkfeedback__icon"><svg viewBox="0 0 24 24"><path d="M20.47,16.72l-6.66-11.53c-.37-.65-1.07-1.05-1.81-1.05s-1.44.4-1.81,1.05l-6.66,11.53c-.37.65-.37,1.45,0,2.09.37.65,1.07,1.05,1.81,1.05h13.32c.75,0,1.44-.4,1.81-1.05.37-.64.37-1.45,0-2.09ZM19.17,18.06c-.11.18-.3.3-.51.3H5.34c-.21,0-.41-.11-.51-.3-.1-.18-.1-.41,0-.59l6.66-11.53h0c.11-.18.3-.3.51-.3s.41.11.51.3l6.66,11.53c.11.18.11.41,0,.59Z"/><path d="M12.35,13.9s.37-4.72.37-4.72c.05-1.08-1.58-1.06-1.5.02l.48,4.71c.03.38.62.37.64,0Z"/><path d="M11.97,15.06c-.52,0-.94.42-.94.94s.42.94.94.94.94-.42.94-.94-.42-.94-.94-.94Z"/></svg></i>
3217
+ </div>
3218
+ <div>Report</div>
3219
+ </div>
3220
+ </div>
3221
+ <div id="zasdkFeedbackForm" class="zasdk-form">
3222
+ <label class="zasdkdesclbl" for="zasdkDescription">Add details here<span class="zasdkmadfeld">*</span> <span class="zaleftcharspan"><span class="zaleftcharcnt">5000</span><span class="zaleftchartxt">character left</span></span></label>
3223
+ <textarea id="zasdkDescription" name="description" placeholder="Please provide more details for us to help you better." reqname="name" maxlength="5000"></textarea>
3224
+ <div id="zasdkreccheckcnt" class="zasdkqz-container">
3225
+ <div class="zasdkqz-checkbox-wrapper">
3226
+ <input type="checkbox" id="zasdkqz-consent" class="zasdkqz-checkbox">
3227
+ <label for="zasdkqz-consent">By agreeing to record screen, I allow the collection of the required information for further analysis.</label>
3228
+ </div>
3229
+ <div id="zasdkqz-content" class="zasdkqz-content">
3230
+ <div class="zasdkqz-prop-content">Data collected while screen recording.
3231
+ <div class="zasdk_rec_hdr_icon zasdk-tooltip-container zasdk_rec_hdr">
3232
+ <i><svg viewBox="0 0 24 24"><path d="M12,20.6c-4.74,0-8.6-3.86-8.6-8.6S7.26,3.4,12,3.4s8.6,3.86,8.6,8.6-3.86,8.6-8.6,8.6ZM12,4.6c-4.08,0-7.4,3.32-7.4,7.4s3.32,7.4,7.4,7.4,7.4-3.32,7.4-7.4-3.32-7.4-7.4-7.4ZM12.6,15.97v-4.91c0-.33-.27-.6-.6-.6s-.6.27-.6.6v4.91c0,.33.27.6.6.6s.6-.27.6-.6ZM12.61,9.03c0-.33-.27-.6-.6-.6h-.01c-.33,0-.59.27-.59.6s.28.6.61.6.6-.27.6-.6Z"/></svg></i>
3233
+ <div class="zasdk-tooltip-top zasdk-prop-info">
3234
+ <ul>
3235
+ <li>- A recording of the window or region specified.</li>
3236
+ <li>- Audio captured during recording. (You can mute or re-record)</li>
3237
+ <li>- IP address.</li>
3238
+ <li>- Browser console logs generated while recording.</li>
3239
+ <li>- Device details (screen resolution, OS, and browser version).</li>
3240
+ <li>- Client-side performance metrics (DNS lookup time, TCP connection time, etc.).</li>
3241
+ </ul>
3242
+ </div>
3243
+ </div>
3244
+ </div>
3245
+ <div id="zasdkqz_rec_button" class="zasdkqz-button">Start screen recording</div>
3246
+ </div>
3247
+ </div>
3248
+ <div id="editRecordedVideo" class="zasdk_recordedClipArea" style="">
3249
+ <div id="recordScreenDesc" class="zasdk_rec_hdr"> Recorded Clip
3250
+ <div class="zasdk_rec_hdr_icon zasdk-tooltip-container">
3251
+ <i><svg viewBox="0 0 24 24"><path d="M12,20.6c-4.74,0-8.6-3.86-8.6-8.6S7.26,3.4,12,3.4s8.6,3.86,8.6,8.6-3.86,8.6-8.6,8.6ZM12,4.6c-4.08,0-7.4,3.32-7.4,7.4s3.32,7.4,7.4,7.4,7.4-3.32,7.4-7.4-3.32-7.4-7.4-7.4ZM12.6,15.97v-4.91c0-.33-.27-.6-.6-.6s-.6.27-.6.6v4.91c0,.33.27.6.6.6s.6-.27.6-.6ZM12.61,9.03c0-.33-.27-.6-.6-.6h-.01c-.33,0-.59.27-.59.6s.28.6.61.6.6-.27.6-.6Z"/></svg></i>
3252
+ <div class="zasdk-tooltip">This recording will be sent along with your feedback for further analysis.</div>
3253
+ </div>
3254
+ </div>
3255
+ <div class="zasdk_playEdit">
3256
+ <div class="zasdk_clipAreaButtonCnt zasdk-tooltip-container">
3257
+ <div id="preivewPlayButton" class="zasdk_clipAreaButton" title="Preview">
3258
+ <svg viewBox="0 0 24 24"><path d="M6.24,20.6c-.2,0-.39-.05-.57-.15-.38-.21-.62-.61-.62-1.05V4.59c0-.44.24-.84.62-1.05.38-.21.85-.19,1.22.04l11.52,7.41c.34.22.55.6.55,1,0,.41-.2.78-.55,1l-11.52,7.41c-.2.13-.42.19-.64.19ZM6.56,4.1l-.33.5v14.81s11.52-7.41,11.52-7.41L6.23,4.6l.33-.5Z"/></svg>
3259
+ </div>
3260
+ <div class="zasdk-tooltip">Preview</div>
3261
+ </div>
3262
+ <div class="zasdk_clipAreaButtonCnt zasdk-tooltip-container">
3263
+ <div id="videoEditButton" class="zasdk_clipAreaButton">
3264
+ <svg id="Layer_2" viewBox="0 0 24 24"><path d="M20.6,7.18c0-.58-.23-1.13-.65-1.54l-1.53-1.51c-.83-.83-2.29-.83-3.12,0l-1.31,1.3h0S4.88,14.41,4.88,14.41c-.12.12-.21.27-.26.44l-1.19,4.4c-.09.34,0,.7.26.95.19.18.44.29.7.29.09,0,.17-.01.26-.04l4.44-1.2c.16-.05.31-.13.44-.25l8.54-8.43h0s1.88-1.86,1.88-1.86c.06-.06.12-.12.17-.19h0c.31-.39.48-.86.48-1.36ZM8.72,18.12l-4.03,1.09,1.08-3.98L13.97,7.12l2.88,2.97-8.13,8.03ZM17.71,9.25l-2.88-2.97,1.32-1.3c.38-.38,1.04-.38,1.43,0l1.53,1.51c.19.18.29.43.29.69,0,.22-.08.43-.21.6h0s-.04.05-.08.08l-1.4,1.39Z"/></svg>
3265
+ </div>
3266
+ <div class="zasdk-tooltip">Edit</div>
3267
+ </div>
3268
+ <div class="zasdk_clipAreaButtonCnt zasdk-tooltip-container">
3269
+ <div id="videoDelButton" class="zasdk_clipAreaButton">
3270
+ <svg id="Layer_2" viewBox="0 0 24 24"><path d="M19.2,6.6h-3.4v-1c0-1.21-.99-2.2-2.2-2.2h-3.2c-1.21,0-2.2.99-2.2,2.2v1h-3.4c-.33,0-.6.27-.6.6s.27.6.6.6h1v10.6c0,1.21.99,2.2,2.2,2.2h8c1.21,0,2.2-.99,2.2-2.2V7.8h1c.33,0,.6-.27.6-.6s-.27-.6-.6-.6ZM9.4,5.6c0-.55.45-1,1-1h3.2c.55,0,1,.45,1,1v1h-5.2v-1ZM17,18.4c0,.55-.45,1-1,1h-8c-.55,0-1-.45-1-1V7.8h10v10.6ZM11,11.2v4.8c0,.33-.27.6-.6.6s-.6-.27-.6-.6v-4.8c0-.33.27-.6.6-.6s.6.27.6.6ZM14.2,11.2v4.8c0,.33-.27.6-.6.6s-.6-.27-.6-.6v-4.8c0-.33.27-.6.6-.6s.6.27.6.6Z"/></svg>
3271
+ </div>
3272
+ <div class="zasdk-tooltip">Delete</div>
3273
+ </div>
3274
+ </div>
3275
+ <div class="zasdk_videoPrev">
3276
+ <video id="previewVideoElemId" width="320" height="240" src=""></video>
3277
+ </div>
3278
+ </div>
3279
+
3280
+ <div class="zasdk-buttons">
3281
+ <div class="zasdk-btncnt">
3282
+ <div id="zasdkattachinfo" class="zasdkattachinf">Maximum 5 attachments with a total file size of 10MB is allowed</div>
3283
+ <input type="file" id="zasdkFileInput" class="zasdk-file-input" multiple accept=".png, .jpg">
3284
+ <label id="zasdkAttachBtn" class="zasdk-btn zasdk-attach-btn" for="zasdkFileInput">
3285
+ <svg id="Layer_2" viewBox="0 0 24 24" style=" width: 25px; "><path d="M12,20.6c-2.63,0-4.78-2.14-4.78-4.78V6.78c0-1.87,1.52-3.38,3.38-3.38s3.38,1.52,3.38,3.38v9.04c0,1.1-.9,1.99-2,1.99s-1.99-.89-1.99-1.99V7.48c0-.33.27-.6.6-.6h0c.33,0,.6.27.6.6v8.35c0,.44.35.79.79.79s.79-.36.79-.79V6.78c0-1.2-.98-2.18-2.18-2.18s-2.18.98-2.18,2.18v9.04c0,1.97,1.6,3.58,3.58,3.58s3.58-1.6,3.58-3.58V6.78c0-.33.27-.6.6-.6s.6.27.6.6v9.04c0,2.63-2.14,4.78-4.78,4.78Z"/></svg>
3286
+ <span>Attachments</span>
3287
+ </label>
3288
+ <div id="zasdkfilesizeinfo" class="zasdkfilesizeinf"></div>
3289
+ <ul id="zasakfileList" class="zasakfilelstcnt"></ul>
3290
+ </div>
3291
+ </div>
3292
+ </div>
3293
+ <div class="zasdk_submit_btncnt">
3294
+ <div id="zasdksubmitstatus" class="zasdkstsmsg" style="display:none;" ></div>
3295
+ <div id="zasdk_submit_btnsubcnt" class="zasdk_submit_btnsubcnt">
3296
+ <input type="button" id="zasdk_submit_btn" class="zasdk-btn zasdk-send-btn zasdkbtndisableclr" value="Send">
3297
+ </div>
3298
+ </div>
3299
+ </div>`,
3300
+ timerHtml : `<div id="zasdkrectimer" class="zasdk-timer-container zasdk-tooltip-container">
3301
+ <div class="zasdk-timer-control" id="zasdkRecordButton"><div class="zasdk-timer-stopcricle"></div></div>
3302
+ <span class="zasdk-timer-display" id="zasdkTimerDisplay">05:00</span>
3303
+ <div class="zasdk-tooltip-top">Stop Recording</div>
3304
+ </div>`,
3305
+ typeObj : {'bug' : 0, 'feedback': 1,'help': 2, 'report': 3},
3306
+ init : function(){
3307
+ var modal = document.getElementById("zasdkFeedbackModal");
3308
+ if(!isEmpty(modal)){
3309
+ return;
3310
+ }
3311
+ const tempContainer = document.createElement('div');
3312
+ feedback.uploadFileAry = [];
3313
+ tempContainer.id = 'zasdkFeedbackModal';
3314
+ tempContainer.className = 'zasdk-modal';
3315
+ tempContainer.innerHTML = feedback.feedbackUIHtml;
3316
+ document.body.appendChild(tempContainer);
3317
+ var modal = document.getElementById("zasdkFeedbackModal");
3318
+ var defaultoption = window.appticssettings._defaultoptions;
3319
+ var qz_ws_key = defaultoption.quartz_workspace_key;
3320
+ var qz_dp_key = defaultoption.quartz_department_key;
3321
+ var quartz_DC = {
3322
+ "Local": "https://quartz.localzoho.com", // No i18n
3323
+ "US": "https://quartz.zoho.com", // No i18n
3324
+ "EU": "https://quartz.zoho.eu", // No i18n
3325
+ "IN": "https://quartz.zoho.in", // No i18n
3326
+ "AU": "https://quartz.zoho.com.au", // No i18n
3327
+ "CN": "https://quartz.zoho.com.cn", // No i18n
3328
+ "CA": "https://quartz.zohocloud.ca", // No i18n
3329
+ "JP": "https://quartz.zoho.jp", // No i18n
3330
+ "SA": "https://quartz.zoho.sa", // No i18n
3331
+ "AE": "https://quartz.zoho.ae" // No i18n
3332
+ }
3333
+ var qz_dc_key = defaultoption.DC !== '-' ? quartz_DC[defaultoption.DC] : []; // No i18n
3334
+ var closeBtn = modal.querySelector("#appticsfeedbackclose");
3335
+ var feedbackTypes = modal.querySelectorAll(".zasdk-feedback-type");
3336
+ feedback.fileInput = modal.querySelector("#zasdkFileInput");
3337
+ var quartzrecbtn = modal.querySelector("#zasdkqz_rec_button");
3338
+ var submitbtn = modal.querySelector("#zasdk_submit_btn");
3339
+ var submitbtncnt = modal.querySelector("#zasdk_submit_btnsubcnt");
3340
+
3341
+ const checkbox = modal.querySelector('#zasdkqz-consent');
3342
+ const content = modal.querySelector('#zasdkqz-content');
3343
+ const preivewButton = modal.querySelector('#preivewPlayButton');
3344
+ const videoEditButton = modal.querySelector('#videoEditButton');
3345
+ const deleteVideoButton = modal.querySelector('#videoDelButton');
3346
+
3347
+ const feedbackbox = modal.querySelector('#zasdkFeedbackconatiner');
3348
+
3349
+ const descriptionField = modal.querySelector('#zasdkDescription');
3350
+
3351
+ feedback.isScaled = false;
3352
+ feedback.isHidden = false;
3353
+
3354
+ document.removeEventListener('quartz-feedback-state', feedback.bindQuartzEvent);
3355
+ document.addEventListener('quartz-feedback-state', feedback.bindQuartzEvent);
3356
+
3357
+ document.removeEventListener('quartz-feedback-error', feedback.handleErrorQuartzEvent);
3358
+ document.addEventListener('quartz-feedback-error', feedback.handleErrorQuartzEvent);
3359
+
3360
+ modal.querySelector("#zasdkreccheckcnt").style.display = "block";
3361
+
3362
+ if(qz_ws_key == "-" || qz_dp_key == "-" || qz_dc_key == "-"){
3363
+ modal.querySelector("#zasdkreccheckcnt").style.display = "none";
3364
+ }
3365
+
3366
+ feedbackbox.addEventListener('transitionend', function(event) {
3367
+ if (event.propertyName === 'transform' && feedback.isScaled) {
3368
+ feedback.toggleWindowAnimation();
3369
+ }
3370
+ });
3371
+
3372
+ quartzrecbtn.onclick = function(e){
3373
+ e.stopImmediatePropagation();
3374
+
3375
+ let recordingDetails = {
3376
+ workspaceLinkName : qz_ws_key,
3377
+ departmentLinkName : qz_dp_key,
3378
+ recordingDomain : qz_dc_key
3379
+ };
3380
+ feedback.handleStatusmsg();
3381
+ Quartz.startRecording(recordingDetails);
3382
+ }
3383
+
3384
+ closeBtn.onclick = function() {
3385
+ feedback.close();
3386
+ }
3387
+
3388
+ preivewButton.addEventListener('click', function() {
3389
+ Quartz.previewVideo();
3390
+ });
3391
+
3392
+ videoEditButton.addEventListener('click', function() {
3393
+ Quartz.editVideo({quartzBranding:false});
3394
+ });
3395
+
3396
+ deleteVideoButton.addEventListener('click', function() {
3397
+ var modal = document.getElementById("zasdkFeedbackModal");
3398
+ Quartz.clearFeedback();
3399
+ checkbox.checked = false;
3400
+ content.classList.remove('zasdkqz-show');
3401
+ modal.querySelector("#editRecordedVideo").style.display = "none";
3402
+ modal.querySelector("#zasdkreccheckcnt").style.display = "block";
3403
+ feedback.validateFeedbackField();
3404
+ });
3405
+
3406
+ submitbtn.addEventListener('click', async () => {
3407
+ // Disable button and show loader
3408
+ feedback.validateFeedbackField();
3409
+ submitbtn.disabled = true;
3410
+
3411
+ submitbtncnt.classList.add('zasdkbtndisabled');
3412
+ submitbtn.value = '';
3413
+
3414
+ try {
3415
+ let videoElement = modal.querySelector('#previewVideoElemId'),
3416
+ videoVisible = videoElement && videoElement.offsetParent !== null;
3417
+ if(videoVisible){
3418
+ feedback.submitRecording();
3419
+ }
3420
+ else{
3421
+ let dataobj = {},
3422
+ seltype = modal.querySelector('.zasdk-selected').getAttribute('data-type'),
3423
+ feedtype = !isEmpty(seltype) ? feedback.typeObj[seltype] : 1,
3424
+ fileInput = modal.querySelector("#zasdkFileInput"),
3425
+ files = fileInput.files,
3426
+ message = modal.querySelector('#zasdkDescription').value.trim();
3427
+ if(message.length > 0){
3428
+ dataobj = {
3429
+ type : feedtype,
3430
+ message : message,
3431
+ attachment : files,
3432
+ quartzid : ''
3433
+ }
3434
+ sendFeedbackData(dataobj);
3435
+ }
3436
+ else{
3437
+ feedback.handleStatusmsg(false, 'Please provide more details for us to help you better.'); // no i18n
3438
+ }
3439
+ }
3440
+
3441
+ }
3442
+ catch (error) {
3443
+ Console.error("sendFeedbackData : " + error);
3444
+ }
3445
+ });
3446
+
3447
+ checkbox.addEventListener('change', function() {
3448
+ if (this.checked) {
3449
+ content.classList.add('zasdkqz-show');
3450
+ } else {
3451
+ content.classList.remove('zasdkqz-show');
3452
+ }
3453
+ });
3454
+
3455
+ descriptionField.addEventListener('input', feedback.validateFeedbackField);
3456
+
3457
+ feedbackTypes.forEach(function(type) {
3458
+ type.addEventListener("click", function() {
3459
+ feedbackTypes.forEach(function(t) {
3460
+ t.classList.remove("zasdk-selected");
3461
+ });
3462
+ this.classList.add("zasdk-selected");
3463
+ });
3464
+ });
3465
+
3466
+ feedback.selectedFiles = new DataTransfer();
3467
+ feedback.fileInput.onchange = function() {
3468
+ if(feedback.selectedFiles.files.length + this.files.length > 5) {
3469
+ feedback.handleStatusmsg(false, 'Maximum 5 attachments is allowed'); // no i18n
3470
+ feedback.fileInput.files = feedback.selectedFiles.files;
3471
+ this.files = feedback.selectedFiles.files;
3472
+ return;
3473
+ }
3474
+ if (this.files.length > 0) {
3475
+ let filesize = 0;
3476
+ for (let file of this.files) {
3477
+ filesize = filesize + file.size;
3478
+ if(filesize > 10485760) {
3479
+ feedback.handleStatusmsg(false, 'Maximum limit reached. The total file size cannot exceed 10MB.'); // no i18n
3480
+ this.files = feedback.selectedFiles.files;
3481
+ return;
3482
+ }
3483
+ else {
3484
+ feedback.selectedFiles.items.add(file); // Add new files
3485
+ }
3486
+ }
3487
+ this.files = feedback.selectedFiles.files; // Update input with all files
3488
+ feedback.fileAttachment(); // Update UI
3489
+ }
3490
+ else if (feedback.selectedFiles.files.length > 0) {
3491
+ // Restore previous files if the user cancels
3492
+ this.files = feedback.selectedFiles.files;
3493
+ }
3494
+
3495
+ }
3496
+
3497
+ feedback.handleStatusmsg();
3498
+ feedback.updateLeftCount();
3499
+ },
3500
+ updateLeftCount : function() {
3501
+ document.querySelectorAll("[reqname]").forEach(function (elem) {
3502
+ let val = elem.value;
3503
+ let maxLength = parseInt(elem.getAttribute("maxlength"), 10);
3504
+ let counterElem = elem.parentElement.querySelector(".zaleftcharcnt");
3505
+
3506
+ if (counterElem) {
3507
+ counterElem.textContent = maxLength - val.length; // Update remaining count
3508
+ }
3509
+ });
3510
+ },
3511
+
3512
+ handleStatusmsg : function(isSuccess, stsmsg){
3513
+ var modal = document.getElementById("zasdkFeedbackModal");
3514
+ if(isEmpty(modal)){
3515
+ return;
3516
+ }
3517
+ var stscnt = modal.querySelector("#zasdksubmitstatus");
3518
+ var submitbtn = modal.querySelector("#zasdk_submit_btn");
3519
+ var submitbtncnt = modal.querySelector("#zasdk_submit_btnsubcnt");
3520
+
3521
+ var successmsgclass = '';
3522
+ var successmsg = '';
3523
+ stscnt.style.display = "none";
3524
+ if(!isEmpty(isSuccess)){
3525
+ successmsgclass = isSuccess ? 'zasdkstssucs' : 'zasdkstserror';
3526
+ successmsg = !isEmpty(stsmsg) ? stsmsg : '';
3527
+
3528
+
3529
+ stscnt.textContent = successmsg;
3530
+ stscnt.classList.add(successmsgclass);
3531
+
3532
+ stscnt.style.display = "block";
3533
+ submitbtncnt.classList.remove('zasdkbtndisabled');
3534
+ submitbtn.disabled = false;
3535
+ submitbtn.value = 'Send';
3536
+ }
3537
+ },
3538
+ handleErrorQuartzEvent : function(event){
3539
+ let errorType = event.detail.error.name;
3540
+ let errorMessage = event.detail.error.message;
3541
+ if(errorType == QuartzConstants.ErrorType.RECORDING_INTERRUPTED || errorType == QuartzConstants.ErrorType.FEEDBACK_TIMEOUT){
3542
+ feedback.stopRecording();
3543
+ }
3544
+ feedback.handleStatusmsg(false, errorMessage);
3545
+ },
3546
+ bindQuartzEvent : function(event){
3547
+ let feedbackState = event.detail.feedbackState;
3548
+ if(feedbackState == QuartzConstants.FeedbackState.RECORDING_IN_PROGRESS){
3549
+ var feedbacktimer = document.getElementById("zasdkrectimer");
3550
+ if(!isEmpty(feedbacktimer)){
3551
+ feedbacktimer.remove();
3552
+ }
3553
+ const tempDiv = document.createElement("div");
3554
+ tempDiv.innerHTML = feedback.timerHtml;
3555
+ const node = tempDiv.firstElementChild;
3556
+ document.body.appendChild(node);
3557
+ var feedbacktimer = document.getElementById("zasdkrectimer");
3558
+ feedback.timerDisplay = feedbacktimer.querySelector('#zasdkTimerDisplay');
3559
+ feedback.recordButton = feedbacktimer;
3560
+ feedback.TOTAL_TIME = 5 * 60; // 5 minutes in seconds
3561
+ feedback.remainingTime = feedback.TOTAL_TIME;
3562
+ feedback.isRecording = true;
3563
+ feedback.timerInterval;
3564
+ feedback.recordButton.addEventListener('click', feedback.toggleRecording);
3565
+ feedback.startRecording();
3566
+ feedback.toggleWindowAnimation();
3567
+ }
3568
+ },
3569
+ close : function(){
3570
+ var modal = document.getElementById("zasdkFeedbackModal");
3571
+ document.removeEventListener('quartz-feedback-state', feedback.bindQuartzEvent);
3572
+ Quartz.clearFeedback();
3573
+ if(!isEmpty(modal)){
3574
+ modal.remove();
3575
+ }
3576
+ },
3577
+ toggleWindowAnimation : function() {
3578
+ var modal = document.getElementById("zasdkFeedbackModal");
3579
+ const box = modal.querySelector('#zasdkFeedbackconatiner');
3580
+
3581
+ if (feedback.isHidden) {
3582
+ modal.style.display = 'block';
3583
+ setTimeout(() => {
3584
+ box.style.transform = 'scale(1)';
3585
+ feedback.validateFeedbackField();
3586
+ }, 10);
3587
+ feedback.isHidden = false;
3588
+ feedback.isScaled = false;
3589
+ }
3590
+ else if (feedback.isScaled) {
3591
+ modal.style.display = 'none';
3592
+ feedback.isHidden = true;
3593
+ }
3594
+ else {
3595
+ box.style.transform = 'scale(0)';
3596
+ feedback.isScaled = true;
3597
+ }
3598
+ },
3599
+ fileAttachment : function(){
3600
+
3601
+ var uploadedFiles = feedback.fileInput.files;
3602
+ feedback.uploadTotalSize = 0;
3603
+ feedback.MaxFileSize = 10 * 1024 * 1024; //10 MB
3604
+
3605
+ var modal = document.getElementById("zasdkFeedbackModal");
3606
+ var sizeInfo = modal.querySelector("#zasdkfilesizeinfo");
3607
+ var fileUl = modal.querySelector("#zasakfileList");
3608
+ sizeInfo.textContent = "";
3609
+
3610
+
3611
+ if ((uploadedFiles.length) > 5) {
3612
+ isLimitExceeded = true;
3613
+ feedback.fileLimitExceeded(false, true);
3614
+ feedback.validateFeedbackField();
3615
+ return;
3616
+ }
3617
+
3618
+ fileUl.innerHTML = '';
3619
+
3620
+ if (uploadedFiles.length > 0) {
3621
+ var totalSize = 0;
3622
+
3623
+ for (var i = 0; i < uploadedFiles.length; i++) {
3624
+
3625
+ var fileName = uploadedFiles[i].name;
3626
+ var fName = fileName.substring(0, fileName.lastIndexOf('.'));
3627
+ var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
3628
+
3629
+ var bytes = uploadedFiles[i].size;
3630
+ totalSize += bytes;
3631
+
3632
+ var fileLi = feedback.elementCreator("li");
3633
+ fileLi.title = fileName;
3634
+
3635
+ var span1 = feedback.elementCreator("span");
3636
+ span1.className = "zaskFdBckFlName";
3637
+ span1.innerText = (fName.length > 10) ? fName.slice(0, 3) + "..." + fName.slice(-3) : fName;
3638
+
3639
+ var span2 = feedback.elementCreator("span");
3640
+ span2.innerText = "." + extension;
3641
+
3642
+ var span3 = feedback.elementCreator("span");
3643
+ span3.className = "zasdkFdBckFlCls";
3644
+ span3.setAttribute("data-name", fileName);
3645
+ span3.innerHTML = "&times;"; // No i18N
3646
+
3647
+
3648
+ fileLi.appendChild(span1);
3649
+ fileLi.appendChild(span2);
3650
+ fileLi.appendChild(span3);
3651
+ fileUl.appendChild(fileLi);
3652
+
3653
+ }
3654
+ var limitReached = totalSize > feedback.MaxFileSize;
3655
+ feedback.fileLimitExceeded(limitReached);
3656
+
3657
+ sizeInfo.textContent = `${feedback.formatFileSize(totalSize)} Used / 10 MB`;
3658
+
3659
+ const clearFile = modal.querySelectorAll('.zasdkFdBckFlCls');
3660
+ Array.from(clearFile).forEach(function(element) {
3661
+ element.addEventListener('click', function() {
3662
+ feedback.handleStatusmsg();
3663
+ feedback.removeFileFromList(this);
3664
+ feedback.fileAttachment();
3665
+ });
3666
+ });
3667
+ }
3668
+ feedback.validateFeedbackField();
3669
+ },
3670
+ elementCreator : function(elType){
3671
+ return document.createElement(elType);
3672
+ },
3673
+ fileLimitExceeded : function(limitReached, filesLengthReached) {
3674
+ var infoMsg = "";
3675
+ if(filesLengthReached || limitReached)
3676
+ {
3677
+ infoMsg = filesLengthReached ? 'Maximum 5 attachments is allowed' : 'Maximum limit reached. The total file size cannot exceed 10MB.';
3678
+ }
3679
+ feedback.handleStatusmsg(false, infoMsg);
3680
+ },
3681
+ removeFileFromList: function(elem) {
3682
+ const fileName = elem.getAttribute('data-name');
3683
+
3684
+ // Find the index of the element in the file preview list
3685
+ const indexToRemove = Array.from(elem.parentNode.parentNode.children).indexOf(elem.parentNode);
3686
+
3687
+ elem.parentNode.remove(); // Remove the file preview
3688
+
3689
+ let newFiles = new DataTransfer();
3690
+ feedback.selectedFiles = new DataTransfer();
3691
+
3692
+ for (let i = 0; i < feedback.fileInput.files.length; i++) {
3693
+ if (i !== indexToRemove) {
3694
+ newFiles.items.add(feedback.fileInput.files[i]);
3695
+ feedback.selectedFiles.items.add(feedback.fileInput.files[i]);
3696
+ }
3697
+ }
3698
+
3699
+ feedback.fileInput.files = newFiles.files;
3700
+ },
3701
+ formatFileSize : function(bytes) {
3702
+ if (bytes === 0) return '0 KB';
3703
+
3704
+ const k = 1024;
3705
+ const sizeInKB = bytes / k;
3706
+ const sizeInMB = bytes / (k * k);
3707
+ const sizeInGB = bytes / (k * k * k);
3708
+
3709
+ if (sizeInGB >= 1) {
3710
+ return sizeInGB.toFixed(2) + ' GB';
3711
+ } else if (sizeInMB >= 1) {
3712
+ return sizeInMB.toFixed(2) + ' MB';
3713
+ } else {
3714
+ return sizeInKB.toFixed(2) + ' KB';
3715
+ }
3716
+ },
3717
+ toggleRecording : function() {
3718
+ feedback.isRecording = !feedback.isRecording;
3719
+
3720
+ if (feedback.isRecording) {
3721
+ feedback.startRecording();
3722
+ } else {
3723
+ feedback.stopRecording();
3724
+ }
3725
+ },
3726
+ startRecording : function() {
3727
+ if (!feedback.timerInterval) {
3728
+ feedback.timerInterval = setInterval(feedback.updateTimer, 1000);
3729
+ }
3730
+ },
3731
+ stopRecording : function() {
3732
+ var modal = document.getElementById("zasdkFeedbackModal");
3733
+ clearInterval(feedback.timerInterval);
3734
+ feedback.timerInterval = null;
3735
+ document.querySelector("#zasdkrectimer").remove();
3736
+ feedback.toggleWindowAnimation();
3737
+ modal.querySelector("#editRecordedVideo").style.display = "block";
3738
+ modal.querySelector("#zasdkreccheckcnt").style.display = "none";
3739
+
3740
+ let videoElem = modal.querySelector('#previewVideoElemId');
3741
+ Quartz.endRecording();
3742
+ Quartz.setVideoContent(videoElem);
3743
+ },
3744
+ submitRecording : function(){
3745
+ var modal = document.getElementById("zasdkFeedbackModal");
3746
+ let testEmail = 'user@example.com',
3747
+ testMessage = '-',
3748
+ zqrecid = '',
3749
+ userEmail = (_user_info.hasOwnProperty('userid') && !isEmpty(_user_info.userid)) ? _user_info.userid : testEmail,
3750
+ subject = modal.querySelector('#zasdkDescription').value.trim() || testMessage,
3751
+ emailRegex = /^[\w]([\w\-\.\+\'=]*)@([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,22}$/;
3752
+ userEmail = (emailRegex.test(userEmail) && opt_in_status() == AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII) ? userEmail : testEmail;
3753
+
3754
+ // Submit the recording
3755
+ Quartz.submitRecording({
3756
+ email: userEmail,
3757
+ subject: subject,
3758
+ })
3759
+ .then((response) => {
3760
+ zqrecid = !isEmptyObj(response) && response.hasOwnProperty('feedbackLink') ? response.feedbackLink : '';
3761
+ let dataobj = {},
3762
+ seltype = modal.querySelector('.zasdk-selected').getAttribute('data-type'),
3763
+ feedtype = !isEmpty(seltype) ? feedback.typeObj[seltype] : 1,
3764
+ fileInput = modal.querySelector("#zasdkFileInput"),
3765
+ files = fileInput.files,
3766
+ message = subject;
3767
+ dataobj = {
3768
+ type : feedtype,
3769
+ message : message,
3770
+ attachment : files,
3771
+ quartzid : zqrecid
3772
+ }
3773
+ sendFeedbackData(dataobj);
3774
+ });
3775
+ },
3776
+ updateTimer : function() {
3777
+ if (feedback.remainingTime > 0) {
3778
+ feedback.remainingTime--;
3779
+ feedback.updateDisplay();
3780
+ } else {
3781
+ feedback.stopRecording();
3782
+ }
3783
+ },
3784
+ updateDisplay : function() {
3785
+ const minutes = Math.floor(feedback.remainingTime / 60);
3786
+ const seconds = feedback.remainingTime % 60;
3787
+ feedback.timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
3788
+ },
3789
+ validateFeedbackField : function()
3790
+ {
3791
+ feedback.updateLeftCount();
3792
+ var modal = document.getElementById("zasdkFeedbackModal");
3793
+ const descriptionField = modal.querySelector('#zasdkDescription');
3794
+ const attachmentField = modal.querySelector('#zasdkFileInput');
3795
+ const videoElement = modal.querySelector('#previewVideoElemId');
3796
+ const sendButton = modal.querySelector('#zasdk_submit_btn');
3797
+
3798
+ const descriptionFilled = descriptionField && descriptionField.value.trim() !== '' && descriptionField.value.trim().length > 0;
3799
+ const attachmentSelected = attachmentField && attachmentField.files && attachmentField.files.length > 0;
3800
+ const videoVisible = videoElement && videoElement.offsetParent !== null; // Check if visible
3801
+
3802
+ const errorElements = modal.querySelectorAll('.zasdkmaxerror');
3803
+ const hasErrors = feedback.getVisibleErrorCount(errorElements) > 0;
3804
+
3805
+ if (!hasErrors && descriptionFilled) {
3806
+ sendButton.classList.remove('zasdkbtndisableclr');
3807
+ feedback.handleStatusmsg();
3808
+ } else {
3809
+ sendButton.classList.add('zasdkbtndisableclr');
3810
+ }
3811
+ },
3812
+ getVisibleErrorCount : function(elements) {
3813
+ return Array.from(elements).filter(element => element.offsetParent !== null).length;
3814
+ }
3815
+ }
3816
+
3817
+ })(this, document);