Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/rrweb-snapshot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import rebuild, {
rebuildIntoSandboxedIframe,
} from './rebuild';
export * from './types';
// Legacy broad export kept for compatibility. New internal imports should
// prefer snapshot-utils.ts / rebuild-utils.ts domain entrypoints.
export * from './utils';

export {
Expand Down
37 changes: 37 additions & 0 deletions packages/rrweb-snapshot/src/rebuild-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NodeType } from '@rrweb/types';
import type {
documentNode,
documentTypeNode,
elementNode,
serializedNode,
textNode,
} from '@rrweb/types';

export { isElement, Mirror, extractFileExtension } from './shared-utils';

export function isNodeMetaEqual(a: serializedNode, b: serializedNode): boolean {
if (!a || !b || a.type !== b.type) return false;
if (a.type === NodeType.Document)
return a.compatMode === (b as documentNode).compatMode;
else if (a.type === NodeType.DocumentType)
return (
a.name === (b as documentTypeNode).name &&
a.publicId === (b as documentTypeNode).publicId &&
a.systemId === (b as documentTypeNode).systemId
);
else if (
a.type === NodeType.Comment ||
a.type === NodeType.Text ||
a.type === NodeType.CDATA
)
return a.textContent === (b as textNode).textContent;
else if (a.type === NodeType.Element)
return (
a.tagName === (b as elementNode).tagName &&
JSON.stringify(a.attributes) ===
JSON.stringify((b as elementNode).attributes) &&
a.isSVG === (b as elementNode).isSVG &&
a.needBlock === (b as elementNode).needBlock
);
return false;
}
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
Mirror,
isNodeMetaEqual,
extractFileExtension,
} from './utils';
} from './rebuild-utils';
import postcss from 'postcss';

const tagMap: tagMap = {
Expand Down Expand Up @@ -795,7 +795,7 @@
options: RebuildIntoSandboxedIframeOptions,
): { iframe: HTMLIFrameElement; node: Node | null } {
const iframe = createSandboxedIframe(options);
const doc = iframe.contentDocument!;

Check warning on line 798 in packages/rrweb-snapshot/src/rebuild.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

let node: Node | null;
try {
Expand Down
97 changes: 97 additions & 0 deletions packages/rrweb-snapshot/src/shared-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { idNodeMap, nodeMetaMap } from './types';
import type { IMirror, serializedNodeWithId } from '@rrweb/types';

export function isElement(n: Node): n is Element {
return n.nodeType === n.ELEMENT_NODE;
}

export class Mirror implements IMirror<Node> {
private idNodeMap: idNodeMap = new Map();
private nodeMetaMap: nodeMetaMap = new WeakMap();

getId(n: Node | undefined | null): number {
if (!n) return -1;

const id = this.getMeta(n)?.id;

// if n is not a serialized Node, use -1 as its id.
return id ?? -1;
}

getNode(id: number): Node | null {
return this.idNodeMap.get(id) || null;
}

getIds(): number[] {
return Array.from(this.idNodeMap.keys());
}

getMeta(n: Node): serializedNodeWithId | null {
return this.nodeMetaMap.get(n) || null;
}

// removes the node from idNodeMap
// doesn't remove the node from nodeMetaMap
removeNodeFromMap(n: Node) {
const id = this.getId(n);
this.idNodeMap.delete(id);

if (n.childNodes) {
n.childNodes.forEach((childNode) =>
this.removeNodeFromMap(childNode as unknown as Node),
);
}
}

has(id: number): boolean {
return this.idNodeMap.has(id);
}

hasNode(node: Node): boolean {
return this.nodeMetaMap.has(node);
}

add(n: Node, meta: serializedNodeWithId) {
const id = meta.id;
this.idNodeMap.set(id, n);
this.nodeMetaMap.set(n, meta);
}

replace(id: number, n: Node) {
const oldNode = this.getNode(id);
if (oldNode) {
const meta = this.nodeMetaMap.get(oldNode);
if (meta) this.nodeMetaMap.set(n, meta);
}
this.idNodeMap.set(id, n);
}

reset() {
this.idNodeMap = new Map();
this.nodeMetaMap = new WeakMap();
}
}

export function createMirror(): Mirror {
return new Mirror();
}

/**
* Extracts the file extension from an a path, considering search parameters and fragments.
* @param path - Path to file
* @param baseURL - [optional] Base URL of the page, used to resolve relative paths. Defaults to current page URL.
*/
export function extractFileExtension(
path: string,
baseURL?: string,
): string | null {
let url;
try {
url = new URL(path, baseURL ?? window.location.href);
} catch (err) {
return null;
}
const regex = /\.([0-9a-z]+)(?:$)/i;
const match = url.pathname.match(regex);
return match?.[1] ?? null;
}
Loading
Loading