@pendo/agent 2.339.1 → 2.340.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.
@@ -4046,8 +4046,8 @@ let SERVER = '';
4046
4046
  let ASSET_HOST = '';
4047
4047
  let ASSET_PATH = '';
4048
4048
  let DESIGNER_SERVER = '';
4049
- let VERSION = '2.339.1_';
4050
- let PACKAGE_VERSION = '2.339.1';
4049
+ let VERSION = '2.340.0_';
4050
+ let PACKAGE_VERSION = '2.340.0';
4051
4051
  let LOADER = 'xhr';
4052
4052
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4053
4053
  /**
@@ -4464,6 +4464,135 @@ class LogEmitter extends EventTarget {
4464
4464
  }
4465
4465
  const log = new LogEmitter();
4466
4466
 
4467
+ /** Key used to lookup the consent collection policy in the pendo configuration. */
4468
+ const CONSENT_COLLECTION = 'cookieConsentCollection';
4469
+ /** Key used to lookup the analytics events in the consent collection policy. */
4470
+ const ANALYTICS = 'analytics';
4471
+ /** Key used to lookup the guide analytics events in the consent collection policy. */
4472
+ const GUIDE_ANALYTICS = 'guideAnalytics';
4473
+ /** Key used to lookup the text collection mode in the consent collection policy. */
4474
+ const TEXT_COLLECTION = 'textCollection';
4475
+ const TEXT_COLLECTION_LEVELS = ['none', 'whitelist', 'all'];
4476
+ let enabledProducts = null;
4477
+ function getClassification() {
4478
+ return ConfigReader.get(CONSENT_COLLECTION) || {};
4479
+ }
4480
+ function toTierList(tiers) {
4481
+ if (_.isString(tiers))
4482
+ return [tiers];
4483
+ return _.isArray(tiers) ? tiers : [];
4484
+ }
4485
+ function warnOnUnconfiguredTiers(grantedTiers, classification) {
4486
+ _.each(grantedTiers, function (tier) {
4487
+ if (_.isUndefined(classification[tier])) {
4488
+ log.warn('Consent tier "' + tier + '" is not present in ' + CONSENT_COLLECTION + '.');
4489
+ }
4490
+ });
4491
+ }
4492
+ function moreTextPermissive(a, b) {
4493
+ return TEXT_COLLECTION_LEVELS.indexOf(b) > TEXT_COLLECTION_LEVELS.indexOf(a) ? b : a;
4494
+ }
4495
+ function orBoolean(a, b) {
4496
+ return a || Boolean(b);
4497
+ }
4498
+ function unionArrays(a, b) {
4499
+ return _.union(a, b);
4500
+ }
4501
+ // How a granted tier's value for a product combines with what's been resolved so far.
4502
+ // `initial` doubles as what an ungranted tier contributes -- an unlisted product falls
4503
+ // back to DEFAULT_RESOLVER, the plain "any granted tier turns it on" boolean rule.
4504
+ const RESOLVERS = {
4505
+ [TEXT_COLLECTION]: { initial: TEXT_COLLECTION_LEVELS[0], combine: moreTextPermissive },
4506
+ [ANALYTICS]: { initial: [], combine: unionArrays },
4507
+ [GUIDE_ANALYTICS]: { initial: [], combine: unionArrays }
4508
+ };
4509
+ const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
4510
+ /**
4511
+ * Resolves the granted tiers against the configured product classification.
4512
+ *
4513
+ * A product the classification mentions anywhere starts at its resolver's `initial` value
4514
+ * -- a tier the visitor hasn't opted into contributes that same `initial` value, so it has
4515
+ * no say over the product, even to explicitly allow something. Only a granted tier's actual
4516
+ * value is combined in, so opting into an additional tier only ever adds capability. Most
4517
+ * products are plain booleans (DEFAULT_RESOLVER); `textCollection`, `analytics`, and
4518
+ * `guideAnalytics` have their own entries in RESOLVERS since none of them is a boolean.
4519
+ *
4520
+ * @access private
4521
+ * @param {string | string[]} tiers tier or tiers the visitor consented to
4522
+ * @returns {object} map of product to its resolved value
4523
+ */
4524
+ function applyConsentTiers(tiers) {
4525
+ const cookieConsentCollection = getClassification();
4526
+ const consentedCookieTiers = toTierList(tiers);
4527
+ const resolved = {};
4528
+ warnOnUnconfiguredTiers(consentedCookieTiers, cookieConsentCollection);
4529
+ _.each(cookieConsentCollection, function (products, tier) {
4530
+ var isGranted = _.contains(consentedCookieTiers, tier);
4531
+ _.each(products, function (level, product) {
4532
+ var resolver = RESOLVERS[product] || DEFAULT_RESOLVER;
4533
+ var current = _.has(resolved, product) ? resolved[product] : resolver.initial;
4534
+ var contributed = isGranted ? level : resolver.initial;
4535
+ resolved[product] = resolver.combine(current, contributed);
4536
+ });
4537
+ });
4538
+ enabledProducts = resolved;
4539
+ return getEnabledProducts();
4540
+ }
4541
+ function getEnabledProducts() {
4542
+ return enabledProducts ? _.extend({}, enabledProducts) : null;
4543
+ }
4544
+ /**
4545
+ * A product is enabled unless a tier has explicitly resolved it to false -- with no cookie
4546
+ * consent configured at all, or a product no tier ever mentions, there's nothing to deny.
4547
+ *
4548
+ * @access private
4549
+ * @param {string} product
4550
+ * @returns {boolean}
4551
+ */
4552
+ function isProductEnabled(product) {
4553
+ if (!enabledProducts || _.isUndefined(enabledProducts[product]))
4554
+ return true;
4555
+ return enabledProducts[product] === true;
4556
+ }
4557
+ function isTrackingConsentDenied() {
4558
+ return !isProductEnabled('identity');
4559
+ }
4560
+ /**
4561
+ * Session replay is denied whenever tracking itself is denied (DNT) -- a visitor
4562
+ * who can't be identified shouldn't have their session recorded either -- or when a
4563
+ * tier explicitly denies the `replay` product on its own.
4564
+ *
4565
+ * @access private
4566
+ * @returns {boolean}
4567
+ */
4568
+ function isReplayConsentDenied() {
4569
+ return isTrackingConsentDenied() || !isProductEnabled('replay');
4570
+ }
4571
+ /**
4572
+ * Returns the consented text collection mode of the visitor.
4573
+ *
4574
+ * @access private
4575
+ * @returns {string} 'all', 'whitelist', or 'none'
4576
+ */
4577
+ function getTextCollectionMode() {
4578
+ if (!enabledProducts)
4579
+ return 'all';
4580
+ var mode = enabledProducts[TEXT_COLLECTION];
4581
+ return _.isUndefined(mode) ? 'all' : mode;
4582
+ }
4583
+ function hasConsentForProduct(type, product) {
4584
+ if (!enabledProducts)
4585
+ return true;
4586
+ var allowed = enabledProducts[product];
4587
+ return _.isUndefined(allowed) ? true : _.contains(allowed, type);
4588
+ }
4589
+ function hasConsentForAnalyticsEvent(type) {
4590
+ return !isTrackingConsentDenied() && hasConsentForProduct(type, ANALYTICS);
4591
+ }
4592
+ function hasConsentForGuideEvent(type) {
4593
+ return hasConsentForProduct(type, GUIDE_ANALYTICS);
4594
+ }
4595
+
4467
4596
  const allowKeys = function () {
4468
4597
  var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
4469
4598
  return _.isArray(allowKeys) ? _.indexBy(allowKeys) : allowKeys;
@@ -4529,7 +4658,7 @@ function createStorageShim(storageAccessor) {
4529
4658
  }
4530
4659
  }
4531
4660
  var pendoLocalStorage$3 = createStorageShim(function () { return window.localStorage; });
4532
- var pendoSessionStorage$2 = createStorageShim(function () { return window.sessionStorage; });
4661
+ var pendoSessionStorage$3 = createStorageShim(function () { return window.sessionStorage; });
4533
4662
 
4534
4663
  /**
4535
4664
  * Safely deserializes JSON if value is a valid json string.
@@ -4556,9 +4685,11 @@ var areCookiesEnabled = true;
4556
4685
  var allowLocalStorageOnly = function () {
4557
4686
  return ConfigReader.get('localStorageOnly');
4558
4687
  };
4559
- // This config setting really describes any persistence at all.
4688
+ /*
4689
+ * This config setting really describes any persistence at all.
4690
+ */
4560
4691
  var storageIsDisabled = function () {
4561
- return !!ConfigReader.get('disableCookies');
4692
+ return !!ConfigReader.get('disableCookies') || isTrackingConsentDenied();
4562
4693
  };
4563
4694
  function enableCookies(enabled) {
4564
4695
  areCookiesEnabled = enabled;
@@ -4914,7 +5045,7 @@ var agentStorage = (function () {
4914
5045
  }
4915
5046
  });
4916
5047
  }
4917
- const wrappedPendoSessionStorage = _.extend({}, pendoSessionStorage$2);
5048
+ const wrappedPendoSessionStorage = _.extend({}, pendoSessionStorage$3);
4918
5049
  wrappedPendoSessionStorage.setItem = wrapStorageWriteMethod(wrappedPendoSessionStorage.setItem, 'hasSession');
4919
5050
  const wrappedPendoLocalStorage = _.extend({}, pendoLocalStorage$3);
4920
5051
  wrappedPendoLocalStorage.setItem = wrapStorageWriteMethod(wrappedPendoLocalStorage.setItem, 'hasLocal');
@@ -5501,7 +5632,7 @@ else {
5501
5632
  };
5502
5633
  }
5503
5634
 
5504
- const pendoSessionStorage$1 = agentStorage.getSession();
5635
+ const pendoSessionStorage$2 = agentStorage.getSession();
5505
5636
  /*
5506
5637
  * EventTracer.addTracerIds - Returns a new copy of the original object with
5507
5638
  * additional, contextual IDs added that provide a means of tracing event output
@@ -5583,7 +5714,7 @@ function createEventTracer(window) {
5583
5714
  return tabId;
5584
5715
  }
5585
5716
  function getSessionStorage() {
5586
- return sessionStorageMemoryCache(MemoryStorage, pendoSessionStorage$1);
5717
+ return sessionStorageMemoryCache(MemoryStorage, pendoSessionStorage$2);
5587
5718
  }
5588
5719
  function tabIdChanged(callback) {
5589
5720
  if (!sniffer.addEventListener)
@@ -5771,6 +5902,8 @@ var Events = (function () {
5771
5902
  new EventType('ptm:paused', [LIFECYCLE]),
5772
5903
  new EventType('recording:unpaused', [LIFECYCLE]),
5773
5904
  new EventType('recording:paused', [LIFECYCLE]),
5905
+ new EventType('consent:changed', [LIFECYCLE]),
5906
+ new EventType('identifyRequested', [DEBUG, LIFECYCLE]),
5774
5907
  new EventType('childFrameJoined', [FRAMES]),
5775
5908
  new EventType('tabIdChanged:self', [DEBUG, LIFECYCLE])
5776
5909
  ];
@@ -5987,6 +6120,7 @@ var SIZE_UNIQUE_ID = 11;
5987
6120
  const VISITOR_ID_KEY = 'visitorId';
5988
6121
  const OLD_VISITOR_ID_KEY = 'oldVisitorId';
5989
6122
  const ACCOUNT_ID_KEY = 'accountId';
6123
+ const COOKIELESS_VISITOR_ID = '__cookieless_visitor__';
5990
6124
  /**
5991
6125
  * Generate a unique id string, usually for a visitor id. Default for anonymous visitor ids.
5992
6126
  *
@@ -6038,6 +6172,9 @@ var DEFAULT_VISITOR_ID = 'VISITOR-UNIQUE-ID';
6038
6172
  var isDefaultVisitor = function (vId) {
6039
6173
  return DEFAULT_VISITOR_ID === vId;
6040
6174
  };
6175
+ var isVisitorDNT = function (vId = pendo$1.visitorId) {
6176
+ return vId === COOKIELESS_VISITOR_ID;
6177
+ };
6041
6178
  var SUBACCOUNT_DELIMITER = '::';
6042
6179
  var isSubaccount = function (aId) {
6043
6180
  return new RegExp(SUBACCOUNT_DELIMITER).test(aId);
@@ -6085,6 +6222,9 @@ function generate_anonymous_visitor_id() {
6085
6222
  return generate_unique_id(TEMP_PREFIX);
6086
6223
  }
6087
6224
  function persistVisitorId(visitorId) {
6225
+ if (isTrackingConsentDenied()) {
6226
+ visitorId = COOKIELESS_VISITOR_ID;
6227
+ }
6088
6228
  if (pendo$1.visitorId === visitorId)
6089
6229
  return visitorId;
6090
6230
  if (shouldPersist()) {
@@ -6153,6 +6293,10 @@ var set_visitor_id = function (new_visitor_id) {
6153
6293
  log.info('Invalid visitor id ' + new_visitor_id);
6154
6294
  return;
6155
6295
  }
6296
+ if (isTrackingConsentDenied() && new_visitor_id !== COOKIELESS_VISITOR_ID) {
6297
+ log.warn('Ignoring visitor id ' + new_visitor_id + ' while identity consent is dnt.');
6298
+ return;
6299
+ }
6156
6300
  persistVisitorId('' + new_visitor_id);
6157
6301
  };
6158
6302
  function set_old_visitor_id(new_old_visitor_id) {
@@ -6197,6 +6341,10 @@ var set_account_id = function (new_account_id) {
6197
6341
  log.info('No account id provided');
6198
6342
  return;
6199
6343
  }
6344
+ if (isTrackingConsentDenied()) {
6345
+ log.warn('Ignoring account id ' + new_account_id + ' while identity consent is dnt.');
6346
+ return;
6347
+ }
6200
6348
  if (!isValidId(new_account_id)) {
6201
6349
  clearAccount();
6202
6350
  log.info('Invalid account id: ' + new_account_id + '. Account cleared');
@@ -6239,10 +6387,12 @@ function clearAccount() {
6239
6387
  *
6240
6388
  * @access public
6241
6389
  * @category Identity
6390
+ * @param {object} [options]
6391
+ * @param {boolean} [options.doNotTrack] register the visitor as DNT instead of a new anonymous visitor
6242
6392
  * @example
6243
6393
  * pendo.clearSession()
6244
6394
  */
6245
- function clearSession() {
6395
+ function clearSession(options = { doNotTrack: false }) {
6246
6396
  var props = {
6247
6397
  old_visitor_id: get_visitor_id({ generate: false }),
6248
6398
  old_account_id: get_account_id(),
@@ -6251,7 +6401,12 @@ function clearSession() {
6251
6401
  removeIdentificationCookies();
6252
6402
  delete pendo$1.visitorId;
6253
6403
  delete pendo$1.accountId;
6254
- get_visitor_id({ generate: true });
6404
+ if (options.doNotTrack) {
6405
+ set_visitor_id(COOKIELESS_VISITOR_ID);
6406
+ }
6407
+ else {
6408
+ get_visitor_id({ generate: true });
6409
+ }
6255
6410
  _.extend(props, {
6256
6411
  visitor_id: get_visitor_id(),
6257
6412
  account_id: get_account_id()
@@ -11072,6 +11227,9 @@ function wouldBeVisibleAfterAutoScroll(element) {
11072
11227
  function scrollElementIntoParentRect(element, scrollParent) {
11073
11228
  var clientRect = getClientRect(element);
11074
11229
  var scrollRect = getClientRect(scrollParent);
11230
+ // A fixed element should already be visible so no need to scroll to it.
11231
+ if (clientRect.fixed && !scrollRect.fixed)
11232
+ return;
11075
11233
  var yScrollAmount = 0;
11076
11234
  var xScrollAmount = 0;
11077
11235
  var diff;
@@ -13394,7 +13552,7 @@ function createSendQueue(options, send, guaranteedSend) {
13394
13552
  }
13395
13553
  function filterNonGuideAnalytics(beacon) {
13396
13554
  return function (event, next) {
13397
- var isGuidesOnly = ConfigReader.get('excludeNonGuideAnalytics');
13555
+ var isGuidesOnly = ConfigReader.get('excludeNonGuideAnalytics') || isTrackingConsentDenied();
13398
13556
  if (beacon === 'ptm' && isGuidesOnly) {
13399
13557
  return;
13400
13558
  }
@@ -13403,7 +13561,7 @@ function filterNonGuideAnalytics(beacon) {
13403
13561
  }
13404
13562
  function filterAnalyticsByType(event, next) {
13405
13563
  var excludeEvents = ConfigReader.get('analytics.excludeEvents');
13406
- if (_.indexOf(excludeEvents, event.type) >= 0) {
13564
+ if (_.indexOf(excludeEvents, event.type) >= 0 || !hasConsentForAnalyticsEvent(event.type)) {
13407
13565
  return;
13408
13566
  }
13409
13567
  next(event);
@@ -14039,13 +14197,15 @@ const internalEvents = {
14039
14197
  onEventCaptured: 1,
14040
14198
  segmentFlagsUpdated: 1,
14041
14199
  segmentFlagsReady: 1,
14042
- segmentFlagsError: 1
14200
+ segmentFlagsError: 1,
14201
+ identifyRequested: 1
14043
14202
  };
14044
14203
  const browserEvents = {
14045
14204
  ready: 1,
14046
14205
  segmentFlagsUpdated: 1,
14047
14206
  segmentFlagsReady: 1,
14048
- segmentFlagsError: 1
14207
+ segmentFlagsError: 1,
14208
+ identifyRequested: 1
14049
14209
  };
14050
14210
  const supportedPublicEvents = [
14051
14211
  'ready',
@@ -14059,7 +14219,8 @@ const supportedPublicEvents = [
14059
14219
  'segmentFlagsError',
14060
14220
  'validateGuide',
14061
14221
  'validateLauncher',
14062
- 'validateGlobalScript'
14222
+ 'validateGlobalScript',
14223
+ 'identifyRequested'
14063
14224
  ];
14064
14225
  function forwardInternalEvents(Events) {
14065
14226
  const unsubscribes = _.reduce(supportedPublicEvents, function (unsubscribes, eventName) {
@@ -18391,6 +18552,10 @@ var updateVisitorOptions = function (options = {}) {
18391
18552
  if (!_.isObject(options)) {
18392
18553
  options = {};
18393
18554
  }
18555
+ if (isTrackingConsentDenied()) {
18556
+ log.warn('Ignoring identify/updateOptions metadata while identity consent is dnt.');
18557
+ return { options: getMetadata(), hashChanged: false, storeChanged: false };
18558
+ }
18394
18559
  if (options.jwt && options.signingKeyName) {
18395
18560
  var jwtOptions = JWT.getJwtOptions(options, 'updateOptions');
18396
18561
  if (jwtOptions) {
@@ -25030,9 +25195,15 @@ var processGuideEventCache = function (options) {
25030
25195
  var getGuideEventCache = function () {
25031
25196
  return guideEvtCache;
25032
25197
  };
25198
+ function filterGuideAnalyticsByType(event, next) {
25199
+ if (!hasConsentForGuideEvent(event.type)) {
25200
+ return;
25201
+ }
25202
+ next(event);
25203
+ }
25033
25204
  function createGuideEventQueue(options) {
25034
25205
  const processSilos = pipeline(filterAnalyticsDisabled, splitSiloOnFieldChange('visitor_id'), splitSiloOnFieldChange('props.source'), filterAccountIdsForSendQueue, compressSilo, errorLogger(), addSiloParams(options), addAccountIdParamIfAdoptPartner, addSourceParamIfPresent, convertSiloToObject);
25035
- const packageSilos = pipeline(filterBrokenTimestamps, fireEventCaptured, eventSequence, siloReducer(options.cache), _.noop // do not move to the silo queue until manual flush
25206
+ const packageSilos = pipeline(filterBrokenTimestamps, filterGuideAnalyticsByType, fireEventCaptured, eventSequence, siloReducer(options.cache), _.noop // do not move to the silo queue until manual flush
25036
25207
  );
25037
25208
  return new EventQueue(_.extend({ processSilos, packageSilos }, options));
25038
25209
  }
@@ -25137,6 +25308,12 @@ var initGuides = function (observer) {
25137
25308
  const { guideIds } = event.data[0];
25138
25309
  pruneBadges(guideIds);
25139
25310
  });
25311
+ Events['consent:changed'].on(function (event) {
25312
+ var { guides } = event.data[0];
25313
+ if (_.isUndefined(guides))
25314
+ return;
25315
+ guides ? enableGuides() : disableGuides();
25316
+ });
25140
25317
  teardownFns.push(onTurbolinksPageLoad(document, function onPageLoad() {
25141
25318
  if (pendoDotUrl.get() === reloadGuides.lastUrl) {
25142
25319
  // Force a reload if Turbolinks replaces the document body without changing the url
@@ -25257,6 +25434,14 @@ var setGuidesDisabled = function (areDisabled) {
25257
25434
  originalOptions.disableGuides = areDisabled;
25258
25435
  ConfigReader.setLocalConfig(originalOptions);
25259
25436
  };
25437
+ function enableGuides() {
25438
+ setGuidesDisabled(false);
25439
+ forceGuideReload();
25440
+ }
25441
+ function disableGuides() {
25442
+ setGuidesDisabled(true);
25443
+ resetPendoUI();
25444
+ }
25260
25445
  var setGuidesDelayed = function (areDelayed) {
25261
25446
  var originalOptions = ConfigReader.getLocalConfig();
25262
25447
  delete originalOptions.delayGuides;
@@ -27017,11 +27202,24 @@ const Keys = (function () {
27017
27202
  })();
27018
27203
 
27019
27204
  const pendoLocalStorage = agentStorage.getLocal();
27205
+ const pendoSessionStorage$1 = agentStorage.getSession();
27206
+ const DESIGNER_LAUNCH_RECEIPT = 'pendo-designer-launch-receipt';
27020
27207
  var DesignerV2 = (function () {
27021
27208
  // Always use the production server for CNAME customers
27022
27209
  var host = DESIGNER_SERVER || SERVER;
27023
27210
  var bucket = 'in-app-designer';
27024
27211
  var target = 'latest';
27212
+ /**
27213
+ * The designerId of a designer session launched from this tab. Read back after a
27214
+ * page reload to tell a relaunch of that session apart from a URL token planted
27215
+ * by another origin.
27216
+ *
27217
+ * @name pendo-designer-launch-receipt
27218
+ * @category Cookies/sessionStorage
27219
+ * @access public
27220
+ * @label DESIGNER_LAUNCH_RECEIPT
27221
+ */
27222
+ agentStorage.registry.addSession(DESIGNER_LAUNCH_RECEIPT);
27025
27223
  Events.leaderChanged.on(function () {
27026
27224
  // check to make sure that we aren't trying to launch nav mode from a designer frame
27027
27225
  var inDesignerOrPreview = /pendo-/.test(window.name) || isInDesignerPreviewMode();
@@ -27146,13 +27344,20 @@ var DesignerV2 = (function () {
27146
27344
  return false;
27147
27345
  }
27148
27346
  }
27347
+ function writeLaunchReceipt(designerId) {
27348
+ if (!designerId)
27349
+ return;
27350
+ pendoSessionStorage$1.setItem(DESIGNER_LAUNCH_RECEIPT, designerId);
27351
+ }
27352
+ function hasLaunchReceipt(designerId) {
27353
+ if (!designerId)
27354
+ return false;
27355
+ return pendoSessionStorage$1.getItem(DESIGNER_LAUNCH_RECEIPT) === designerId;
27356
+ }
27149
27357
  function launchOnToken(url) {
27150
27358
  if (isDesignerFrame(window.name)) {
27151
27359
  return;
27152
27360
  }
27153
- if (isPendoDomain(window.location.origin) && !hasSameOriginOpener()) {
27154
- return;
27155
- }
27156
27361
  var tokenParamTest = /pendo-designer=([A-Za-z0-9-]+)/;
27157
27362
  if (!tokenParamTest.test(url))
27158
27363
  return;
@@ -27160,6 +27365,9 @@ var DesignerV2 = (function () {
27160
27365
  if (!tokenMatch)
27161
27366
  return;
27162
27367
  var parsedToken = parsePendoToken(tokenMatch[1]);
27368
+ if (isPendoDomain(window.location.origin) && !hasSameOriginOpener() && !hasLaunchReceipt(parsedToken.designerId)) {
27369
+ return;
27370
+ }
27163
27371
  var validAssetHost = getValidDesignerHost(parsedToken);
27164
27372
  var validTarget = getValidTarget(parsedToken);
27165
27373
  // clear stored designer state if we're launching from a token
@@ -27211,6 +27419,7 @@ var DesignerV2 = (function () {
27211
27419
  if (!options) {
27212
27420
  options = {};
27213
27421
  }
27422
+ writeLaunchReceipt(options.designerId);
27214
27423
  var validAssetHost = getValidDesignerHost(options);
27215
27424
  var validBucket = getValidGCSBucket(options);
27216
27425
  var validTarget = getValidTarget(options);
@@ -28960,7 +29169,10 @@ const PluginAPI = {
28960
29169
  agentStorage,
28961
29170
  analytics: {
28962
29171
  ptm: () => eventQueue,
28963
- collectEvent
29172
+ collectEvent,
29173
+ hasConsentForAnalyticsEvent,
29174
+ getTextCollectionMode,
29175
+ isReplayConsentDenied
28964
29176
  },
28965
29177
  attachEvent,
28966
29178
  attachEventInternal,
@@ -29163,14 +29375,27 @@ function initIdentityEvents() {
29163
29375
  }
29164
29376
  }
29165
29377
  };
29378
+ const requestReidentification = function () {
29379
+ if (!isTrackingConsentDenied() && isVisitorDNT()) {
29380
+ Events.identifyRequested.trigger();
29381
+ }
29382
+ };
29383
+ const handleConsentChange = function (event) {
29384
+ var { identity } = event.data[0];
29385
+ if (_.isUndefined(identity))
29386
+ return;
29387
+ identity ? requestReidentification() : clearSession({ doNotTrack: true });
29388
+ };
29166
29389
  Events.identify.on(collectIdentifyEvent);
29167
29390
  Events.metadata.on(collectMetadataEvent);
29168
29391
  Events.identify.on(handleIdentifyEvent);
29392
+ Events['consent:changed'].on(handleConsentChange);
29169
29393
  initialized = true;
29170
29394
  return function () {
29171
29395
  Events.identify.off(collectIdentifyEvent);
29172
29396
  Events.metadata.off(collectMetadataEvent);
29173
29397
  Events.identify.off(handleIdentifyEvent);
29398
+ Events['consent:changed'].off(handleConsentChange);
29174
29399
  initialized = false;
29175
29400
  };
29176
29401
  }
@@ -29524,7 +29749,7 @@ function registerEventHandlers({ events = [] }) {
29524
29749
  function applyDoNotTrackConfigOverrides(options) {
29525
29750
  options.excludeNonGuideAnalytics = true;
29526
29751
  options.disableCookies = true;
29527
- options.visitor = { id: '__cookieless_visitor__' };
29752
+ options.visitor = { id: COOKIELESS_VISITOR_ID };
29528
29753
  delete options.account;
29529
29754
  delete options.parentAccount;
29530
29755
  }
@@ -30901,6 +31126,28 @@ logPublic.setActiveContexts = setActiveContexts;
30901
31126
  logPublic.showLogHistory = showLogHistory;
30902
31127
  logPublic.getLoggedContexts = getLoggedContexts;
30903
31128
 
31129
+ /**
31130
+ * Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
31131
+ * and is used to determine whether each product the web SDK may run.
31132
+ *
31133
+ * @access public
31134
+ * @category Core
31135
+ * @param {string | string[]} tiers tier or tiers the visitor consented to
31136
+ * @returns {object} map of product to its resolved value -- `guides` and `identity` are
31137
+ * booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
31138
+ * `analytics` and `guideAnalytics` are each an array of allowed event types
31139
+ * @example
31140
+ * pendo.updateCookieConsent('strictlyNecessary');
31141
+ * @example
31142
+ * pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
31143
+ */
31144
+ function updateCookieConsent(tiers) {
31145
+ const enabledProducts = applyConsentTiers(tiers);
31146
+ Events['consent:changed'].trigger(enabledProducts);
31147
+ log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
31148
+ return enabledProducts;
31149
+ }
31150
+
30904
31151
  /**
30905
31152
  * Shared helper for event collection
30906
31153
  */
@@ -31973,6 +32220,7 @@ function exportPublicApi(pendo) {
31973
32220
  // cache.js
31974
32221
  pendo.flushNow = exportPendoCoreOnly(flushNow);
31975
32222
  pendo.teardown = makeSafe(teardown);
32223
+ pendo.updateCookieConsent = makeSafe(updateCookieConsent);
31976
32224
  // guides.js
31977
32225
  pendo.initGuides = exportPendoCoreOnly(initGuides);
31978
32226
  pendo.loadGuides = exportPendoCoreOnly(loadGuides);
@@ -34512,12 +34760,15 @@ var FramesModule = (function () {
34512
34760
  receiveLastGuideStepSeen(context, payload) {
34513
34761
  if (payload.frameId === context.state.frameId)
34514
34762
  return;
34515
- // The extension and native installs will never have the same visitor id
34763
+ // The extension and native installs will never have the same visitor id,
34764
+ // and neither will frames from different applications (apiKeys).
34516
34765
  // However, all frames need to keep an accurate record of what has been viewed, to ensure
34517
- // that native frames do not attempt to re-display extension guides, and vice versa
34766
+ // that one agent's frames do not attempt to re-display another agent's guides
34518
34767
  var originatingFrame = context.state.frames[payload.frameId];
34519
34768
  var originatingInstallType = _.get(originatingFrame, 'agentInstallType');
34520
- if (originatingInstallType && originatingInstallType !== context.state.agentInstallType) {
34769
+ var originatingApiKey = _.get(originatingFrame, 'apiKey');
34770
+ if ((originatingInstallType && originatingInstallType !== context.state.agentInstallType) ||
34771
+ (originatingApiKey && originatingApiKey !== pendo$1.apiKey)) {
34521
34772
  delete payload.lastGuideStepSeen.visitorId;
34522
34773
  }
34523
34774
  context.dispatch('guideState/updateLastGuideStepSeen', payload.lastGuideStepSeen, { root: true });
@@ -42313,9 +42564,11 @@ function TextCapture() {
42313
42564
  }
42314
42565
  function isEnabled() {
42315
42566
  // this preserves the ability for a user to disable text collection after initialization
42316
- return !pendoGlobal.excludeAllText;
42567
+ return !pendoGlobal.excludeAllText && pluginAPI.analytics.getTextCollectionMode() === 'all';
42317
42568
  }
42318
42569
  function isTextCapturable(text) {
42570
+ if (pluginAPI.analytics.getTextCollectionMode() === 'none')
42571
+ return false;
42319
42572
  var trimmedText = pluginAPI.util.trim.call(text);
42320
42573
  // pass the text as an array so that our toPath override doesn't try to split it on `.`
42321
42574
  return isEnabled() || pendoGlobal._.has(whitelist, [trimmedText]) || pendoGlobal._.has(whitelist, [text]);
@@ -51405,7 +51658,8 @@ class SessionRecorder {
51405
51658
  this.api.attachEvent(this.api.Events, 'eventCaptured', bind(this.addRecordingId, this)),
51406
51659
  this.api.attachEvent(this.api.Events, 'transmit:locked', bind(this.stop, this)),
51407
51660
  this.api.attachEvent(this.api.Events, 'transmit:unlocked', bind(this._start, this)),
51408
- this.api.attachEvent(this.api.Events, 'tabIdChanged:self', bind(this.handleTabIdChange, this))
51661
+ this.api.attachEvent(this.api.Events, 'tabIdChanged:self', bind(this.handleTabIdChange, this)),
51662
+ this.api.attachEvent(this.api.Events, 'consent:changed', bind(this.handleConsentChange, this))
51409
51663
  ];
51410
51664
  // APP-153357 Zone.js could bypass filterCrossWindowMessages (don't use attachEvent)
51411
51665
  const channel = this.api.frames.getChannel();
@@ -51516,6 +51770,20 @@ class SessionRecorder {
51516
51770
  return this.checkVisitorEligibility();
51517
51771
  }
51518
51772
  }
51773
+ handleConsentChange(event) {
51774
+ var { identity, replay } = event.data[0];
51775
+ if (this.pendo._.isUndefined(identity) && this.pendo._.isUndefined(replay))
51776
+ return;
51777
+ if (this.api.analytics.isReplayConsentDenied()) {
51778
+ if (this.isRecording()) {
51779
+ this.logStopReason('CONSENT_DENIED');
51780
+ this.stop();
51781
+ }
51782
+ }
51783
+ else if (!this.isRecording()) {
51784
+ this.start();
51785
+ }
51786
+ }
51519
51787
  checkVisitorEligibility(continuationCallback) {
51520
51788
  if (!this.isRecording() && this.pendo._.isNull(this.visitorId) && this.pendo._.isNull(this.accountId))
51521
51789
  return;
@@ -51598,6 +51866,8 @@ class SessionRecorder {
51598
51866
  * pendo.recording.start()
51599
51867
  */
51600
51868
  start() {
51869
+ if (this.api.analytics.isReplayConsentDenied())
51870
+ return;
51601
51871
  this._restartPtm = this.api.analytics.ptm().pause();
51602
51872
  this.allowStart = true;
51603
51873
  this.visitorId = this.pendo.get_visitor_id();
@@ -51655,6 +51925,11 @@ class SessionRecorder {
51655
51925
  });
51656
51926
  }
51657
51927
  _startRecordingForVisitor(visitorConfig) {
51928
+ if (this.api.analytics.isReplayConsentDenied()) {
51929
+ this.restartPtm();
51930
+ this.logStopReason('CONSENT_DENIED');
51931
+ return;
51932
+ }
51658
51933
  this.api.Events.trigger('recording:unpaused');
51659
51934
  this.transport.start(this.config);
51660
51935
  const disableFallback = this.pendo._.get(this.config, 'disableFallback', true);
@@ -56009,6 +56284,8 @@ class PredictApiService {
56009
56284
 
56010
56285
  const PREDICT_STEP_REGEX = /\$\$predict\$\$/;
56011
56286
  const DRAG_THRESHOLD = 5;
56287
+ const FLOATING_MODAL_OFFSET = '20px';
56288
+ const DEFAULT_INITIAL_POSITION = 'TOP_RIGHT';
56012
56289
  const STYLE_ID = 'pendo-predict-plugin-styles';
56013
56290
  const GUIDE_BUTTON_IDENTIFIER = 'button:contains("token")';
56014
56291
  const pluginVersion = '1.0.1';
@@ -56070,7 +56347,7 @@ const injectStyles = (pendoContainerId, log = () => { }) => {
56070
56347
  }
56071
56348
  .frame-explanation {
56072
56349
  aspect-ratio: 16/9; overflow: hidden; min-height: 300px;
56073
- width: 100vw; height: 100vh; background: transparent;
56350
+ width: 554px; height: 100vh; background: transparent;
56074
56351
  position: fixed; bottom: 0; right: 0; z-index: 1; border: none;
56075
56352
  visibility: hidden; opacity: 0; pointer-events: none;
56076
56353
  transition: opacity 0.1s ease-out, visibility 0s linear 0.1s;
@@ -56228,7 +56505,9 @@ const setupDragAndDrop = (handle, container, frameTargetOrigin) => {
56228
56505
  initialLeft = rect.left;
56229
56506
  initialTop = rect.top;
56230
56507
  container.style.left = `${initialLeft}px`;
56508
+ container.style.top = `${initialTop}px`;
56231
56509
  container.style.right = 'auto';
56510
+ container.style.bottom = 'auto';
56232
56511
  startX = e.clientX;
56233
56512
  startY = e.clientY;
56234
56513
  document.addEventListener('mousemove', onMouseMove);
@@ -56301,6 +56580,19 @@ const createExplanationIframe = ({ analysisId, recordId, configuration, predictB
56301
56580
  src: buildFrameSrc(`${base}?${params}`, wrapperBase)
56302
56581
  }, document.body);
56303
56582
  };
56583
+ // The floating modal defaults to the top-right corner (see injected styles).
56584
+ // `initialPosition` overrides that by anchoring the container to the requested
56585
+ // corner; the drag logic later switches to explicit left/top on first drag.
56586
+ const applyInitialPosition = (container, initialPosition = DEFAULT_INITIAL_POSITION) => {
56587
+ var _a;
56588
+ const corners = {
56589
+ TOP_LEFT: { top: FLOATING_MODAL_OFFSET, left: FLOATING_MODAL_OFFSET, bottom: 'auto', right: 'auto' },
56590
+ TOP_RIGHT: { top: FLOATING_MODAL_OFFSET, right: FLOATING_MODAL_OFFSET, bottom: 'auto', left: 'auto' },
56591
+ BOTTOM_LEFT: { bottom: FLOATING_MODAL_OFFSET, left: FLOATING_MODAL_OFFSET, top: 'auto', right: 'auto' },
56592
+ BOTTOM_RIGHT: { bottom: FLOATING_MODAL_OFFSET, right: FLOATING_MODAL_OFFSET, top: 'auto', left: 'auto' }
56593
+ };
56594
+ Object.assign(container.style, (_a = corners[initialPosition]) !== null && _a !== void 0 ? _a : corners[DEFAULT_INITIAL_POSITION]);
56595
+ };
56304
56596
  // ----- Floating Modal Creation -----
56305
56597
  const createFloatingModal = ({ recordId, configuration, predictBaseUrl, wrapperBase = '' }) => {
56306
56598
  var _a, _b;
@@ -56315,6 +56607,7 @@ const createFloatingModal = ({ recordId, configuration, predictBaseUrl, wrapperB
56315
56607
  });
56316
56608
  }
56317
56609
  const container = createElement('div', { id: 'floatingModalContainer', className: 'floating-modal-container' }, document.body);
56610
+ applyInitialPosition(container, configuration.initialPosition);
56318
56611
  const dragHandle = createElement('div', {
56319
56612
  className: 'floating-modal-drag-handle',
56320
56613
  innerHTML: `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
@@ -56455,7 +56748,7 @@ const fetchWidgetConfiguration = (_a) => __awaiter(void 0, void 0, void 0, funct
56455
56748
  if (!widgetSettings) {
56456
56749
  return inlineConfiguration;
56457
56750
  }
56458
- return Object.assign(Object.assign({}, inlineConfiguration), { analysisIds: mapAnalysisIds(widgetSettings.analysesHierarchy), recordRegexPattern: widgetSettings.recordRegexPattern, recordRegexName: widgetSettings.recordRegexKeyword });
56751
+ return Object.assign(Object.assign({}, inlineConfiguration), { analysisIds: mapAnalysisIds(widgetSettings.analysesHierarchy), recordRegexPattern: widgetSettings.recordRegexPattern, recordRegexName: widgetSettings.recordRegexKeyword, initialPosition: widgetSettings.initialPosition });
56459
56752
  });
56460
56753
  const predictGuidesScript = ({ step, pendo, cleanupArray, cleanup, log, designerServer }) => __awaiter(void 0, void 0, void 0, function* () {
56461
56754
  var _c;