@pendo/agent 2.345.0 → 2.346.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.
@@ -668,7 +668,7 @@ function loadStagingAgent(config, pendo, debuggingEnabled) {
668
668
  var altAgentUrl = getAlternateAgentUrl(config, true, debuggingEnabled);
669
669
  if (altAgentUrl) {
670
670
  setupMethodCapture(pendo);
671
- includeScript(altAgentUrl, pendo);
671
+ includeScript(altAgentUrl, pendo, config.pendoGlobalKey);
672
672
  return true;
673
673
  }
674
674
  }
@@ -679,7 +679,7 @@ function loadUnminifiedAgent(config, pendo, debuggingEnabled) {
679
679
  var altAgentUrl = getAlternateAgentUrl(config, false, debuggingEnabled);
680
680
  if (altAgentUrl) {
681
681
  setupMethodCapture(pendo);
682
- includeScript(altAgentUrl, pendo);
682
+ includeScript(altAgentUrl, pendo, config.pendoGlobalKey);
683
683
  return true;
684
684
  }
685
685
  }
@@ -688,11 +688,16 @@ function loadUnminifiedAgent(config, pendo, debuggingEnabled) {
688
688
  function loadAlternateAgent(config, pendo, debuggingEnabled) {
689
689
  return loadStagingAgent(config, pendo, debuggingEnabled) || loadUnminifiedAgent(config, pendo, debuggingEnabled);
690
690
  }
691
- function includeScript(scriptUrl, pendo) {
691
+ function includeScript(scriptUrl, pendo, globalKey) {
692
692
  var scriptTagName = 'script';
693
693
  var stagingScriptTag = document.createElement(scriptTagName);
694
694
  stagingScriptTag.async = true;
695
695
  stagingScriptTag.src = getPolicy(pendo).createScriptURL(scriptUrl);
696
+ // The alternate agent reads its global key from its own script tag, and its
697
+ // bundled PendoConfig doesn't carry the key the snippet set.
698
+ if (globalKey) {
699
+ stagingScriptTag.setAttribute('data-pendo-global-key', globalKey);
700
+ }
696
701
  var otherScriptTag = document.getElementsByTagName(scriptTagName)[0];
697
702
  if (otherScriptTag && otherScriptTag.parentNode) {
698
703
  otherScriptTag.parentNode.insertBefore(stagingScriptTag, otherScriptTag);
@@ -4009,6 +4014,9 @@ function shouldWrapHashChange() {
4009
4014
  function isMobileUserAgent() {
4010
4015
  return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
4011
4016
  }
4017
+ function supportsPopover() {
4018
+ return !!window.HTMLElement && isNativeCode(window.HTMLElement.prototype.showPopover);
4019
+ }
4012
4020
  function isSafari(userAgent = getUA()) {
4013
4021
  return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
4014
4022
  }
@@ -4021,6 +4029,7 @@ function isChromium(userAgent = getUA()) {
4021
4029
  const sniffer = {
4022
4030
  supportsHistoryApi: shouldWrapNativeHistory,
4023
4031
  supportsHashChange: shouldWrapHashChange,
4032
+ supportsPopover,
4024
4033
  hasEvent(event) {
4025
4034
  // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
4026
4035
  // it. In particular the event is not fired when backspace or delete key are pressed or
@@ -4129,8 +4138,8 @@ let SERVER = '';
4129
4138
  let ASSET_HOST = '';
4130
4139
  let ASSET_PATH = '';
4131
4140
  let DESIGNER_SERVER = '';
4132
- let VERSION = '2.345.0_';
4133
- let PACKAGE_VERSION = '2.345.0';
4141
+ let VERSION = '2.346.0_';
4142
+ let PACKAGE_VERSION = '2.346.0';
4134
4143
  let LOADER = 'xhr';
4135
4144
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4136
4145
  /**
@@ -23855,6 +23864,78 @@ var backdropVisibilityStep = {
23855
23864
  script: backdropVisibilityRuntimeScript
23856
23865
  };
23857
23866
 
23867
+ // ":modal" also matches a fullscreen element, which cannot take children
23868
+ const MODAL_DIALOG = 'dialog:modal';
23869
+ function queryModalDialogs() {
23870
+ try {
23871
+ return document.querySelectorAll(MODAL_DIALOG);
23872
+ }
23873
+ catch (e) {
23874
+ return [];
23875
+ }
23876
+ }
23877
+ function closestModalDialog(element) {
23878
+ try {
23879
+ return element && _.isFunction(element.closest) ? element.closest(MODAL_DIALOG) : null;
23880
+ }
23881
+ catch (e) {
23882
+ return null;
23883
+ }
23884
+ }
23885
+ function queryConfiguredSelector(step) {
23886
+ const selector = _.get(step, 'attributes.popover.selector');
23887
+ if (!selector)
23888
+ return null;
23889
+ try {
23890
+ return dom(selector)[0] || null;
23891
+ }
23892
+ catch (e) {
23893
+ log.info('Invalid popover selector', { contexts: ['error', 'guide', 'render'] });
23894
+ return null;
23895
+ }
23896
+ }
23897
+ function findHost(step) {
23898
+ const host = queryConfiguredSelector(step) ||
23899
+ closestModalDialog(step.targetElement) ||
23900
+ _.last(queryModalDialogs());
23901
+ // a host inside a shadow root would change the guide's root node
23902
+ return host && host.getRootNode() === document ? host : null;
23903
+ }
23904
+ function promoteToPopover(element) {
23905
+ element.setAttribute('popover', 'manual');
23906
+ try {
23907
+ element.showPopover();
23908
+ }
23909
+ catch (e) {
23910
+ element.removeAttribute('popover');
23911
+ log.info('Failed to render guide as popover', { contexts: ['error', 'guide', 'render'] });
23912
+ }
23913
+ }
23914
+ // Registered ahead of backdrop-visibility-step and autofocus-step, which mark the siblings of the
23915
+ // guide's ancestors: the guide must already be inside the dialog or the dialog ends up hidden.
23916
+ function popoverRuntimeScript(step, guide, pendo) {
23917
+ if (pendo.designer || !step.guideElement)
23918
+ return;
23919
+ if (!_.get(step, 'attributes.popover.enabled') || !sniffer.supportsPopover())
23920
+ return;
23921
+ if (_.get(guide, 'attributes.resourceCenter') || _.get(guide, 'attributes.embedConfig'))
23922
+ return;
23923
+ const guideContainer = step.guideElement.find(`#${step.containerId}`);
23924
+ if (!guideContainer.length)
23925
+ return;
23926
+ // positioned in document coordinates, which a fixed root in the top layer breaks
23927
+ if (guideContainer.attr('data-vertical-alignment') === 'Relative to Element')
23928
+ return;
23929
+ const host = findHost(step);
23930
+ if (!host)
23931
+ return;
23932
+ host.appendChild(step.guideElement[0]);
23933
+ promoteToPopover(step.guideElement[0]);
23934
+ }
23935
+ var popoverStep = {
23936
+ script: popoverRuntimeScript
23937
+ };
23938
+
23858
23939
  function isTooltip({ _ }, step) {
23859
23940
  const tooltip = 'tooltip';
23860
23941
  return _.get(step, 'attributes.calculatedType') === tooltip ||
@@ -24011,6 +24092,7 @@ function registerPostrenderScripts(runtime) {
24011
24092
  _.each([
24012
24093
  advanceOnElement,
24013
24094
  announcementMarkSeen,
24095
+ popoverStep,
24014
24096
  autofocusStep,
24015
24097
  backdropVisibilityStep,
24016
24098
  repositionOnScroll,
@@ -25966,8 +26048,14 @@ var MESSAGE_ENDED = 'pendo-live-preview::ended';
25966
26048
  var MESSAGE_END_ACK = 'pendo-live-preview::end-ack';
25967
26049
  var RELAY_END_MESSAGE_TIMEOUT = 2000;
25968
26050
  var activeRelay = null;
26051
+ // event.source alone is not enough: an ancestor page can navigate the relay frame to its own
26052
+ // document and keep the same window object, so the sender's origin has to be the relay's too.
26053
+ // An unknown relay origin ('*') cannot be matched, so it never authenticates a message.
25969
26054
  function isRelayMessage(event, relay) {
25970
- return event.source === relay.frame.contentWindow && !!event.data;
26055
+ return relay.origin !== '*' &&
26056
+ event.origin === relay.origin &&
26057
+ event.source === relay.frame.contentWindow &&
26058
+ !!event.data;
25971
26059
  }
25972
26060
  function sendConfig(relay) {
25973
26061
  if (!relay.frame.contentWindow)
@@ -28502,7 +28590,8 @@ class NetworkRequestIntercept {
28502
28590
  return MAX_RESPONSE_SIZE_MESSAGE;
28503
28591
  }
28504
28592
  }
28505
- const text = yield config.text();
28593
+ const canStream = config.body && _.isFunction(config.body.getReader) && typeof TextDecoder !== 'undefined';
28594
+ const text = canStream ? yield this.readBodyStream(config.body) : yield config.text();
28506
28595
  if (text.length > MAX_RESPONSE_SIZE) {
28507
28596
  return MAX_RESPONSE_SIZE_MESSAGE;
28508
28597
  }
@@ -28513,6 +28602,26 @@ class NetworkRequestIntercept {
28513
28602
  }
28514
28603
  });
28515
28604
  }
28605
+ // Reads chunk by chunk so a long or never-ending stream stops buffering once it passes the cap
28606
+ readBodyStream(body) {
28607
+ return __awaiter(this, void 0, void 0, function* () {
28608
+ const reader = body.getReader();
28609
+ const decoder = new TextDecoder();
28610
+ let text = '';
28611
+ let bytesRead = 0;
28612
+ let chunk = yield reader.read();
28613
+ while (!chunk.done) {
28614
+ bytesRead += chunk.value.byteLength;
28615
+ if (bytesRead > MAX_RESPONSE_SIZE) {
28616
+ reader.cancel().catch(_.noop);
28617
+ return MAX_RESPONSE_SIZE_MESSAGE;
28618
+ }
28619
+ text += decoder.decode(chunk.value, { stream: true });
28620
+ chunk = yield reader.read();
28621
+ }
28622
+ return text + decoder.decode();
28623
+ });
28624
+ }
28516
28625
  extractConfigHeaders(config) {
28517
28626
  let headers = {};
28518
28627
  if (config.headers) {
@@ -28585,33 +28694,7 @@ class NetworkRequestIntercept {
28585
28694
  // Make the actual fetch call and capture response
28586
28695
  try {
28587
28696
  const res = yield networkInterceptor._originalFetch.apply(this, args);
28588
- // Clone the response to avoid consuming the body
28589
- const responseClone = res.clone();
28590
- try {
28591
- // Capture response data
28592
- const responseBody = yield networkInterceptor.safelyReadBody(responseClone, 'response');
28593
- const { status, statusText } = res;
28594
- const headers = networkInterceptor.extractConfigHeaders(res);
28595
- _.each(networkInterceptor.callbackFns, ({ response }) => {
28596
- if (_.isFunction(response)) {
28597
- response({
28598
- requestId,
28599
- status,
28600
- statusText,
28601
- body: responseBody,
28602
- headers,
28603
- url: transformedUrl,
28604
- method
28605
- });
28606
- }
28607
- });
28608
- }
28609
- catch (e) {
28610
- _.each(networkInterceptor.callbackFns, ({ error }) => {
28611
- if (_.isFunction(error))
28612
- error({ context: 'fetchResponse', error: e });
28613
- });
28614
- }
28697
+ networkInterceptor.captureResponse(res, { requestId, url: transformedUrl, method });
28615
28698
  return res;
28616
28699
  }
28617
28700
  catch (error) {
@@ -28635,6 +28718,29 @@ class NetworkRequestIntercept {
28635
28718
  });
28636
28719
  };
28637
28720
  }
28721
+ // Not awaited by the fetch wrapper: the app gets its response before the body is read
28722
+ captureResponse(res, { requestId, url, method }) {
28723
+ return __awaiter(this, void 0, void 0, function* () {
28724
+ try {
28725
+ // Clone synchronously, before the app can consume the body
28726
+ const responseClone = res.clone();
28727
+ const body = yield this.safelyReadBody(responseClone, 'response');
28728
+ const { status, statusText } = res;
28729
+ const headers = this.extractConfigHeaders(res);
28730
+ _.each(this.callbackFns, ({ response }) => {
28731
+ if (_.isFunction(response)) {
28732
+ response({ requestId, status, statusText, body, headers, url, method });
28733
+ }
28734
+ });
28735
+ }
28736
+ catch (e) {
28737
+ _.each(this.callbackFns, ({ error }) => {
28738
+ if (_.isFunction(error))
28739
+ error({ context: 'fetchResponse', error: e });
28740
+ });
28741
+ }
28742
+ });
28743
+ }
28638
28744
  patchXHR() {
28639
28745
  const networkInterceptor = this;
28640
28746
  networkInterceptor._originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
@@ -42167,7 +42273,7 @@ class PromptPlugin {
42167
42273
  });
42168
42274
  const currentlyActiveIds = this._.map(this._.filter(this.prompts, prompt => prompt.isActive), prompt => prompt.id);
42169
42275
  this._.each(allAgents, aiAgent => {
42170
- if (this.isConversationAgent(aiAgent)) {
42276
+ if (this.isConversationAgent(aiAgent) || this.isNetworkAgent(aiAgent)) {
42171
42277
  return;
42172
42278
  }
42173
42279
  const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
@@ -42192,30 +42298,21 @@ class PromptPlugin {
42192
42298
  isConversationAgent(agent) {
42193
42299
  return agent.preset && agent.preset != 'chatgpt';
42194
42300
  }
42301
+ // network tracking is not supported; these agents are ignored rather than falling back to DOM.
42302
+ isNetworkAgent(agent) {
42303
+ return agent.trackingMethod === 'network';
42304
+ }
42195
42305
  onConfigLoaded(aiAgents = []) {
42196
42306
  if (!aiAgents || !this._.isArray(aiAgents) || aiAgents.length === 0) {
42197
42307
  return;
42198
42308
  }
42199
- const networkAgents = [];
42200
42309
  const domAgents = [];
42201
42310
  for (const agent of aiAgents) {
42202
- if (this.isConversationAgent(agent)) {
42311
+ if (this.isConversationAgent(agent) || this.isNetworkAgent(agent)) {
42203
42312
  continue;
42204
42313
  }
42205
- if (agent.trackingMethod === 'network') {
42206
- networkAgents.push(agent);
42207
- }
42208
- else if (agent.trackingMethod === 'dom') {
42209
- domAgents.push(agent);
42210
- }
42211
- else {
42212
- // Default to DOM if no trackingMethod specified
42213
- domAgents.push(agent);
42214
- }
42215
- }
42216
- if (networkAgents.length > 0) {
42217
- this.networkAgents = networkAgents;
42218
- this.api.NetworkRequest.on({ request: this.handleRequest, error: this.handleError });
42314
+ // Default to DOM if no trackingMethod specified
42315
+ domAgents.push(agent);
42219
42316
  }
42220
42317
  // Cache agents for fast lookup
42221
42318
  if (!this.agentsCache) {
@@ -42236,29 +42333,6 @@ class PromptPlugin {
42236
42333
  }
42237
42334
  });
42238
42335
  }
42239
- handleRequest({ method, url, body }) {
42240
- const currentUrl = getNormalizedUrl();
42241
- this._.each(this.networkAgents, aiAgent => {
42242
- if (!this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id)) {
42243
- return;
42244
- }
42245
- const originalBody = body || '';
42246
- this.api.analytics.collectEvent('prompt', {
42247
- prompt: originalBody,
42248
- agentId: aiAgent.id,
42249
- promptType: method,
42250
- url
42251
- }, undefined, 'agentic');
42252
- });
42253
- }
42254
- handleError({ error, context }) {
42255
- if (context == 'fetch') {
42256
- this.api.log.debug('[PromptPlugin] Fetch tracking failed:', error);
42257
- }
42258
- if (context == 'xhr') {
42259
- this.api.log.debug('[PromptPlugin] XHR tracking failed:', error);
42260
- }
42261
- }
42262
42336
  observePrompt(config) {
42263
42337
  const { id, cssSelectors } = config;
42264
42338
  // Check if agent already exists
@@ -42297,7 +42371,6 @@ class PromptPlugin {
42297
42371
  if (this.agentsCache) {
42298
42372
  this.agentsCache.clear();
42299
42373
  }
42300
- this.api.NetworkRequest.off({ request: this.handleRequest, error: this.handleError });
42301
42374
  }
42302
42375
  }
42303
42376
  var PromptAnalytics = new PromptPlugin();
@@ -43650,6 +43723,13 @@ function encodeMetadataForUrl(value, urlBeforePlaceholder = '') {
43650
43723
  }
43651
43724
  return window.encodeURI(str);
43652
43725
  }
43726
+ // Resolve through the browser's own URL parser: it strips tabs/newlines inside the scheme
43727
+ // (e.g. "java\tscript:"), which a regex over the raw string would miss.
43728
+ function hasDangerousUrlScheme(url) {
43729
+ const anchor = document.createElement('a');
43730
+ anchor.href = url;
43731
+ return DANGEROUS_URL_SCHEME.test(anchor.protocol);
43732
+ }
43653
43733
 
43654
43734
  const MetadataSubstitution = {
43655
43735
  name: 'MetadataSubstitution',
@@ -43736,27 +43816,37 @@ function processPlaceholder(placeholder, pendo) {
43736
43816
  let match;
43737
43817
  const { data, target } = placeholder;
43738
43818
  const subRegex = new RegExp(guideMarkdownUtil.substitutionRegex);
43819
+ const isUrlTarget = target === 'href' || target === 'value';
43820
+ const startedWithDangerousScheme = isUrlTarget && hasDangerousUrlScheme(getRawTargetValue(data, target));
43739
43821
  while ((match = matchPlaceholder(placeholder, subRegex))) {
43740
43822
  const usedArrayPath = Array.isArray(match) && match.length >= 2;
43741
43823
  const currentStr = target === 'textContent'
43742
43824
  ? data[target]
43743
- : decodeURI((data.getAttribute && (target === 'href' || target === 'value') ? (data.getAttribute(target) || '') : data[target]));
43825
+ : decodeURI(getRawTargetValue(data, target));
43744
43826
  const matched = usedArrayPath ? match : subRegex.exec(currentStr);
43745
43827
  if (!matched)
43746
43828
  continue;
43747
43829
  const mdValue = getSubstituteValue(matched, pendo);
43748
43830
  substituteMetadataByTarget(data, target, mdValue, matched);
43831
+ // Substituted values can carry new placeholders into the next pass, so a dangerous
43832
+ // scheme can be assembled across passes. Check the whole URL, not each fragment.
43833
+ if (isUrlTarget && !startedWithDangerousScheme && hasDangerousUrlScheme(getRawTargetValue(data, target))) {
43834
+ data[target] = '';
43835
+ return;
43836
+ }
43749
43837
  }
43750
43838
  }
43839
+ function getRawTargetValue(data, target) {
43840
+ return data.getAttribute && (target === 'href' || target === 'value')
43841
+ ? (data.getAttribute(target) || '')
43842
+ : data[target];
43843
+ }
43751
43844
  function matchPlaceholder(placeholder, regex) {
43752
43845
  if (placeholder.target === 'textContent') {
43753
43846
  return placeholder.data[placeholder.target].match(regex);
43754
43847
  }
43755
43848
  else {
43756
- const raw = placeholder.data.getAttribute && (placeholder.target === 'href' || placeholder.target === 'value')
43757
- ? (placeholder.data.getAttribute(placeholder.target) || '')
43758
- : placeholder.data[placeholder.target];
43759
- return decodeURI(raw).match(regex);
43849
+ return decodeURI(getRawTargetValue(placeholder.data, placeholder.target)).match(regex);
43760
43850
  }
43761
43851
  }
43762
43852
  function getMetadataValueCaseInsensitive(metadata, type, property) {
@@ -53413,6 +53503,7 @@ function createReplayPlugin() {
53413
53503
  /**
53414
53504
  * limit of number of keys in an object
53415
53505
  * if an object contains more keys than this limit, we would call its toString function directly
53506
+ * arrays longer than this limit are cut down to their first NUM_OF_KEYS_LIMIT elements
53416
53507
  */
53417
53508
  const NUM_OF_KEYS_LIMIT = 50;
53418
53509
  /**
@@ -53484,8 +53575,9 @@ function shouldIgnore(_obj) {
53484
53575
  /**
53485
53576
  * stringify any argument
53486
53577
  * @param arg - the arg to stringify
53578
+ * @param maxLength - stop serializing once this many characters have been visited
53487
53579
  */
53488
- function stringify(arg) {
53580
+ function stringify(arg, maxLength = Infinity) {
53489
53581
  if (arg === undefined)
53490
53582
  return 'undefined';
53491
53583
  // Handle primitives
@@ -53496,11 +53588,19 @@ function stringify(arg) {
53496
53588
  return arg.toString();
53497
53589
  return String(arg);
53498
53590
  }
53591
+ let charsVisited = 0;
53499
53592
  return JSON.stringify(arg, function (key, value) {
53593
+ if (charsVisited > maxLength)
53594
+ return '';
53595
+ const valueLength = typeof value === 'string' ? value.length : 0;
53596
+ charsVisited += key.length + valueLength;
53500
53597
  if (value === null)
53501
53598
  return value;
53502
53599
  if (value === undefined)
53503
53600
  return 'undefined';
53601
+ if (Array.isArray(value) && value.length > NUM_OF_KEYS_LIMIT) {
53602
+ return value.slice(0, NUM_OF_KEYS_LIMIT);
53603
+ }
53504
53604
  // Handle objects
53505
53605
  if (shouldIgnore(value)) {
53506
53606
  return value.toString ? value.toString() : '[object Object]';
@@ -54127,8 +54227,11 @@ function ConsoleCapture() {
54127
54227
  let isRecordingActive = false;
54128
54228
  const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
54129
54229
  const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
54230
+ const CONSOLE_LOG_LEVELS_CONFIG = 'consoleLogLevels';
54130
54231
  const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
54131
54232
  const DEV_LOG_SUB_TYPE = 'console';
54233
+ const DEFAULT_CONSOLE_LOG_LEVELS = ['error', 'warn', 'info'];
54234
+ let consoleLogLevels = DEFAULT_CONSOLE_LOG_LEVELS;
54132
54235
  // deduplicate logs
54133
54236
  let lastLogKey = '';
54134
54237
  return {
@@ -54147,6 +54250,8 @@ function ConsoleCapture() {
54147
54250
  onPtmPaused,
54148
54251
  onPtmUnpaused,
54149
54252
  securityPolicyViolationFn,
54253
+ uncaughtErrorFn,
54254
+ unhandledRejectionFn,
54150
54255
  get isCapturingConsoleLogs() {
54151
54256
  return isCapturingConsoleLogs;
54152
54257
  },
@@ -54166,10 +54271,12 @@ function ConsoleCapture() {
54166
54271
  const { ConfigReader } = pluginAPI;
54167
54272
  ConfigReader.addOption(CAPTURE_CONSOLE_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
54168
54273
  ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
54274
+ ConfigReader.addOption(CONSOLE_LOG_LEVELS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], DEFAULT_CONSOLE_LOG_LEVELS);
54169
54275
  const captureConsoleEnabled = ConfigReader.get(CAPTURE_CONSOLE_CONFIG);
54170
54276
  if (!captureConsoleEnabled)
54171
54277
  return;
54172
54278
  alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
54279
+ consoleLogLevels = ConfigReader.get(CONSOLE_LOG_LEVELS_CONFIG);
54173
54280
  globalPendo = pendo;
54174
54281
  buffer = new DevlogBuffer(pendo, pluginAPI);
54175
54282
  transport = new DevlogTransport(pendo, pluginAPI);
@@ -54230,6 +54337,8 @@ function ConsoleCapture() {
54230
54337
  try {
54231
54338
  addIntercepts();
54232
54339
  pluginAPI.attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
54340
+ pluginAPI.attachEventInternal(window, 'error', uncaughtErrorFn);
54341
+ pluginAPI.attachEventInternal(window, 'unhandledrejection', unhandledRejectionFn);
54233
54342
  }
54234
54343
  catch (error) {
54235
54344
  pluginAPI.log.error('Error setting up console capture intercepts: ', error);
@@ -54270,16 +54379,36 @@ function ConsoleCapture() {
54270
54379
  createConsoleEvent([message], 'error', { skipStackTrace: true, skipScrubPII: true });
54271
54380
  }
54272
54381
  }
54382
+ function uncaughtErrorFn(evt) {
54383
+ if (!evt || typeof evt !== 'object')
54384
+ return;
54385
+ const rawMessage = evt.message || (evt.error && String(evt.error));
54386
+ if (!rawMessage)
54387
+ return;
54388
+ const message = rawMessage.indexOf('Uncaught') === 0 ? rawMessage : `Uncaught ${rawMessage}`;
54389
+ const stackTrace = evt.error && evt.error.stack;
54390
+ createConsoleEvent([stackTrace ? `${message}\n${stackTrace}` : message], 'error', { skipStackTrace: true });
54391
+ }
54392
+ function unhandledRejectionFn(evt) {
54393
+ if (!evt || typeof evt !== 'object')
54394
+ return;
54395
+ const { reason } = evt;
54396
+ const detail = reason && reason.stack ? reason.stack : reason;
54397
+ createConsoleEvent(['Uncaught (in promise)', detail], 'error', { skipStackTrace: true });
54398
+ }
54273
54399
  function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
54274
54400
  if (!isCapturingConsoleLogs || !args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
54275
54401
  return;
54276
54402
  if (isErrorOnlyCapture() && methodName !== 'error')
54277
54403
  return;
54404
+ const devLogLevel = methodName === 'log' ? 'info' : methodName;
54405
+ if (!_.contains(consoleLogLevels, devLogLevel))
54406
+ return;
54278
54407
  // stringify args
54279
54408
  let message = _.compact(_.map(args, arg => {
54280
54409
  let stringifiedArg;
54281
54410
  try {
54282
- stringifiedArg = stringify(arg);
54411
+ stringifiedArg = stringify(arg, MAX_LENGTH);
54283
54412
  }
54284
54413
  catch (error) {
54285
54414
  pluginAPI.log.error('Error stringifying arg: ', error);
@@ -54303,10 +54432,10 @@ function ConsoleCapture() {
54303
54432
  buffer.lastEvent.devLogCount++;
54304
54433
  return;
54305
54434
  }
54306
- const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
54307
- const consoleEvent = Object.assign(Object.assign({}, devLogEnvelope), { subType: DEV_LOG_SUB_TYPE, devLogLevel: methodName === 'log' ? 'info' : methodName, devLogMessage: skipScrubPII ? message : scrubPII({ string: message, _ }), devLogTrace: stackTrace, devLogCount: 1 });
54308
54435
  if (!buffer.hasTokenAvailable())
54309
- return consoleEvent;
54436
+ return;
54437
+ const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
54438
+ const consoleEvent = Object.assign(Object.assign({}, devLogEnvelope), { subType: DEV_LOG_SUB_TYPE, devLogLevel, devLogMessage: skipScrubPII ? message : scrubPII({ string: message, _ }), devLogTrace: stackTrace, devLogCount: 1 });
54310
54439
  if (!isPtmPaused) {
54311
54440
  pluginAPI.Events.eventCaptured.trigger(consoleEvent);
54312
54441
  }
@@ -54364,6 +54493,8 @@ function ConsoleCapture() {
54364
54493
  pluginAPI.Events['recording:unpaused'].off(recordingStarted);
54365
54494
  pluginAPI.Events['recording:paused'].off(recordingStopped);
54366
54495
  pluginAPI.detachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
54496
+ pluginAPI.detachEventInternal(window, 'error', uncaughtErrorFn);
54497
+ pluginAPI.detachEventInternal(window, 'unhandledrejection', unhandledRejectionFn);
54367
54498
  _.each(CONSOLE_METHODS, function (methodName) {
54368
54499
  if (!console[methodName])
54369
54500
  return _.noop;