@posthog/rrweb-snapshot 0.0.28 → 0.0.30

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/index.d.cts CHANGED
@@ -30,6 +30,8 @@ export declare type cdataNode = {
30
30
  textContent: '';
31
31
  };
32
32
 
33
+ export declare function checkDataURLSize(dataURL: string, maxLength: number | undefined): string;
34
+
33
35
  export declare function classMatchesRegex(node: Node | null, regex: RegExp, checkAncestors: boolean): boolean;
34
36
 
35
37
  export declare function cleanupSnapshot(): void;
@@ -188,6 +190,8 @@ export declare function rebuild(n: serializedNodeWithId, options: {
188
190
  mirror: Mirror;
189
191
  }): Node | null;
190
192
 
193
+ export declare function recompressBase64Image(img: HTMLImageElement, dataURL: string, type?: string, quality?: number): string;
194
+
191
195
  export declare type serializedElementNodeWithId = Extract<serializedNodeWithId, Record<'type', NodeType.Element>>;
192
196
 
193
197
  export declare type serializedNode = (documentNode | documentTypeNode | elementNode | textNode | cdataNode | commentNode) & {
@@ -276,7 +280,7 @@ export declare type textNode = {
276
280
 
277
281
  export declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
278
282
 
279
- export declare function transformAttribute(doc: Document, tagName: Lowercase<string>, name: Lowercase<string>, value: string | null): string | null;
283
+ export declare function transformAttribute(doc: Document, tagName: Lowercase<string>, name: Lowercase<string>, value: string | null, element?: HTMLElement, dataURLOptions?: DataURLOptions): string | null;
280
284
 
281
285
  export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
282
286
 
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ export declare type cdataNode = {
30
30
  textContent: '';
31
31
  };
32
32
 
33
+ export declare function checkDataURLSize(dataURL: string, maxLength: number | undefined): string;
34
+
33
35
  export declare function classMatchesRegex(node: Node | null, regex: RegExp, checkAncestors: boolean): boolean;
34
36
 
35
37
  export declare function cleanupSnapshot(): void;
@@ -188,6 +190,8 @@ export declare function rebuild(n: serializedNodeWithId, options: {
188
190
  mirror: Mirror;
189
191
  }): Node | null;
190
192
 
193
+ export declare function recompressBase64Image(img: HTMLImageElement, dataURL: string, type?: string, quality?: number): string;
194
+
191
195
  export declare type serializedElementNodeWithId = Extract<serializedNodeWithId, Record<'type', NodeType.Element>>;
192
196
 
193
197
  export declare type serializedNode = (documentNode | documentTypeNode | elementNode | textNode | cdataNode | commentNode) & {
@@ -276,7 +280,7 @@ export declare type textNode = {
276
280
 
277
281
  export declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
278
282
 
279
- export declare function transformAttribute(doc: Document, tagName: Lowercase<string>, name: Lowercase<string>, value: string | null): string | null;
283
+ export declare function transformAttribute(doc: Document, tagName: Lowercase<string>, name: Lowercase<string>, value: string | null, element?: HTMLElement, dataURLOptions?: DataURLOptions): string | null;
280
284
 
281
285
  export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
282
286
 
@@ -203,6 +203,91 @@ function isShadowRoot(n) {
203
203
  function isNativeShadowDom(shadowRoot2) {
204
204
  return Object.prototype.toString.call(shadowRoot2) === "[object ShadowRoot]";
205
205
  }
206
+ function fixBrowserCompatibilityIssuesInCSS(cssText) {
207
+ if (cssText.includes(" background-clip: text;") && !cssText.includes(" -webkit-background-clip: text;")) {
208
+ cssText = cssText.replace(
209
+ /\sbackground-clip:\s*text;/g,
210
+ " -webkit-background-clip: text; background-clip: text;"
211
+ );
212
+ }
213
+ return cssText;
214
+ }
215
+ function escapeImportStatement(rule2) {
216
+ const { cssText } = rule2;
217
+ if (cssText.split('"').length < 3) return cssText;
218
+ const statement = ["@import", `url(${JSON.stringify(rule2.href)})`];
219
+ if (rule2.layerName === "") {
220
+ statement.push(`layer`);
221
+ } else if (rule2.layerName) {
222
+ statement.push(`layer(${rule2.layerName})`);
223
+ }
224
+ if (rule2.supportsText) {
225
+ statement.push(`supports(${rule2.supportsText})`);
226
+ }
227
+ if (rule2.media.length) {
228
+ statement.push(rule2.media.mediaText);
229
+ }
230
+ return statement.join(" ") + ";";
231
+ }
232
+ function stringifyStylesheet(s) {
233
+ try {
234
+ const rules = s.rules || s.cssRules;
235
+ if (!rules) {
236
+ return null;
237
+ }
238
+ let sheetHref = s.href;
239
+ if (!sheetHref && s.ownerNode) {
240
+ sheetHref = s.ownerNode.baseURI;
241
+ }
242
+ const stringifiedRules = Array.from(
243
+ rules,
244
+ (rule2) => stringifyRule(rule2, sheetHref)
245
+ ).join("");
246
+ return fixBrowserCompatibilityIssuesInCSS(stringifiedRules);
247
+ } catch (error) {
248
+ return null;
249
+ }
250
+ }
251
+ function stringifyRule(rule2, sheetHref) {
252
+ var _a;
253
+ if (isCSSImportRule(rule2)) {
254
+ let importStringified;
255
+ try {
256
+ importStringified = // for same-origin stylesheets,
257
+ // we can access the imported stylesheet rules directly
258
+ stringifyStylesheet(rule2.styleSheet) || // work around browser issues with the raw string `@import url(...)` statement
259
+ escapeImportStatement(rule2);
260
+ } catch (error) {
261
+ importStringified = rule2.cssText;
262
+ }
263
+ try {
264
+ if (importStringified && ((_a = rule2.styleSheet) == null ? void 0 : _a.href)) {
265
+ return absolutifyURLs(importStringified, rule2.styleSheet.href);
266
+ }
267
+ } catch {
268
+ }
269
+ return importStringified;
270
+ } else {
271
+ let ruleStringified = rule2.cssText;
272
+ if (isCSSStyleRule(rule2) && rule2.selectorText.includes(":")) {
273
+ ruleStringified = fixSafariColons(ruleStringified);
274
+ }
275
+ if (sheetHref) {
276
+ return absolutifyURLs(ruleStringified, sheetHref);
277
+ }
278
+ return ruleStringified;
279
+ }
280
+ }
281
+ function fixSafariColons(cssStringified) {
282
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
283
+ return cssStringified.replace(regex, "$1\\$2");
284
+ }
285
+ function isCSSImportRule(rule2) {
286
+ return "styleSheet" in rule2;
287
+ }
288
+ function isCSSStyleRule(rule2) {
289
+ return "selectorText" in rule2;
290
+ }
206
291
  class Mirror {
207
292
  constructor() {
208
293
  __publicField(this, "idNodeMap", /* @__PURE__ */ new Map());
@@ -337,91 +422,6 @@ function extractFileExtension(path, baseURL) {
337
422
  const match = url.pathname.match(regex);
338
423
  return (match == null ? void 0 : match[1]) ?? null;
339
424
  }
340
- function fixBrowserCompatibilityIssuesInCSS(cssText) {
341
- if (cssText.includes(" background-clip: text;") && !cssText.includes(" -webkit-background-clip: text;")) {
342
- cssText = cssText.replace(
343
- /\sbackground-clip:\s*text;/g,
344
- " -webkit-background-clip: text; background-clip: text;"
345
- );
346
- }
347
- return cssText;
348
- }
349
- function escapeImportStatement(rule2) {
350
- const { cssText } = rule2;
351
- if (cssText.split('"').length < 3) return cssText;
352
- const statement = ["@import", `url(${JSON.stringify(rule2.href)})`];
353
- if (rule2.layerName === "") {
354
- statement.push(`layer`);
355
- } else if (rule2.layerName) {
356
- statement.push(`layer(${rule2.layerName})`);
357
- }
358
- if (rule2.supportsText) {
359
- statement.push(`supports(${rule2.supportsText})`);
360
- }
361
- if (rule2.media.length) {
362
- statement.push(rule2.media.mediaText);
363
- }
364
- return statement.join(" ") + ";";
365
- }
366
- function stringifyStylesheet(s) {
367
- try {
368
- const rules = s.rules || s.cssRules;
369
- if (!rules) {
370
- return null;
371
- }
372
- let sheetHref = s.href;
373
- if (!sheetHref && s.ownerNode) {
374
- sheetHref = s.ownerNode.baseURI;
375
- }
376
- const stringifiedRules = Array.from(
377
- rules,
378
- (rule2) => stringifyRule(rule2, sheetHref)
379
- ).join("");
380
- return fixBrowserCompatibilityIssuesInCSS(stringifiedRules);
381
- } catch (error) {
382
- return null;
383
- }
384
- }
385
- function stringifyRule(rule2, sheetHref) {
386
- var _a;
387
- if (isCSSImportRule(rule2)) {
388
- let importStringified;
389
- try {
390
- importStringified = // for same-origin stylesheets,
391
- // we can access the imported stylesheet rules directly
392
- stringifyStylesheet(rule2.styleSheet) || // work around browser issues with the raw string `@import url(...)` statement
393
- escapeImportStatement(rule2);
394
- } catch (error) {
395
- importStringified = rule2.cssText;
396
- }
397
- try {
398
- if (importStringified && ((_a = rule2.styleSheet) == null ? void 0 : _a.href)) {
399
- return absolutifyURLs(importStringified, rule2.styleSheet.href);
400
- }
401
- } catch {
402
- }
403
- return importStringified;
404
- } else {
405
- let ruleStringified = rule2.cssText;
406
- if (isCSSStyleRule(rule2) && rule2.selectorText.includes(":")) {
407
- ruleStringified = fixSafariColons(ruleStringified);
408
- }
409
- if (sheetHref) {
410
- return absolutifyURLs(ruleStringified, sheetHref);
411
- }
412
- return ruleStringified;
413
- }
414
- }
415
- function fixSafariColons(cssStringified) {
416
- const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
417
- return cssStringified.replace(regex, "$1\\$2");
418
- }
419
- function isCSSImportRule(rule2) {
420
- return "styleSheet" in rule2;
421
- }
422
- function isCSSStyleRule(rule2) {
423
- return "selectorText" in rule2;
424
- }
425
425
  function extractOrigin(url) {
426
426
  let origin = "";
427
427
  if (url.indexOf("//") > -1) {
@@ -470,6 +470,36 @@ function absolutifyURLs(cssText, href) {
470
470
  }
471
471
  );
472
472
  }
473
+ const STRIPED_PLACEHOLDER_SVG = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzPgogICAgPHBhdHRlcm4gaWQ9InN0cmlwZXMiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+CiAgICAgIDxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iYmxhY2siLz4KICAgICAgPHBhdGggZD0iTTggMEgxNkwwIDE2VjhMOCAwWiIgZmlsbD0iIzJEMkQyRCIvPgogICAgICA8cGF0aCBkPSJNMTYgOFYxNkg4TDE2IDhaIiBmaWxsPSIjMkQyRDJEIi8+CiAgICA8L3BhdHRlcm4+CiAgPC9kZWZzPgogIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjc3RyaXBlcykiLz4KPC9zdmc+Cg==";
474
+ const MAX_IMAGE_DIMENSION_FOR_RECOMPRESSION = 4096;
475
+ function recompressBase64Image(img, dataURL, type, quality) {
476
+ if (!img.complete || img.naturalWidth === 0) {
477
+ return dataURL;
478
+ }
479
+ if (img.naturalWidth > MAX_IMAGE_DIMENSION_FOR_RECOMPRESSION || img.naturalHeight > MAX_IMAGE_DIMENSION_FOR_RECOMPRESSION) {
480
+ return dataURL;
481
+ }
482
+ try {
483
+ const canvas = document.createElement("canvas");
484
+ canvas.width = img.naturalWidth;
485
+ canvas.height = img.naturalHeight;
486
+ const ctx = canvas.getContext("2d");
487
+ if (!ctx) {
488
+ return dataURL;
489
+ }
490
+ ctx.drawImage(img, 0, 0);
491
+ const recompressed = canvas.toDataURL(type || "image/webp", quality ?? 0.4);
492
+ return recompressed;
493
+ } catch (err) {
494
+ return dataURL;
495
+ }
496
+ }
497
+ function checkDataURLSize(dataURL, maxLength) {
498
+ if (!maxLength || dataURL.length <= maxLength) {
499
+ return dataURL;
500
+ }
501
+ return STRIPED_PLACEHOLDER_SVG;
502
+ }
473
503
  let _id = 1;
474
504
  const tagNameRegex = new RegExp("[^a-z0-9-_:]");
475
505
  const IGNORED_NODE = -2;
@@ -568,12 +598,32 @@ function getHref(doc, customHref) {
568
598
  a.setAttribute("href", customHref);
569
599
  return a.href;
570
600
  }
571
- function transformAttribute(doc, tagName, name, value) {
601
+ function transformAttribute(doc, tagName, name, value, element, dataURLOptions) {
572
602
  if (!value) {
573
603
  return value;
574
604
  }
575
605
  if (name === "src" || name === "href" && !(tagName === "use" && value[0] === "#")) {
576
- return absoluteToDoc(doc, value);
606
+ const transformedValue = absoluteToDoc(doc, value);
607
+ if (tagName === "img" && transformedValue.startsWith("data:") && element) {
608
+ const img = element;
609
+ let processedDataURL = transformedValue;
610
+ if ((dataURLOptions == null ? void 0 : dataURLOptions.type) || (dataURLOptions == null ? void 0 : dataURLOptions.quality) !== void 0) {
611
+ processedDataURL = recompressBase64Image(
612
+ img,
613
+ transformedValue,
614
+ dataURLOptions.type,
615
+ dataURLOptions.quality
616
+ );
617
+ }
618
+ if (dataURLOptions == null ? void 0 : dataURLOptions.maxBase64ImageLength) {
619
+ processedDataURL = checkDataURLSize(
620
+ processedDataURL,
621
+ dataURLOptions.maxBase64ImageLength
622
+ );
623
+ }
624
+ return processedDataURL;
625
+ }
626
+ return transformedValue;
577
627
  } else if (name === "xlink:href" && value[0] !== "#") {
578
628
  return absoluteToDoc(doc, value);
579
629
  } else if (name === "background" && (tagName === "table" || tagName === "td" || tagName === "th")) {
@@ -864,7 +914,9 @@ function serializeElementNode(n, options) {
864
914
  doc,
865
915
  tagName,
866
916
  toLowerCase(attr.name),
867
- attr.value
917
+ attr.value,
918
+ n,
919
+ dataURLOptions
868
920
  );
869
921
  }
870
922
  }
@@ -1393,15 +1445,6 @@ function visitSnapshot(node2, onVisit) {
1393
1445
  function cleanupSnapshot() {
1394
1446
  _id = 1;
1395
1447
  }
1396
- var NodeType = /* @__PURE__ */ ((NodeType2) => {
1397
- NodeType2[NodeType2["Document"] = 0] = "Document";
1398
- NodeType2[NodeType2["DocumentType"] = 1] = "DocumentType";
1399
- NodeType2[NodeType2["Element"] = 2] = "Element";
1400
- NodeType2[NodeType2["Text"] = 3] = "Text";
1401
- NodeType2[NodeType2["CDATA"] = 4] = "CDATA";
1402
- NodeType2[NodeType2["Comment"] = 5] = "Comment";
1403
- return NodeType2;
1404
- })(NodeType || {});
1405
1448
  const MEDIA_SELECTOR = /(max|min)-device-(width|height)/;
1406
1449
  const MEDIA_SELECTOR_GLOBAL = new RegExp(MEDIA_SELECTOR.source, "g");
1407
1450
  const mediaSelectorPlugin = {
@@ -5323,6 +5366,15 @@ function requireSafeParse() {
5323
5366
  }
5324
5367
  var safeParseExports = requireSafeParse();
5325
5368
  const safeParser = /* @__PURE__ */ getDefaultExportFromCjs(safeParseExports);
5369
+ var NodeType = /* @__PURE__ */ ((NodeType2) => {
5370
+ NodeType2[NodeType2["Document"] = 0] = "Document";
5371
+ NodeType2[NodeType2["DocumentType"] = 1] = "DocumentType";
5372
+ NodeType2[NodeType2["Element"] = 2] = "Element";
5373
+ NodeType2[NodeType2["Text"] = 3] = "Text";
5374
+ NodeType2[NodeType2["CDATA"] = 4] = "CDATA";
5375
+ NodeType2[NodeType2["Comment"] = 5] = "Comment";
5376
+ return NodeType2;
5377
+ })(NodeType || {});
5326
5378
  var postcssExports = requirePostcss();
5327
5379
  const postcss = /* @__PURE__ */ getDefaultExportFromCjs(postcssExports);
5328
5380
  postcss.stringify;
@@ -5349,22 +5401,6 @@ postcss.Input;
5349
5401
  postcss.Rule;
5350
5402
  postcss.Root;
5351
5403
  postcss.Node;
5352
- function adaptCssForReplay(cssText, cache) {
5353
- const cachedStyle = cache == null ? void 0 : cache.stylesWithHoverClass.get(cssText);
5354
- if (cachedStyle) return cachedStyle;
5355
- let result2 = cssText;
5356
- try {
5357
- const ast = postcss([
5358
- mediaSelectorPlugin,
5359
- pseudoClassPlugin
5360
- ]).process(cssText, { parser: safeParser });
5361
- result2 = ast.css;
5362
- } catch (error) {
5363
- console.warn("Failed to adapt css for replay", error);
5364
- }
5365
- cache == null ? void 0 : cache.stylesWithHoverClass.set(cssText, result2);
5366
- return result2;
5367
- }
5368
5404
  const tagMap = {
5369
5405
  script: "noscript",
5370
5406
  // camel case svg element tag names
@@ -5412,6 +5448,22 @@ function getTagName(n) {
5412
5448
  }
5413
5449
  return tagName;
5414
5450
  }
5451
+ function adaptCssForReplay(cssText, cache) {
5452
+ const cachedStyle = cache == null ? void 0 : cache.stylesWithHoverClass.get(cssText);
5453
+ if (cachedStyle) return cachedStyle;
5454
+ let result2 = cssText;
5455
+ try {
5456
+ const ast = postcss([
5457
+ mediaSelectorPlugin,
5458
+ pseudoClassPlugin
5459
+ ]).process(cssText, { parser: safeParser });
5460
+ result2 = ast.css;
5461
+ } catch (error) {
5462
+ console.warn("Failed to adapt css for replay", error);
5463
+ }
5464
+ cache == null ? void 0 : cache.stylesWithHoverClass.set(cssText, result2);
5465
+ return result2;
5466
+ }
5415
5467
  function createCache() {
5416
5468
  const stylesWithHoverClass = /* @__PURE__ */ new Map();
5417
5469
  return {
@@ -5735,6 +5787,7 @@ exports.NodeType = NodeType;
5735
5787
  exports.absolutifyURLs = absolutifyURLs;
5736
5788
  exports.adaptCssForReplay = adaptCssForReplay;
5737
5789
  exports.buildNodeWithSN = buildNodeWithSN;
5790
+ exports.checkDataURLSize = checkDataURLSize;
5738
5791
  exports.classMatchesRegex = classMatchesRegex;
5739
5792
  exports.cleanupSnapshot = cleanupSnapshot;
5740
5793
  exports.createCache = createCache;
@@ -5755,6 +5808,7 @@ exports.isShadowRoot = isShadowRoot;
5755
5808
  exports.maskInputValue = maskInputValue;
5756
5809
  exports.needMaskingText = needMaskingText;
5757
5810
  exports.rebuild = rebuild;
5811
+ exports.recompressBase64Image = recompressBase64Image;
5758
5812
  exports.serializeNodeWithId = serializeNodeWithId;
5759
5813
  exports.snapshot = snapshot;
5760
5814
  exports.stringifyRule = stringifyRule;