diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index f8997f418..b118778b4 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -2,7 +2,7 @@ package messages func IsReplayerType(id int) bool { - return 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 42 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 80 != id && 81 != id && 82 != id && 112 != id && 115 != id && 125 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id + return 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 42 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 80 != id && 81 != id && 82 != id && 112 != id && 115 != id && 117 != id && 125 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id } func IsIOSType(id int) bool { @@ -10,5 +10,5 @@ func IsIOSType(id int) bool { } func IsDOMType(id int) bool { - return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id + return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 118 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id } diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 7a51c6ac9..064e1224d 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -74,13 +74,15 @@ const ( MsgJSException = 78 MsgZustand = 79 MsgBatchMeta = 80 - MsgBatchMetadata = 81 + MsgBatchMetadataDeprecated = 81 MsgPartitionedMessage = 82 MsgInputChange = 112 MsgSelectionChange = 113 MsgMouseThrashing = 114 MsgUnbindNodes = 115 MsgResourceTiming = 116 + MsgBatchMetadata = 117 + MsgTabChange = 118 MsgIssueEvent = 125 MsgSessionEnd = 126 MsgSessionSearch = 127 @@ -1974,7 +1976,7 @@ func (msg *BatchMeta) TypeID() int { return 80 } -type BatchMetadata struct { +type BatchMetadataDeprecated struct { message Version uint64 PageNo uint64 @@ -1983,7 +1985,7 @@ type BatchMetadata struct { Location string } -func (msg *BatchMetadata) Encode() []byte { +func (msg *BatchMetadataDeprecated) Encode() []byte { buf := make([]byte, 51+len(msg.Location)) buf[0] = 81 p := 1 @@ -1995,11 +1997,11 @@ func (msg *BatchMetadata) Encode() []byte { return buf[:p] } -func (msg *BatchMetadata) Decode() Message { +func (msg *BatchMetadataDeprecated) Decode() Message { return msg } -func (msg *BatchMetadata) TypeID() int { +func (msg *BatchMetadataDeprecated) TypeID() int { return 81 } @@ -2163,6 +2165,58 @@ func (msg *ResourceTiming) TypeID() int { return 116 } +type BatchMetadata struct { + message + Version uint64 + PageNo uint64 + FirstIndex uint64 + Timestamp int64 + Location string + TabId string +} + +func (msg *BatchMetadata) Encode() []byte { + buf := make([]byte, 61+len(msg.Location)+len(msg.TabId)) + buf[0] = 117 + p := 1 + p = WriteUint(msg.Version, buf, p) + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + p = WriteString(msg.Location, buf, p) + p = WriteString(msg.TabId, buf, p) + return buf[:p] +} + +func (msg *BatchMetadata) Decode() Message { + return msg +} + +func (msg *BatchMetadata) TypeID() int { + return 117 +} + +type TabChange struct { + message + TabId string +} + +func (msg *TabChange) Encode() []byte { + buf := make([]byte, 11+len(msg.TabId)) + buf[0] = 118 + p := 1 + p = WriteString(msg.TabId, buf, p) + return buf[:p] +} + +func (msg *TabChange) Decode() Message { + return msg +} + +func (msg *TabChange) TypeID() int { + return 118 +} + type IssueEvent struct { message MessageID uint64 diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index a51200dc0..46a27e482 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -1188,9 +1188,9 @@ func DecodeBatchMeta(reader BytesReader) (Message, error) { return msg, err } -func DecodeBatchMetadata(reader BytesReader) (Message, error) { +func DecodeBatchMetadataDeprecated(reader BytesReader) (Message, error) { var err error = nil - msg := &BatchMetadata{} + msg := &BatchMetadataDeprecated{} if msg.Version, err = reader.ReadUint(); err != nil { return nil, err } @@ -1314,6 +1314,39 @@ func DecodeResourceTiming(reader BytesReader) (Message, error) { return msg, err } +func DecodeBatchMetadata(reader BytesReader) (Message, error) { + var err error = nil + msg := &BatchMetadata{} + if msg.Version, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadInt(); err != nil { + return nil, err + } + if msg.Location, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.TabId, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeTabChange(reader BytesReader) (Message, error) { + var err error = nil + msg := &TabChange{} + if msg.TabId, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + func DecodeIssueEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IssueEvent{} @@ -1914,7 +1947,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { case 80: return DecodeBatchMeta(reader) case 81: - return DecodeBatchMetadata(reader) + return DecodeBatchMetadataDeprecated(reader) case 82: return DecodePartitionedMessage(reader) case 112: @@ -1927,6 +1960,10 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { return DecodeUnbindNodes(reader) case 116: return DecodeResourceTiming(reader) + case 117: + return DecodeBatchMetadata(reader) + case 118: + return DecodeTabChange(reader) case 125: return DecodeIssueEvent(reader) case 126: diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 7e2f28152..15294707d 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -71,7 +71,7 @@ class CreateDocument(Message): __id__ = 7 def __init__(self, ): - pass + class CreateElementNode(Message): @@ -689,7 +689,7 @@ class BatchMeta(Message): self.timestamp = timestamp -class BatchMetadata(Message): +class BatchMetadataDeprecated(Message): __id__ = 81 def __init__(self, version, page_no, first_index, timestamp, location): @@ -759,6 +759,25 @@ class ResourceTiming(Message): self.cached = cached +class BatchMetadata(Message): + __id__ = 117 + + def __init__(self, version, page_no, first_index, timestamp, location, tab_id): + self.version = version + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + self.location = location + self.tab_id = tab_id + + +class TabChange(Message): + __id__ = 118 + + def __init__(self, tab_id): + self.tab_id = tab_id + + class IssueEvent(Message): __id__ = 125 diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index 64d094521..f5ae78a17 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -634,7 +634,7 @@ class MessageCodec(Codec): ) if message_id == 81: - return BatchMetadata( + return BatchMetadataDeprecated( version=self.read_uint(reader), page_no=self.read_uint(reader), first_index=self.read_uint(reader), @@ -689,6 +689,21 @@ class MessageCodec(Codec): cached=self.read_boolean(reader) ) + if message_id == 117: + return BatchMetadata( + version=self.read_uint(reader), + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader), + location=self.read_string(reader), + tab_id=self.read_string(reader) + ) + + if message_id == 118: + return TabChange( + tab_id=self.read_string(reader) + ) + if message_id == 125: return IssueEvent( message_id=self.read_uint(reader), diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 135960cef..4acc91e9e 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -673,6 +673,14 @@ export default class RawMessageReader extends PrimitiveReader { }; } + case 118: { + const tabId = this.readString(); if (tabId === null) { return resetPointer() } + return { + tp: MType.TabChange, + tabId, + }; + } + case 90: { const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } const projectID = this.readUint(); if (projectID === null) { return resetPointer() } diff --git a/frontend/app/player/web/messages/filters.gen.ts b/frontend/app/player/web/messages/filters.gen.ts index 2cd1b6c25..8fccfe5f5 100644 --- a/frontend/app/player/web/messages/filters.gen.ts +++ b/frontend/app/player/web/messages/filters.gen.ts @@ -3,7 +3,7 @@ import { MType } from './raw.gen' -const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,37,38,49,50,51,54,55,57,58,59,60,61,67,69,70,71,72,73,74,75,76,77,113,114,90,93,96,100,102,103,105] +const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,37,38,49,50,51,54,55,57,58,59,60,61,67,69,70,71,72,73,74,75,76,77,113,114,118,90,93,96,100,102,103,105] export function isDOMType(t: MType) { return DOM_TYPES.includes(t) } \ No newline at end of file diff --git a/frontend/app/player/web/messages/message.gen.ts b/frontend/app/player/web/messages/message.gen.ts index 6a8916cd4..ab0101336 100644 --- a/frontend/app/player/web/messages/message.gen.ts +++ b/frontend/app/player/web/messages/message.gen.ts @@ -58,6 +58,7 @@ import type { RawSelectionChange, RawMouseThrashing, RawResourceTiming, + RawTabChange, RawIosSessionStart, RawIosCustomEvent, RawIosScreenChanges, @@ -178,6 +179,8 @@ export type MouseThrashing = RawMouseThrashing & Timed export type ResourceTiming = RawResourceTiming & Timed +export type TabChange = RawTabChange & Timed + export type IosSessionStart = RawIosSessionStart & Timed export type IosCustomEvent = RawIosCustomEvent & Timed diff --git a/frontend/app/player/web/messages/raw.gen.ts b/frontend/app/player/web/messages/raw.gen.ts index ecd88631c..f1f772764 100644 --- a/frontend/app/player/web/messages/raw.gen.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -56,6 +56,7 @@ export const enum MType { SelectionChange = 113, MouseThrashing = 114, ResourceTiming = 116, + TabChange = 118, IosSessionStart = 90, IosCustomEvent = 93, IosScreenChanges = 96, @@ -447,6 +448,11 @@ export interface RawResourceTiming { cached: boolean, } +export interface RawTabChange { + tp: MType.TabChange, + tabId: string, +} + export interface RawIosSessionStart { tp: MType.IosSessionStart, timestamp: number, @@ -518,4 +524,4 @@ export interface RawIosNetworkCall { } -export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequest | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall; +export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequest | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall; diff --git a/frontend/app/player/web/messages/tracker-legacy.gen.ts b/frontend/app/player/web/messages/tracker-legacy.gen.ts index 7215b8c3c..16527ebb0 100644 --- a/frontend/app/player/web/messages/tracker-legacy.gen.ts +++ b/frontend/app/player/web/messages/tracker-legacy.gen.ts @@ -57,6 +57,7 @@ export const TP_MAP = { 113: MType.SelectionChange, 114: MType.MouseThrashing, 116: MType.ResourceTiming, + 118: MType.TabChange, 90: MType.IosSessionStart, 93: MType.IosCustomEvent, 96: MType.IosScreenChanges, diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index a8f9a7f14..7d4125561 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -414,7 +414,7 @@ type TrZustand = [ state: string, ] -type TrBatchMetadata = [ +type TrBatchMetadataDeprecated = [ type: 81, version: number, pageNo: number, @@ -470,8 +470,23 @@ type TrResourceTiming = [ cached: boolean, ] +type TrBatchMetadata = [ + type: 117, + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, + tabId: string, +] -export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming +type TrTabChange = [ + type: 118, + tabId: string, +] + + +export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadataDeprecated | TrPartitionedMessage | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrBatchMetadata | TrTabChange export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { @@ -940,6 +955,13 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { } } + case 118: { + return { + tp: MType.TabChange, + tabId: tMsg[1], + } + } + default: return null } diff --git a/mobs/messages.rb b/mobs/messages.rb index 5ac7b6ff2..5f7a6c2ba 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -432,7 +432,7 @@ message 80, 'BatchMeta', :replayer => false, :tracker => false do end # since tracker 3.6.0 TODO: for webworker only -message 81, 'BatchMetadata', :replayer => false do +message 81, 'BatchMetadataDeprecated', :replayer => false do uint 'Version' uint 'PageNo' uint 'FirstIndex' @@ -484,6 +484,19 @@ message 116, 'ResourceTiming', :replayer => :devtools do boolean 'Cached' end +message 117, 'BatchMetadata', :replayer => false do + uint 'Version' + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' + string 'Location' + string 'TabId' +end + +message 118, 'TabChange' do + string 'TabId' +end + ## Backend-only message 125, 'IssueEvent', :replayer => false, :tracker => false do uint 'MessageID' diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index da9cb3173..bdebd9f56 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,7 @@ +# 8.0.0 + +- **[breaking]** support for multi-tab sessions + # 7.0.3 - Prevent auto restart after manual stop diff --git a/tracker/tracker/src/common/interaction.ts b/tracker/tracker/src/common/interaction.ts index 27bd4e73a..bdd1c0408 100644 --- a/tracker/tracker/src/common/interaction.ts +++ b/tracker/tracker/src/common/interaction.ts @@ -11,6 +11,7 @@ type Start = { pageNo: number timestamp: number url: string + tabId: string } & Options type Auth = { diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index a96343098..cc6665bff 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -61,13 +61,15 @@ export declare const enum Type { AdoptedSSRemoveOwner = 77, JSException = 78, Zustand = 79, - BatchMetadata = 81, + BatchMetadataDeprecated = 81, PartitionedMessage = 82, InputChange = 112, SelectionChange = 113, MouseThrashing = 114, UnbindNodes = 115, ResourceTiming = 116, + BatchMetadata = 117, + TabChange = 118, } @@ -480,8 +482,8 @@ export type Zustand = [ /*state:*/ string, ] -export type BatchMetadata = [ - /*type:*/ Type.BatchMetadata, +export type BatchMetadataDeprecated = [ + /*type:*/ Type.BatchMetadataDeprecated, /*version:*/ number, /*pageNo:*/ number, /*firstIndex:*/ number, @@ -536,6 +538,21 @@ export type ResourceTiming = [ /*cached:*/ boolean, ] +export type BatchMetadata = [ + /*type:*/ Type.BatchMetadata, + /*version:*/ number, + /*pageNo:*/ number, + /*firstIndex:*/ number, + /*timestamp:*/ number, + /*location:*/ string, + /*tabId:*/ string, +] -type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming +export type TabChange = [ + /*type:*/ Type.TabChange, + /*tabId:*/ string, +] + + +type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadataDeprecated | PartitionedMessage | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | BatchMetadata | TabChange export default Message diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index e0c3311c4..ad10f1c7c 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -64,6 +64,7 @@ type AppOptions = { session_reset_key: string session_token_key: string session_pageno_key: string + session_tabid_key: string local_uuid_key: string ingestPoint: string resourceBaseHref: string | null // resourceHref? @@ -124,6 +125,7 @@ export default class App { session_token_key: '__openreplay_token', session_pageno_key: '__openreplay_pageno', session_reset_key: '__openreplay_reset', + session_tabid_key: '__openreplay_tabid', local_uuid_key: '__openreplay_uuid', ingestPoint: DEFAULT_INGEST_POINT, resourceBaseHref: null, @@ -455,6 +457,7 @@ export default class App { url: document.URL, connAttemptCount: this.options.connAttemptCount, connAttemptGap: this.options.connAttemptGap, + tabId: this.session.getTabId(), }) const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 46d672d2d..c6cdf257e 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -764,15 +764,15 @@ export function Zustand( ] } -export function BatchMetadata( +export function BatchMetadataDeprecated( version: number, pageNo: number, firstIndex: number, timestamp: number, location: string, -): Messages.BatchMetadata { +): Messages.BatchMetadataDeprecated { return [ - Messages.Type.BatchMetadata, + Messages.Type.BatchMetadataDeprecated, version, pageNo, firstIndex, @@ -869,3 +869,31 @@ export function ResourceTiming( ] } +export function BatchMetadata( + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, + tabId: string, +): Messages.BatchMetadata { + return [ + Messages.Type.BatchMetadata, + version, + pageNo, + firstIndex, + timestamp, + location, + tabId, + ] +} + +export function TabChange( + tabId: string, +): Messages.TabChange { + return [ + Messages.Type.TabChange, + tabId, + ] +} + diff --git a/tracker/tracker/src/main/app/session.ts b/tracker/tracker/src/main/app/session.ts index 4682bcc43..614a2648b 100644 --- a/tracker/tracker/src/main/app/session.ts +++ b/tracker/tracker/src/main/app/session.ts @@ -1,4 +1,5 @@ import type App from './index.js' +import { generateRandomId } from '../utils.js' interface SessionInfo { sessionID: string | undefined @@ -12,6 +13,7 @@ type OnUpdateCallback = (i: Partial) => void export type Options = { session_token_key: string session_pageno_key: string + session_tabid_key: string } export default class Session { @@ -21,8 +23,11 @@ export default class Session { private readonly callbacks: OnUpdateCallback[] = [] private timestamp = 0 private projectID: string | undefined + private readonly tabId: string - constructor(private readonly app: App, private readonly options: Options) {} + constructor(private readonly app: App, private readonly options: Options) { + this.createTabId() + } attachUpdateCallback(cb: OnUpdateCallback) { this.callbacks.push(cb) @@ -61,6 +66,7 @@ export default class Session { this.metadata[key] = value this.handleUpdate({ metadata: { [key]: value } }) } + setUserID(userID: string) { this.userID = userID this.handleUpdate({ userID }) @@ -86,10 +92,11 @@ export default class Session { } getSessionToken(): string | undefined { - return this.app.sessionStorage.getItem(this.options.session_token_key) || undefined + return this.app.localStorage.getItem(this.options.session_token_key) || undefined } + setSessionToken(token: string): void { - this.app.sessionStorage.setItem(this.options.session_token_key, token) + this.app.localStorage.setItem(this.options.session_token_key, token) } applySessionHash(hash: string) { @@ -102,7 +109,7 @@ export default class Session { if (!pageNoStr || !token) { return } - this.app.sessionStorage.setItem(this.options.session_token_key, token) + this.app.localStorage.setItem(this.options.session_token_key, token) this.app.sessionStorage.setItem(this.options.session_pageno_key, pageNoStr) } @@ -115,6 +122,14 @@ export default class Session { return encodeURI(String(pageNo) + '&' + token) } + public getTabId(): string { + return this.tabId + } + + private createTabId() { + this.app.sessionStorage.setItem(this.options.session_tabid_key, generateRandomId(16)) + } + getInfo(): SessionInfo { return { sessionID: this.sessionID, @@ -126,7 +141,7 @@ export default class Session { } reset(): void { - this.app.sessionStorage.removeItem(this.options.session_token_key) + this.app.localStorage.removeItem(this.options.session_token_key) this.metadata = {} this.userID = null this.sessionID = undefined diff --git a/tracker/tracker/src/main/index.ts b/tracker/tracker/src/main/index.ts index bc4ff0775..a9201e012 100644 --- a/tracker/tracker/src/main/index.ts +++ b/tracker/tracker/src/main/index.ts @@ -25,6 +25,7 @@ import Fonts from './modules/fonts.js' import Network from './modules/network.js' import ConstructedStyleSheets from './modules/constructedStyleSheets.js' import Selection from './modules/selection.js' +import Tabs from './modules/tabs.js' import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js' import type { Options as AppOptions } from './app/index.js' @@ -136,6 +137,7 @@ export default class API { Fonts(app) Network(app, options.network) Selection(app) + Tabs(app) ;(window as any).__OPENREPLAY__ = this if (options.autoResetOnWindowOpen) { diff --git a/tracker/tracker/src/main/modules/tabs.ts b/tracker/tracker/src/main/modules/tabs.ts new file mode 100644 index 000000000..26281d844 --- /dev/null +++ b/tracker/tracker/src/main/modules/tabs.ts @@ -0,0 +1,12 @@ +import type App from '../app/index.js' +import { TabChange } from '../app/messages.gen.js' + +export default function (app: App): void { + function changeTab() { + if (!document.hidden) app.safe(() => app.send(TabChange(app.session.getTabId()))) + } + + if (document.hidden !== undefined) { + app.attachEventListener(document, 'visibilitychange', changeTab as EventListener, false, false) + } +} diff --git a/tracker/tracker/src/main/utils.ts b/tracker/tracker/src/main/utils.ts index 4ef650a0b..ba919a64a 100644 --- a/tracker/tracker/src/main/utils.ts +++ b/tracker/tracker/src/main/utils.ts @@ -100,3 +100,16 @@ export function canAccessIframe(iframe: HTMLIFrameElement) { return false } } + +function dec2hex(dec: number) { + return dec.toString(16).padStart(2, '0') +} + +export function generateRandomId(len: number) { + const arr: Uint8Array = new Uint8Array((len || 40) / 2) + // msCrypto = IE11 + // @ts-ignore + const safeCrypto = window.crypto || window.msCrypto + safeCrypto.getRandomValues(arr) + return Array.from(arr, dec2hex).join('') +} diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index bd1d42084..d3f2938ac 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -19,6 +19,7 @@ export default class BatchWriter { private timestamp: number, private url: string, private readonly onBatch: (batch: Uint8Array) => void, + private tabId: string, ) { this.prepare() } @@ -50,6 +51,7 @@ export default class BatchWriter { this.nextIndex, this.timestamp, this.url, + this.tabId, ] this.writeType(batchMetadata) this.writeFields(batchMetadata) diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index 69fc7b35f..48c9cec18 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -246,7 +246,7 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.string(msg[1]) && this.string(msg[2]) break - case Messages.Type.BatchMetadata: + case Messages.Type.BatchMetadataDeprecated: return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) break @@ -274,6 +274,14 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) && this.uint(msg[9]) && this.boolean(msg[10]) break + case Messages.Type.BatchMetadata: + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) && this.string(msg[6]) + break + + case Messages.Type.TabChange: + return this.string(msg[1]) + break + } } diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 68a1e4467..7f5605236 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -21,8 +21,9 @@ const AUTO_SEND_INTERVAL = 10 * 1000 let sender: QueueSender | null = null let writer: BatchWriter | null = null +// eslint-disable-next-line @typescript-eslint/no-unused-vars let workerStatus: WorkerStatus = WorkerStatus.NotActive -// let afterSleepRestarts = 0 + function finalize(): void { if (!writer) { return @@ -73,7 +74,7 @@ let sendIntervalID: ReturnType | null = null let restartTimeoutID: ReturnType // @ts-ignore -self.onmessage = ({ data }: any): any => { +self.onmessage = ({ data }: { data: ToWorkerData }): any => { if (data == null) { finalize() return @@ -146,6 +147,7 @@ self.onmessage = ({ data }: any): any => { data.timestamp, data.url, (batch) => sender && sender.push(batch), + data.tabId, ) if (sendIntervalID === null) { sendIntervalID = setInterval(finalize, AUTO_SEND_INTERVAL)