@pendo/agent 2.280.1 → 2.281.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.
@@ -3901,8 +3901,8 @@ var SERVER = '';
3901
3901
  var ASSET_HOST = '';
3902
3902
  var ASSET_PATH = '';
3903
3903
  var DESIGNER_ENV = '';
3904
- var VERSION = '2.280.1_';
3905
- var PACKAGE_VERSION = '2.280.1';
3904
+ var VERSION = '2.281.0_';
3905
+ var PACKAGE_VERSION = '2.281.0';
3906
3906
  var LOADER = 'xhr';
3907
3907
  /* eslint-enable agent-eslint-rules/no-gulp-env-references */
3908
3908
  /**
@@ -8699,6 +8699,13 @@ function dom(selection, context) {
8699
8699
  });
8700
8700
  self.context = context;
8701
8701
  self.length = nodes.length;
8702
+ if (typeof Symbol !== 'undefined' && Symbol.iterator) {
8703
+ self[Symbol.iterator] = function* () {
8704
+ for (const node of nodes) {
8705
+ yield node;
8706
+ }
8707
+ };
8708
+ }
8702
8709
  return self;
8703
8710
  }
8704
8711
 
@@ -11264,6 +11271,7 @@ class ElementGetter {
11264
11271
  if (el && isInDoc)
11265
11272
  return el;
11266
11273
  if (el && !isInDoc) {
11274
+ this.teardown(el);
11267
11275
  return undefined;
11268
11276
  }
11269
11277
  el = dom(this.cssSelector)[0];
@@ -11288,15 +11296,14 @@ class ElementGetter {
11288
11296
  el.addEventListener(event, (e) => this.onEvent(e));
11289
11297
  }
11290
11298
  }
11291
- this.listeners[event] = this.listeners[event]
11292
- ? this.listeners[event].push(callback) : [].concat(callback);
11299
+ this.listeners[event] = this.listeners[event] || [];
11300
+ this.listeners[event].push(callback);
11293
11301
  }
11294
11302
  onEvent(evt) {
11295
11303
  const type = evt.type;
11296
11304
  _.each(this.listeners[type], (cb) => cb(evt));
11297
11305
  }
11298
- teardown() {
11299
- const el = this.get();
11306
+ teardown(el = this.get()) {
11300
11307
  if (el) {
11301
11308
  _.each(this.events, (evtType) => el.removeEventListener(evtType, this.onEvent));
11302
11309
  }
@@ -23862,13 +23869,16 @@ var onGuideDismissed = function (evt, step) {
23862
23869
  }
23863
23870
  };
23864
23871
  /**
23865
- * Snoozes the current guide. Guide will redisplay after snooze length, or default
23866
- * of one day. If another guide is eligible to be shown, it will show after snooze.
23872
+ * Snoozes the current guide. If another guide is eligible to be shown automatically, it will show after snooze.
23873
+ * Guide will redisplay after one day by default, or a custom `snoozeDuration` can be set in milliseconds as the third argument.
23867
23874
  *
23868
23875
  * @access public
23869
23876
  * @category Guides
23870
23877
  * @example
23871
23878
  * pendo.onGuideSnoozed()
23879
+ * @example
23880
+ * pendo.onGuideSnoozed(null, null, 30 * 60 * 1000)
23881
+ * // snoozes guide for 30 minutes
23872
23882
  */
23873
23883
  var onGuideSnoozed = function (evt, step, snoozeDuration) {
23874
23884
  log.info('onGuideSnoozed called', { 'contexts': ['guides'] });
@@ -36574,7 +36584,7 @@ class SessionManager {
36574
36584
  }
36575
36585
  isExpired(sessionInfo, timestamp) {
36576
36586
  const prevLastEmitTime = _.get(sessionInfo, 'timestamp');
36577
- if (prevLastEmitTime && timestamp - prevLastEmitTime > this.inactivityDuration) {
36587
+ if (!this.pendo._.isNumber(prevLastEmitTime) || timestamp - prevLastEmitTime > this.inactivityDuration) {
36578
36588
  return true;
36579
36589
  }
36580
36590
  return false;
@@ -36843,7 +36853,6 @@ function calculateReferrerValue({ referrer, currentHost }) {
36843
36853
  const referrerHost = referrerUrl.hostname;
36844
36854
  let referrerValue = 'self';
36845
36855
  if (referrerHost !== currentHost) {
36846
- // TODO: what about subdomains or other variations you own that you'd rather consider 'self' instead?
36847
36856
  referrerValue = referrerHost === null || referrerHost === void 0 ? void 0 : referrerHost.toLowerCase();
36848
36857
  }
36849
36858
  return referrerValue;
@@ -36857,60 +36866,65 @@ const paidMediumRegex = new RegExp('^(.*cp.*|ppc|retargeting)$');
36857
36866
  // add other email-related values here, if needed
36858
36867
  const emailRegex = new RegExp('(.*email.*|.*e_mail.*|.*e mail.*|.*e-mail.*)', 'i');
36859
36868
  const channels = [
36860
- ['Email', ({ source, medium, referrer }) => {
36861
- return referrer === 'direct' &&
36862
- (medium.match(emailRegex) !== null ||
36863
- source.match(emailRegex) !== null ||
36864
- medium === 'newsletter');
36869
+ [undefined, ({ medium, source, referrer, gclid, fbclid }) => {
36870
+ return !medium && !source && referrer === 'self' && !gclid && !fbclid;
36871
+ }],
36872
+ ['Direct', ({ source, medium, referrer }) => {
36873
+ return !medium && (!source || source === 'direct') && referrer === 'direct';
36874
+ }],
36875
+ ['Cross-network', ({ source, medium }) => {
36876
+ return medium === 'cross-network' || source.match(crossNetworkPlatformsRegex);
36865
36877
  }],
36866
36878
  ['Paid Search', ({ source, medium, gclid, _ }) => {
36867
36879
  return gclid ||
36868
36880
  medium === 'paid-search' ||
36869
36881
  (medium.match(paidMediumRegex) && _.contains(searchProviders, source));
36870
36882
  }],
36871
- ['Organic Search', ({ source, medium, referrer, _ }) => {
36872
- return (medium === 'organic' && _.contains(searchProviders, source)) || (!medium && isReferral(searchProviderRegex, referrer));
36873
- }],
36874
- ['Display', ({ medium, _ }) => {
36875
- return _.contains(['display', 'cpm', 'banner', 'interstitial', 'expandable', 'programmatic'], medium);
36876
- }],
36877
36883
  ['Paid Social', ({ source, medium, fbclid, _ }) => {
36878
36884
  return fbclid ||
36879
36885
  medium === 'paid-social' ||
36880
36886
  (medium.match(paidMediumRegex) && _.contains(socialMediaSources, source));
36881
36887
  }],
36888
+ ['Paid Video', ({ medium, source, _ }) => {
36889
+ return medium == 'paid-video' ||
36890
+ (!medium.match(paidMediumRegex) && _.contains(videoSites, source));
36891
+ }],
36892
+ ['Paid Shopping', ({ medium }) => {
36893
+ return medium == 'shopping' || medium == 'paid-shopping';
36894
+ }],
36895
+ ['Display', ({ medium, _ }) => {
36896
+ return _.contains(['display', 'cpm', 'banner', 'interstitial', 'expandable', 'programmatic'], medium);
36897
+ }],
36898
+ ['Paid Other', ({ medium, source, _ }) => {
36899
+ // Must be after all other paid channels in the list
36900
+ return medium == 'paid-other' ||
36901
+ medium.match(paidMediumRegex);
36902
+ }],
36903
+ ['Organic Search', ({ source, medium, referrer, _ }) => {
36904
+ return (medium === 'organic' && _.contains(searchProviders, source)) || (!medium && isReferral(searchProviderRegex, referrer));
36905
+ }],
36882
36906
  ['Organic Social', ({ source, medium, referrer, _ }) => {
36883
36907
  return (!medium && isReferral(socialMediaRegex, referrer)) ||
36884
36908
  medium === 'social' ||
36885
36909
  _.contains(socialMediaSources, source);
36886
36910
  }],
36887
- ['Paid Shopping', ({ medium }) => {
36888
- return medium == 'shopping' || medium == 'paid-shopping';
36911
+ ['Organic Video', ({ medium, referrer }) => {
36912
+ return medium === 'video' ||
36913
+ (!medium && isReferral(videoRegex, referrer));
36889
36914
  }],
36890
36915
  ['Organic Shopping', ({ medium, referrer }) => {
36891
36916
  return medium === 'organic-shopping' ||
36892
36917
  (!medium && isReferral(shoppingRegex, referrer));
36893
36918
  }],
36894
- ['Paid Video', ({ medium, source, _ }) => {
36895
- return medium == 'paid-video' ||
36896
- (!medium.match(paidMediumRegex) && _.contains(videoSites, source));
36897
- }],
36898
- ['Organic Video', ({ medium, referrer }) => {
36899
- return medium === 'video' ||
36900
- (!medium && isReferral(videoRegex, referrer));
36919
+ ['Email', ({ source, medium, referrer }) => {
36920
+ return referrer === 'direct' &&
36921
+ (medium.match(emailRegex) !== null ||
36922
+ source.match(emailRegex) !== null ||
36923
+ medium === 'newsletter');
36901
36924
  }],
36902
36925
  ['Affiliates', ({ medium }) => {
36903
36926
  return medium === 'affiliate' || medium === 'partner';
36904
36927
  }],
36905
- ['Audio', ({ medium }) => {
36906
- return medium === 'podcast' || medium === 'audio';
36907
- }],
36908
- ['Cross-network', ({ source, medium }) => {
36909
- return medium === 'cross-network' || source.match(crossNetworkPlatformsRegex);
36910
- }],
36911
- ['Other Advertising', ({ medium }) => {
36912
- return medium === 'cpv' || medium === 'cpa';
36913
- }],
36914
36928
  ['SMS', ({ source, medium }) => {
36915
36929
  return source === 'sms' || medium === 'sms';
36916
36930
  }],
@@ -36919,20 +36933,15 @@ const channels = [
36919
36933
  medium.indexOf('notification') >= 0 ||
36920
36934
  source === 'firebase';
36921
36935
  }],
36936
+ ['Audio', ({ medium }) => {
36937
+ return medium === 'podcast' || medium === 'audio';
36938
+ }],
36922
36939
  ['Referral', ({ medium, referrer }) => {
36923
36940
  return medium === 'referral' ||
36924
36941
  (!!referrer && referrer !== 'self' && referrer !== 'direct');
36925
36942
  }],
36926
- ['Paid Other', ({ medium, source, _ }) => {
36927
- // Must be after all other paid channels in the list
36928
- return medium == 'paid-other' ||
36929
- medium.match(paidMediumRegex);
36930
- }],
36931
- ['Other', ({ medium }) => {
36932
- return !!medium;
36933
- }],
36934
- ['Direct', ({ source, medium }) => {
36935
- return !medium && !source;
36943
+ ['Other', ({ medium, source }) => {
36944
+ return !!medium || !!source;
36936
36945
  }]
36937
36946
  ];
36938
36947
  const WEB_ANALYTICS_STORAGE_KEY = 'utm';
@@ -36974,39 +36983,35 @@ class WebAnalytics {
36974
36983
  const locationUrl = new URL(url);
36975
36984
  const currentHost = locationUrl.hostname;
36976
36985
  const referrerValue = calculateReferrerValue({ referrer, currentHost });
36977
- // Check if this is a self-referral.
36978
- if (referrerValue !== 'self') {
36979
- const parsedUrl = this.api.util.parseUrl(url);
36980
- if (!parsedUrl)
36981
- return {};
36982
- const { query } = parsedUrl;
36983
- // It is not a self referral, therefore we should calculate the channel and track the visit
36984
- const source = query.utm_source;
36985
- const medium = query.utm_medium;
36986
- const campaign = query.utm_campaign;
36987
- const term = query.utm_term;
36988
- const content = query.utm_content;
36989
- const gclid = query.gclid;
36990
- const fbclid = query.fbclid;
36991
- if (this.pendo._.compact([source, medium, campaign, term, content, gclid]).length) {
36992
- const utm = {
36993
- source,
36994
- medium,
36995
- campaign,
36996
- term,
36997
- content
36998
- };
36999
- utm.channel = this.calculateUtmChannel({ referrerValue, utm, gclid, fbclid });
37000
- if (referrer && this.api.ConfigReader.get(CAPTURE_REFERRER_CONFIG_KEY)) {
37001
- utm.referrer = referrer;
37002
- }
37003
- else if (referrerValue !== 'direct') {
37004
- utm.referrer = referrerValue;
37005
- }
37006
- return utm;
37007
- }
36986
+ const parsedUrl = this.api.util.parseUrl(url);
36987
+ if (!parsedUrl)
36988
+ return {};
36989
+ const { query } = parsedUrl;
36990
+ const source = query.utm_source;
36991
+ const medium = query.utm_medium;
36992
+ const campaign = query.utm_campaign;
36993
+ const term = query.utm_term;
36994
+ const content = query.utm_content;
36995
+ const gclid = query.gclid;
36996
+ const fbclid = query.fbclid;
36997
+ const utm = {
36998
+ source,
36999
+ medium,
37000
+ campaign,
37001
+ term,
37002
+ content
37003
+ };
37004
+ utm.channel = this.calculateUtmChannel({ referrerValue, utm, gclid, fbclid });
37005
+ if (referrer && this.api.ConfigReader.get(CAPTURE_REFERRER_CONFIG_KEY)) {
37006
+ utm.referrer = referrer;
37008
37007
  }
37009
- return {};
37008
+ else if (referrerValue === 'self') {
37009
+ utm.referrer = currentHost;
37010
+ }
37011
+ else if (referrerValue !== 'direct') {
37012
+ utm.referrer = referrerValue;
37013
+ }
37014
+ return utm;
37010
37015
  }
37011
37016
  calculateUtmChannel({ referrerValue, utm, gclid, fbclid }) {
37012
37017
  var _a, _b;
@@ -37024,11 +37029,9 @@ class WebAnalytics {
37024
37029
  if (channelPair) {
37025
37030
  return channelPair[0];
37026
37031
  }
37027
- return 'Direct';
37028
37032
  }
37029
37033
  storeParameters(params, storage) {
37030
37034
  if (this.pendo._.size(params) > 0) {
37031
- this.utm = params;
37032
37035
  storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params), undefined, false, true, this.suffix);
37033
37036
  }
37034
37037
  }
@@ -37044,16 +37047,20 @@ class WebAnalytics {
37044
37047
  unsubscribe();
37045
37048
  }
37046
37049
  this.subscriptions.length = 0;
37047
- this.utm = null;
37048
37050
  }
37049
37051
  addUtmToEvent(event) {
37050
- if (this.utm) {
37052
+ let utm;
37053
+ try {
37054
+ utm = this.loadParameters(this.api.agentStorage);
37055
+ }
37056
+ catch (e) {
37057
+ }
37058
+ if (utm) {
37051
37059
  const capturedEvent = event.data[0];
37052
- capturedEvent.utm = this.utm;
37060
+ capturedEvent.utm = utm;
37053
37061
  }
37054
37062
  }
37055
37063
  sessionChanged() {
37056
- this.utm = null;
37057
37064
  const agentStorage = this.api.agentStorage;
37058
37065
  agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY);
37059
37066
  agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, this.suffix);
@@ -37617,7 +37624,7 @@ class DOMPrompt {
37617
37624
  // capture value from copy / paste
37618
37625
  this.capturePromptValue();
37619
37626
  }, true);
37620
- this.inputEl.addEventListener('keydown', (evt) => {
37627
+ this.inputEl.addEventListener('keyup', (evt) => {
37621
37628
  const wasEnterKey = evt.code === 'Enter';
37622
37629
  this.capturePromptValue(wasEnterKey);
37623
37630
  if (wasEnterKey) {
@@ -37625,6 +37632,7 @@ class DOMPrompt {
37625
37632
  }
37626
37633
  }, true);
37627
37634
  this.submitEl.addEventListener('click', () => {
37635
+ this.capturePromptValue();
37628
37636
  this.submit(this.latestPromptValue);
37629
37637
  }, true);
37630
37638
  this.promptContainer = new this.dom.Observer();
@@ -37638,14 +37646,15 @@ class DOMPrompt {
37638
37646
  }
37639
37647
  /*
37640
37648
  * Genernally we want to capture the value from "input" but there can be implementation
37641
- * dependent scenarios where the input's value has already been cleared by the time we
37642
- * get the event handler is called. So, in that case, we don't want to throw our saved value out.
37649
+ * dependent scenarios where the input's value has already been cleared by the time
37650
+ * the event handler is called. So, in that case, we don't want to throw our saved value out.
37643
37651
  */
37644
37652
  capturePromptValue(onlyUpdateIfNotEmpty = false) {
37645
37653
  const tmp = this.getPromptValue();
37646
37654
  if (tmp || !onlyUpdateIfNotEmpty) {
37647
37655
  this.latestPromptValue = tmp;
37648
37656
  }
37657
+ return tmp;
37649
37658
  }
37650
37659
  getPromptValue() {
37651
37660
  return this.inputEl.getText();
@@ -49310,6 +49319,11 @@ function initCanvasWebGLMutationObserver(cb, win, blockClass, blockSelector) {
49310
49319
  handlers.forEach((h) => h());
49311
49320
  };
49312
49321
  }
49322
+ function WorkerWrapper(options) {
49323
+ return function() {
49324
+ return { onerror: null, onmessage: null };
49325
+ };
49326
+ }
49313
49327
  class CanvasManager {
49314
49328
  constructor(options) {
49315
49329
  __publicField(this, "pendingCanvasMutations", /* @__PURE__ */ new Map());
@@ -49340,6 +49354,10 @@ class CanvasManager {
49340
49354
  this.mirror = options.mirror;
49341
49355
  if (recordCanvas && sampling === "all")
49342
49356
  this.initCanvasMutationObserver(win, blockClass, blockSelector);
49357
+ if (recordCanvas && typeof sampling === "number")
49358
+ this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, {
49359
+ dataURLOptions
49360
+ });
49343
49361
  }
49344
49362
  reset() {
49345
49363
  this.pendingCanvasMutations.clear();
@@ -49357,6 +49375,100 @@ class CanvasManager {
49357
49375
  unlock() {
49358
49376
  this.locked = false;
49359
49377
  }
49378
+ initCanvasFPSObserver(fps, win, blockClass, blockSelector, options) {
49379
+ const canvasContextReset = initCanvasContextObserver(
49380
+ win,
49381
+ blockClass,
49382
+ blockSelector,
49383
+ true
49384
+ );
49385
+ const snapshotInProgressMap = /* @__PURE__ */ new Map();
49386
+ const worker = new WorkerWrapper();
49387
+ worker.onmessage = (e2) => {
49388
+ const { id } = e2.data;
49389
+ snapshotInProgressMap.set(id, false);
49390
+ if (!("base64" in e2.data)) return;
49391
+ const { base64, type, width, height } = e2.data;
49392
+ this.mutationCb({
49393
+ id,
49394
+ type: CanvasContext["2D"],
49395
+ commands: [
49396
+ {
49397
+ property: "clearRect",
49398
+ // wipe canvas
49399
+ args: [0, 0, width, height]
49400
+ },
49401
+ {
49402
+ property: "drawImage",
49403
+ // draws (semi-transparent) image
49404
+ args: [
49405
+ {
49406
+ rr_type: "ImageBitmap",
49407
+ args: [
49408
+ {
49409
+ rr_type: "Blob",
49410
+ data: [{ rr_type: "ArrayBuffer", base64 }],
49411
+ type
49412
+ }
49413
+ ]
49414
+ },
49415
+ 0,
49416
+ 0
49417
+ ]
49418
+ }
49419
+ ]
49420
+ });
49421
+ };
49422
+ const timeBetweenSnapshots = 1e3 / fps;
49423
+ let lastSnapshotTime = 0;
49424
+ let rafId;
49425
+ const getCanvas = () => {
49426
+ const matchedCanvas = [];
49427
+ win.document.querySelectorAll("canvas").forEach((canvas) => {
49428
+ if (!isBlocked(canvas, blockClass, blockSelector, true)) {
49429
+ matchedCanvas.push(canvas);
49430
+ }
49431
+ });
49432
+ return matchedCanvas;
49433
+ };
49434
+ const takeCanvasSnapshots = (timestamp) => {
49435
+ if (lastSnapshotTime && timestamp - lastSnapshotTime < timeBetweenSnapshots) {
49436
+ rafId = requestAnimationFrame(takeCanvasSnapshots);
49437
+ return;
49438
+ }
49439
+ lastSnapshotTime = timestamp;
49440
+ getCanvas().forEach(async (canvas) => {
49441
+ var _a2;
49442
+ const id = this.mirror.getId(canvas);
49443
+ if (snapshotInProgressMap.get(id)) return;
49444
+ if (canvas.width === 0 || canvas.height === 0) return;
49445
+ snapshotInProgressMap.set(id, true);
49446
+ if (["webgl", "webgl2"].includes(canvas.__context)) {
49447
+ const context = canvas.getContext(canvas.__context);
49448
+ if (((_a2 = context == null ? void 0 : context.getContextAttributes()) == null ? void 0 : _a2.preserveDrawingBuffer) === false) {
49449
+ context.clear(context.COLOR_BUFFER_BIT);
49450
+ }
49451
+ }
49452
+ const bitmap = await createImageBitmap(canvas);
49453
+ worker.postMessage(
49454
+ {
49455
+ id,
49456
+ bitmap,
49457
+ width: canvas.width,
49458
+ height: canvas.height,
49459
+ dataURLOptions: options.dataURLOptions
49460
+ },
49461
+ [bitmap]
49462
+ );
49463
+ });
49464
+ rafId = requestAnimationFrame(takeCanvasSnapshots);
49465
+ };
49466
+ rafId = requestAnimationFrame(takeCanvasSnapshots);
49467
+ this.resetObservers = () => {
49468
+ canvasContextReset();
49469
+ cancelAnimationFrame(rafId);
49470
+ };
49471
+ }
49360
49472
  initCanvasMutationObserver(win, blockClass, blockSelector) {
49361
49473
  this.startRAFTimestamping();
49362
49474
  this.startPendingCanvasMutationFlusher();