diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000000..298aa758576
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+.eslintrc.js
+jest.config.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index d8a5b12f00f..e9434574a73 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -54,6 +54,7 @@ module.exports = {
'compat',
'posthog',
'simple-import-sort',
+ 'import',
],
rules: {
'no-console': ['error', { allow: ['warn', 'error'] }],
@@ -261,6 +262,19 @@ module.exports = {
'no-constant-condition': 'off',
'no-prototype-builtins': 'off',
'no-irregular-whitespace': 'off',
+ 'import/no-restricted-paths': [
+ 'error',
+ {
+ zones: [
+ {
+ target: './frontend/**',
+ from: './ee/frontend/**',
+ message:
+ "EE licensed TypeScript should only be accessed via the posthogEE objects. Use `import posthogEE from '@posthog/ee/exports'`",
+ },
+ ],
+ },
+ ],
},
overrides: [
{
diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml
index c586598152f..7d49cd5c23c 100644
--- a/.github/workflows/ci-frontend.yml
+++ b/.github/workflows/ci-frontend.yml
@@ -107,6 +107,10 @@ jobs:
if: needs.changes.outputs.frontend == 'true'
run: pnpm schema:build:json && git diff --exit-code
+ - name: Check if mobile replay "schema.json" is up to date
+ if: needs.changes.outputs.frontend == 'true'
+ run: pnpm mobile-replay:schema:build:json && git diff --exit-code
+
- name: Check toolbar bundle size
if: needs.changes.outputs.frontend == 'true'
uses: preactjs/compressed-size-action@v2
diff --git a/.run/PostHog.run.xml b/.run/PostHog.run.xml
index df41d468add..b36e0c48a93 100644
--- a/.run/PostHog.run.xml
+++ b/.run/PostHog.run.xml
@@ -17,7 +17,7 @@
-
+
@@ -48,4 +48,4 @@
-
\ No newline at end of file
+
diff --git a/ee/frontend/exports.ts b/ee/frontend/exports.ts
index 29d80016d73..d973463019b 100644
--- a/ee/frontend/exports.ts
+++ b/ee/frontend/exports.ts
@@ -1,13 +1,12 @@
import { PostHogEE } from '@posthog/ee/types'
-const myTestCode = (): void => {
- // eslint-disable-next-line no-console
- console.log('it works!')
-}
+import { transformEventToWeb, transformToWeb } from './mobile-replay'
-const postHogEE: PostHogEE = {
- enabled: true,
- myTestCode,
-}
-
-export default postHogEE
+export default async (): Promise =>
+ Promise.resolve({
+ enabled: true,
+ mobileReplay: {
+ transformEventToWeb,
+ transformToWeb,
+ },
+ })
diff --git a/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap
new file mode 100644
index 00000000000..0d5adb4440b
--- /dev/null
+++ b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap
@@ -0,0 +1,429 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`replay/transform transform can convert images 1`] = `
+[
+ {
+ "data": {
+ "height": 600,
+ "href": "",
+ "width": 300,
+ },
+ "timestamp": 1,
+ "type": 4,
+ },
+ {
+ "data": {
+ "initialOffset": {
+ "left": 0,
+ "top": 0,
+ },
+ "node": {
+ "childNodes": [
+ {
+ "id": 2,
+ "name": "html",
+ "publicId": "",
+ "systemId": "",
+ "type": 1,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [],
+ "id": 4,
+ "tagName": "head",
+ "type": 2,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {
+ "style": "color: #ffffff;width: 100px;height: 30px;position: absolute;left: 11px;top: 12px;overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "id": 12345,
+ "textContent": "Ⱏ遲䩞㡛쓯잘ጫ䵤㥦鷁끞鈅毅┌빯湌Თ",
+ "type": 3,
+ },
+ ],
+ "id": 102,
+ "tagName": "div",
+ "type": 2,
+ },
+ {
+ "attributes": {
+ "height": 30,
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=",
+ "style": "width: 100px;height: 30px;position: absolute;left: 25px;top: 42px;",
+ "width": 100,
+ },
+ "childNodes": [],
+ "id": 12345,
+ "tagName": "img",
+ "type": 2,
+ },
+ ],
+ "id": 101,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 5,
+ "tagName": "body",
+ "type": 2,
+ },
+ ],
+ "id": 3,
+ "tagName": "html",
+ "type": 2,
+ },
+ ],
+ "id": 1,
+ "type": 0,
+ },
+ },
+ "timestamp": 1,
+ "type": 2,
+ },
+]
+`;
+
+exports[`replay/transform transform can convert rect with text 1`] = `
+[
+ {
+ "data": {
+ "height": 600,
+ "href": "",
+ "width": 300,
+ },
+ "timestamp": 1,
+ "type": 4,
+ },
+ {
+ "data": {
+ "initialOffset": {
+ "left": 0,
+ "top": 0,
+ },
+ "node": {
+ "childNodes": [
+ {
+ "id": 2,
+ "name": "html",
+ "publicId": "",
+ "systemId": "",
+ "type": 1,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [],
+ "id": 4,
+ "tagName": "head",
+ "type": 2,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {
+ "style": "width: 100px;height: 30px;position: absolute;left: 11px;top: 12px;",
+ "viewBox": "0 0 100 30",
+ },
+ "childNodes": [
+ {
+ "attributes": {
+ "fill": "transparent",
+ "height": 30,
+ "rx": "10px",
+ "stroke": "#ee3ee4",
+ "stroke-width": "4",
+ "width": 100,
+ "x": 0,
+ "y": 0,
+ },
+ "childNodes": [],
+ "id": 104,
+ "tagName": "rect",
+ "type": 2,
+ },
+ ],
+ "id": 12345,
+ "tagName": "svg",
+ "type": 2,
+ },
+ {
+ "attributes": {
+ "style": "width: 100px;height: 30px;position: absolute;left: 13px;top: 17px;overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "id": 12345,
+ "textContent": "i am in the box",
+ "type": 3,
+ },
+ ],
+ "id": 105,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 103,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 5,
+ "tagName": "body",
+ "type": 2,
+ },
+ ],
+ "id": 3,
+ "tagName": "html",
+ "type": 2,
+ },
+ ],
+ "id": 1,
+ "type": 0,
+ },
+ },
+ "timestamp": 1,
+ "type": 2,
+ },
+]
+`;
+
+exports[`replay/transform transform can ignore unknown wireframe types 1`] = `
+[
+ {
+ "data": {
+ "height": 600,
+ "href": "",
+ "width": 300,
+ },
+ "timestamp": 1,
+ "type": 4,
+ },
+ {
+ "data": {
+ "initialOffset": {
+ "left": 0,
+ "top": 0,
+ },
+ "node": {
+ "childNodes": [
+ {
+ "id": 2,
+ "name": "html",
+ "publicId": "",
+ "systemId": "",
+ "type": 1,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [],
+ "id": 4,
+ "tagName": "head",
+ "type": 2,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [],
+ "id": 100,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 5,
+ "tagName": "body",
+ "type": 2,
+ },
+ ],
+ "id": 3,
+ "tagName": "html",
+ "type": 2,
+ },
+ ],
+ "id": 1,
+ "type": 0,
+ },
+ },
+ "timestamp": 1,
+ "type": 2,
+ },
+]
+`;
+
+exports[`replay/transform transform can short-circuit non-mobile full snapshot 1`] = `
+[
+ {
+ "data": {
+ "height": 600,
+ "href": "https://my-awesome.site",
+ "width": 300,
+ },
+ "timestamp": 1,
+ "type": 4,
+ },
+ {
+ "data": {
+ "node": {
+ "the": "payload",
+ },
+ },
+ "timestamp": 1,
+ "type": 2,
+ },
+]
+`;
+
+exports[`replay/transform transform child wireframes are processed 1`] = `
+[
+ {
+ "data": {
+ "height": 600,
+ "href": "",
+ "width": 300,
+ },
+ "timestamp": 1,
+ "type": 4,
+ },
+ {
+ "data": {
+ "initialOffset": {
+ "left": 0,
+ "top": 0,
+ },
+ "node": {
+ "childNodes": [
+ {
+ "id": 2,
+ "name": "html",
+ "publicId": "",
+ "systemId": "",
+ "type": 1,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [],
+ "id": 4,
+ "tagName": "head",
+ "type": 2,
+ },
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {},
+ "childNodes": [
+ {
+ "attributes": {
+ "style": "overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "attributes": {
+ "style": "overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "attributes": {
+ "style": "color: #ffffff;background-color: #000000;border-width: 4px;border-radius: 10px;border-color: #000ddd;border-style: solid;width: 100px;height: 30px;position: absolute;left: 11px;top: 12px;overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "id": 12345,
+ "textContent": "first nested",
+ "type": 3,
+ },
+ ],
+ "id": 107,
+ "tagName": "div",
+ "type": 2,
+ },
+ {
+ "attributes": {
+ "style": "color: #ffffff;background-color: #000000;border-width: 4px;border-radius: 10px;border-color: #000ddd;border-style: solid;width: 100px;height: 30px;position: absolute;left: 11px;top: 12px;overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "id": 12345,
+ "textContent": "second nested",
+ "type": 3,
+ },
+ ],
+ "id": 108,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 98765,
+ "tagName": "div",
+ "type": 2,
+ },
+ {
+ "attributes": {
+ "style": "color: #ffffff;background-color: #000000;border-width: 4px;border-radius: 10px;border-color: #000ddd;border-style: solid;width: 100px;height: 30px;position: absolute;left: 11px;top: 12px;overflow:hidden;white-space:nowrap;",
+ },
+ "childNodes": [
+ {
+ "id": 12345,
+ "textContent": "third (different level) nested",
+ "type": 3,
+ },
+ ],
+ "id": 109,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 123456789,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 106,
+ "tagName": "div",
+ "type": 2,
+ },
+ ],
+ "id": 5,
+ "tagName": "body",
+ "type": 2,
+ },
+ ],
+ "id": 3,
+ "tagName": "html",
+ "type": 2,
+ },
+ ],
+ "id": 1,
+ "type": 0,
+ },
+ },
+ "timestamp": 1,
+ "type": 2,
+ },
+]
+`;
diff --git a/ee/frontend/mobile-replay/index.ts b/ee/frontend/mobile-replay/index.ts
new file mode 100644
index 00000000000..25fa125265e
--- /dev/null
+++ b/ee/frontend/mobile-replay/index.ts
@@ -0,0 +1,73 @@
+import { eventWithTime } from '@rrweb/types'
+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 { makeFullEvent, makeMetaEvent } from './transformers'
+
+const ajv = new Ajv({
+ allowUnionTypes: true,
+}) // options can be passed, e.g. {allErrors: true}
+
+const transformers: Record eventWithTime> = {
+ 4: makeMetaEvent,
+ 2: 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)
+
+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): eventWithTime | null {
+ if (!couldBeEventWithTime(event)) {
+ console.warn(`No type in event: ${JSON.stringify(event)}`)
+ return null
+ }
+
+ const transformer = transformers[event.type]
+ if (transformer) {
+ const transformed = transformer(event)
+ validateAgainstWebSchema(transformed)
+ return transformed
+ } else {
+ console.warn(`No transformer for event type ${event.type}`)
+ return event as eventWithTime
+ }
+}
+
+export function transformToWeb(mobileData: (eventWithTime | mobileEventWithTime)[]): eventWithTime[] {
+ return mobileData.reduce((acc, event) => {
+ const transformed = transformEventToWeb(event)
+ if (transformed) {
+ acc.push(transformed)
+ }
+ 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
+ // TODO would we ever want to reject here?
+ console.error(webSchemaValidator.errors)
+ }
+
+ return validationResult
+}
diff --git a/ee/frontend/mobile-replay/mobile.types.ts b/ee/frontend/mobile-replay/mobile.types.ts
new file mode 100644
index 00000000000..a6c0958efe5
--- /dev/null
+++ b/ee/frontend/mobile-replay/mobile.types.ts
@@ -0,0 +1,172 @@
+// 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' | 'div'
+
+export type MobileStyles = {
+ /**
+ * @description maps to CSS color. Accepts any valid CSS color value. Expects a #RGB value e.g. #000 or #000000
+ */
+ color?: string
+ /**
+ * @description maps to CSS background-color. Accepts any valid CSS color value. Expects a #RGB value e.g. #000 or #000000
+ */
+ 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
+ /**
+ * @description vertical alignment with respect to its parent
+ */
+ verticalAlign?: 'top' | 'bottom' | 'center'
+ /**
+ * @description horizontal alignment with respect to its parent
+ */
+ horizontalAlign?: 'left' | 'right' | 'center'
+}
+
+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, if they are not present this is equivalent to setting them to 0
+ */
+ x: number
+ y: number
+ /*
+ * @description width and height are the dimensions of the element, the only accepted units is pixels. You can omit the unit.
+ */
+ 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 wireframeDiv = wireframeBase & {
+ /*
+ * @description this is the default type, if no type is specified then it is assumed to be a div
+ */
+ type: 'div'
+}
+
+export type wireframe = wireframeText | wireframeImage | wireframeRectangle | wireframeDiv
+
+// 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: {
+ href?: string
+ width: number
+ height: number
+ }
+}
+
+export type mobileEvent = fullSnapshotEvent | metaEvent | customEvent
+
+export type mobileEventWithTime = mobileEvent & {
+ timestamp: number
+ delay?: number
+}
diff --git a/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json
new file mode 100644
index 00000000000..c345ce7f4e2
--- /dev/null
+++ b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json
@@ -0,0 +1,324 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "initialOffset": {
+ "additionalProperties": false,
+ "properties": {
+ "left": {
+ "type": "number"
+ },
+ "top": {
+ "type": "number"
+ }
+ },
+ "required": ["top", "left"],
+ "type": "object"
+ },
+ "wireframes": {
+ "description": "This mimics the RRWeb full snapshot event type, except instead of reporting a serialized DOM it reports a wireframe representation of the screen.",
+ "items": {
+ "$ref": "#/definitions/wireframe"
+ },
+ "type": "array"
+ }
+ },
+ "required": ["wireframes", "initialOffset"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.FullSnapshot"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "height": {
+ "type": "number"
+ },
+ "href": {
+ "type": "string"
+ },
+ "width": {
+ "type": "number"
+ }
+ },
+ "required": ["width", "height"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Meta"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "payload": {},
+ "tag": {
+ "type": "string"
+ }
+ },
+ "required": ["tag", "payload"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Custom"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ }
+ ],
+ "definitions": {
+ "EventType.Custom": {
+ "const": 5,
+ "type": "number"
+ },
+ "EventType.FullSnapshot": {
+ "const": 2,
+ "type": "number"
+ },
+ "EventType.Meta": {
+ "const": 4,
+ "type": "number"
+ },
+ "MobileNodeType": {
+ "enum": ["text", "image", "rectangle", "div"],
+ "type": "string"
+ },
+ "MobileStyles": {
+ "additionalProperties": false,
+ "properties": {
+ "backgroundColor": {
+ "description": "maps to CSS background-color. Accepts any valid CSS color value. Expects a #RGB value e.g. #000 or #000000",
+ "type": "string"
+ },
+ "borderColor": {
+ "description": "if borderColor is present, then border style is assumed to be solid",
+ "type": "string"
+ },
+ "borderRadius": {
+ "description": "if borderRadius is present, then border style is assumed to be solid",
+ "type": ["string", "number"]
+ },
+ "borderWidth": {
+ "description": "if borderWidth is present, then border style is assumed to be solid",
+ "type": ["string", "number"]
+ },
+ "color": {
+ "description": "maps to CSS color. Accepts any valid CSS color value. Expects a #RGB value e.g. #000 or #000000",
+ "type": "string"
+ },
+ "horizontalAlign": {
+ "description": "horizontal alignment with respect to its parent",
+ "enum": ["left", "right", "center"],
+ "type": "string"
+ },
+ "verticalAlign": {
+ "description": "vertical alignment with respect to its parent",
+ "enum": ["top", "bottom", "center"],
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "wireframe": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/wireframeText"
+ },
+ {
+ "$ref": "#/definitions/wireframeImage"
+ },
+ {
+ "$ref": "#/definitions/wireframeRectangle"
+ },
+ {
+ "$ref": "#/definitions/wireframeDiv"
+ }
+ ]
+ },
+ "wireframeDiv": {
+ "additionalProperties": false,
+ "properties": {
+ "childWireframes": {
+ "items": {
+ "$ref": "#/definitions/wireframe"
+ },
+ "type": "array"
+ },
+ "height": {
+ "type": "number"
+ },
+ "id": {
+ "type": "number"
+ },
+ "style": {
+ "$ref": "#/definitions/MobileStyles"
+ },
+ "type": {
+ "$ref": "#/definitions/MobileNodeType"
+ },
+ "width": {
+ "type": "number"
+ },
+ "x": {
+ "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0",
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["height", "id", "type", "width", "x", "y"],
+ "type": "object"
+ },
+ "wireframeImage": {
+ "additionalProperties": false,
+ "properties": {
+ "base64": {
+ "description": "this will be used as base64 encoded image source, with no other attributes it is assumed to be a PNG",
+ "type": "string"
+ },
+ "childWireframes": {
+ "items": {
+ "$ref": "#/definitions/wireframe"
+ },
+ "type": "array"
+ },
+ "height": {
+ "type": "number"
+ },
+ "id": {
+ "type": "number"
+ },
+ "style": {
+ "$ref": "#/definitions/MobileStyles"
+ },
+ "type": {
+ "$ref": "#/definitions/MobileNodeType"
+ },
+ "width": {
+ "type": "number"
+ },
+ "x": {
+ "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0",
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["base64", "height", "id", "type", "width", "x", "y"],
+ "type": "object"
+ },
+ "wireframeRectangle": {
+ "additionalProperties": false,
+ "properties": {
+ "childWireframes": {
+ "items": {
+ "$ref": "#/definitions/wireframe"
+ },
+ "type": "array"
+ },
+ "height": {
+ "type": "number"
+ },
+ "id": {
+ "type": "number"
+ },
+ "style": {
+ "$ref": "#/definitions/MobileStyles"
+ },
+ "type": {
+ "$ref": "#/definitions/MobileNodeType"
+ },
+ "width": {
+ "type": "number"
+ },
+ "x": {
+ "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0",
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["height", "id", "type", "width", "x", "y"],
+ "type": "object"
+ },
+ "wireframeText": {
+ "additionalProperties": false,
+ "properties": {
+ "childWireframes": {
+ "items": {
+ "$ref": "#/definitions/wireframe"
+ },
+ "type": "array"
+ },
+ "height": {
+ "type": "number"
+ },
+ "id": {
+ "type": "number"
+ },
+ "style": {
+ "$ref": "#/definitions/MobileStyles"
+ },
+ "text": {
+ "type": "string"
+ },
+ "type": {
+ "$ref": "#/definitions/MobileNodeType"
+ },
+ "width": {
+ "type": "number"
+ },
+ "x": {
+ "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0",
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["height", "id", "text", "type", "width", "x", "y"],
+ "type": "object"
+ }
+ }
+}
diff --git a/ee/frontend/mobile-replay/schema/web/rr-web-schema.json b/ee/frontend/mobile-replay/schema/web/rr-web-schema.json
new file mode 100644
index 00000000000..26f0f34428b
--- /dev/null
+++ b/ee/frontend/mobile-replay/schema/web/rr-web-schema.json
@@ -0,0 +1,951 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {},
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.DomContentLoaded"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {},
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Load"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "initialOffset": {
+ "additionalProperties": false,
+ "properties": {
+ "left": {
+ "type": "number"
+ },
+ "top": {
+ "type": "number"
+ }
+ },
+ "required": ["top", "left"],
+ "type": "object"
+ },
+ "node": {}
+ },
+ "required": ["node", "initialOffset"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.FullSnapshot"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/incrementalData"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.IncrementalSnapshot"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "height": {
+ "type": "number"
+ },
+ "href": {
+ "type": "string"
+ },
+ "width": {
+ "type": "number"
+ }
+ },
+ "required": ["href", "width", "height"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Meta"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "payload": {},
+ "tag": {
+ "type": "string"
+ }
+ },
+ "required": ["tag", "payload"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Custom"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "additionalProperties": false,
+ "properties": {
+ "payload": {},
+ "plugin": {
+ "type": "string"
+ }
+ },
+ "required": ["plugin", "payload"],
+ "type": "object"
+ },
+ "delay": {
+ "type": "number"
+ },
+ "timestamp": {
+ "type": "number"
+ },
+ "type": {
+ "$ref": "#/definitions/EventType.Plugin"
+ }
+ },
+ "required": ["data", "timestamp", "type"],
+ "type": "object"
+ }
+ ],
+ "definitions": {
+ "CanvasContext": {
+ "enum": [0, 1, 2],
+ "type": "number"
+ },
+ "EventType.Custom": {
+ "const": 5,
+ "type": "number"
+ },
+ "EventType.DomContentLoaded": {
+ "const": 0,
+ "type": "number"
+ },
+ "EventType.FullSnapshot": {
+ "const": 2,
+ "type": "number"
+ },
+ "EventType.IncrementalSnapshot": {
+ "const": 3,
+ "type": "number"
+ },
+ "EventType.Load": {
+ "const": 1,
+ "type": "number"
+ },
+ "EventType.Meta": {
+ "const": 4,
+ "type": "number"
+ },
+ "EventType.Plugin": {
+ "const": 6,
+ "type": "number"
+ },
+ "FontDisplay": {
+ "enum": ["auto", "block", "fallback", "optional", "swap"],
+ "type": "string"
+ },
+ "FontFaceDescriptors": {
+ "additionalProperties": false,
+ "properties": {
+ "ascentOverride": {
+ "type": "string"
+ },
+ "descentOverride": {
+ "type": "string"
+ },
+ "display": {
+ "$ref": "#/definitions/FontDisplay"
+ },
+ "featureSettings": {
+ "type": "string"
+ },
+ "lineGapOverride": {
+ "type": "string"
+ },
+ "stretch": {
+ "type": "string"
+ },
+ "style": {
+ "type": "string"
+ },
+ "unicodeRange": {
+ "type": "string"
+ },
+ "variant": {
+ "type": "string"
+ },
+ "weight": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "IncrementalSource.AdoptedStyleSheet": {
+ "const": 15,
+ "type": "number"
+ },
+ "IncrementalSource.CanvasMutation": {
+ "const": 9,
+ "type": "number"
+ },
+ "IncrementalSource.Drag": {
+ "const": 12,
+ "type": "number"
+ },
+ "IncrementalSource.Font": {
+ "const": 10,
+ "type": "number"
+ },
+ "IncrementalSource.Input": {
+ "const": 5,
+ "type": "number"
+ },
+ "IncrementalSource.MediaInteraction": {
+ "const": 7,
+ "type": "number"
+ },
+ "IncrementalSource.MouseInteraction": {
+ "const": 2,
+ "type": "number"
+ },
+ "IncrementalSource.MouseMove": {
+ "const": 1,
+ "type": "number"
+ },
+ "IncrementalSource.Mutation": {
+ "const": 0,
+ "type": "number"
+ },
+ "IncrementalSource.Scroll": {
+ "const": 3,
+ "type": "number"
+ },
+ "IncrementalSource.Selection": {
+ "const": 14,
+ "type": "number"
+ },
+ "IncrementalSource.StyleDeclaration": {
+ "const": 13,
+ "type": "number"
+ },
+ "IncrementalSource.StyleSheetRule": {
+ "const": 8,
+ "type": "number"
+ },
+ "IncrementalSource.TouchMove": {
+ "const": 6,
+ "type": "number"
+ },
+ "IncrementalSource.ViewportResize": {
+ "const": 4,
+ "type": "number"
+ },
+ "MediaInteractions": {
+ "enum": [0, 1, 2, 3, 4],
+ "type": "number"
+ },
+ "MouseInteractions": {
+ "enum": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ "type": "number"
+ },
+ "PointerTypes": {
+ "enum": [0, 1, 2],
+ "type": "number"
+ },
+ "SelectionRange": {
+ "additionalProperties": false,
+ "properties": {
+ "end": {
+ "type": "number"
+ },
+ "endOffset": {
+ "type": "number"
+ },
+ "start": {
+ "type": "number"
+ },
+ "startOffset": {
+ "type": "number"
+ }
+ },
+ "required": ["start", "startOffset", "end", "endOffset"],
+ "type": "object"
+ },
+ "addedNodeMutation": {
+ "additionalProperties": false,
+ "properties": {
+ "nextId": {
+ "type": ["number", "null"]
+ },
+ "node": {},
+ "parentId": {
+ "type": "number"
+ },
+ "previousId": {
+ "type": ["number", "null"]
+ }
+ },
+ "required": ["parentId", "nextId", "node"],
+ "type": "object"
+ },
+ "adoptedStyleSheetData": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.AdoptedStyleSheet"
+ },
+ "styleIds": {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ },
+ "styles": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "rules": {
+ "items": {
+ "$ref": "#/definitions/styleSheetAddRule"
+ },
+ "type": "array"
+ },
+ "styleId": {
+ "type": "number"
+ }
+ },
+ "required": ["styleId", "rules"],
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "required": ["id", "source", "styleIds"],
+ "type": "object"
+ },
+ "attributeMutation": {
+ "additionalProperties": false,
+ "properties": {
+ "attributes": {
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/definitions/styleOMValue"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "type": "object"
+ },
+ "id": {
+ "type": "number"
+ }
+ },
+ "required": ["id", "attributes"],
+ "type": "object"
+ },
+ "canvasMutationCommand": {
+ "additionalProperties": false,
+ "properties": {
+ "args": {
+ "items": {},
+ "type": "array"
+ },
+ "property": {
+ "type": "string"
+ },
+ "setter": {
+ "const": true,
+ "type": "boolean"
+ }
+ },
+ "required": ["property", "args"],
+ "type": "object"
+ },
+ "canvasMutationData": {
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "commands": {
+ "items": {
+ "$ref": "#/definitions/canvasMutationCommand"
+ },
+ "type": "array"
+ },
+ "id": {
+ "type": "number"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.CanvasMutation"
+ },
+ "type": {
+ "$ref": "#/definitions/CanvasContext"
+ }
+ },
+ "required": ["commands", "id", "source", "type"],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "args": {
+ "items": {},
+ "type": "array"
+ },
+ "id": {
+ "type": "number"
+ },
+ "property": {
+ "type": "string"
+ },
+ "setter": {
+ "const": true,
+ "type": "boolean"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.CanvasMutation"
+ },
+ "type": {
+ "$ref": "#/definitions/CanvasContext"
+ }
+ },
+ "required": ["args", "id", "property", "source", "type"],
+ "type": "object"
+ }
+ ]
+ },
+ "fontData": {
+ "additionalProperties": false,
+ "properties": {
+ "buffer": {
+ "type": "boolean"
+ },
+ "descriptors": {
+ "$ref": "#/definitions/FontFaceDescriptors"
+ },
+ "family": {
+ "type": "string"
+ },
+ "fontSource": {
+ "type": "string"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.Font"
+ }
+ },
+ "required": ["buffer", "family", "fontSource", "source"],
+ "type": "object"
+ },
+ "incrementalData": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/mutationData"
+ },
+ {
+ "$ref": "#/definitions/mousemoveData"
+ },
+ {
+ "$ref": "#/definitions/mouseInteractionData"
+ },
+ {
+ "$ref": "#/definitions/scrollData"
+ },
+ {
+ "$ref": "#/definitions/viewportResizeData"
+ },
+ {
+ "$ref": "#/definitions/inputData"
+ },
+ {
+ "$ref": "#/definitions/mediaInteractionData"
+ },
+ {
+ "$ref": "#/definitions/styleSheetRuleData"
+ },
+ {
+ "$ref": "#/definitions/canvasMutationData"
+ },
+ {
+ "$ref": "#/definitions/fontData"
+ },
+ {
+ "$ref": "#/definitions/selectionData"
+ },
+ {
+ "$ref": "#/definitions/styleDeclarationData"
+ },
+ {
+ "$ref": "#/definitions/adoptedStyleSheetData"
+ }
+ ]
+ },
+ "inputData": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "isChecked": {
+ "type": "boolean"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.Input"
+ },
+ "text": {
+ "type": "string"
+ },
+ "userTriggered": {
+ "type": "boolean"
+ }
+ },
+ "required": ["id", "isChecked", "source", "text"],
+ "type": "object"
+ },
+ "mediaInteractionData": {
+ "additionalProperties": false,
+ "properties": {
+ "currentTime": {
+ "type": "number"
+ },
+ "id": {
+ "type": "number"
+ },
+ "muted": {
+ "type": "boolean"
+ },
+ "playbackRate": {
+ "type": "number"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.MediaInteraction"
+ },
+ "type": {
+ "$ref": "#/definitions/MediaInteractions"
+ },
+ "volume": {
+ "type": "number"
+ }
+ },
+ "required": ["id", "source", "type"],
+ "type": "object"
+ },
+ "mouseInteractionData": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "pointerType": {
+ "$ref": "#/definitions/PointerTypes"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.MouseInteraction"
+ },
+ "type": {
+ "$ref": "#/definitions/MouseInteractions"
+ },
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["id", "source", "type", "x", "y"],
+ "type": "object"
+ },
+ "mousePosition": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "timeOffset": {
+ "type": "number"
+ },
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["x", "y", "id", "timeOffset"],
+ "type": "object"
+ },
+ "mousemoveData": {
+ "additionalProperties": false,
+ "properties": {
+ "positions": {
+ "items": {
+ "$ref": "#/definitions/mousePosition"
+ },
+ "type": "array"
+ },
+ "source": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IncrementalSource.MouseMove"
+ },
+ {
+ "$ref": "#/definitions/IncrementalSource.TouchMove"
+ },
+ {
+ "$ref": "#/definitions/IncrementalSource.Drag"
+ }
+ ]
+ }
+ },
+ "required": ["source", "positions"],
+ "type": "object"
+ },
+ "mutationData": {
+ "additionalProperties": false,
+ "properties": {
+ "adds": {
+ "items": {
+ "$ref": "#/definitions/addedNodeMutation"
+ },
+ "type": "array"
+ },
+ "attributes": {
+ "items": {
+ "$ref": "#/definitions/attributeMutation"
+ },
+ "type": "array"
+ },
+ "isAttachIframe": {
+ "const": true,
+ "type": "boolean"
+ },
+ "removes": {
+ "items": {
+ "$ref": "#/definitions/removedNodeMutation"
+ },
+ "type": "array"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.Mutation"
+ },
+ "texts": {
+ "items": {
+ "$ref": "#/definitions/textMutation"
+ },
+ "type": "array"
+ }
+ },
+ "required": ["adds", "attributes", "removes", "source", "texts"],
+ "type": "object"
+ },
+ "removedNodeMutation": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "isShadow": {
+ "type": "boolean"
+ },
+ "parentId": {
+ "type": "number"
+ }
+ },
+ "required": ["parentId", "id"],
+ "type": "object"
+ },
+ "scrollData": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.Scroll"
+ },
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "required": ["id", "source", "x", "y"],
+ "type": "object"
+ },
+ "selectionData": {
+ "additionalProperties": false,
+ "properties": {
+ "ranges": {
+ "items": {
+ "$ref": "#/definitions/SelectionRange"
+ },
+ "type": "array"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.Selection"
+ }
+ },
+ "required": ["ranges", "source"],
+ "type": "object"
+ },
+ "styleDeclarationData": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "index": {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ },
+ "remove": {
+ "additionalProperties": false,
+ "properties": {
+ "property": {
+ "type": "string"
+ }
+ },
+ "required": ["property"],
+ "type": "object"
+ },
+ "set": {
+ "additionalProperties": false,
+ "properties": {
+ "priority": {
+ "type": "string"
+ },
+ "property": {
+ "type": "string"
+ },
+ "value": {
+ "type": ["string", "null"]
+ }
+ },
+ "required": ["property", "value"],
+ "type": "object"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.StyleDeclaration"
+ },
+ "styleId": {
+ "type": "number"
+ }
+ },
+ "required": ["index", "source"],
+ "type": "object"
+ },
+ "styleOMValue": {
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/styleValueWithPriority"
+ },
+ {
+ "type": "string"
+ },
+ {
+ "const": false,
+ "type": "boolean"
+ }
+ ]
+ },
+ "type": "object"
+ },
+ "styleSheetAddRule": {
+ "additionalProperties": false,
+ "properties": {
+ "index": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ }
+ ]
+ },
+ "rule": {
+ "type": "string"
+ }
+ },
+ "required": ["rule"],
+ "type": "object"
+ },
+ "styleSheetDeleteRule": {
+ "additionalProperties": false,
+ "properties": {
+ "index": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "required": ["index"],
+ "type": "object"
+ },
+ "styleSheetRuleData": {
+ "additionalProperties": false,
+ "properties": {
+ "adds": {
+ "items": {
+ "$ref": "#/definitions/styleSheetAddRule"
+ },
+ "type": "array"
+ },
+ "id": {
+ "type": "number"
+ },
+ "removes": {
+ "items": {
+ "$ref": "#/definitions/styleSheetDeleteRule"
+ },
+ "type": "array"
+ },
+ "replace": {
+ "type": "string"
+ },
+ "replaceSync": {
+ "type": "string"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.StyleSheetRule"
+ },
+ "styleId": {
+ "type": "number"
+ }
+ },
+ "required": ["source"],
+ "type": "object"
+ },
+ "styleValueWithPriority": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array"
+ },
+ "textMutation": {
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "value": {
+ "type": ["string", "null"]
+ }
+ },
+ "required": ["id", "value"],
+ "type": "object"
+ },
+ "viewportResizeData": {
+ "additionalProperties": false,
+ "properties": {
+ "height": {
+ "type": "number"
+ },
+ "source": {
+ "$ref": "#/definitions/IncrementalSource.ViewportResize"
+ },
+ "width": {
+ "type": "number"
+ }
+ },
+ "required": ["height", "source", "width"],
+ "type": "object"
+ }
+ }
+}
diff --git a/ee/frontend/mobile-replay/transform.test.ts b/ee/frontend/mobile-replay/transform.test.ts
new file mode 100644
index 00000000000..a130efddad4
--- /dev/null
+++ b/ee/frontend/mobile-replay/transform.test.ts
@@ -0,0 +1,301 @@
+import posthogEE from '@posthog/ee/exports'
+import { EventType } from '@rrweb/types'
+import { ifEeDescribe } from 'lib/ee.test'
+
+import { PostHogEE } from '../../../frontend/@posthog/ee/types'
+import { validateAgainstWebSchema, validateFromMobile } from './index'
+
+const heartEyesEmojiURL =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII='
+
+describe('replay/transform', () => {
+ describe('validation', () => {
+ test('example of validating incoming _invalid_ data', () => {
+ const invalidData = {
+ foo: 'abc',
+ bar: 'abc',
+ }
+
+ expect(validateFromMobile(invalidData).isValid).toBe(false)
+ })
+
+ test('example of validating mobile meta event', () => {
+ const validData = {
+ data: { width: 1, height: 1 },
+ timestamp: 1,
+ type: EventType.Meta,
+ }
+
+ expect(validateFromMobile(validData)).toStrictEqual({
+ isValid: true,
+ errors: null,
+ })
+ })
+
+ describe('validate web schema', () => {
+ test('does not block when invalid', () => {
+ expect(validateAgainstWebSchema({})).toBeFalsy()
+ })
+
+ test('should be valid when...', () => {
+ expect(validateAgainstWebSchema({ data: {}, timestamp: 12345, type: 0 })).toBeTruthy()
+ })
+ })
+ })
+
+ ifEeDescribe('transform', () => {
+ let posthogEEModule: PostHogEE
+ beforeEach(async () => {
+ posthogEEModule = await posthogEE()
+ })
+ test('can ignore unknown types', () => {
+ expect(
+ posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: { width: 300, height: 600 },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ data: { href: 'included when present', width: 300, height: 600 },
+ timestamp: 1,
+ type: 4,
+ },
+ { type: 9999 },
+ ])
+ ).toStrictEqual([
+ { type: 4, data: { href: '', width: 300, height: 600 }, timestamp: 1 },
+ { type: 4, data: { href: 'included when present', width: 300, height: 600 }, timestamp: 1 },
+ ])
+ })
+
+ test('can ignore unknown wireframe types', () => {
+ const unexpectedWireframeType = posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: { screen: 'App Home Page', width: 300, height: 600 },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ type: 2,
+ data: {
+ wireframes: [
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ type: 'something in the SDK but not yet the transformer',
+ },
+ ],
+ },
+ timestamp: 1,
+ },
+ ])
+ expect(unexpectedWireframeType).toMatchSnapshot()
+ })
+
+ test('can short-circuit non-mobile full snapshot', () => {
+ const allWeb = posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: { href: 'https://my-awesome.site', width: 300, height: 600 },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ type: 2,
+ data: {
+ node: { the: 'payload' },
+ },
+ timestamp: 1,
+ },
+ ])
+ expect(allWeb).toMatchSnapshot()
+ })
+
+ test('can convert images', () => {
+ const exampleWithImage = posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: {
+ screen: 'App Home Page',
+ width: 300,
+ height: 600,
+ },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ type: 2,
+ data: {
+ wireframes: [
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ // clip: {
+ // bottom: 83,
+ // right: 44,
+ // },
+ type: 'text',
+ text: 'Ⱏ遲䩞㡛쓯잘ጫ䵤㥦鷁끞鈅毅┌빯湌Თ',
+ style: {
+ // family: '疴ꖻ䖭㋑⁃⻋ꑧٹ㧕Ⓖ',
+ // size: 4220431756569966319,
+ color: '#ffffff',
+ },
+ },
+ {
+ id: 12345,
+ x: 25,
+ y: 42,
+ width: 100,
+ height: 30,
+ // clip: {
+ // bottom: 83,
+ // right: 44,
+ // },
+ type: 'image',
+ base64: heartEyesEmojiURL,
+ },
+ ],
+ },
+ timestamp: 1,
+ },
+ ])
+ expect(exampleWithImage).toMatchSnapshot()
+ })
+
+ test('can convert rect with text', () => {
+ const exampleWithRectAndText = posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: {
+ width: 300,
+ height: 600,
+ },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ type: 2,
+ data: {
+ wireframes: [
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ type: 'rectangle',
+ style: {
+ color: '#ee3ee4',
+ borderColor: '#ee3ee4',
+ borderWidth: '4',
+ borderRadius: '10px',
+ },
+ },
+ {
+ id: 12345,
+ x: 13,
+ y: 17,
+ width: 100,
+ height: 30,
+ verticalAlign: 'top',
+ horizontalAlign: 'right',
+ type: 'text',
+ text: 'i am in the box',
+ },
+ ],
+ },
+ timestamp: 1,
+ },
+ ])
+ expect(exampleWithRectAndText).toMatchSnapshot()
+ })
+
+ test('child wireframes are processed', () => {
+ const textEvent = posthogEEModule.mobileReplay?.transformToWeb([
+ {
+ data: { screen: 'App Home Page', width: 300, height: 600 },
+ timestamp: 1,
+ type: 4,
+ },
+ {
+ type: 2,
+ data: {
+ wireframes: [
+ {
+ id: 123456789,
+ childWireframes: [
+ {
+ id: 98765,
+ childWireframes: [
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ type: 'text',
+ text: 'first nested',
+ style: {
+ color: '#ffffff',
+ backgroundColor: '#000000',
+ borderWidth: '4px',
+ borderColor: '#000ddd',
+ borderRadius: '10px',
+ },
+ },
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ type: 'text',
+ text: 'second nested',
+ style: {
+ color: '#ffffff',
+ backgroundColor: '#000000',
+ borderWidth: '4px',
+ borderColor: '#000ddd',
+ borderRadius: '10px',
+ },
+ },
+ ],
+ },
+ {
+ id: 12345,
+ x: 11,
+ y: 12,
+ width: 100,
+ height: 30,
+ // clip: {
+ // bottom: 83,
+ // right: 44,
+ // },
+ type: 'text',
+ text: 'third (different level) nested',
+ style: {
+ // family: '疴ꖻ䖭㋑⁃⻋ꑧٹ㧕Ⓖ',
+ // size: 4220431756569966319,
+ color: '#ffffff',
+ backgroundColor: '#000000',
+ borderWidth: '4px',
+ borderColor: '#000ddd',
+ borderRadius: '10', // you can omit the pixels
+ },
+ },
+ ],
+ },
+ ],
+ },
+ timestamp: 1,
+ },
+ ])
+ expect(textEvent).toMatchSnapshot()
+ })
+ })
+})
diff --git a/ee/frontend/mobile-replay/transformers.ts b/ee/frontend/mobile-replay/transformers.ts
new file mode 100644
index 00000000000..10042c93468
--- /dev/null
+++ b/ee/frontend/mobile-replay/transformers.ts
@@ -0,0 +1,269 @@
+import { EventType, fullSnapshotEvent, metaEvent } from '@rrweb/types'
+
+import {
+ fullSnapshotEvent as MobileFullSnapshotEvent,
+ metaEvent as MobileMetaEvent,
+ NodeType,
+ serializedNodeWithId,
+ wireframe,
+ wireframeDiv,
+ wireframeImage,
+ wireframeRectangle,
+ wireframeText,
+} from './mobile.types'
+import { makePositionStyles, makeStylesString, makeSvgBorder } from './wireframeStyle'
+
+/**
+ * generates a sequence of ids
+ * from 100 to 9,999,999
+ * the transformer reserves ids in the range 0 to 9,999,999
+ * we reserve a range of ids because we need nodes to have stable ids across snapshots
+ * in order for incremental snapshots to work
+ * some mobile elements have to be wrapped in other elements in order to be styled correctly
+ * which means the web version of a mobile replay will use ids that don't exist in the mobile replay,
+ * and we need to ensure they don't clash
+ * -----
+ * id is typed as a number in rrweb
+ * and there's a few places in their code where rrweb uses a check for `id === -1` to bail out of processing
+ * so, it's safest to assume that id is expected to be a positive integer
+ */
+function* ids(): Generator {
+ let i = 100
+ while (i < 9999999) {
+ yield i++
+ }
+}
+const idSequence = ids()
+
+export const makeMetaEvent = (
+ mobileMetaEvent: MobileMetaEvent & {
+ timestamp: number
+ }
+): metaEvent & {
+ timestamp: number
+ delay?: number
+} => ({
+ type: EventType.Meta,
+ data: {
+ href: mobileMetaEvent.data.href || '', // the replay doesn't use the href, so we safely ignore any absence
+ // mostly we need width and height in order to size the viewport
+ width: mobileMetaEvent.data.width,
+ height: mobileMetaEvent.data.height,
+ },
+ timestamp: mobileMetaEvent.timestamp,
+})
+
+function _isPositiveInteger(id: unknown): boolean {
+ return typeof id === 'number' && id > 0 && id % 1 === 0
+}
+
+function makeDivElement(wireframe: wireframeDiv, children: serializedNodeWithId[]): serializedNodeWithId | null {
+ const _id = _isPositiveInteger(wireframe.id) ? wireframe.id : idSequence.next().value
+ return {
+ type: NodeType.Element,
+ tagName: 'div',
+ attributes: {
+ style: makeStylesString(wireframe) + 'overflow:hidden;white-space:nowrap;',
+ },
+ id: _id,
+ childNodes: children,
+ }
+}
+
+function makeTextElement(wireframe: wireframeText, children: serializedNodeWithId[]): serializedNodeWithId | null {
+ if (wireframe.type !== 'text') {
+ console.error('Passed incorrect wireframe type to makeTextElement')
+ return null
+ }
+
+ // because we might have to style the text, we always wrap it in a div
+ // and apply styles to that
+ return {
+ type: NodeType.Element,
+ tagName: 'div',
+ attributes: {
+ style: makeStylesString(wireframe) + 'overflow:hidden;white-space:nowrap;',
+ },
+ id: idSequence.next().value,
+ childNodes: [
+ {
+ type: NodeType.Text,
+ textContent: wireframe.text,
+ id: wireframe.id,
+ },
+ ...children,
+ ],
+ }
+}
+
+function makeImageElement(wireframe: wireframeImage, children: serializedNodeWithId[]): serializedNodeWithId | null {
+ let src = wireframe.base64
+ if (!src.startsWith('data:image/')) {
+ src = 'data:image/png;base64,' + src
+ }
+ return {
+ type: NodeType.Element,
+ tagName: 'img',
+ attributes: {
+ src: src,
+ width: wireframe.width,
+ height: wireframe.height,
+ style: makeStylesString(wireframe),
+ },
+ id: wireframe.id,
+ childNodes: children,
+ }
+}
+
+function makeRectangleElement(
+ wireframe: wireframeRectangle,
+ children: serializedNodeWithId[]
+): serializedNodeWithId | null {
+ return {
+ type: NodeType.Element,
+ tagName: 'svg',
+ attributes: {
+ style: makePositionStyles(wireframe),
+ viewBox: `0 0 ${wireframe.width} ${wireframe.height}`,
+ },
+ id: wireframe.id,
+ childNodes: [
+ {
+ type: NodeType.Element,
+ tagName: 'rect',
+ attributes: {
+ x: 0,
+ y: 0,
+ width: wireframe.width,
+ height: wireframe.height,
+ fill: wireframe.style?.backgroundColor || 'transparent',
+ ...makeSvgBorder(wireframe.style),
+ },
+ id: idSequence.next().value,
+ childNodes: children,
+ },
+ ],
+ }
+}
+
+function chooseConverter(
+ wireframe: T
+): (wireframe: T, children: serializedNodeWithId[]) => serializedNodeWithId | null {
+ // in theory type is always present
+ // but since this is coming over the wire we can't really be sure
+ // and so we default to div
+ const converterType = wireframe.type || 'div'
+ switch (converterType) {
+ case 'text':
+ return makeTextElement as unknown as (
+ wireframe: T,
+ children: serializedNodeWithId[]
+ ) => serializedNodeWithId | null
+ case 'image':
+ return makeImageElement as unknown as (
+ wireframe: T,
+ children: serializedNodeWithId[]
+ ) => serializedNodeWithId | null
+ case 'rectangle':
+ return makeRectangleElement as unknown as (
+ wireframe: T,
+ children: serializedNodeWithId[]
+ ) => serializedNodeWithId | null
+ case 'div':
+ return makeDivElement as unknown as (
+ wireframe: T,
+ children: serializedNodeWithId[]
+ ) => serializedNodeWithId | null
+ }
+}
+
+function convertWireframesFor(wireframes: wireframe[] | undefined): serializedNodeWithId[] {
+ if (!wireframes) {
+ return []
+ }
+
+ return wireframes.reduce((acc, wireframe) => {
+ const children = convertWireframesFor(wireframe.childWireframes)
+ const converter = chooseConverter(wireframe)
+ if (!converter) {
+ console.error(`No converter for wireframe type ${wireframe.type}`)
+ return acc
+ }
+ const convertedEl = converter(wireframe, children)
+ if (convertedEl !== null) {
+ acc.push(convertedEl)
+ }
+ return acc
+ }, [] as serializedNodeWithId[])
+}
+
+export const makeFullEvent = (
+ mobileEvent: MobileFullSnapshotEvent & {
+ timestamp: number
+ delay?: number
+ }
+): fullSnapshotEvent & {
+ timestamp: number
+ delay?: number
+} => {
+ if (!('wireframes' in mobileEvent.data)) {
+ return mobileEvent as unknown as fullSnapshotEvent & {
+ timestamp: number
+ delay?: number
+ }
+ }
+
+ return {
+ type: EventType.FullSnapshot,
+ timestamp: mobileEvent.timestamp,
+ data: {
+ node: {
+ type: NodeType.Document,
+ childNodes: [
+ {
+ type: NodeType.DocumentType,
+ name: 'html',
+ publicId: '',
+ systemId: '',
+ id: 2,
+ },
+ {
+ type: NodeType.Element,
+ tagName: 'html',
+ attributes: {},
+ id: 3,
+ childNodes: [
+ {
+ type: NodeType.Element,
+ tagName: 'head',
+ attributes: {},
+ id: 4,
+ childNodes: [],
+ },
+ {
+ type: NodeType.Element,
+ tagName: 'body',
+ attributes: {},
+ id: 5,
+ childNodes: [
+ {
+ type: NodeType.Element,
+ tagName: 'div',
+ attributes: {},
+ id: idSequence.next().value,
+ childNodes: convertWireframesFor(mobileEvent.data.wireframes),
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ id: 1,
+ },
+ initialOffset: {
+ top: 0,
+ left: 0,
+ },
+ },
+ }
+}
diff --git a/ee/frontend/mobile-replay/wireframeStyle.ts b/ee/frontend/mobile-replay/wireframeStyle.ts
new file mode 100644
index 00000000000..312e728bdbc
--- /dev/null
+++ b/ee/frontend/mobile-replay/wireframeStyle.ts
@@ -0,0 +1,95 @@
+import { MobileStyles, wireframe } from './mobile.types'
+
+function ensureUnit(value: string | number): string {
+ return typeof value === 'number' ? `${value}px` : value.replace(/px$/g, '') + 'px'
+}
+
+function makeBorderStyles(wireframe: wireframe): string {
+ let styles = ''
+
+ if (wireframe.style?.borderWidth) {
+ const borderWidth = ensureUnit(wireframe.style.borderWidth)
+ styles += `border-width: ${borderWidth};`
+ }
+ if (wireframe.style?.borderRadius) {
+ const borderRadius = ensureUnit(wireframe.style.borderRadius)
+ styles += `border-radius: ${borderRadius};`
+ }
+ if (wireframe.style?.borderColor) {
+ styles += `border-color: ${wireframe.style.borderColor};`
+ }
+
+ if (styles.length > 0) {
+ styles += `border-style: solid;`
+ }
+
+ return styles
+}
+
+export function makeSvgBorder(style: MobileStyles | undefined): Record {
+ const svgBorderStyles: Record = {}
+
+ if (style?.borderWidth) {
+ svgBorderStyles['stroke-width'] = style.borderWidth.toString()
+ }
+ if (style?.borderColor) {
+ svgBorderStyles.stroke = style.borderColor
+ }
+ if (style?.borderRadius) {
+ svgBorderStyles.rx = ensureUnit(style.borderRadius)
+ }
+
+ return svgBorderStyles
+}
+
+export function makePositionStyles(wireframe: wireframe): string {
+ let styles = ''
+ if (wireframe.width) {
+ styles += `width: ${ensureUnit(wireframe.width)};`
+ }
+ if (wireframe.height) {
+ styles += `height: ${ensureUnit(wireframe.height)};`
+ }
+ if (wireframe.x || wireframe.y) {
+ styles += `position: absolute;`
+ if (wireframe.x) {
+ styles += `left: ${ensureUnit(wireframe.x)};`
+ }
+ if (wireframe.y) {
+ styles += `top: ${ensureUnit(wireframe.y)};`
+ }
+ }
+ return styles
+}
+
+function makeLayoutStyles(wireframe: wireframe): string {
+ let styles = ''
+ if (wireframe.style?.verticalAlign) {
+ styles += `align-items: ${
+ { top: 'flex-start', center: 'center', bottom: 'flex-end' }[wireframe.style.verticalAlign]
+ };`
+ }
+ if (wireframe.style?.horizontalAlign) {
+ styles += `justify-content: ${
+ { left: 'flex-start', center: 'center', right: 'flex-end' }[wireframe.style.horizontalAlign]
+ };`
+ }
+ if (styles.length) {
+ styles += `display: flex;`
+ }
+ return styles
+}
+
+export function makeStylesString(wireframe: wireframe): string {
+ let styles = ''
+ if (wireframe.style?.color) {
+ styles += `color: ${wireframe.style.color};`
+ }
+ if (wireframe.style?.backgroundColor) {
+ styles += `background-color: ${wireframe.style.backgroundColor};`
+ }
+ styles += makeBorderStyles(wireframe)
+ styles += makePositionStyles(wireframe)
+ styles += makeLayoutStyles(wireframe)
+ return styles
+}
diff --git a/frontend/@posthog/ee/exports.ts b/frontend/@posthog/ee/exports.ts
index 96d7dc5a5e9..2cfb28326e9 100644
--- a/frontend/@posthog/ee/exports.ts
+++ b/frontend/@posthog/ee/exports.ts
@@ -1,7 +1,14 @@
import { PostHogEE } from './types'
-const posthogEE: PostHogEE = {
- enabled: false,
+export default async (): Promise => {
+ // eslint-disable-next-line import/no-restricted-paths
+ return import('../../../ee/frontend/exports')
+ .then((ee) => {
+ return ee.default()
+ })
+ .catch(() => {
+ return {
+ enabled: false,
+ }
+ })
}
-
-export default posthogEE
diff --git a/frontend/@posthog/ee/types.ts b/frontend/@posthog/ee/types.ts
index 7270631c7c7..ceed593cb13 100644
--- a/frontend/@posthog/ee/types.ts
+++ b/frontend/@posthog/ee/types.ts
@@ -1,5 +1,12 @@
// NOTE: All exported items from the EE module _must_ be optionally defined to ensure we work well with FOSS
+
+import { eventWithTime } from '@rrweb/types'
+
export type PostHogEE = {
enabled: boolean
- myTestCode?: () => void
+ mobileReplay?: {
+ // defined as unknown while the mobileEventWithTime type is in the ee folder
+ transformEventToWeb(x: unknown): eventWithTime | null
+ transformToWeb(x: unknown[]): eventWithTime[]
+ }
}
diff --git a/frontend/src/lib/ee.test.ts b/frontend/src/lib/ee.test.ts
index e5cddf9ec24..ef9dcb35f63 100644
--- a/frontend/src/lib/ee.test.ts
+++ b/frontend/src/lib/ee.test.ts
@@ -3,15 +3,24 @@ import fs from 'fs'
const eeFolderExists = fs.existsSync('ee/frontend/exports.ts')
export const ifEeIt = eeFolderExists ? it : it.skip
export const ifFossIt = !eeFolderExists ? it : it.skip
+export const ifEeDescribe = eeFolderExists ? describe : describe.skip
+export const ifFossDescribe = !eeFolderExists ? describe : describe.skip
import posthogEE from '@posthog/ee/exports'
+import { PostHogEE } from '../../@posthog/ee/types'
+
describe('ee importing', () => {
+ let posthogEEModule: PostHogEE
+
+ beforeEach(async () => {
+ posthogEEModule = await posthogEE()
+ })
ifEeIt('should import actual ee code', () => {
- expect(posthogEE.enabled).toBe(true)
+ expect(posthogEEModule.enabled).toBe(true)
})
ifFossIt('should import actual ee code', () => {
- expect(posthogEE.enabled).toBe(false)
+ expect(posthogEEModule.enabled).toBe(false)
})
})
diff --git a/frontend/src/scenes/plugins/source/formatSource.ts b/frontend/src/scenes/plugins/source/formatSource.ts
index ab046a4916a..97b0c73971e 100644
--- a/frontend/src/scenes/plugins/source/formatSource.ts
+++ b/frontend/src/scenes/plugins/source/formatSource.ts
@@ -1,6 +1,8 @@
export async function formatSource(filename: string, source: string): Promise {
// Lazy-load prettier, as it's pretty big and its only use is formatting app source code
+ // @ts-expect-error
const prettier = (await import('prettier/standalone')).default
+ // @ts-expect-error
const parserTypeScript = (await import('prettier/parser-typescript')).default
if (filename.endsWith('.json')) {
diff --git a/frontend/src/scenes/session-recordings/__mocks__/recording_snapshots.ts b/frontend/src/scenes/session-recordings/__mocks__/recording_snapshots.ts
index e2e5a8ec6dd..61a0263ee56 100644
--- a/frontend/src/scenes/session-recordings/__mocks__/recording_snapshots.ts
+++ b/frontend/src/scenes/session-recordings/__mocks__/recording_snapshots.ts
@@ -1,3 +1,6 @@
+import { eventWithTime } from '@rrweb/types'
+import { prepareRecordingSnapshots } from 'scenes/session-recordings/player/sessionRecordingDataLogic'
+
import { RecordingSnapshot } from '~/types'
const lineOne =
@@ -7,6 +10,23 @@ const lineTwo =
export const snapshotsAsJSONLines = (): string => `${lineOne}\n${lineTwo}\n`
+export const convertSnapshotsByWindowId = (snapshotsByWindowId: {
+ [key: string]: eventWithTime[]
+}): RecordingSnapshot[] =>
+ Object.entries(snapshotsByWindowId).flatMap(([windowId, snapshots]) => {
+ return snapshots.map((snapshot) => ({
+ ...snapshot,
+ windowId,
+ }))
+ })
+
+export const convertSnapshotsResponse = (
+ snapshotsByWindowId: { [key: string]: eventWithTime[] },
+ existingSnapshots?: RecordingSnapshot[]
+): RecordingSnapshot[] => {
+ return prepareRecordingSnapshots(convertSnapshotsByWindowId(snapshotsByWindowId), existingSnapshots)
+}
+
export const sortedRecordingSnapshots = (): { snapshot_data_by_window_id: Record } => {
const sortedRecordingSnapshotsJson = { snapshot_data_by_window_id: {} }
diff --git a/frontend/src/scenes/session-recordings/player/inspector/PlayerInspectorList.tsx b/frontend/src/scenes/session-recordings/player/inspector/PlayerInspectorList.tsx
index dcebd362ee6..fbd4199237c 100644
--- a/frontend/src/scenes/session-recordings/player/inspector/PlayerInspectorList.tsx
+++ b/frontend/src/scenes/session-recordings/player/inspector/PlayerInspectorList.tsx
@@ -21,7 +21,12 @@ import { PlayerInspectorListItem } from './components/PlayerInspectorListItem'
import { playerInspectorLogic } from './playerInspectorLogic'
function isLocalhost(url: string | null | undefined): boolean {
- return !!url && ['localhost', '127.0.0.1'].includes(new URL(url).hostname)
+ try {
+ return !!url && ['localhost', '127.0.0.1'].includes(new URL(url).hostname)
+ } catch (e) {
+ // for e.g. mobile doesn't have a URL, so we can swallow this and move on
+ return false
+ }
}
function EmptyNetworkTab({
diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts
index 14ad52c0a62..adf0e836d45 100644
--- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts
+++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.test.ts
@@ -1,8 +1,8 @@
import { expectLogic } from 'kea-test-utils'
import { api, MOCK_TEAM_ID } from 'lib/api.mock'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
+import { convertSnapshotsByWindowId } from 'scenes/session-recordings/__mocks__/recording_snapshots'
import {
- convertSnapshotsByWindowId,
prepareRecordingSnapshots,
sessionRecordingDataLogic,
} from 'scenes/session-recordings/player/sessionRecordingDataLogic'
diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts
index 623a902ae42..c360a172388 100644
--- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts
+++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataLogic.ts
@@ -1,3 +1,4 @@
+import posthogEE from '@posthog/ee/exports'
import { EventType, eventWithTime } from '@rrweb/types'
import { captureException } from '@sentry/react'
import { actions, connect, defaults, kea, key, listeners, path, props, reducers, selectors } from 'kea'
@@ -29,22 +30,35 @@ import {
SessionRecordingUsageType,
} from '~/types'
+import { PostHogEE } from '../../../../@posthog/ee/types'
import type { sessionRecordingDataLogicType } from './sessionRecordingDataLogicType'
import { createSegments, mapSnapshotsToWindowId } from './utils/segmenter'
const IS_TEST_MODE = process.env.NODE_ENV === 'test'
const BUFFER_MS = 60000 // +- before and after start and end of a recording to query for.
-const parseEncodedSnapshots = (items: (EncodedRecordingSnapshot | string)[], sessionId: string): RecordingSnapshot[] =>
- items.flatMap((l) => {
+let postHogEEModule: PostHogEE
+
+const parseEncodedSnapshots = async (
+ items: (EncodedRecordingSnapshot | string)[],
+ sessionId: string
+): Promise => {
+ if (!postHogEEModule) {
+ postHogEEModule = await posthogEE()
+ }
+ return items.flatMap((l) => {
try {
const snapshotLine = typeof l === 'string' ? (JSON.parse(l) as EncodedRecordingSnapshot) : l
const snapshotData = snapshotLine['data']
- return snapshotData.map((d: any) => ({
- windowId: snapshotLine['window_id'],
- ...d,
- }))
+ // TODO can we type this better and still have mobileEventWithTime in ee folder?
+ return snapshotData.map((d: unknown) => {
+ const snap = postHogEEModule?.mobileReplay?.transformEventToWeb(d) || (d as eventWithTime)
+ return {
+ windowId: snapshotLine['window_id'],
+ ...(snap || (d as eventWithTime)),
+ }
+ })
} catch (e) {
posthog.capture('session recording had unparseable line', {
sessionId,
@@ -54,6 +68,7 @@ const parseEncodedSnapshots = (items: (EncodedRecordingSnapshot | string)[], ses
return []
}
})
+}
const getHrefFromSnapshot = (snapshot: RecordingSnapshot): string | undefined => {
return (snapshot.data as any)?.href || (snapshot.data as any)?.payload?.href
@@ -65,7 +80,7 @@ export const prepareRecordingSnapshots = (
): RecordingSnapshot[] => {
const seenHashes: Record = {}
- const prepared = (newSnapshots || [])
+ return (newSnapshots || [])
.concat(existingSnapshots ? existingSnapshots ?? [] : [])
.filter((snapshot) => {
// For a multitude of reasons, there can be duplicate snapshots in the same recording.
@@ -89,27 +104,6 @@ export const prepareRecordingSnapshots = (
return true
})
.sort((a, b) => a.timestamp - b.timestamp)
-
- return prepared
-}
-
-export const convertSnapshotsByWindowId = (snapshotsByWindowId: {
- [key: string]: eventWithTime[]
-}): RecordingSnapshot[] => {
- return Object.entries(snapshotsByWindowId).flatMap(([windowId, snapshots]) => {
- return snapshots.map((snapshot) => ({
- ...snapshot,
- windowId,
- }))
- })
-}
-
-// Until we change the API to return a simple list of snapshots, we need to convert this ourselves
-export const convertSnapshotsResponse = (
- snapshotsByWindowId: { [key: string]: eventWithTime[] },
- existingSnapshots?: RecordingSnapshot[]
-): RecordingSnapshot[] => {
- return prepareRecordingSnapshots(convertSnapshotsByWindowId(snapshotsByWindowId), existingSnapshots)
}
const generateRecordingReportDurations = (
@@ -346,8 +340,9 @@ export const sessionRecordingDataLogic = kea([
props.sessionRecordingId,
source.blob_key
)
+
data.snapshots = prepareRecordingSnapshots(
- parseEncodedSnapshots(encodedResponse, props.sessionRecordingId),
+ await parseEncodedSnapshots(encodedResponse, props.sessionRecordingId),
values.sessionPlayerSnapshotData?.snapshots ?? []
)
} else {
@@ -359,7 +354,7 @@ export const sessionRecordingDataLogic = kea([
const response = await api.recordings.listSnapshots(props.sessionRecordingId, params)
if (response.snapshots) {
data.snapshots = prepareRecordingSnapshots(
- parseEncodedSnapshots(response.snapshots, props.sessionRecordingId),
+ await parseEncodedSnapshots(response.snapshots, props.sessionRecordingId),
values.sessionPlayerSnapshotData?.snapshots ?? []
)
}
diff --git a/frontend/src/scenes/session-recordings/player/utils/segmenter.test.ts b/frontend/src/scenes/session-recordings/player/utils/segmenter.test.ts
index e08cedf8f03..1b9c17602ad 100644
--- a/frontend/src/scenes/session-recordings/player/utils/segmenter.test.ts
+++ b/frontend/src/scenes/session-recordings/player/utils/segmenter.test.ts
@@ -1,10 +1,12 @@
import { dayjs } from 'lib/dayjs'
import recordingMetaJson from 'scenes/session-recordings/__mocks__/recording_meta.json'
-import { sortedRecordingSnapshots } from 'scenes/session-recordings/__mocks__/recording_snapshots'
+import {
+ convertSnapshotsResponse,
+ sortedRecordingSnapshots,
+} from 'scenes/session-recordings/__mocks__/recording_snapshots'
import { RecordingSnapshot } from '~/types'
-import { convertSnapshotsResponse } from '../sessionRecordingDataLogic'
import { createSegments } from './segmenter'
describe('segmenter', () => {
diff --git a/frontend/utils.mjs b/frontend/utils.mjs
index ba5a901bf57..d0204981fbc 100644
--- a/frontend/utils.mjs
+++ b/frontend/utils.mjs
@@ -309,7 +309,7 @@ export async function buildOrWatch(config) {
if (isDev) {
chokidar
- .watch(path.resolve(absWorkingDir, 'src'), {
+ .watch([path.resolve(absWorkingDir, 'src'), path.resolve(absWorkingDir, '../ee/frontend')], {
ignored: /.*(Type|\.test\.stories)\.[tj]sx$/,
ignoreInitial: true,
})
diff --git a/jest.config.ts b/jest.config.ts
index 3ff4eeff1d4..7316923cd34 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -1,4 +1,5 @@
import type { Config } from 'jest'
+import fs from 'fs'
process.env.TZ = process.env.TZ || 'UTC'
@@ -8,6 +9,14 @@ process.env.TZ = process.env.TZ || 'UTC'
*/
const esmModules = ['query-selector-shadow-dom', 'react-syntax-highlighter', '@react-hook', '@medv']
+const eeFolderExists = fs.existsSync('ee/frontend/exports.ts')
+function rootDirectories() {
+ const rootDirectories = ['/frontend/src']
+ if (eeFolderExists) {
+ rootDirectories.push('/ee/frontend')
+ }
+ return rootDirectories
+}
const config: Config = {
// All imported modules in your tests should be mocked automatically
@@ -85,16 +94,16 @@ const config: Config = {
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
- '^.+\\.(css|less|scss|svg|png|lottie)$': '/test/mocks/styleMock.js',
- '^~/(.*)$': '/$1',
- '^@posthog/lemon-ui(|/.*)$': '/../@posthog/lemon-ui/src/$1',
- '^@posthog/apps-common(|/.*)$': '/../@posthog/apps-common/src/$1',
- '^@posthog/ee/exports': ['/../../ee/frontend/exports', '/../@posthog/ee/exports'],
- '^lib/(.*)$': '/lib/$1',
- '^scenes/(.*)$': '/scenes/$1',
+ '^.+\\.(css|less|scss|svg|png|lottie)$': '/frontend/src/test/mocks/styleMock.js',
+ '^~/(.*)$': '/frontend/src/$1',
+ '^@posthog/lemon-ui(|/.*)$': '/frontend/@posthog/lemon-ui/src/$1',
+ '^@posthog/apps-common(|/.*)$': '/frontend/@posthog/apps-common/src/$1',
+ '^@posthog/ee/exports': ['/ee/frontend/exports', '/frontend/@posthog/ee/exports'],
+ '^lib/(.*)$': '/frontend/src/lib/$1',
+ '^scenes/(.*)$': '/frontend/src/scenes/$1',
'^antd/es/(.*)$': 'antd/lib/$1',
'^react-virtualized/dist/es/(.*)$': 'react-virtualized/dist/commonjs/$1',
- d3: '/../../node_modules/d3/dist/d3.min.js',
+ d3: '/node_modules/d3/dist/d3.min.js',
'^d3-(.*)$': `d3-$1/dist/d3-$1`,
},
@@ -129,22 +138,19 @@ const config: Config = {
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
- rootDir: 'frontend/src',
modulePaths: ['/'],
// A list of paths to directories that Jest should use to search for files in
- // roots: [
- // ""
- // ],
+ roots: rootDirectories(),
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
- setupFiles: ['../../jest.setup.ts'],
+ setupFiles: ['/jest.setup.ts'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
- setupFilesAfterEnv: ['../../jest.setupAfterEnv.ts', 'givens/setup', './mocks/jest.ts'],
+ setupFilesAfterEnv: ['/jest.setupAfterEnv.ts', 'givens/setup', '/frontend/src/mocks/jest.ts'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
diff --git a/package.json b/package.json
index 8c17019f5d2..48bf5eeb009 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,10 @@
"build-storybook": "storybook build",
"dev:migrate:postgres": "export DEBUG=1 && source env/bin/activate && python manage.py migrate",
"dev:migrate:clickhouse": "export DEBUG=1 && source env/bin/activate && python manage.py migrate_clickhouse",
- "prepare": "husky install"
+ "prepare": "husky install",
+ "mobile-replay:web:schema:build:json": "ts-json-schema-generator -f tsconfig.json --path 'node_modules/@rrweb/types/dist/index.d.ts' --type 'eventWithTime' --expose all --no-top-ref --out ee/frontend/mobile-replay/schema/web/rr-web-schema.json && prettier --write ee/frontend/mobile-replay/schema/web/rr-web-schema.json",
+ "mobile-replay:mobile:schema:build:json": "ts-json-schema-generator -f tsconfig.json --path 'ee/frontend/mobile-replay/mobile.types.ts' --type 'mobileEventWithTime' --expose all --no-top-ref --out ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json && prettier --write ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json",
+ "mobile-replay:schema:build:json": "pnpm mobile-replay:web:schema:build:json && pnpm mobile-replay:mobile:schema:build:json"
},
"dependencies": {
"@ant-design/icons": "^4.7.0",
@@ -95,6 +98,7 @@
"@types/md5": "^2.3.0",
"@types/react-transition-group": "^4.4.5",
"@types/react-virtualized": "^9.21.23",
+ "ajv": "^8.12.0",
"antd": "^4.17.1",
"antd-dayjs-webpack-plugin": "^1.0.6",
"babel-preset-nano-react-app": "^0.1.0",
@@ -246,6 +250,7 @@
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-import": "^2.29.0",
"eslint-plugin-jest": "^27.4.3",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-posthog": "link:./eslint-rules",
@@ -258,7 +263,7 @@
"history": "^5.0.1",
"html-webpack-harddisk-plugin": "^2.0.0",
"html-webpack-plugin": "^5.5.3",
- "jest": "^29.3.1",
+ "jest": "^29.7.0",
"jest-canvas-mock": "^2.4.0",
"jest-environment-jsdom": "^29.3.1",
"jest-image-snapshot": "^6.1.0",
@@ -286,7 +291,7 @@
"stylelint-order": "^6.0.3",
"sucrase": "^3.29.0",
"timekeeper": "^2.2.0",
- "ts-json-schema-generator": "^1.2.0",
+ "ts-json-schema-generator": "^1.4.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.5",
"webpack": "^5.88.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff43d2900d1..b003dd8c877 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -95,6 +95,9 @@ dependencies:
'@types/react-virtualized':
specifier: ^9.21.23
version: 9.21.26
+ ajv:
+ specifier: ^8.12.0
+ version: 8.12.0
antd:
specifier: ^4.17.1
version: 4.17.1(react-dom@18.2.0)(react@18.2.0)
@@ -413,7 +416,7 @@ devDependencies:
version: 7.5.1
'@sucrase/jest-plugin':
specifier: ^3.0.0
- version: 3.0.0(jest@29.3.1)(sucrase@3.29.0)
+ version: 3.0.0(jest@29.7.0)(sucrase@3.29.0)
'@testing-library/jest-dom':
specifier: ^5.16.2
version: 5.16.5
@@ -540,9 +543,12 @@ devDependencies:
eslint-plugin-eslint-comments:
specifier: ^3.2.0
version: 3.2.0(eslint@8.52.0)
+ eslint-plugin-import:
+ specifier: ^2.29.0
+ version: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0)
eslint-plugin-jest:
specifier: ^27.4.3
- version: 27.4.3(@typescript-eslint/eslint-plugin@6.9.0)(eslint@8.52.0)(jest@29.3.1)(typescript@4.9.5)
+ version: 27.4.3(@typescript-eslint/eslint-plugin@6.9.0)(eslint@8.52.0)(jest@29.7.0)(typescript@4.9.5)
eslint-plugin-no-only-tests:
specifier: ^3.1.0
version: 3.1.0
@@ -577,8 +583,8 @@ devDependencies:
specifier: ^5.5.3
version: 5.5.3(webpack@5.88.2)
jest:
- specifier: ^29.3.1
- version: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@18.11.9)(ts-node@10.9.1)
jest-canvas-mock:
specifier: ^2.4.0
version: 2.4.0
@@ -587,7 +593,7 @@ devDependencies:
version: 29.3.1
jest-image-snapshot:
specifier: ^6.1.0
- version: 6.1.0(jest@29.3.1)
+ version: 6.1.0(jest@29.7.0)
kea-typegen:
specifier: ^3.3.0
version: 3.3.0(typescript@4.9.5)
@@ -661,8 +667,8 @@ devDependencies:
specifier: ^2.2.0
version: 2.2.0
ts-json-schema-generator:
- specifier: ^1.2.0
- version: 1.2.0
+ specifier: ^1.4.0
+ version: 1.4.1
ts-node:
specifier: ^10.9.1
version: 10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5)
@@ -2844,18 +2850,6 @@ packages:
engines: {node: '>=8'}
dev: true
- /@jest/console@29.3.1:
- resolution: {integrity: sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/types': 29.6.3
- '@types/node': 18.18.4
- chalk: 4.1.2
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- slash: 3.0.0
- dev: true
-
/@jest/console@29.7.0:
resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -2868,49 +2862,6 @@ packages:
slash: 3.0.0
dev: true
- /@jest/core@29.3.1(ts-node@10.9.1):
- resolution: {integrity: sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
- dependencies:
- '@jest/console': 29.3.1
- '@jest/reporters': 29.3.1
- '@jest/test-result': 29.3.1
- '@jest/transform': 29.3.1
- '@jest/types': 29.3.1
- '@types/node': 18.18.4
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- ci-info: 3.5.0
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-changed-files: 29.2.0
- jest-config: 29.3.1(@types/node@18.18.4)(ts-node@10.9.1)
- jest-haste-map: 29.3.1
- jest-message-util: 29.3.1
- jest-regex-util: 29.2.0
- jest-resolve: 29.3.1
- jest-resolve-dependencies: 29.3.1
- jest-runner: 29.3.1
- jest-runtime: 29.3.1
- jest-snapshot: 29.3.1
- jest-util: 29.3.1
- jest-validate: 29.3.1
- jest-watcher: 29.3.1
- micromatch: 4.0.5
- pretty-format: 29.3.1
- slash: 3.0.0
- strip-ansi: 6.0.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- - ts-node
- dev: true
-
/@jest/core@29.7.0(ts-node@10.9.1):
resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3029,18 +2980,6 @@ packages:
jest-util: 29.7.0
dev: true
- /@jest/globals@29.3.1:
- resolution: {integrity: sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/expect': 29.7.0
- '@jest/types': 29.6.3
- jest-mock: 29.7.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@jest/globals@29.7.0:
resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3053,43 +2992,6 @@ packages:
- supports-color
dev: true
- /@jest/reporters@29.3.1:
- resolution: {integrity: sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
- dependencies:
- '@bcoe/v8-coverage': 0.2.3
- '@jest/console': 29.3.1
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.3.1
- '@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.20
- '@types/node': 18.18.4
- chalk: 4.1.2
- collect-v8-coverage: 1.0.1
- exit: 0.1.2
- glob: 7.2.3
- graceful-fs: 4.2.11
- istanbul-lib-coverage: 3.2.0
- istanbul-lib-instrument: 5.2.1
- istanbul-lib-report: 3.0.0
- istanbul-lib-source-maps: 4.0.1
- istanbul-reports: 3.1.5
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- jest-worker: 29.3.1
- slash: 3.0.0
- string-length: 4.0.2
- strip-ansi: 6.0.1
- v8-to-istanbul: 9.0.1
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@jest/reporters@29.7.0:
resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3141,15 +3043,6 @@ packages:
'@sinclair/typebox': 0.27.8
dev: true
- /@jest/source-map@29.2.0:
- resolution: {integrity: sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jridgewell/trace-mapping': 0.3.20
- callsites: 3.1.0
- graceful-fs: 4.2.11
- dev: true
-
/@jest/source-map@29.6.3:
resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3159,16 +3052,6 @@ packages:
graceful-fs: 4.2.11
dev: true
- /@jest/test-result@29.3.1:
- resolution: {integrity: sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/console': 29.3.1
- '@jest/types': 29.6.3
- '@types/istanbul-lib-coverage': 2.0.4
- collect-v8-coverage: 1.0.1
- dev: true
-
/@jest/test-result@29.7.0:
resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3179,16 +3062,6 @@ packages:
collect-v8-coverage: 1.0.1
dev: true
- /@jest/test-sequencer@29.3.1:
- resolution: {integrity: sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/test-result': 29.7.0
- graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- slash: 3.0.0
- dev: true
-
/@jest/test-sequencer@29.7.0:
resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -5546,13 +5419,13 @@ packages:
file-system-cache: 2.3.0
dev: true
- /@sucrase/jest-plugin@3.0.0(jest@29.3.1)(sucrase@3.29.0):
+ /@sucrase/jest-plugin@3.0.0(jest@29.7.0)(sucrase@3.29.0):
resolution: {integrity: sha512-VRY6YKYImVWiRg1H3Yu24hwB1UPJDSDR62R/n+lOHR3+yDrfHEIAoddJivblMYN6U3vD+ndfTSrecZ9Jl+iGNw==}
peerDependencies:
jest: '>=27'
sucrase: '>=3.25.0'
dependencies:
- jest: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
+ jest: 29.7.0(@types/node@18.11.9)(ts-node@10.9.1)
sucrase: 3.29.0
dev: true
@@ -6563,6 +6436,10 @@ packages:
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
dev: true
+ /@types/json5@0.0.29:
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ dev: true
+
/@types/less@3.0.6:
resolution: {integrity: sha512-PecSzorDGdabF57OBeQO/xFbAkYWo88g4Xvnsx7LRwqLC17I7OoKtA3bQB9uXkY6UkMWCOsA8HSVpaoitscdXw==}
dev: false
@@ -6682,10 +6559,6 @@ packages:
'@types/node': 18.18.4
dev: true
- /@types/prettier@2.7.1:
- resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==}
- dev: true
-
/@types/pretty-hrtime@1.0.1:
resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==}
dev: true
@@ -7363,7 +7236,7 @@ packages:
indent-string: 4.0.0
dev: true
- /ajv-formats@2.1.1(ajv@8.11.0):
+ /ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
@@ -7371,7 +7244,7 @@ packages:
ajv:
optional: true
dependencies:
- ajv: 8.11.0
+ ajv: 8.12.0
dev: true
/ajv-keywords@3.5.2(ajv@6.12.6):
@@ -7382,12 +7255,12 @@ packages:
ajv: 6.12.6
dev: true
- /ajv-keywords@5.1.0(ajv@8.11.0):
+ /ajv-keywords@5.1.0(ajv@8.12.0):
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
peerDependencies:
ajv: ^8.8.2
dependencies:
- ajv: 8.11.0
+ ajv: 8.12.0
fast-deep-equal: 3.1.3
dev: true
@@ -7400,14 +7273,13 @@ packages:
uri-js: 4.4.1
dev: true
- /ajv@8.11.0:
- resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==}
+ /ajv@8.12.0:
+ resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
- dev: true
/alphanum-sort@1.0.2:
resolution: {integrity: sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==}
@@ -7621,6 +7493,17 @@ packages:
is-string: 1.0.7
dev: true
+ /array-includes@3.1.7:
+ resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.2
+ is-string: 1.0.7
+ dev: true
+
/array-tree-filter@2.1.0:
resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
dev: false
@@ -7630,6 +7513,27 @@ packages:
engines: {node: '>=8'}
dev: true
+ /array.prototype.findlastindex@1.2.3:
+ resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.0
+ get-intrinsic: 1.2.2
+ dev: true
+
+ /array.prototype.flat@1.3.2:
+ resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.0
+ dev: true
+
/array.prototype.flatmap@1.3.1:
resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==}
engines: {node: '>= 0.4'}
@@ -7640,6 +7544,16 @@ packages:
es-shim-unscopables: 1.0.0
dev: true
+ /array.prototype.flatmap@1.3.2:
+ resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.0
+ dev: true
+
/array.prototype.reduce@1.0.5:
resolution: {integrity: sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==}
engines: {node: '>= 0.4'}
@@ -7815,24 +7729,6 @@ packages:
'@babel/core': 7.22.10
dev: true
- /babel-jest@29.3.1(@babel/core@7.22.10):
- resolution: {integrity: sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@babel/core': ^7.8.0
- dependencies:
- '@babel/core': 7.22.10
- '@jest/transform': 29.7.0
- '@types/babel__core': 7.20.4
- babel-plugin-istanbul: 6.1.1
- babel-preset-jest: 29.2.0(@babel/core@7.22.10)
- chalk: 4.1.2
- graceful-fs: 4.2.11
- slash: 3.0.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/babel-jest@29.7.0(@babel/core@7.22.10):
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7902,16 +7798,6 @@ packages:
- supports-color
dev: true
- /babel-plugin-jest-hoist@29.2.0:
- resolution: {integrity: sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@babel/template': 7.22.15
- '@babel/types': 7.23.4
- '@types/babel__core': 7.20.4
- '@types/babel__traverse': 7.20.4
- dev: true
-
/babel-plugin-jest-hoist@29.6.3:
resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7989,17 +7875,6 @@ packages:
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.10)
dev: true
- /babel-preset-jest@29.2.0(@babel/core@7.22.10):
- resolution: {integrity: sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@babel/core': ^7.0.0
- dependencies:
- '@babel/core': 7.22.10
- babel-plugin-jest-hoist: 29.2.0
- babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.10)
- dev: true
-
/babel-preset-jest@29.6.3(@babel/core@7.22.10):
resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -8655,6 +8530,11 @@ packages:
engines: {node: '>=14'}
dev: true
+ /commander@11.1.0:
+ resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
+ engines: {node: '>=16'}
+ dev: true
+
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
@@ -10355,6 +10235,45 @@ packages:
eslint: 8.52.0
dev: true
+ /eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ dependencies:
+ debug: 3.2.7(supports-color@8.1.1)
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@4.9.5)
+ debug: 3.2.7(supports-color@8.1.1)
+ eslint: 8.52.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/eslint-plugin-compat@4.2.0(eslint@8.52.0):
resolution: {integrity: sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==}
engines: {node: '>=14.x'}
@@ -10391,7 +10310,42 @@ packages:
ignore: 5.2.4
dev: true
- /eslint-plugin-jest@27.4.3(@typescript-eslint/eslint-plugin@6.9.0)(eslint@8.52.0)(jest@29.3.1)(typescript@4.9.5):
+ /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0):
+ resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@4.9.5)
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.3
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7(supports-color@8.1.1)
+ doctrine: 2.1.0
+ eslint: 8.52.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0)
+ hasown: 2.0.0
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.1
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.14.2
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-plugin-jest@27.4.3(@typescript-eslint/eslint-plugin@6.9.0)(eslint@8.52.0)(jest@29.7.0)(typescript@4.9.5):
resolution: {integrity: sha512-7S6SmmsHsgIm06BAGCAxL+ABd9/IB3MWkz2pudj6Qqor2y1qQpWPfuFU4SG9pWj4xDjF0e+D7Llh5useuSzAZw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -10407,7 +10361,7 @@ packages:
'@typescript-eslint/eslint-plugin': 6.9.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0)(typescript@4.9.5)
'@typescript-eslint/utils': 5.55.0(eslint@8.52.0)(typescript@4.9.5)
eslint: 8.52.0
- jest: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
+ jest: 29.7.0(@types/node@18.11.9)(ts-node@10.9.1)
transitivePeerDependencies:
- supports-color
- typescript
@@ -12634,14 +12588,6 @@ packages:
moo-color: 1.0.3
dev: true
- /jest-changed-files@29.2.0:
- resolution: {integrity: sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- execa: 5.1.1
- p-limit: 3.1.0
- dev: true
-
/jest-changed-files@29.7.0:
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -12680,35 +12626,6 @@ packages:
- supports-color
dev: true
- /jest-cli@29.3.1(@types/node@18.11.9)(ts-node@10.9.1):
- resolution: {integrity: sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
- dependencies:
- '@jest/core': 29.3.1(ts-node@10.9.1)
- '@jest/test-result': 29.3.1
- '@jest/types': 29.3.1
- chalk: 4.1.2
- exit: 0.1.2
- graceful-fs: 4.2.11
- import-local: 3.1.0
- jest-config: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
- jest-util: 29.3.1
- jest-validate: 29.3.1
- prompts: 2.4.2
- yargs: 17.6.2
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
- dev: true
-
/jest-cli@29.7.0(@types/node@18.11.9)(ts-node@10.9.1):
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -12737,88 +12654,6 @@ packages:
- ts-node
dev: true
- /jest-config@29.3.1(@types/node@18.11.9)(ts-node@10.9.1):
- resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@types/node': '*'
- ts-node: '>=9.0.0'
- peerDependenciesMeta:
- '@types/node':
- optional: true
- ts-node:
- optional: true
- dependencies:
- '@babel/core': 7.22.10
- '@jest/test-sequencer': 29.3.1
- '@jest/types': 29.6.3
- '@types/node': 18.11.9
- babel-jest: 29.3.1(@babel/core@7.22.10)
- chalk: 4.1.2
- ci-info: 3.5.0
- deepmerge: 4.2.2
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-circus: 29.7.0
- jest-environment-node: 29.7.0
- jest-get-type: 29.2.0
- jest-regex-util: 29.2.0
- jest-resolve: 29.3.1
- jest-runner: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.3.1
- micromatch: 4.0.5
- parse-json: 5.2.0
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-json-comments: 3.1.1
- ts-node: 10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5)
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- dev: true
-
- /jest-config@29.3.1(@types/node@18.18.4)(ts-node@10.9.1):
- resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@types/node': '*'
- ts-node: '>=9.0.0'
- peerDependenciesMeta:
- '@types/node':
- optional: true
- ts-node:
- optional: true
- dependencies:
- '@babel/core': 7.22.10
- '@jest/test-sequencer': 29.3.1
- '@jest/types': 29.6.3
- '@types/node': 18.18.4
- babel-jest: 29.3.1(@babel/core@7.22.10)
- chalk: 4.1.2
- ci-info: 3.5.0
- deepmerge: 4.2.2
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-circus: 29.7.0
- jest-environment-node: 29.7.0
- jest-get-type: 29.2.0
- jest-regex-util: 29.2.0
- jest-resolve: 29.3.1
- jest-runner: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.3.1
- micromatch: 4.0.5
- parse-json: 5.2.0
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-json-comments: 3.1.1
- ts-node: 10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5)
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- dev: true
-
/jest-config@29.7.0(@types/node@18.11.9)(ts-node@10.9.1):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -12921,13 +12756,6 @@ packages:
pretty-format: 29.7.0
dev: true
- /jest-docblock@29.2.0:
- resolution: {integrity: sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- detect-newline: 3.1.0
- dev: true
-
/jest-docblock@29.7.0:
resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13029,7 +12857,7 @@ packages:
fsevents: 2.3.3
dev: true
- /jest-image-snapshot@6.1.0(jest@29.3.1):
+ /jest-image-snapshot@6.1.0(jest@29.7.0):
resolution: {integrity: sha512-LZYoks6V1HAkYqyi80gUjMWVsa++Oy0fckAGMLBQseVweZT9AmJNKAINwHLqX1fpeMy2hTG5CCEe4IUX2N3Nmg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -13038,7 +12866,7 @@ packages:
chalk: 4.1.2
get-stdin: 5.0.1
glur: 1.1.2
- jest: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
+ jest: 29.7.0(@types/node@18.11.9)(ts-node@10.9.1)
lodash: 4.17.21
mkdirp: 0.5.6
pixelmatch: 5.3.0
@@ -13057,14 +12885,6 @@ packages:
xml: 1.0.1
dev: true
- /jest-leak-detector@29.3.1:
- resolution: {integrity: sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
- dev: true
-
/jest-leak-detector@29.7.0:
resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13164,18 +12984,6 @@ packages:
- supports-color
dev: true
- /jest-pnp-resolver@1.2.2(jest-resolve@29.3.1):
- resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==}
- engines: {node: '>=6'}
- peerDependencies:
- jest-resolve: '*'
- peerDependenciesMeta:
- jest-resolve:
- optional: true
- dependencies:
- jest-resolve: 29.3.1
- dev: true
-
/jest-pnp-resolver@1.2.2(jest-resolve@29.7.0):
resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==}
engines: {node: '>=6'}
@@ -13216,16 +13024,6 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dev: true
- /jest-resolve-dependencies@29.3.1:
- resolution: {integrity: sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- jest-regex-util: 29.2.0
- jest-snapshot: 29.7.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/jest-resolve-dependencies@29.7.0:
resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13236,21 +13034,6 @@ packages:
- supports-color
dev: true
- /jest-resolve@29.3.1:
- resolution: {integrity: sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- chalk: 4.1.2
- graceful-fs: 4.2.11
- jest-haste-map: 29.3.1
- jest-pnp-resolver: 1.2.2(jest-resolve@29.3.1)
- jest-util: 29.7.0
- jest-validate: 29.3.1
- resolve: 1.22.8
- resolve.exports: 1.1.0
- slash: 3.0.0
- dev: true
-
/jest-resolve@29.7.0:
resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13266,35 +13049,6 @@ packages:
slash: 3.0.0
dev: true
- /jest-runner@29.3.1:
- resolution: {integrity: sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/console': 29.3.1
- '@jest/environment': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.3.1
- '@jest/types': 29.6.3
- '@types/node': 18.18.4
- chalk: 4.1.2
- emittery: 0.13.1
- graceful-fs: 4.2.11
- jest-docblock: 29.2.0
- jest-environment-node: 29.7.0
- jest-haste-map: 29.3.1
- jest-leak-detector: 29.3.1
- jest-message-util: 29.7.0
- jest-resolve: 29.3.1
- jest-runtime: 29.7.0
- jest-util: 29.7.0
- jest-watcher: 29.3.1
- jest-worker: 29.3.1
- p-limit: 3.1.0
- source-map-support: 0.5.13
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/jest-runner@29.7.0:
resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13324,36 +13078,6 @@ packages:
- supports-color
dev: true
- /jest-runtime@29.3.1:
- resolution: {integrity: sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/globals': 29.3.1
- '@jest/source-map': 29.2.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.3.1
- '@jest/types': 29.6.3
- '@types/node': 18.18.4
- chalk: 4.1.2
- cjs-module-lexer: 1.2.2
- collect-v8-coverage: 1.0.1
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-haste-map: 29.3.1
- jest-message-util: 29.7.0
- jest-mock: 29.7.0
- jest-regex-util: 29.2.0
- jest-resolve: 29.3.1
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- slash: 3.0.0
- strip-bom: 4.0.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/jest-runtime@29.7.0:
resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13390,38 +13114,6 @@ packages:
diffable-html: 4.1.0
dev: true
- /jest-snapshot@29.3.1:
- resolution: {integrity: sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@babel/core': 7.22.10
- '@babel/generator': 7.23.0
- '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.10)
- '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.10)
- '@babel/traverse': 7.23.2
- '@babel/types': 7.23.4
- '@jest/expect-utils': 29.3.1
- '@jest/transform': 29.3.1
- '@jest/types': 29.6.3
- '@types/babel__traverse': 7.20.3
- '@types/prettier': 2.7.1
- babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.10)
- chalk: 4.1.2
- expect: 29.3.1
- graceful-fs: 4.2.11
- jest-diff: 29.3.1
- jest-get-type: 29.2.0
- jest-haste-map: 29.3.1
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- natural-compare: 1.4.0
- pretty-format: 29.7.0
- semver: 7.5.4
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/jest-snapshot@29.7.0:
resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13474,18 +13166,6 @@ packages:
picomatch: 2.3.1
dev: true
- /jest-validate@29.3.1:
- resolution: {integrity: sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/types': 29.6.3
- camelcase: 6.3.0
- chalk: 4.1.2
- jest-get-type: 29.2.0
- leven: 3.1.0
- pretty-format: 29.7.0
- dev: true
-
/jest-validate@29.7.0:
resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13514,20 +13194,6 @@ packages:
strip-ansi: 7.0.1
dev: true
- /jest-watcher@29.3.1:
- resolution: {integrity: sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 18.18.4
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- emittery: 0.13.1
- jest-util: 29.7.0
- string-length: 4.0.2
- dev: true
-
/jest-watcher@29.7.0:
resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13571,27 +13237,6 @@ packages:
supports-color: 8.1.1
dev: true
- /jest@29.3.1(@types/node@18.11.9)(ts-node@10.9.1):
- resolution: {integrity: sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
- peerDependencies:
- node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
- peerDependenciesMeta:
- node-notifier:
- optional: true
- dependencies:
- '@jest/core': 29.3.1(ts-node@10.9.1)
- '@jest/types': 29.3.1
- import-local: 3.1.0
- jest-cli: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1)
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
- dev: true
-
/jest@29.7.0(@types/node@18.11.9)(ts-node@10.9.1):
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13749,7 +13394,6 @@ packages:
/json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
- dev: true
/json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@@ -13779,6 +13423,13 @@ packages:
minimist: 1.2.8
dev: true
+ /json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ dev: true
+
/json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -14918,6 +14569,15 @@ packages:
es-abstract: 1.20.4
dev: true
+ /object.fromentries@2.0.7:
+ resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
/object.getownpropertydescriptors@2.1.4:
resolution: {integrity: sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==}
engines: {node: '>= 0.8'}
@@ -14928,6 +14588,15 @@ packages:
es-abstract: 1.22.3
dev: true
+ /object.groupby@1.0.1:
+ resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.2
+ dev: true
+
/object.hasown@1.1.2:
resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==}
dependencies:
@@ -14958,6 +14627,15 @@ packages:
es-abstract: 1.20.4
dev: true
+ /object.values@1.1.7:
+ resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
/objectorarray@1.0.5:
resolution: {integrity: sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==}
dev: true
@@ -16159,7 +15837,6 @@ packages:
/punycode@2.1.1:
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
engines: {node: '>=6'}
- dev: true
/puppeteer-core@2.1.1:
resolution: {integrity: sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==}
@@ -17436,11 +17113,6 @@ packages:
protocol-buffers-schema: 3.6.0
dev: false
- /resolve.exports@1.1.0:
- resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==}
- engines: {node: '>=10'}
- dev: true
-
/resolve.exports@2.0.2:
resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==}
engines: {node: '>=10'}
@@ -17609,8 +17281,8 @@ packages:
is-regex: 1.1.4
dev: true
- /safe-stable-stringify@2.4.1:
- resolution: {integrity: sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==}
+ /safe-stable-stringify@2.4.3:
+ resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
engines: {node: '>=10'}
dev: true
@@ -17689,9 +17361,9 @@ packages:
engines: {node: '>= 12.13.0'}
dependencies:
'@types/json-schema': 7.0.12
- ajv: 8.11.0
- ajv-formats: 2.1.1(ajv@8.11.0)
- ajv-keywords: 5.1.0(ajv@8.11.0)
+ ajv: 8.12.0
+ ajv-formats: 2.1.1(ajv@8.12.0)
+ ajv-keywords: 5.1.0(ajv@8.12.0)
dev: true
/scroll-into-view-if-needed@2.2.26:
@@ -18239,6 +17911,11 @@ packages:
ansi-regex: 6.0.1
dev: true
+ /strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+ dev: true
+
/strip-bom@4.0.0:
resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
engines: {node: '>=8'}
@@ -18553,7 +18230,7 @@ packages:
resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==}
engines: {node: '>=10.0.0'}
dependencies:
- ajv: 8.11.0
+ ajv: 8.12.0
lodash.truncate: 4.4.2
slice-ansi: 4.0.0
string-width: 4.2.3
@@ -18843,18 +18520,18 @@ packages:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
- /ts-json-schema-generator@1.2.0:
- resolution: {integrity: sha512-tUMeO3ZvA12d3HHh7T/AK8W5hmUhDRNtqWRHSMN3ZRbUFt+UmV0oX8k1RK4SA+a+BKNHpmW2v06MS49e8Fi3Yg==}
+ /ts-json-schema-generator@1.4.1:
+ resolution: {integrity: sha512-wnhPMtskk9KvsTuU8AYx0TNdm1YrLVUEontT22+jL12JIPqPXdaoxPgsYBhlqDXsR9R9Nm2bJgH5r4IrTMbTSg==}
engines: {node: '>=10.0.0'}
hasBin: true
dependencies:
'@types/json-schema': 7.0.12
- commander: 9.4.1
+ commander: 11.1.0
glob: 8.0.3
json5: 2.2.3
normalize-path: 3.0.0
- safe-stable-stringify: 2.4.1
- typescript: 4.9.5
+ safe-stable-stringify: 2.4.3
+ typescript: 5.3.2
dev: true
/ts-node@10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5):
@@ -18889,6 +18566,15 @@ packages:
yn: 3.1.1
dev: true
+ /tsconfig-paths@3.14.2:
+ resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+ dev: true
+
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -19034,6 +18720,12 @@ packages:
hasBin: true
dev: true
+ /typescript@5.3.2:
+ resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
/typewise-core@1.2.0:
resolution: {integrity: sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==}
dev: false
@@ -19200,7 +18892,6 @@ packages:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
punycode: 2.1.1
- dev: true
/url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
diff --git a/tsconfig.json b/tsconfig.json
index ef6815e5be0..29058e0c156 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -31,8 +31,10 @@
"noUnusedParameters": true, // Report errors on unused parameters
"experimentalDecorators": true, // Enables experimental support for ES decorators
"noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement
+ // TODO this will be deprecated in TS 5. and we have _many_ of these
"suppressImplicitAnyIndexErrors": true, // Index objects by number
- "lib": ["dom", "es2019"]
+ "lib": ["dom", "es2019"],
+ "ignoreDeprecations": "5.0"
},
"include": ["frontend/**/*", ".storybook/**/*", "ee/frontend/**/*"],
"exclude": ["frontend/dist/**/*"],