-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add ee licensed replay transformer
- Loading branch information
1 parent
e12454a
commit d8afa8d
Showing
15 changed files
with
1,994 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { eventWithTime } from '@rrweb/types' | ||
import Ajv, { ErrorObject } from 'ajv' | ||
|
||
import mobileSchema from './schema/mobile/rr-mobile-schema.json' | ||
import webSchema from './schema/web/rr-web-schema.json' | ||
import { makeFullEvent, makeMetaEvent } from './transformers' | ||
|
||
const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} | ||
|
||
const transformers: Record<number, (x: any) => eventWithTime> = { | ||
4: makeMetaEvent, | ||
10: makeFullEvent, | ||
} | ||
|
||
const mobileSchemaValidator = ajv.compile(mobileSchema) | ||
|
||
export function validateFromMobile(data: unknown): { | ||
isValid: boolean | ||
errors: ErrorObject[] | null | undefined | ||
} { | ||
const isValid = mobileSchemaValidator(data) | ||
return { | ||
isValid, | ||
errors: isValid ? null : mobileSchemaValidator.errors, | ||
} | ||
} | ||
|
||
const webSchemaValidator = ajv.compile(webSchema) | ||
|
||
export class TransformationError implements Error { | ||
name = 'TransformationError' | ||
message = 'Failed to transform to web schema' | ||
errors: ErrorObject<string, Record<string, unknown>, unknown>[] | null | undefined | ||
|
||
constructor(_errors: ErrorObject<string, Record<string, unknown>, unknown>[] | null | undefined) { | ||
this.errors = _errors | ||
} | ||
} | ||
|
||
export function transformToWeb(mobileData: any[]): string { | ||
const response = mobileData.reduce((acc, event) => { | ||
const transformer = transformers[event.type] | ||
if (!transformer) { | ||
console.warn(`No transformer for event type ${event.type}`) | ||
} else { | ||
const transformed = transformer(event) | ||
validateAgainstWebSchema(transformed) | ||
acc.push(transformed) | ||
} | ||
return acc | ||
}, []) | ||
|
||
return JSON.stringify(response) | ||
} | ||
|
||
export function validateAgainstWebSchema(data: unknown): boolean { | ||
const validationResult = webSchemaValidator(data) | ||
if (!validationResult) { | ||
console.error(webSchemaValidator.errors) | ||
throw new TransformationError(webSchemaValidator.errors) | ||
} | ||
return validationResult | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// copied from rrweb-snapshot, not included in rrweb types | ||
import { customEvent, EventType } from '@rrweb/types' | ||
|
||
export enum NodeType { | ||
Document = 0, | ||
DocumentType = 1, | ||
Element = 2, | ||
Text = 3, | ||
CDATA = 4, | ||
Comment = 5, | ||
} | ||
|
||
export type documentNode = { | ||
type: NodeType.Document | ||
childNodes: serializedNodeWithId[] | ||
compatMode?: string | ||
} | ||
|
||
export type documentTypeNode = { | ||
type: NodeType.DocumentType | ||
name: string | ||
publicId: string | ||
systemId: string | ||
} | ||
|
||
export type attributes = { | ||
[key: string]: string | number | true | null | ||
} | ||
|
||
export type elementNode = { | ||
type: NodeType.Element | ||
tagName: string | ||
attributes: attributes | ||
childNodes: serializedNodeWithId[] | ||
isSVG?: true | ||
needBlock?: boolean | ||
// This is a custom element or not. | ||
isCustom?: true | ||
} | ||
|
||
export type textNode = { | ||
type: NodeType.Text | ||
textContent: string | ||
isStyle?: true | ||
} | ||
|
||
export type cdataNode = { | ||
type: NodeType.CDATA | ||
textContent: '' | ||
} | ||
|
||
export type commentNode = { | ||
type: NodeType.Comment | ||
textContent: string | ||
} | ||
|
||
export type serializedNode = (documentNode | documentTypeNode | elementNode | textNode | cdataNode | commentNode) & { | ||
rootId?: number | ||
isShadowHost?: boolean | ||
isShadow?: boolean | ||
} | ||
|
||
export type serializedNodeWithId = serializedNode & { id: number } | ||
|
||
// end copied section | ||
|
||
export type MobileNodeType = 'text' | 'image' | 'rectangle' | ||
|
||
export type MobileStyles = { | ||
color?: string | ||
backgroundColor?: string | ||
/** | ||
* @description if borderWidth is present, then border style is assumed to be solid | ||
*/ | ||
borderWidth?: string | number | ||
/** | ||
* @description if borderRadius is present, then border style is assumed to be solid | ||
*/ | ||
borderRadius?: string | number | ||
/** | ||
* @description if borderColor is present, then border style is assumed to be solid | ||
*/ | ||
borderColor?: string | ||
} | ||
|
||
type wireframeBase = { | ||
id: number | ||
/** | ||
* @description x and y are the top left corner of the element, if they are present then the element is absolutely positioned | ||
*/ | ||
x: number | ||
y: number | ||
width: number | ||
height: number | ||
childWireframes?: wireframe[] | ||
type: MobileNodeType | ||
style?: MobileStyles | ||
} | ||
|
||
export type wireframeText = wireframeBase & { | ||
type: 'text' | ||
text: string | ||
} | ||
|
||
export type wireframeImage = wireframeBase & { | ||
type: 'image' | ||
/** | ||
* @description this will be used as base64 encoded image source, with no other attributes it is assumed to be a PNG | ||
*/ | ||
base64: string | ||
} | ||
|
||
export type wireframeRectangle = wireframeBase & { | ||
type: 'rectangle' | ||
} | ||
|
||
export type wireframe = wireframeText | wireframeImage | wireframeRectangle | ||
|
||
// the rrweb full snapshot event type, but it contains wireframes not html | ||
export type fullSnapshotEvent = { | ||
type: EventType.FullSnapshot | ||
data: { | ||
/** | ||
* @description This mimics the RRWeb full snapshot event type, except instead of reporting a serialized DOM it reports a wireframe representation of the screen. | ||
*/ | ||
wireframes: wireframe[] | ||
initialOffset: { | ||
top: number | ||
left: number | ||
} | ||
} | ||
} | ||
|
||
export type metaEvent = { | ||
type: EventType.Meta | ||
data: { | ||
/** | ||
* @description This mimics the RRWeb meta event type, except does not report href. | ||
*/ | ||
width: number | ||
height: number | ||
} | ||
} | ||
|
||
export type mobileEvent = fullSnapshotEvent | metaEvent | customEvent | ||
|
||
export type mobileEventWithTime = mobileEvent & { | ||
timestamp: number | ||
delay?: number | ||
} |
Oops, something went wrong.