0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-25 11:17:50 +01:00
posthog/ee/frontend/mobile-replay/index.ts
Paul D'Ambra 0fdb1e0fe3
feat: dedupe incremental mutations for mobile replay (#19974)
* start passing context around instead of multiple parameters

* start passing a result and context back from conversions

* even more using the context and the results

* get id sequences under control

* manually run prettier

* add lint staged rules for ee TS code

* remove console logs

* start tracking ids as they are processedD

* Add a new test case and so update _all_ of the ids :/

* don't process the same add or update id more than once

* refactor similar closer together

* move keyboard style override into context

* snapshots

* remove constant

* need to fangle context in case select options ever starts to change it
2024-01-26 16:14:14 +00:00

83 lines
2.8 KiB
TypeScript

import { eventWithTime } from '@rrweb/types'
import { captureException, captureMessage } from '@sentry/react'
import Ajv, { ErrorObject } from 'ajv'
import { mobileEventWithTime } from './mobile.types'
import mobileSchema from './schema/mobile/rr-mobile-schema.json'
import webSchema from './schema/web/rr-web-schema.json'
import { makeCustomEvent, makeFullEvent, makeIncrementalEvent, makeMetaEvent } from './transformer/transformers'
const ajv = new Ajv({
allowUnionTypes: true,
}) // options can be passed, e.g. {allErrors: true}
const transformers: Record<number, (x: any) => eventWithTime> = {
2: makeFullEvent,
3: makeIncrementalEvent,
4: makeMetaEvent,
5: makeCustomEvent,
}
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)
function couldBeEventWithTime(x: unknown): x is eventWithTime | mobileEventWithTime {
return typeof x === 'object' && x !== null && 'type' in x && 'timestamp' in x
}
export function transformEventToWeb(event: unknown, validateTransformation?: boolean): eventWithTime {
// the transformation needs to never break a recording itself
// so, we default to returning what we received
// replacing it only if there's a valid transformation
let result = event as eventWithTime
try {
if (couldBeEventWithTime(event)) {
const transformer = transformers[event.type]
if (transformer) {
const transformed = transformer(event)
if (validateTransformation) {
validateAgainstWebSchema(transformed)
}
result = transformed
}
} else {
captureMessage(`No type in event`, { extra: { event } })
}
} catch (e) {
captureException(e, { extra: { event } })
}
return result
}
export function transformToWeb(mobileData: (eventWithTime | mobileEventWithTime)[]): eventWithTime[] {
return mobileData.reduce((acc, event) => {
const transformed = transformEventToWeb(event)
acc.push(transformed ? transformed : (event as eventWithTime))
return acc
}, [] as eventWithTime[])
}
export function validateAgainstWebSchema(data: unknown): boolean {
const validationResult = webSchemaValidator(data)
if (!validationResult) {
// we are passing all data through this validation now and don't know how safe the schema is
captureMessage('transformation did not match schema', {
extra: { data, errors: webSchemaValidator.errors },
})
}
return validationResult
}