expensify-common 2.0.180 → 2.0.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/CONST.d.ts CHANGED
@@ -804,6 +804,9 @@ declare const CONST: {
804
804
  readonly SUSPICIOUS_PAN_ENTRY: 8;
805
805
  readonly SUSPICIOUS_PAN_ENTRY_CLEARED: 9;
806
806
  readonly SUSPICIOUS_PAN_ENTRY_CONFIRMED: 10;
807
+ readonly SUSPICIOUS_TRANSACTIONS_DETECTED: 11;
808
+ readonly SUSPICIOUS_TRANSACTIONS_DETECTED_CLEARED: 12;
809
+ readonly SUSPICIOUS_TRANSACTIONS_DETECTED_CONFIRMED: 13;
807
810
  };
808
811
  };
809
812
  readonly TRAVEL_BOOKING: {
package/dist/CONST.js CHANGED
@@ -848,6 +848,9 @@ const CONST = {
848
848
  SUSPICIOUS_PAN_ENTRY: 8,
849
849
  SUSPICIOUS_PAN_ENTRY_CLEARED: 9,
850
850
  SUSPICIOUS_PAN_ENTRY_CONFIRMED: 10,
851
+ SUSPICIOUS_TRANSACTIONS_DETECTED: 11,
852
+ SUSPICIOUS_TRANSACTIONS_DETECTED_CLEARED: 12,
853
+ SUSPICIOUS_TRANSACTIONS_DETECTED_CONFIRMED: 13,
851
854
  },
852
855
  },
853
856
  TRAVEL_BOOKING: {
@@ -54,6 +54,11 @@ type TruncateOptions = {
54
54
  removeImageTag?: boolean;
55
55
  };
56
56
  export default class ExpensiMark {
57
+ extractVictoryChartTags: (text: string) => {
58
+ text: string;
59
+ tags: string[];
60
+ };
61
+ restoreVictoryChartTags: (text: string, tags: string[]) => string;
57
62
  getAttributeCache: (extras?: Extras) => {
58
63
  attrCachingFn: ((vidSource: string, attrs: string) => void) | undefined;
59
64
  attrCache: Record<string, string> | undefined;
@@ -47,6 +47,13 @@ const MARKDOWN_LINK_REGEX = new RegExp(`\\[((?:[^\\[\\]\\r\\n]*(?:\\[[^\\[\\]\\r
47
47
  const MARKDOWN_IMAGE_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)])?\\(${UrlPatterns.MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi');
48
48
  const MARKDOWN_VIDEO_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)])?\\(((${UrlPatterns.MARKDOWN_URL_REGEX})\\.(?:${Constants.CONST.VIDEO_EXTENSIONS.join('|')}))\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi');
49
49
  const SLACK_SPAN_NEW_LINE_TAG = '<span class="c-mrkdwn__br" data-stringify-type="paragraph-break" style="box-sizing: inherit; display: block; height: unset;"></span>';
50
+ // Preserve VirtualCFO chart blocks by matching the outer <VictoryChart> container.
51
+ // This captures all nested Victory components and prevents markup escaping during conversion.
52
+ const VICTORY_CHART_REGEX = /<VictoryChart\b[^>]*\/>|<VictoryChart\b[^>]*>[\s\S]*?<\/VictoryChart>/gi;
53
+ // Chart placeholders use NUL characters as delimiters to prevent conflicts with user content.
54
+ const VICTORY_CHART_PLACEHOLDER_DELIMITER = String.fromCharCode(0);
55
+ const VICTORY_CHART_PLACEHOLDER_PATTERN = new RegExp(`${VICTORY_CHART_PLACEHOLDER_DELIMITER}(\\d+)${VICTORY_CHART_PLACEHOLDER_DELIMITER}`, 'g');
56
+ const createVictoryChartPlaceholder = (index) => `${VICTORY_CHART_PLACEHOLDER_DELIMITER}${index}${VICTORY_CHART_PLACEHOLDER_DELIMITER}`;
50
57
  class ExpensiMark {
51
58
  /**
52
59
  * Set the logger to use for logging inside of the ExpensiMark class
@@ -56,6 +63,21 @@ class ExpensiMark {
56
63
  ExpensiMark.Log = logger;
57
64
  }
58
65
  constructor() {
66
+ this.extractVictoryChartTags = (text) => {
67
+ const tags = [];
68
+ const out = text.replace(VICTORY_CHART_REGEX, (match) => {
69
+ const placeholder = createVictoryChartPlaceholder(tags.length);
70
+ tags.push(match);
71
+ return placeholder;
72
+ });
73
+ return { text: out, tags };
74
+ };
75
+ this.restoreVictoryChartTags = (text, tags) => {
76
+ if (tags.length === 0) {
77
+ return text;
78
+ }
79
+ return text.replace(VICTORY_CHART_PLACEHOLDER_PATTERN, (match, idx) => { var _a; return (_a = tags[Number(idx)]) !== null && _a !== void 0 ? _a : match; });
80
+ };
59
81
  this.getAttributeCache = (extras) => {
60
82
  var _a, _b;
61
83
  if (!extras) {
@@ -725,6 +747,11 @@ class ExpensiMark {
725
747
  regex: /<video[^><]*data-expensify-source\s*=\s*(['"])(\S*?)\1(.*?)>([^><]*)<\/video>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi,
726
748
  replacement: '[Attachment]',
727
749
  },
750
+ {
751
+ name: 'victoryChart',
752
+ regex: /<VictoryChart\b[^>]*\/>|<VictoryChart\b[^>]*>[\s\S]*?<\/VictoryChart>/gi,
753
+ replacement: '[chart]',
754
+ },
728
755
  {
729
756
  name: 'otherAttachments',
730
757
  regex: /<a[^><]*data-expensify-source\s*=\s*(['"])(\S*?)\1(.*?)>([^><]*)<\/a>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi,
@@ -824,8 +851,11 @@ class ExpensiMark {
824
851
  if (!text) {
825
852
  return '';
826
853
  }
854
+ // Extract VictoryChart blocks to preserve their markup during processing.
855
+ // Only safe for trusted server input - user input must be escaped to prevent XSS.
856
+ const { text: textWithPlaceholders, tags: victoryChartTags } = shouldEscapeText ? { text, tags: [] } : this.extractVictoryChartTags(text);
827
857
  // This ensures that any html the user puts into the comment field shows as raw html
828
- let replacedText = shouldEscapeText ? Utils.escapeText(text) : text;
858
+ let replacedText = shouldEscapeText ? Utils.escapeText(textWithPlaceholders) : textWithPlaceholders;
829
859
  const rules = this.getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput);
830
860
  const processRule = (rule) => {
831
861
  // Pre-process text before applying regex
@@ -855,7 +885,7 @@ class ExpensiMark {
855
885
  // We want to return text without applying rules if exception occurs during replacing
856
886
  return shouldEscapeText ? Utils.escapeText(text) : text;
857
887
  }
858
- return replacedText;
888
+ return this.restoreVictoryChartTags(replacedText, victoryChartTags);
859
889
  }
860
890
  /**
861
891
  * Checks matched URLs for validity and replace valid links with html elements
@@ -1087,6 +1117,9 @@ class ExpensiMark {
1087
1117
  generatedMarkdown = parseBodyTag[2];
1088
1118
  }
1089
1119
  generatedMarkdown = this.unpackNestedQuotes(generatedMarkdown);
1120
+ // Extract VictoryChart blocks before HTML stripping, then restore them.
1121
+ const { text: textWithPlaceholders, tags: victoryChartTags } = this.extractVictoryChartTags(generatedMarkdown);
1122
+ generatedMarkdown = textWithPlaceholders;
1090
1123
  const processRule = (rule) => {
1091
1124
  // Pre-processes input HTML before applying regex
1092
1125
  if (rule.pre) {
@@ -1095,7 +1128,8 @@ class ExpensiMark {
1095
1128
  generatedMarkdown = this.replaceTextWithExtras(generatedMarkdown, rule.regex, extras, rule.replacement);
1096
1129
  };
1097
1130
  this.htmlToMarkdownRules.forEach(processRule);
1098
- return str_1.default.htmlDecode(this.replaceBlockElementWithNewLine(generatedMarkdown));
1131
+ const decoded = str_1.default.htmlDecode(this.replaceBlockElementWithNewLine(generatedMarkdown));
1132
+ return this.restoreVictoryChartTags(decoded, victoryChartTags);
1099
1133
  }
1100
1134
  /**
1101
1135
  * Convert HTML to text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expensify-common",
3
- "version": "2.0.180",
3
+ "version": "2.0.182",
4
4
  "author": "Expensify, Inc.",
5
5
  "description": "Expensify libraries and components shared across different repos",
6
6
  "homepage": "https://expensify.com",