fix(tracker): use node guards instead of instanceof in some cases; import type App

This commit is contained in:
Alex Kaminskii 2022-06-03 14:17:53 +02:00
parent 1495f3bc5d
commit e57d90e5a1
14 changed files with 74 additions and 36 deletions

View file

@ -0,0 +1,32 @@
export function isSVGElement(node: Element): node is SVGElement {
return node.namespaceURI === 'http://www.w3.org/2000/svg';
}
export function isElementNode(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE
}
export function isTextNode(node: Node): node is Text {
return node.nodeType === Node.TEXT_NODE
}
export function isRootNode(node: Node): boolean {
return node.nodeType === Node.DOCUMENT_NODE ||
node.nodeType === Node.DOCUMENT_FRAGMENT_NODE
}
type TagTypeMap = {
HTML: HTMLHtmlElement
IMG: HTMLImageElement
INPUT: HTMLInputElement
TEXTAREA: HTMLTextAreaElement
SELECT: HTMLSelectElement
LABEL: HTMLLabelElement
IFRAME: HTMLIFrameElement
STYLE: HTMLStyleElement | SVGStyleElement
LINK: HTMLLinkElement
}
export function hasTag<T extends keyof TagTypeMap>(el: Node, tagName: T): el is TagTypeMap[typeof tagName] {
return el.nodeName.toUpperCase() === tagName
}

View file

@ -32,7 +32,7 @@ export default class Nodes {
listeners.push([type, elementListener]);
}
registerNode(node: Node): [number, boolean] {
registerNode(node: Node): [id: number, isNew: boolean] {
let id: number = (node as any)[this.node_id];
const isNew = id === undefined;
if (isNew) {

View file

@ -1,6 +1,6 @@
import type App from "./index.js";
import { stars, hasOpenreplayAttribute } from "../utils.js";
import App from "./index.js";
import { isInstance } from "./context.js";
import { isElementNode } from "./guards.js";
export interface Options {
obscureTextEmails: boolean;
@ -21,7 +21,7 @@ export default class Sanitizer {
handleNode(id: number, parentID: number, node: Node) {
if (
this.masked.has(parentID) ||
(isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))
(isElementNode(node) && hasOpenreplayAttribute(node, 'masked'))
) {
this.masked.add(id);
}

View file

@ -1,4 +1,5 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import { hasTag } from "../app/guards.js";
import { IN_BROWSER } from "../utils.js";
import { ConsoleLog } from "../../common/messages.js";
@ -139,7 +140,7 @@ export default function (app: App, opts: Partial<Options>): void {
patchConsole(window.console);
app.nodes.attachNodeCallback(app.safe(node => {
if (node instanceof HTMLIFrameElement) {
if (hasTag(node, "IFRAME")) { // TODO: newContextCallback
let context = node.contentWindow
if (context) {
patchConsole((context as (Window & typeof globalThis)).console)

View file

@ -1,5 +1,7 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from "../../common/messages.js";
import { hasTag } from "../app/guards.js";
export default function(app: App | null) {
if (app === null) {
@ -41,10 +43,7 @@ export default function(app: App | null) {
};
app.nodes.attachNodeCallback((node: Node): void => {
if (!(node instanceof HTMLStyleElement)) {
return;
}
if (!(node.sheet instanceof CSSStyleSheet)) {
if (!hasTag(node, "STYLE") || !node.sheet) {
return;
}
if (node.textContent !== null && node.textContent.trim().length > 0) {

View file

@ -1,5 +1,5 @@
import type App from "../app/index.js";
import type Message from "../../common/messages.js";
import App from "../app/index.js";
import { JSException } from "../../common/messages.js";
import ErrorStackParser from 'error-stack-parser';

View file

@ -1,6 +1,8 @@
import type App from "../app/index.js";
import { timestamp, isURL } from "../utils.js";
import App from "../app/index.js";
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../../common/messages.js";
import { hasTag } from "../app/guards.js";
const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
@ -51,7 +53,7 @@ export default function (app: App): void {
});
app.nodes.attachNodeCallback((node: Node): void => {
if (!(node instanceof HTMLImageElement)) {
if (!hasTag(node, "IMG")) {
return;
}
app.nodes.attachElementListener('error', node, sendImgSrc);

View file

@ -1,19 +1,20 @@
import type App from "../app/index.js";
import {
normSpaces,
IN_BROWSER,
getLabelAttribute,
hasOpenreplayAttribute,
} from "../utils.js";
import App from "../app/index.js";
import { hasTag } from "../app/guards.js";
import { SetInputTarget, SetInputValue, SetInputChecked } from "../../common/messages.js";
// TODO: take into consideration "contenteditable" attribute
type TextEditableElement = HTMLInputElement | HTMLTextAreaElement
function isTextEditable(node: any): node is TextEditableElement {
if (node instanceof HTMLTextAreaElement) {
if (hasTag(node, "TEXTAREA")) {
return true;
}
if (!(node instanceof HTMLInputElement)) {
if (!hasTag(node, "INPUT")) {
return false;
}
const type = node.type;
@ -28,7 +29,7 @@ function isTextEditable(node: any): node is TextEditableElement {
}
function isCheckable(node: any): node is HTMLInputElement {
if (!(node instanceof HTMLInputElement)) {
if (!hasTag(node, "INPUT")) {
return false;
}
const type = node.type;
@ -42,7 +43,7 @@ const labelElementFor: (
? (node) => {
let p: Node | null = node;
while ((p = p.parentNode) !== null) {
if (p instanceof HTMLLabelElement) {
if (hasTag(p, "LABEL")) {
return p
}
}
@ -54,7 +55,7 @@ const labelElementFor: (
: (node) => {
let p: Node | null = node;
while ((p = p.parentNode) !== null) {
if (p instanceof HTMLLabelElement) {
if (hasTag(p, "LABEL")) {
return p as HTMLLabelElement;
}
}
@ -183,7 +184,7 @@ export default function (app: App, opts: Partial<Options>): void {
return;
}
// TODO: support multiple select (?): use selectedOptions; Need send target?
if (node instanceof HTMLSelectElement) {
if (hasTag(node, "SELECT")) {
sendInputValue(id, node)
app.attachEventListener(node, "change", () => {
sendInputValue(id, node)

View file

@ -1,4 +1,4 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import { LongTask } from "../../common/messages.js";
// https://w3c.github.io/performance-timeline/#the-performanceentry-interface

View file

@ -1,9 +1,10 @@
import type App from "../app/index.js";
import { hasTag, isSVGElement } from "../app/guards.js";
import {
normSpaces,
hasOpenreplayAttribute,
getLabelAttribute,
} from "../utils.js";
import App from "../app/index.js";
import { MouseMove, MouseClick } from "../../common/messages.js";
import { getInputLabel } from "./input.js";
@ -56,7 +57,7 @@ function _getTarget(target: Element): Element | null {
}
element = element.parentElement;
}
if (target instanceof SVGElement) {
if (isSVGElement(target)) {
let owner = target.ownerSVGElement;
while (owner !== null) {
target = owner;
@ -89,7 +90,7 @@ export default function (app: App): void {
if (dl !== null) {
return dl;
}
if (target instanceof HTMLInputElement) {
if (hasTag(target, "INPUT")) {
return getInputLabel(target)
}
if (isClickable(target)) {

View file

@ -1,4 +1,4 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import { IN_BROWSER } from "../utils.js";
import { PerformanceTrack } from "../../common/messages.js";

View file

@ -1,5 +1,6 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import { SetViewportScroll, SetNodeScroll } from "../../common/messages.js";
import { isElementNode } from "../app/guards.js";
export default function (app: App): void {
let documentScroll = false;
@ -34,11 +35,11 @@ export default function (app: App): void {
nodeScroll.clear();
});
app.nodes.attachNodeCallback(node => {
if (node instanceof Element && node.scrollLeft + node.scrollTop > 0) {
nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
}
})
// app.nodes.attachNodeCallback(node => {
// if (isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
// nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
// }
// })
app.attachEventListener(window, 'scroll', (e: Event): void => {
const target = e.target;

View file

@ -1,8 +1,9 @@
import type Message from "../../common/messages.js";
import type App from "../app/index.js";
import { hasTag } from "../app/guards.js";
import { isURL } from "../utils.js";
import App from "../app/index.js";
import { ResourceTiming, PageLoadTiming, PageRenderTiming } from "../../common/messages.js";
// Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js
interface ResourcesTimeMap {
@ -21,7 +22,7 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array<PaintBlock> {
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
let src = '';
if (element instanceof HTMLImageElement) {
if (hasTag(element, "IMG")) {
src = element.currentSrc || element.src;
}
if (!src) {

View file

@ -1,4 +1,4 @@
import App from "../app/index.js";
import type App from "../app/index.js";
import {
SetPageLocation,
SetViewportSize,