Tracker 14.x.x changes (#2240)
* feat tracker: add document titles to tabs * feat: titles for tabs * feat tracker: send initial title, parse titles better * feat ui: tab name styles * feat tracker: update changelogs * fix tracker: fix tests * fix tracker: fix failing tests, add some coverage * fix tracker: fix failing tests, add some coverage * Heatmaps (#2264) * feat ui: start heatmaps ui and tracker update * fix ui: drop clickmap from session * fix ui: refactor heatmap painter * fix ui: store click coords as int percent * feat(backend): insert normalized x and y to PG * feat(backend): insert normalized x and y to CH * feat(connector): added missing import * feat(backend): fixed different uint type issue * fix tracker: use max scrollable size for doc * fix gen files * fix ui: fix random crash, remove demo data generator * fix ui: rm some dead code --------- Co-authored-by: Alexander <zavorotynskiy@pm.me> * fix tracker: add heatmap changelog to tracker CHANGELOG.md * fix tracker: fix peerjs version to 1.5.4 (was 1.5.1) * fix document height calculation * Crossdomain tracking (#2277) * feat tracker: crossdomain tracking (start commit) * catch crossdomain messages, add nodeid placeholder * click scroll * frame placeholder number -> dynamic * click rewriter, fix scroll prop * some docs * some docs * fix options merging * CHANGELOG.md update * checking that crossdomain will not fire automatically * simplify func declaration * update test data * change clickmap document height calculation to scrollheight (which should be true height) --------- Co-authored-by: Alexander <zavorotynskiy@pm.me>
This commit is contained in:
parent
82d2023019
commit
960da9f037
53 changed files with 1944 additions and 874 deletions
|
|
@ -12,7 +12,7 @@ type bulksTask struct {
|
|||
}
|
||||
|
||||
func NewBulksTask() *bulksTask {
|
||||
return &bulksTask{bulks: make([]Bulk, 0, 15)}
|
||||
return &bulksTask{bulks: make([]Bulk, 0, 16)}
|
||||
}
|
||||
|
||||
type BulkSet struct {
|
||||
|
|
@ -32,6 +32,7 @@ type BulkSet struct {
|
|||
webIssueEvents Bulk
|
||||
webCustomEvents Bulk
|
||||
webClickEvents Bulk
|
||||
webClickXYEvents Bulk
|
||||
webNetworkRequest Bulk
|
||||
webCanvasNodes Bulk
|
||||
webTagTriggers Bulk
|
||||
|
|
@ -82,6 +83,8 @@ func (conn *BulkSet) Get(name string) Bulk {
|
|||
return conn.webCustomEvents
|
||||
case "webClickEvents":
|
||||
return conn.webClickEvents
|
||||
case "webClickXYEvents":
|
||||
return conn.webClickXYEvents
|
||||
case "webNetworkRequest":
|
||||
return conn.webNetworkRequest
|
||||
case "canvasNodes":
|
||||
|
|
@ -203,6 +206,14 @@ func (conn *BulkSet) initBulks() {
|
|||
if err != nil {
|
||||
conn.log.Fatal(conn.ctx, "can't create webClickEvents bulk: %s", err)
|
||||
}
|
||||
conn.webClickXYEvents, err = NewBulk(conn.c,
|
||||
"events.clicks",
|
||||
"(session_id, message_id, timestamp, label, selector, url, path, hesitation, normalized_x, normalized_y)",
|
||||
"($%d, $%d, $%d, NULLIF(LEFT($%d, 2000), ''), LEFT($%d, 8000), LEFT($%d, 2000), LEFT($%d, 2000), $%d, $%d, $%d)",
|
||||
10, 200)
|
||||
if err != nil {
|
||||
conn.log.Fatal(conn.ctx, "can't create webClickEvents bulk: %s", err)
|
||||
}
|
||||
conn.webNetworkRequest, err = NewBulk(conn.c,
|
||||
"events_common.requests",
|
||||
"(session_id, timestamp, seq_index, url, host, path, query, request_body, response_body, status_code, method, duration, success, transfer_size)",
|
||||
|
|
@ -246,6 +257,7 @@ func (conn *BulkSet) Send() {
|
|||
newTask.bulks = append(newTask.bulks, conn.webIssueEvents)
|
||||
newTask.bulks = append(newTask.bulks, conn.webCustomEvents)
|
||||
newTask.bulks = append(newTask.bulks, conn.webClickEvents)
|
||||
newTask.bulks = append(newTask.bulks, conn.webClickXYEvents)
|
||||
newTask.bulks = append(newTask.bulks, conn.webNetworkRequest)
|
||||
newTask.bulks = append(newTask.bulks, conn.webCanvasNodes)
|
||||
newTask.bulks = append(newTask.bulks, conn.webTagTriggers)
|
||||
|
|
|
|||
|
|
@ -132,9 +132,16 @@ func (conn *Conn) InsertWebClickEvent(sess *sessions.Session, e *messages.MouseC
|
|||
}
|
||||
var host, path string
|
||||
host, path, _, _ = url.GetURLParts(e.Url)
|
||||
if err := conn.bulks.Get("webClickEvents").Append(sess.SessionID, truncSqIdx(e.MsgID()), e.Timestamp, e.Label, e.Selector, host+path, path, e.HesitationTime); err != nil {
|
||||
sessCtx := context.WithValue(context.Background(), "sessionID", sess.SessionID)
|
||||
conn.log.Error(sessCtx, "insert web click event in bulk err: %s", err)
|
||||
if e.NormalizedX <= 100 && e.NormalizedY <= 100 {
|
||||
if err := conn.bulks.Get("webClickXYEvents").Append(sess.SessionID, truncSqIdx(e.MsgID()), e.Timestamp, e.Label, e.Selector, host+path, path, e.HesitationTime, e.NormalizedX, e.NormalizedY); err != nil {
|
||||
sessCtx := context.WithValue(context.Background(), "sessionID", sess.SessionID)
|
||||
conn.log.Error(sessCtx, "insert web click event in bulk err: %s", err)
|
||||
}
|
||||
} else {
|
||||
if err := conn.bulks.Get("webClickEvents").Append(sess.SessionID, truncSqIdx(e.MsgID()), e.Timestamp, e.Label, e.Selector, host+path, path, e.HesitationTime); err != nil {
|
||||
sessCtx := context.WithValue(context.Background(), "sessionID", sess.SessionID)
|
||||
conn.log.Error(sessCtx, "insert web click event in bulk err: %s", err)
|
||||
}
|
||||
}
|
||||
// Add new value set to autocomplete bulk
|
||||
conn.InsertAutocompleteValue(sess.SessionID, sess.ProjectID, "CLICK", e.Label)
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ func IsMobileType(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 || 117 == id || 118 == id || 119 == id || 93 == id || 96 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 106 == id || 111 == 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 || 68 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 117 == id || 118 == id || 119 == id || 122 == id || 93 == id || 96 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 106 == id || 111 == id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ func transformDeprecated(msg Message) Message {
|
|||
TransferredSize: 0,
|
||||
Cached: false,
|
||||
}
|
||||
case *MouseClickDeprecated:
|
||||
return &MouseClick{
|
||||
ID: m.ID,
|
||||
HesitationTime: m.HesitationTime,
|
||||
Label: m.Label,
|
||||
Selector: m.Selector,
|
||||
NormalizedX: 101, // 101 is a magic number to signal that the value is not present
|
||||
NormalizedY: 101, // 101 is a magic number to signal that the value is not present
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const (
|
|||
MsgTimestamp = 0
|
||||
MsgSessionStart = 1
|
||||
MsgSessionEndDeprecated = 3
|
||||
MsgSetPageLocation = 4
|
||||
MsgSetPageLocationDeprecated = 4
|
||||
MsgSetViewportSize = 5
|
||||
MsgSetViewportScroll = 6
|
||||
MsgCreateDocument = 7
|
||||
|
|
@ -62,7 +62,8 @@ const (
|
|||
MsgCustomIssue = 64
|
||||
MsgAssetCache = 66
|
||||
MsgCSSInsertRuleURLBased = 67
|
||||
MsgMouseClick = 69
|
||||
MsgMouseClick = 68
|
||||
MsgMouseClickDeprecated = 69
|
||||
MsgCreateIFrameDocument = 70
|
||||
MsgAdoptedSSReplaceURLBased = 71
|
||||
MsgAdoptedSSReplace = 72
|
||||
|
|
@ -88,6 +89,7 @@ const (
|
|||
MsgCanvasNode = 119
|
||||
MsgTagTrigger = 120
|
||||
MsgRedux = 121
|
||||
MsgSetPageLocation = 122
|
||||
MsgIssueEvent = 125
|
||||
MsgSessionEnd = 126
|
||||
MsgSessionSearch = 127
|
||||
|
|
@ -205,14 +207,14 @@ func (msg *SessionEndDeprecated) TypeID() int {
|
|||
return 3
|
||||
}
|
||||
|
||||
type SetPageLocation struct {
|
||||
type SetPageLocationDeprecated struct {
|
||||
message
|
||||
URL string
|
||||
Referrer string
|
||||
NavigationStart uint64
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) Encode() []byte {
|
||||
func (msg *SetPageLocationDeprecated) Encode() []byte {
|
||||
buf := make([]byte, 31+len(msg.URL)+len(msg.Referrer))
|
||||
buf[0] = 4
|
||||
p := 1
|
||||
|
|
@ -222,11 +224,11 @@ func (msg *SetPageLocation) Encode() []byte {
|
|||
return buf[:p]
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) Decode() Message {
|
||||
func (msg *SetPageLocationDeprecated) Decode() Message {
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) TypeID() int {
|
||||
func (msg *SetPageLocationDeprecated) TypeID() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
|
|
@ -1693,9 +1695,40 @@ type MouseClick struct {
|
|||
HesitationTime uint64
|
||||
Label string
|
||||
Selector string
|
||||
NormalizedX uint64
|
||||
NormalizedY uint64
|
||||
}
|
||||
|
||||
func (msg *MouseClick) Encode() []byte {
|
||||
buf := make([]byte, 61+len(msg.Label)+len(msg.Selector))
|
||||
buf[0] = 68
|
||||
p := 1
|
||||
p = WriteUint(msg.ID, buf, p)
|
||||
p = WriteUint(msg.HesitationTime, buf, p)
|
||||
p = WriteString(msg.Label, buf, p)
|
||||
p = WriteString(msg.Selector, buf, p)
|
||||
p = WriteUint(msg.NormalizedX, buf, p)
|
||||
p = WriteUint(msg.NormalizedY, buf, p)
|
||||
return buf[:p]
|
||||
}
|
||||
|
||||
func (msg *MouseClick) Decode() Message {
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *MouseClick) TypeID() int {
|
||||
return 68
|
||||
}
|
||||
|
||||
type MouseClickDeprecated struct {
|
||||
message
|
||||
ID uint64
|
||||
HesitationTime uint64
|
||||
Label string
|
||||
Selector string
|
||||
}
|
||||
|
||||
func (msg *MouseClickDeprecated) Encode() []byte {
|
||||
buf := make([]byte, 41+len(msg.Label)+len(msg.Selector))
|
||||
buf[0] = 69
|
||||
p := 1
|
||||
|
|
@ -1706,11 +1739,11 @@ func (msg *MouseClick) Encode() []byte {
|
|||
return buf[:p]
|
||||
}
|
||||
|
||||
func (msg *MouseClick) Decode() Message {
|
||||
func (msg *MouseClickDeprecated) Decode() Message {
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *MouseClick) TypeID() int {
|
||||
func (msg *MouseClickDeprecated) TypeID() int {
|
||||
return 69
|
||||
}
|
||||
|
||||
|
|
@ -2351,6 +2384,33 @@ func (msg *Redux) TypeID() int {
|
|||
return 121
|
||||
}
|
||||
|
||||
type SetPageLocation struct {
|
||||
message
|
||||
URL string
|
||||
Referrer string
|
||||
NavigationStart uint64
|
||||
DocumentTitle string
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) Encode() []byte {
|
||||
buf := make([]byte, 41+len(msg.URL)+len(msg.Referrer)+len(msg.DocumentTitle))
|
||||
buf[0] = 122
|
||||
p := 1
|
||||
p = WriteString(msg.URL, buf, p)
|
||||
p = WriteString(msg.Referrer, buf, p)
|
||||
p = WriteUint(msg.NavigationStart, buf, p)
|
||||
p = WriteString(msg.DocumentTitle, buf, p)
|
||||
return buf[:p]
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) Decode() Message {
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *SetPageLocation) TypeID() int {
|
||||
return 122
|
||||
}
|
||||
|
||||
type IssueEvent struct {
|
||||
message
|
||||
MessageID uint64
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@ func DecodeSessionEndDeprecated(reader BytesReader) (Message, error) {
|
|||
return msg, err
|
||||
}
|
||||
|
||||
func DecodeSetPageLocation(reader BytesReader) (Message, error) {
|
||||
func DecodeSetPageLocationDeprecated(reader BytesReader) (Message, error) {
|
||||
var err error = nil
|
||||
msg := &SetPageLocation{}
|
||||
msg := &SetPageLocationDeprecated{}
|
||||
if msg.URL, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1032,6 +1032,30 @@ func DecodeMouseClick(reader BytesReader) (Message, error) {
|
|||
if msg.Selector, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.NormalizedX, err = reader.ReadUint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.NormalizedY, err = reader.ReadUint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func DecodeMouseClickDeprecated(reader BytesReader) (Message, error) {
|
||||
var err error = nil
|
||||
msg := &MouseClickDeprecated{}
|
||||
if msg.ID, err = reader.ReadUint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.HesitationTime, err = reader.ReadUint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Label, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Selector, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
|
|
@ -1428,6 +1452,24 @@ func DecodeRedux(reader BytesReader) (Message, error) {
|
|||
return msg, err
|
||||
}
|
||||
|
||||
func DecodeSetPageLocation(reader BytesReader) (Message, error) {
|
||||
var err error = nil
|
||||
msg := &SetPageLocation{}
|
||||
if msg.URL, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Referrer, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.NavigationStart, err = reader.ReadUint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.DocumentTitle, err = reader.ReadString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func DecodeIssueEvent(reader BytesReader) (Message, error) {
|
||||
var err error = nil
|
||||
msg := &IssueEvent{}
|
||||
|
|
@ -1899,7 +1941,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
|||
case 3:
|
||||
return DecodeSessionEndDeprecated(reader)
|
||||
case 4:
|
||||
return DecodeSetPageLocation(reader)
|
||||
return DecodeSetPageLocationDeprecated(reader)
|
||||
case 5:
|
||||
return DecodeSetViewportSize(reader)
|
||||
case 6:
|
||||
|
|
@ -2012,8 +2054,10 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
|||
return DecodeAssetCache(reader)
|
||||
case 67:
|
||||
return DecodeCSSInsertRuleURLBased(reader)
|
||||
case 69:
|
||||
case 68:
|
||||
return DecodeMouseClick(reader)
|
||||
case 69:
|
||||
return DecodeMouseClickDeprecated(reader)
|
||||
case 70:
|
||||
return DecodeCreateIFrameDocument(reader)
|
||||
case 71:
|
||||
|
|
@ -2064,6 +2108,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
|||
return DecodeTagTrigger(reader)
|
||||
case 121:
|
||||
return DecodeRedux(reader)
|
||||
case 122:
|
||||
return DecodeSetPageLocation(reader)
|
||||
case 125:
|
||||
return DecodeIssueEvent(reader)
|
||||
case 126:
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ var batches = map[string]string{
|
|||
"resources": "INSERT INTO experimental.resources (session_id, project_id, message_id, datetime, url, type, duration, ttfb, header_size, encoded_body_size, decoded_body_size, success, url_path) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000))",
|
||||
"autocompletes": "INSERT INTO experimental.autocomplete (project_id, type, value) VALUES (?, ?, SUBSTR(?, 1, 8000))",
|
||||
"pages": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint_time, speed_index, visually_complete, time_to_interactive, url_path, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?)",
|
||||
"clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type, selector) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type, selector, normalized_x, normalized_y) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"inputs": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, event_type, duration, hesitation_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type, error_tags_keys, error_tags_values) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
|
|
@ -398,6 +398,14 @@ func (c *connectorImpl) InsertWebClickEvent(session *sessions.Session, msg *mess
|
|||
if msg.Label == "" {
|
||||
return nil
|
||||
}
|
||||
var nX *uint32 = nil
|
||||
var nY *uint32 = nil
|
||||
if msg.NormalizedX <= 100 && msg.NormalizedY <= 100 {
|
||||
nXVal := uint32(msg.NormalizedX)
|
||||
nX = &nXVal
|
||||
nYVal := uint32(msg.NormalizedY)
|
||||
nY = &nYVal
|
||||
}
|
||||
if err := c.batches["clicks"].Append(
|
||||
session.SessionID,
|
||||
uint16(session.ProjectID),
|
||||
|
|
@ -407,6 +415,8 @@ func (c *connectorImpl) InsertWebClickEvent(session *sessions.Session, msg *mess
|
|||
nullableUint32(uint32(msg.HesitationTime)),
|
||||
"CLICK",
|
||||
msg.Selector,
|
||||
nX,
|
||||
nY,
|
||||
); err != nil {
|
||||
c.checkError("clicks", err)
|
||||
return fmt.Errorf("can't append to clicks batch: %s", err)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class SessionEndDeprecated(Message):
|
|||
self.timestamp = timestamp
|
||||
|
||||
|
||||
class SetPageLocation(Message):
|
||||
class SetPageLocationDeprecated(Message):
|
||||
__id__ = 4
|
||||
|
||||
def __init__(self, url, referrer, navigation_start):
|
||||
|
|
@ -585,6 +585,18 @@ class CSSInsertRuleURLBased(Message):
|
|||
|
||||
|
||||
class MouseClick(Message):
|
||||
__id__ = 68
|
||||
|
||||
def __init__(self, id, hesitation_time, label, selector, normalized_x, normalized_y):
|
||||
self.id = id
|
||||
self.hesitation_time = hesitation_time
|
||||
self.label = label
|
||||
self.selector = selector
|
||||
self.normalized_x = normalized_x
|
||||
self.normalized_y = normalized_y
|
||||
|
||||
|
||||
class MouseClickDeprecated(Message):
|
||||
__id__ = 69
|
||||
|
||||
def __init__(self, id, hesitation_time, label, selector):
|
||||
|
|
@ -825,6 +837,16 @@ class Redux(Message):
|
|||
self.action_time = action_time
|
||||
|
||||
|
||||
class SetPageLocation(Message):
|
||||
__id__ = 122
|
||||
|
||||
def __init__(self, url, referrer, navigation_start, document_title):
|
||||
self.url = url
|
||||
self.referrer = referrer
|
||||
self.navigation_start = navigation_start
|
||||
self.document_title = document_title
|
||||
|
||||
|
||||
class IssueEvent(Message):
|
||||
__id__ = 125
|
||||
|
||||
|
|
@ -854,7 +876,7 @@ class SessionSearch(Message):
|
|||
self.partition = partition
|
||||
|
||||
|
||||
class IOSSessionStart(Message):
|
||||
class MobileSessionStart(Message):
|
||||
__id__ = 90
|
||||
|
||||
def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country):
|
||||
|
|
@ -870,14 +892,14 @@ class IOSSessionStart(Message):
|
|||
self.user_country = user_country
|
||||
|
||||
|
||||
class IOSSessionEnd(Message):
|
||||
class MobileSessionEnd(Message):
|
||||
__id__ = 91
|
||||
|
||||
def __init__(self, timestamp):
|
||||
self.timestamp = timestamp
|
||||
|
||||
|
||||
class IOSMetadata(Message):
|
||||
class MobileMetadata(Message):
|
||||
__id__ = 92
|
||||
|
||||
def __init__(self, timestamp, length, key, value):
|
||||
|
|
@ -887,7 +909,7 @@ class IOSMetadata(Message):
|
|||
self.value = value
|
||||
|
||||
|
||||
class IOSEvent(Message):
|
||||
class MobileEvent(Message):
|
||||
__id__ = 93
|
||||
|
||||
def __init__(self, timestamp, length, name, payload):
|
||||
|
|
@ -897,7 +919,7 @@ class IOSEvent(Message):
|
|||
self.payload = payload
|
||||
|
||||
|
||||
class IOSUserID(Message):
|
||||
class MobileUserID(Message):
|
||||
__id__ = 94
|
||||
|
||||
def __init__(self, timestamp, length, id):
|
||||
|
|
@ -906,7 +928,7 @@ class IOSUserID(Message):
|
|||
self.id = id
|
||||
|
||||
|
||||
class IOSUserAnonymousID(Message):
|
||||
class MobileUserAnonymousID(Message):
|
||||
__id__ = 95
|
||||
|
||||
def __init__(self, timestamp, length, id):
|
||||
|
|
@ -915,7 +937,7 @@ class IOSUserAnonymousID(Message):
|
|||
self.id = id
|
||||
|
||||
|
||||
class IOSScreenChanges(Message):
|
||||
class MobileScreenChanges(Message):
|
||||
__id__ = 96
|
||||
|
||||
def __init__(self, timestamp, length, x, y, width, height):
|
||||
|
|
@ -927,7 +949,7 @@ class IOSScreenChanges(Message):
|
|||
self.height = height
|
||||
|
||||
|
||||
class IOSCrash(Message):
|
||||
class MobileCrash(Message):
|
||||
__id__ = 97
|
||||
|
||||
def __init__(self, timestamp, length, name, reason, stacktrace):
|
||||
|
|
@ -938,7 +960,7 @@ class IOSCrash(Message):
|
|||
self.stacktrace = stacktrace
|
||||
|
||||
|
||||
class IOSViewComponentEvent(Message):
|
||||
class MobileViewComponentEvent(Message):
|
||||
__id__ = 98
|
||||
|
||||
def __init__(self, timestamp, length, screen_name, view_name, visible):
|
||||
|
|
@ -949,7 +971,7 @@ class IOSViewComponentEvent(Message):
|
|||
self.visible = visible
|
||||
|
||||
|
||||
class IOSClickEvent(Message):
|
||||
class MobileClickEvent(Message):
|
||||
__id__ = 100
|
||||
|
||||
def __init__(self, timestamp, length, label, x, y):
|
||||
|
|
@ -960,7 +982,7 @@ class IOSClickEvent(Message):
|
|||
self.y = y
|
||||
|
||||
|
||||
class IOSInputEvent(Message):
|
||||
class MobileInputEvent(Message):
|
||||
__id__ = 101
|
||||
|
||||
def __init__(self, timestamp, length, value, value_masked, label):
|
||||
|
|
@ -971,7 +993,7 @@ class IOSInputEvent(Message):
|
|||
self.label = label
|
||||
|
||||
|
||||
class IOSPerformanceEvent(Message):
|
||||
class MobilePerformanceEvent(Message):
|
||||
__id__ = 102
|
||||
|
||||
def __init__(self, timestamp, length, name, value):
|
||||
|
|
@ -981,7 +1003,7 @@ class IOSPerformanceEvent(Message):
|
|||
self.value = value
|
||||
|
||||
|
||||
class IOSLog(Message):
|
||||
class MobileLog(Message):
|
||||
__id__ = 103
|
||||
|
||||
def __init__(self, timestamp, length, severity, content):
|
||||
|
|
@ -991,7 +1013,7 @@ class IOSLog(Message):
|
|||
self.content = content
|
||||
|
||||
|
||||
class IOSInternalError(Message):
|
||||
class MobileInternalError(Message):
|
||||
__id__ = 104
|
||||
|
||||
def __init__(self, timestamp, length, content):
|
||||
|
|
@ -1000,7 +1022,7 @@ class IOSInternalError(Message):
|
|||
self.content = content
|
||||
|
||||
|
||||
class IOSNetworkCall(Message):
|
||||
class MobileNetworkCall(Message):
|
||||
__id__ = 105
|
||||
|
||||
def __init__(self, timestamp, length, type, method, url, request, response, status, duration):
|
||||
|
|
@ -1015,7 +1037,7 @@ class IOSNetworkCall(Message):
|
|||
self.duration = duration
|
||||
|
||||
|
||||
class IOSSwipeEvent(Message):
|
||||
class MobileSwipeEvent(Message):
|
||||
__id__ = 106
|
||||
|
||||
def __init__(self, timestamp, length, label, x, y, direction):
|
||||
|
|
@ -1027,7 +1049,7 @@ class IOSSwipeEvent(Message):
|
|||
self.direction = direction
|
||||
|
||||
|
||||
class IOSBatchMeta(Message):
|
||||
class MobileBatchMeta(Message):
|
||||
__id__ = 107
|
||||
|
||||
def __init__(self, timestamp, length, first_index):
|
||||
|
|
@ -1036,7 +1058,7 @@ class IOSBatchMeta(Message):
|
|||
self.first_index = first_index
|
||||
|
||||
|
||||
class IOSPerformanceAggregated(Message):
|
||||
class MobilePerformanceAggregated(Message):
|
||||
__id__ = 110
|
||||
|
||||
def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_memory, avg_memory, max_memory, min_battery, avg_battery, max_battery):
|
||||
|
|
@ -1056,7 +1078,7 @@ class IOSPerformanceAggregated(Message):
|
|||
self.max_battery = max_battery
|
||||
|
||||
|
||||
class IOSIssueEvent(Message):
|
||||
class MobileIssueEvent(Message):
|
||||
__id__ = 111
|
||||
|
||||
def __init__(self, timestamp, type, context_string, context, payload):
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ cdef class SessionEndDeprecated(PyMessage):
|
|||
self.timestamp = timestamp
|
||||
|
||||
|
||||
cdef class SetPageLocation(PyMessage):
|
||||
cdef class SetPageLocationDeprecated(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public str url
|
||||
cdef public str referrer
|
||||
|
|
@ -872,6 +872,25 @@ cdef class MouseClick(PyMessage):
|
|||
cdef public unsigned long hesitation_time
|
||||
cdef public str label
|
||||
cdef public str selector
|
||||
cdef public unsigned long normalized_x
|
||||
cdef public unsigned long normalized_y
|
||||
|
||||
def __init__(self, unsigned long id, unsigned long hesitation_time, str label, str selector, unsigned long normalized_x, unsigned long normalized_y):
|
||||
self.__id__ = 68
|
||||
self.id = id
|
||||
self.hesitation_time = hesitation_time
|
||||
self.label = label
|
||||
self.selector = selector
|
||||
self.normalized_x = normalized_x
|
||||
self.normalized_y = normalized_y
|
||||
|
||||
|
||||
cdef class MouseClickDeprecated(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long id
|
||||
cdef public unsigned long hesitation_time
|
||||
cdef public str label
|
||||
cdef public str selector
|
||||
|
||||
def __init__(self, unsigned long id, unsigned long hesitation_time, str label, str selector):
|
||||
self.__id__ = 69
|
||||
|
|
@ -1218,6 +1237,21 @@ cdef class Redux(PyMessage):
|
|||
self.action_time = action_time
|
||||
|
||||
|
||||
cdef class SetPageLocation(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public str url
|
||||
cdef public str referrer
|
||||
cdef public unsigned long navigation_start
|
||||
cdef public str document_title
|
||||
|
||||
def __init__(self, str url, str referrer, unsigned long navigation_start, str document_title):
|
||||
self.__id__ = 122
|
||||
self.url = url
|
||||
self.referrer = referrer
|
||||
self.navigation_start = navigation_start
|
||||
self.document_title = document_title
|
||||
|
||||
|
||||
cdef class IssueEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long message_id
|
||||
|
|
@ -1261,7 +1295,7 @@ cdef class SessionSearch(PyMessage):
|
|||
self.partition = partition
|
||||
|
||||
|
||||
cdef class IOSSessionStart(PyMessage):
|
||||
cdef class MobileSessionStart(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long project_id
|
||||
|
|
@ -1288,7 +1322,7 @@ cdef class IOSSessionStart(PyMessage):
|
|||
self.user_country = user_country
|
||||
|
||||
|
||||
cdef class IOSSessionEnd(PyMessage):
|
||||
cdef class MobileSessionEnd(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
|
||||
|
|
@ -1297,7 +1331,7 @@ cdef class IOSSessionEnd(PyMessage):
|
|||
self.timestamp = timestamp
|
||||
|
||||
|
||||
cdef class IOSMetadata(PyMessage):
|
||||
cdef class MobileMetadata(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1312,7 +1346,7 @@ cdef class IOSMetadata(PyMessage):
|
|||
self.value = value
|
||||
|
||||
|
||||
cdef class IOSEvent(PyMessage):
|
||||
cdef class MobileEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1327,7 +1361,7 @@ cdef class IOSEvent(PyMessage):
|
|||
self.payload = payload
|
||||
|
||||
|
||||
cdef class IOSUserID(PyMessage):
|
||||
cdef class MobileUserID(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1340,7 +1374,7 @@ cdef class IOSUserID(PyMessage):
|
|||
self.id = id
|
||||
|
||||
|
||||
cdef class IOSUserAnonymousID(PyMessage):
|
||||
cdef class MobileUserAnonymousID(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1353,7 +1387,7 @@ cdef class IOSUserAnonymousID(PyMessage):
|
|||
self.id = id
|
||||
|
||||
|
||||
cdef class IOSScreenChanges(PyMessage):
|
||||
cdef class MobileScreenChanges(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1372,7 +1406,7 @@ cdef class IOSScreenChanges(PyMessage):
|
|||
self.height = height
|
||||
|
||||
|
||||
cdef class IOSCrash(PyMessage):
|
||||
cdef class MobileCrash(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1389,7 +1423,7 @@ cdef class IOSCrash(PyMessage):
|
|||
self.stacktrace = stacktrace
|
||||
|
||||
|
||||
cdef class IOSViewComponentEvent(PyMessage):
|
||||
cdef class MobileViewComponentEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1406,7 +1440,7 @@ cdef class IOSViewComponentEvent(PyMessage):
|
|||
self.visible = visible
|
||||
|
||||
|
||||
cdef class IOSClickEvent(PyMessage):
|
||||
cdef class MobileClickEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1423,7 +1457,7 @@ cdef class IOSClickEvent(PyMessage):
|
|||
self.y = y
|
||||
|
||||
|
||||
cdef class IOSInputEvent(PyMessage):
|
||||
cdef class MobileInputEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1440,7 +1474,7 @@ cdef class IOSInputEvent(PyMessage):
|
|||
self.label = label
|
||||
|
||||
|
||||
cdef class IOSPerformanceEvent(PyMessage):
|
||||
cdef class MobilePerformanceEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1455,7 +1489,7 @@ cdef class IOSPerformanceEvent(PyMessage):
|
|||
self.value = value
|
||||
|
||||
|
||||
cdef class IOSLog(PyMessage):
|
||||
cdef class MobileLog(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1470,7 +1504,7 @@ cdef class IOSLog(PyMessage):
|
|||
self.content = content
|
||||
|
||||
|
||||
cdef class IOSInternalError(PyMessage):
|
||||
cdef class MobileInternalError(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1483,7 +1517,7 @@ cdef class IOSInternalError(PyMessage):
|
|||
self.content = content
|
||||
|
||||
|
||||
cdef class IOSNetworkCall(PyMessage):
|
||||
cdef class MobileNetworkCall(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1508,7 +1542,7 @@ cdef class IOSNetworkCall(PyMessage):
|
|||
self.duration = duration
|
||||
|
||||
|
||||
cdef class IOSSwipeEvent(PyMessage):
|
||||
cdef class MobileSwipeEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1527,7 +1561,7 @@ cdef class IOSSwipeEvent(PyMessage):
|
|||
self.direction = direction
|
||||
|
||||
|
||||
cdef class IOSBatchMeta(PyMessage):
|
||||
cdef class MobileBatchMeta(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public unsigned long length
|
||||
|
|
@ -1540,7 +1574,7 @@ cdef class IOSBatchMeta(PyMessage):
|
|||
self.first_index = first_index
|
||||
|
||||
|
||||
cdef class IOSPerformanceAggregated(PyMessage):
|
||||
cdef class MobilePerformanceAggregated(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp_start
|
||||
cdef public unsigned long timestamp_end
|
||||
|
|
@ -1575,7 +1609,7 @@ cdef class IOSPerformanceAggregated(PyMessage):
|
|||
self.max_battery = max_battery
|
||||
|
||||
|
||||
cdef class IOSIssueEvent(PyMessage):
|
||||
cdef class MobileIssueEvent(PyMessage):
|
||||
cdef public int __id__
|
||||
cdef public unsigned long timestamp
|
||||
cdef public str type
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 4:
|
||||
return SetPageLocation(
|
||||
return SetPageLocationDeprecated(
|
||||
url=self.read_string(reader),
|
||||
referrer=self.read_string(reader),
|
||||
navigation_start=self.read_uint(reader)
|
||||
|
|
@ -551,8 +551,18 @@ class MessageCodec(Codec):
|
|||
base_url=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 69:
|
||||
if message_id == 68:
|
||||
return MouseClick(
|
||||
id=self.read_uint(reader),
|
||||
hesitation_time=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
selector=self.read_string(reader),
|
||||
normalized_x=self.read_uint(reader),
|
||||
normalized_y=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 69:
|
||||
return MouseClickDeprecated(
|
||||
id=self.read_uint(reader),
|
||||
hesitation_time=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -740,6 +750,14 @@ class MessageCodec(Codec):
|
|||
action_time=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 122:
|
||||
return SetPageLocation(
|
||||
url=self.read_string(reader),
|
||||
referrer=self.read_string(reader),
|
||||
navigation_start=self.read_uint(reader),
|
||||
document_title=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 125:
|
||||
return IssueEvent(
|
||||
message_id=self.read_uint(reader),
|
||||
|
|
@ -764,7 +782,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 90:
|
||||
return IOSSessionStart(
|
||||
return MobileSessionStart(
|
||||
timestamp=self.read_uint(reader),
|
||||
project_id=self.read_uint(reader),
|
||||
tracker_version=self.read_string(reader),
|
||||
|
|
@ -778,12 +796,12 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 91:
|
||||
return IOSSessionEnd(
|
||||
return MobileSessionEnd(
|
||||
timestamp=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 92:
|
||||
return IOSMetadata(
|
||||
return MobileMetadata(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
key=self.read_string(reader),
|
||||
|
|
@ -791,7 +809,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 93:
|
||||
return IOSEvent(
|
||||
return MobileEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -799,21 +817,21 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 94:
|
||||
return IOSUserID(
|
||||
return MobileUserID(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
id=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 95:
|
||||
return IOSUserAnonymousID(
|
||||
return MobileUserAnonymousID(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
id=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 96:
|
||||
return IOSScreenChanges(
|
||||
return MobileScreenChanges(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
x=self.read_uint(reader),
|
||||
|
|
@ -823,7 +841,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 97:
|
||||
return IOSCrash(
|
||||
return MobileCrash(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -832,7 +850,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 98:
|
||||
return IOSViewComponentEvent(
|
||||
return MobileViewComponentEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
screen_name=self.read_string(reader),
|
||||
|
|
@ -841,7 +859,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 100:
|
||||
return IOSClickEvent(
|
||||
return MobileClickEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -850,7 +868,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 101:
|
||||
return IOSInputEvent(
|
||||
return MobileInputEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
value=self.read_string(reader),
|
||||
|
|
@ -859,7 +877,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 102:
|
||||
return IOSPerformanceEvent(
|
||||
return MobilePerformanceEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -867,7 +885,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 103:
|
||||
return IOSLog(
|
||||
return MobileLog(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
severity=self.read_string(reader),
|
||||
|
|
@ -875,14 +893,14 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 104:
|
||||
return IOSInternalError(
|
||||
return MobileInternalError(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
content=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 105:
|
||||
return IOSNetworkCall(
|
||||
return MobileNetworkCall(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
type=self.read_string(reader),
|
||||
|
|
@ -895,7 +913,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 106:
|
||||
return IOSSwipeEvent(
|
||||
return MobileSwipeEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -905,14 +923,14 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 107:
|
||||
return IOSBatchMeta(
|
||||
return MobileBatchMeta(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
first_index=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 110:
|
||||
return IOSPerformanceAggregated(
|
||||
return MobilePerformanceAggregated(
|
||||
timestamp_start=self.read_uint(reader),
|
||||
timestamp_end=self.read_uint(reader),
|
||||
min_fps=self.read_uint(reader),
|
||||
|
|
@ -930,7 +948,7 @@ class MessageCodec(Codec):
|
|||
)
|
||||
|
||||
if message_id == 111:
|
||||
return IOSIssueEvent(
|
||||
return MobileIssueEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
type=self.read_string(reader),
|
||||
context_string=self.read_string(reader),
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 4:
|
||||
return SetPageLocation(
|
||||
return SetPageLocationDeprecated(
|
||||
url=self.read_string(reader),
|
||||
referrer=self.read_string(reader),
|
||||
navigation_start=self.read_uint(reader)
|
||||
|
|
@ -649,8 +649,18 @@ cdef class MessageCodec:
|
|||
base_url=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 69:
|
||||
if message_id == 68:
|
||||
return MouseClick(
|
||||
id=self.read_uint(reader),
|
||||
hesitation_time=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
selector=self.read_string(reader),
|
||||
normalized_x=self.read_uint(reader),
|
||||
normalized_y=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 69:
|
||||
return MouseClickDeprecated(
|
||||
id=self.read_uint(reader),
|
||||
hesitation_time=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -838,6 +848,14 @@ cdef class MessageCodec:
|
|||
action_time=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 122:
|
||||
return SetPageLocation(
|
||||
url=self.read_string(reader),
|
||||
referrer=self.read_string(reader),
|
||||
navigation_start=self.read_uint(reader),
|
||||
document_title=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 125:
|
||||
return IssueEvent(
|
||||
message_id=self.read_uint(reader),
|
||||
|
|
@ -862,7 +880,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 90:
|
||||
return IOSSessionStart(
|
||||
return MobileSessionStart(
|
||||
timestamp=self.read_uint(reader),
|
||||
project_id=self.read_uint(reader),
|
||||
tracker_version=self.read_string(reader),
|
||||
|
|
@ -876,12 +894,12 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 91:
|
||||
return IOSSessionEnd(
|
||||
return MobileSessionEnd(
|
||||
timestamp=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 92:
|
||||
return IOSMetadata(
|
||||
return MobileMetadata(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
key=self.read_string(reader),
|
||||
|
|
@ -889,7 +907,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 93:
|
||||
return IOSEvent(
|
||||
return MobileEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -897,21 +915,21 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 94:
|
||||
return IOSUserID(
|
||||
return MobileUserID(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
id=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 95:
|
||||
return IOSUserAnonymousID(
|
||||
return MobileUserAnonymousID(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
id=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 96:
|
||||
return IOSScreenChanges(
|
||||
return MobileScreenChanges(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
x=self.read_uint(reader),
|
||||
|
|
@ -921,7 +939,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 97:
|
||||
return IOSCrash(
|
||||
return MobileCrash(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -930,7 +948,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 98:
|
||||
return IOSViewComponentEvent(
|
||||
return MobileViewComponentEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
screen_name=self.read_string(reader),
|
||||
|
|
@ -939,7 +957,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 100:
|
||||
return IOSClickEvent(
|
||||
return MobileClickEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -948,7 +966,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 101:
|
||||
return IOSInputEvent(
|
||||
return MobileInputEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
value=self.read_string(reader),
|
||||
|
|
@ -957,7 +975,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 102:
|
||||
return IOSPerformanceEvent(
|
||||
return MobilePerformanceEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
name=self.read_string(reader),
|
||||
|
|
@ -965,7 +983,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 103:
|
||||
return IOSLog(
|
||||
return MobileLog(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
severity=self.read_string(reader),
|
||||
|
|
@ -973,14 +991,14 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 104:
|
||||
return IOSInternalError(
|
||||
return MobileInternalError(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
content=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 105:
|
||||
return IOSNetworkCall(
|
||||
return MobileNetworkCall(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
type=self.read_string(reader),
|
||||
|
|
@ -993,7 +1011,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 106:
|
||||
return IOSSwipeEvent(
|
||||
return MobileSwipeEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
label=self.read_string(reader),
|
||||
|
|
@ -1003,14 +1021,14 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 107:
|
||||
return IOSBatchMeta(
|
||||
return MobileBatchMeta(
|
||||
timestamp=self.read_uint(reader),
|
||||
length=self.read_uint(reader),
|
||||
first_index=self.read_uint(reader)
|
||||
)
|
||||
|
||||
if message_id == 110:
|
||||
return IOSPerformanceAggregated(
|
||||
return MobilePerformanceAggregated(
|
||||
timestamp_start=self.read_uint(reader),
|
||||
timestamp_end=self.read_uint(reader),
|
||||
min_fps=self.read_uint(reader),
|
||||
|
|
@ -1028,7 +1046,7 @@ cdef class MessageCodec:
|
|||
)
|
||||
|
||||
if message_id == 111:
|
||||
return IOSIssueEvent(
|
||||
return MobileIssueEvent(
|
||||
timestamp=self.read_uint(reader),
|
||||
type=self.read_string(reader),
|
||||
context_string=self.read_string(reader),
|
||||
|
|
|
|||
|
|
@ -3,19 +3,15 @@ import { useStore } from 'App/mstore'
|
|||
import { observer } from 'mobx-react-lite'
|
||||
import ClickMapRenderer from 'App/components/Session/Player/ClickMapRenderer'
|
||||
import { connect } from 'react-redux'
|
||||
import { setCustomSession, clearCurrentSession } from 'App/duck/sessions'
|
||||
import { fetchInsights } from 'Duck/sessions';
|
||||
import { fetchInsights } from 'App/duck/sessions'
|
||||
import { NoContent, Icon } from 'App/components/ui'
|
||||
|
||||
function ClickMapCard({
|
||||
setCustomSession,
|
||||
visitedEvents,
|
||||
insights,
|
||||
fetchInsights,
|
||||
insightsFilters,
|
||||
host,
|
||||
clearCurrentSession,
|
||||
}: any) {
|
||||
const [customSession, setCustomSession] = React.useState<any>(null)
|
||||
const { metricStore, dashboardStore } = useStore();
|
||||
const onMarkerClick = (s: string, innerText: string) => {
|
||||
metricStore.changeClickMapSearch(s, innerText)
|
||||
|
|
@ -23,22 +19,24 @@ function ClickMapCard({
|
|||
const mapUrl = metricStore.instance.series[0].filter.filters[0].value[0]
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => clearCurrentSession()
|
||||
return () => setCustomSession(null)
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (metricStore.instance.data.domURL) {
|
||||
setCustomSession(metricStore.instance.data)
|
||||
setCustomSession(null)
|
||||
setTimeout(() => {
|
||||
setCustomSession(metricStore.instance.data)
|
||||
}, 100)
|
||||
}
|
||||
}, [metricStore.instance])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (visitedEvents.length) {
|
||||
const rangeValue = dashboardStore.drillDownPeriod.rangeValue
|
||||
const startDate = dashboardStore.drillDownPeriod.start
|
||||
const endDate = dashboardStore.drillDownPeriod.end
|
||||
fetchInsights({ ...insightsFilters, url: mapUrl || '/', startDate, endDate, rangeValue, clickRage: metricStore.clickMapFilter })
|
||||
}
|
||||
}, [visitedEvents, metricStore.clickMapFilter])
|
||||
const rangeValue = dashboardStore.drillDownPeriod.rangeValue
|
||||
const startDate = dashboardStore.drillDownPeriod.start
|
||||
const endDate = dashboardStore.drillDownPeriod.end
|
||||
fetchInsights({ ...insightsFilters, url: mapUrl || '/', startDate, endDate, rangeValue, clickRage: metricStore.clickMapFilter })
|
||||
}, [dashboardStore.drillDownPeriod.start, dashboardStore.drillDownPeriod.end, dashboardStore.drillDownPeriod.rangeValue, metricStore.clickMapFilter])
|
||||
|
||||
if (!metricStore.instance.data.domURL || insights.size === 0) {
|
||||
return (
|
||||
|
|
@ -57,7 +55,8 @@ function ClickMapCard({
|
|||
/>
|
||||
)
|
||||
}
|
||||
if (!visitedEvents || !visitedEvents.length) {
|
||||
|
||||
if (!metricStore.instance.data || !customSession) {
|
||||
return <div className="py-2">Loading session</div>
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +70,7 @@ function ClickMapCard({
|
|||
return (
|
||||
<div id="clickmap-render">
|
||||
<ClickMapRenderer
|
||||
customSession={metricStore.instance.data}
|
||||
session={customSession}
|
||||
jumpTimestamp={jumpTimestamp}
|
||||
onMarkerClick={onMarkerClick}
|
||||
/>
|
||||
|
|
@ -86,6 +85,6 @@ export default connect(
|
|||
insights: state.getIn(['sessions', 'insights']),
|
||||
host: state.getIn(['sessions', 'host']),
|
||||
}),
|
||||
{ setCustomSession, fetchInsights, clearCurrentSession }
|
||||
{ fetchInsights, }
|
||||
)
|
||||
(observer(ClickMapCard))
|
||||
|
|
|
|||
|
|
@ -11,28 +11,42 @@ import { toast } from 'react-toastify'
|
|||
function WebPlayer(props: any) {
|
||||
const {
|
||||
session,
|
||||
customSession,
|
||||
insights,
|
||||
jumpTimestamp,
|
||||
onMarkerClick,
|
||||
} = props;
|
||||
// @ts-ignore
|
||||
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue);
|
||||
const playerRef = React.useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const [WebPlayerInst, PlayerStore] = createClickMapPlayer(
|
||||
customSession,
|
||||
(state) => makeAutoObservable(state),
|
||||
toast,
|
||||
);
|
||||
setContextValue({ player: WebPlayerInst, store: PlayerStore });
|
||||
const init = () => {
|
||||
const [WebPlayerInst, PlayerStore] = createClickMapPlayer(
|
||||
session,
|
||||
(state) => makeAutoObservable(state),
|
||||
toast,
|
||||
);
|
||||
playerRef.current = WebPlayerInst;
|
||||
setContextValue({ player: WebPlayerInst, store: PlayerStore });
|
||||
}
|
||||
|
||||
if (!playerRef.current) {
|
||||
init()
|
||||
} else {
|
||||
playerRef.current.clean()
|
||||
playerRef.current = null;
|
||||
setContextValue(defaultContextValue);
|
||||
init();
|
||||
}
|
||||
}, [session.sessionId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
WebPlayerInst.clean();
|
||||
playerRef.current && playerRef.current.clean();
|
||||
playerRef.current = null;
|
||||
// @ts-ignore
|
||||
setContextValue(defaultContextValue);
|
||||
}
|
||||
}, [session.sessionId]);
|
||||
}, [])
|
||||
|
||||
const isPlayerReady = contextValue.store?.get().ready
|
||||
|
||||
|
|
@ -43,7 +57,8 @@ function WebPlayer(props: any) {
|
|||
contextValue.player.pause()
|
||||
contextValue.player.jump(jumpTimestamp)
|
||||
contextValue.player.scale()
|
||||
setTimeout(() => { contextValue.player.showClickmap(insights, onMarkerClick) }, 250)
|
||||
|
||||
setTimeout(() => { contextValue.player.showClickmap(insights) }, 250)
|
||||
}, 500)
|
||||
}
|
||||
return () => {
|
||||
|
|
@ -62,7 +77,6 @@ function WebPlayer(props: any) {
|
|||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
session: state.getIn(['sessions', 'current']),
|
||||
insights: state.getIn(['sessions', 'insights']),
|
||||
jwt: state.getIn(['user', 'jwt']),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import PlayerBlock from './PlayerBlock';
|
|||
|
||||
const TABS = {
|
||||
EVENTS: 'Activity',
|
||||
HEATMAPS: 'Click map',
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ function DropdownAudioPlayer({
|
|||
audio.pause();
|
||||
}
|
||||
if (audio.muted !== isMuted) {
|
||||
console.log(isMuted, audio.muted);
|
||||
audio.muted = isMuted;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function Modal({ tabs, currentTab, changeTab, hideModal }: Props) {
|
|||
function SessionTabs({ isLive }: { isLive?: boolean }) {
|
||||
const { showModal, hideModal } = useModal();
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
const { tabs = new Set('back-compat'), currentTab, closedTabs } = store.get();
|
||||
const { tabs = new Set('back-compat'), currentTab, closedTabs, tabNames } = store.get();
|
||||
|
||||
const tabsArr = Array.from(tabs).map((tab, idx) => ({
|
||||
tab,
|
||||
|
|
@ -80,6 +80,7 @@ function SessionTabs({ isLive }: { isLive?: boolean }) {
|
|||
changeTab={changeTab}
|
||||
isLive={isLive}
|
||||
isClosed={tab.isClosed}
|
||||
name={tabNames[tab.tab]}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import cn from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
i: number;
|
||||
|
|
@ -8,13 +9,16 @@ interface Props {
|
|||
changeTab?: (tab: string) => void;
|
||||
isLive?: boolean;
|
||||
isClosed?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
function Tab({ i, tab, currentTab, changeTab, isLive, isClosed }: Props) {
|
||||
function Tab({ i, tab, currentTab, changeTab, isLive, isClosed, name }: Props) {
|
||||
return (
|
||||
<div
|
||||
key={tab}
|
||||
style={{ marginBottom: '-2px' }}
|
||||
style={{
|
||||
marginBottom: '-2px',
|
||||
}}
|
||||
onClick={() => changeTab?.(tab)}
|
||||
className={cn(
|
||||
'self-end py-1 px-4 text-sm',
|
||||
|
|
@ -22,10 +26,27 @@ function Tab({ i, tab, currentTab, changeTab, isLive, isClosed }: Props) {
|
|||
currentTab === tab
|
||||
? 'border-gray-lighter border-t border-l border-r !border-b-white bg-white rounded-tl rounded-tr font-semibold'
|
||||
: 'cursor-pointer border-gray-lighter !border-b !border-t-transparent !border-l-transparent !border-r-transparent',
|
||||
isClosed ? 'line-through': ''
|
||||
)}
|
||||
>
|
||||
Tab {i + 1}
|
||||
<Tooltip title={name && name.length > 20 ? name : ''}>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<div
|
||||
className={
|
||||
'bg-gray-light rounded-full min-w-5 min-h-5 w-5 h-5 flex items-center justify-center text-xs'
|
||||
}
|
||||
>
|
||||
<div>{i + 1}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn('whitespace-nowrap', isClosed ? 'line-through' : '')}
|
||||
style={{
|
||||
maxWidth: 114,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>{name ? name : `Tab ${i + 1}`}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {
|
|||
|
||||
const TABS = {
|
||||
EVENTS: 'Activity',
|
||||
CLICKMAP: 'Click map',
|
||||
INSPECTOR: 'Tag',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ const reducer = (state = initialState, action: IAction) => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
return state
|
||||
.set('current', session)
|
||||
.set('eventsIndex', matching)
|
||||
|
|
|
|||
|
|
@ -161,9 +161,6 @@ export default class MessageLoader {
|
|||
this.messageManager.distributeMessage(msg);
|
||||
});
|
||||
logger.info('Messages count: ', msgs.length, msgs, file);
|
||||
if (file === 'd:dom 2' && 'createTabCloseEvents' in this.messageManager) {
|
||||
this.messageManager.createTabCloseEvents();
|
||||
}
|
||||
this.messageManager.sortDomRemoveMessages(msgs);
|
||||
this.messageManager.setMessagesLoading(false);
|
||||
};
|
||||
|
|
@ -180,6 +177,12 @@ export default class MessageLoader {
|
|||
}
|
||||
}
|
||||
|
||||
createTabCloseEvents() {
|
||||
if ('createTabCloseEvents' in this.messageManager) {
|
||||
this.messageManager.createTabCloseEvents();
|
||||
}
|
||||
}
|
||||
|
||||
preloaded = false;
|
||||
async preloadFirstFile(data: Uint8Array) {
|
||||
this.mobParser = this.createNewParser(true, this.processMessages, 'p:dom');
|
||||
|
|
@ -242,6 +245,7 @@ export default class MessageLoader {
|
|||
);
|
||||
}
|
||||
} finally {
|
||||
this.createTabCloseEvents()
|
||||
this.store.update({ domLoading: false, devtoolsLoading: false });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ export interface State extends ScreenState {
|
|||
tabStates: {
|
||||
[tabId: string]: TabState;
|
||||
};
|
||||
tabNames: {
|
||||
[tabId: string]: string;
|
||||
}
|
||||
|
||||
domContentLoadedTime?: { time: number; value: number };
|
||||
domBuildingTime?: number;
|
||||
|
|
@ -95,6 +98,7 @@ export default class MessageManager {
|
|||
tabChangeEvents: [],
|
||||
closedTabs: [],
|
||||
sessionStart: 0,
|
||||
tabNames: {},
|
||||
};
|
||||
|
||||
private clickManager: ListWalker<MouseClick> = new ListWalker();
|
||||
|
|
@ -190,15 +194,20 @@ export default class MessageManager {
|
|||
|
||||
public createTabCloseEvents = () => {
|
||||
const lastMsgArr: [string, number][] = []
|
||||
Object.entries(this.tabs).forEach((entry, i) => {
|
||||
const [tabId, tab] = entry
|
||||
const namesObj: Record<string, string> = {}
|
||||
for (const [tabId, tab] of Object.entries(this.tabs)) {
|
||||
const { lastMessageTs } = tab
|
||||
if (lastMessageTs && tabId) lastMsgArr.push([tabId, lastMessageTs])
|
||||
})
|
||||
if (lastMessageTs && tabId) {
|
||||
lastMsgArr.push([tabId, lastMessageTs])
|
||||
namesObj[tabId] = ''
|
||||
}
|
||||
}
|
||||
lastMsgArr.sort((a, b) => a[1] - b[1])
|
||||
lastMsgArr.forEach(([tabId, lastMessageTs]) => {
|
||||
this.tabCloseManager.append({ tabId, time: lastMessageTs })
|
||||
})
|
||||
|
||||
this.state.update({ tabNames: namesObj })
|
||||
}
|
||||
|
||||
public startLoading = () => {
|
||||
|
|
@ -331,6 +340,7 @@ export default class MessageManager {
|
|||
case MType.MouseMove:
|
||||
this.mouseMoveManager.append(msg);
|
||||
break;
|
||||
case MType.MouseClickDeprecated:
|
||||
case MType.MouseClick:
|
||||
this.clickManager.append(msg);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -234,8 +234,8 @@ export default class Screen {
|
|||
case ScaleMode.AdjustParentHeight:
|
||||
// we want to scale the document with true height so the clickmap will be scrollable
|
||||
const usedHeight =
|
||||
this.document?.body.offsetHeight && this.document?.body.offsetHeight > height
|
||||
? this.document.body.offsetHeight + 'px'
|
||||
this.document?.body.scrollHeight && this.document?.body.scrollHeight > height
|
||||
? this.document.body.scrollHeight + 'px'
|
||||
: height + 'px';
|
||||
this.scaleRatio = offsetWidth / width;
|
||||
translate = 'translate(-50%, 0)';
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default class TabSessionManager {
|
|||
|
||||
constructor(
|
||||
private session: any,
|
||||
private readonly state: Store<{ tabStates: { [tabId: string]: TabState } }>,
|
||||
private readonly state: Store<{ tabStates: { [tabId: string]: TabState }, tabNames: { [tabId: string]: string } }>,
|
||||
private readonly screen: Screen,
|
||||
private readonly id: string,
|
||||
private readonly setSize: ({ height, width }: { height: number; width: number }) => void,
|
||||
|
|
@ -176,7 +176,7 @@ export default class TabSessionManager {
|
|||
switch (msg.tp) {
|
||||
case MType.CanvasNode:
|
||||
const managerId = `${msg.timestamp}_${msg.nodeId}`;
|
||||
if (!this.canvasManagers[managerId]) {
|
||||
if (!this.canvasManagers[managerId] && this.session.canvasURL?.length) {
|
||||
const fileId = managerId;
|
||||
const delta = msg.timestamp - this.sessionStart;
|
||||
|
||||
|
|
@ -198,6 +198,7 @@ export default class TabSessionManager {
|
|||
this.canvasReplayWalker.append(msg);
|
||||
}
|
||||
break;
|
||||
case MType.SetPageLocationDeprecated:
|
||||
case MType.SetPageLocation:
|
||||
this.locationManager.append(msg);
|
||||
if (msg.navigationStart > 0) {
|
||||
|
|
@ -336,8 +337,12 @@ export default class TabSessionManager {
|
|||
/* === */
|
||||
const lastLocationMsg = this.locationManager.moveGetLast(t, index);
|
||||
if (!!lastLocationMsg) {
|
||||
const tabNames = this.state.get().tabNames;
|
||||
if (lastLocationMsg.documentTitle) {
|
||||
tabNames[this.id] = lastLocationMsg.documentTitle
|
||||
}
|
||||
// @ts-ignore comes from parent state
|
||||
this.state.update({ location: lastLocationMsg.url });
|
||||
this.state.update({ location: lastLocationMsg.url, tabNames });
|
||||
}
|
||||
|
||||
const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index);
|
||||
|
|
|
|||
|
|
@ -1,66 +1,54 @@
|
|||
import type Screen from '../Screen/Screen'
|
||||
import type { Point } from '../Screen/types'
|
||||
import type { Store } from '../../common/types'
|
||||
import { clickmapStyles } from './clickmapStyles'
|
||||
|
||||
const zIndexMap = {
|
||||
400: 3,
|
||||
200: 4,
|
||||
100: 5,
|
||||
50: 6
|
||||
}
|
||||
const widths = Object.keys(zIndexMap)
|
||||
.map(s => parseInt(s, 10))
|
||||
.sort((a,b) => b - a) as [400, 200, 100, 50]
|
||||
import type { Store } from '../../common/types';
|
||||
import type Screen from '../Screen/Screen';
|
||||
import type { Point } from '../Screen/types';
|
||||
import { clickmapStyles } from './clickmapStyles';
|
||||
import heatmapRenderer from './simpleHeatmap';
|
||||
|
||||
function getOffset(el: Element, innerWindow: Window) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {
|
||||
fixedLeft: rect.left + innerWindow.scrollX,
|
||||
fixedTop: rect.top + innerWindow.scrollY,
|
||||
rect,
|
||||
};
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {
|
||||
fixedLeft: rect.left + innerWindow.scrollX,
|
||||
fixedTop: rect.top + innerWindow.scrollY,
|
||||
rect,
|
||||
};
|
||||
}
|
||||
|
||||
interface BoundingRect {
|
||||
top: number,
|
||||
left: number,
|
||||
width: number,
|
||||
height: number,
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface MarkedTarget {
|
||||
boundingRect: BoundingRect,
|
||||
el: Element,
|
||||
selector: string,
|
||||
count: number,
|
||||
index: number,
|
||||
active?: boolean,
|
||||
percent: number
|
||||
boundingRect: BoundingRect;
|
||||
el: Element;
|
||||
selector: string;
|
||||
count: number;
|
||||
index: number;
|
||||
active?: boolean;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
markedTargets: MarkedTarget[] | null,
|
||||
activeTargetIndex: number,
|
||||
markedTargets: MarkedTarget[] | null;
|
||||
activeTargetIndex: number;
|
||||
}
|
||||
|
||||
|
||||
export default class TargetMarker {
|
||||
private clickMapOverlay: HTMLDivElement | null = null
|
||||
private clickContainers: HTMLDivElement[] = []
|
||||
private smallClicks: HTMLDivElement[] = []
|
||||
static INITIAL_STATE: State = {
|
||||
markedTargets: null,
|
||||
activeTargetIndex: 0
|
||||
}
|
||||
private clickMapOverlay: HTMLCanvasElement | null = null;
|
||||
static INITIAL_STATE: State = {
|
||||
markedTargets: null,
|
||||
activeTargetIndex: 0,
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly screen: Screen,
|
||||
private readonly store: Store<State>,
|
||||
) {}
|
||||
constructor(
|
||||
private readonly screen: Screen,
|
||||
private readonly store: Store<State>
|
||||
) {}
|
||||
|
||||
updateMarkedTargets() {
|
||||
const { markedTargets } = this.store.get()
|
||||
updateMarkedTargets() {
|
||||
const { markedTargets } = this.store.get();
|
||||
if (markedTargets) {
|
||||
this.store.update({
|
||||
markedTargets: markedTargets.map((mt: any) => ({
|
||||
|
|
@ -69,55 +57,64 @@ export default class TargetMarker {
|
|||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private calculateRelativeBoundingRect(el: Element): BoundingRect {
|
||||
const parentEl = this.screen.getParentElement()
|
||||
if (!parentEl) return {top:0, left:0, width:0,height:0} //TODO: can be initialized(?) on mounted screen only
|
||||
const { top, left, width, height } = el.getBoundingClientRect()
|
||||
const s = this.screen.getScale()
|
||||
const screenRect = this.screen.overlay.getBoundingClientRect() //this.screen.getBoundingClientRect() (now private)
|
||||
const parentRect = parentEl.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
top: top*s + screenRect.top - parentRect.top,
|
||||
left: left*s + screenRect.left - parentRect.left,
|
||||
width: width*s,
|
||||
height: height*s,
|
||||
if (heatmapRenderer.checkReady()) {
|
||||
heatmapRenderer.resize().draw();
|
||||
}
|
||||
}
|
||||
|
||||
private calculateRelativeBoundingRect(el: Element): BoundingRect {
|
||||
const parentEl = this.screen.getParentElement();
|
||||
if (!parentEl) return { top: 0, left: 0, width: 0, height: 0 }; //TODO: can be initialized(?) on mounted screen only
|
||||
const { top, left, width, height } = el.getBoundingClientRect();
|
||||
const s = this.screen.getScale();
|
||||
const screenRect = this.screen.overlay.getBoundingClientRect(); //this.screen.getBoundingClientRect() (now private)
|
||||
const parentRect = parentEl.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
top: top * s + screenRect.top - parentRect.top,
|
||||
left: left * s + screenRect.left - parentRect.left,
|
||||
width: width * s,
|
||||
height: height * s,
|
||||
};
|
||||
}
|
||||
|
||||
setActiveTarget(index: number) {
|
||||
const window = this.screen.window
|
||||
const markedTargets: MarkedTarget[] | null = this.store.get().markedTargets
|
||||
const target = markedTargets && markedTargets[index]
|
||||
const window = this.screen.window;
|
||||
const markedTargets: MarkedTarget[] | null = this.store.get().markedTargets;
|
||||
const target = markedTargets && markedTargets[index];
|
||||
if (target && window) {
|
||||
const { fixedTop, rect } = getOffset(target.el, window)
|
||||
const scrollToY = fixedTop - window.innerHeight / 1.5
|
||||
const { fixedTop, rect } = getOffset(target.el, window);
|
||||
const scrollToY = fixedTop - window.innerHeight / 1.5;
|
||||
if (rect.top < 0 || rect.top > window.innerHeight) {
|
||||
// behavior hack TODO: fix it somehow when they will decide to remove it from browser api
|
||||
// @ts-ignore
|
||||
window.scrollTo({ top: scrollToY, behavior: 'instant' })
|
||||
window.scrollTo({ top: scrollToY, behavior: 'instant' });
|
||||
setTimeout(() => {
|
||||
if (!markedTargets) { return }
|
||||
if (!markedTargets) {
|
||||
return;
|
||||
}
|
||||
this.store.update({
|
||||
markedTargets: markedTargets.map(t => t === target ? {
|
||||
...target,
|
||||
boundingRect: this.calculateRelativeBoundingRect(target.el),
|
||||
} : t)
|
||||
})
|
||||
}, 0)
|
||||
markedTargets: markedTargets.map((t) =>
|
||||
t === target
|
||||
? {
|
||||
...target,
|
||||
boundingRect: this.calculateRelativeBoundingRect(target.el),
|
||||
}
|
||||
: t
|
||||
),
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
}
|
||||
this.store.update({ activeTargetIndex: index });
|
||||
}
|
||||
|
||||
private actualScroll: Point | null = null
|
||||
markTargets(selections: { selector: string, count: number }[] | null) {
|
||||
private actualScroll: Point | null = null;
|
||||
markTargets(selections: { selector: string; count: number }[] | null) {
|
||||
if (selections) {
|
||||
const totalCount = selections.reduce((a, b) => {
|
||||
return a + b.count
|
||||
return a + b.count;
|
||||
}, 0);
|
||||
const markedTargets: MarkedTarget[] = [];
|
||||
let index = 0;
|
||||
|
|
@ -130,131 +127,80 @@ export default class TargetMarker {
|
|||
el,
|
||||
index: index++,
|
||||
percent: Math.round((s.count * 100) / totalCount),
|
||||
boundingRect: this.calculateRelativeBoundingRect(el),
|
||||
boundingRect: this.calculateRelativeBoundingRect(el),
|
||||
count: s.count,
|
||||
})
|
||||
});
|
||||
});
|
||||
this.actualScroll = this.screen.getCurrentScroll()
|
||||
this.actualScroll = this.screen.getCurrentScroll();
|
||||
this.store.update({ markedTargets });
|
||||
} else {
|
||||
if (this.actualScroll) {
|
||||
this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y)
|
||||
this.actualScroll = null
|
||||
this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y);
|
||||
this.actualScroll = null;
|
||||
}
|
||||
this.store.update({ markedTargets: null });
|
||||
}
|
||||
}
|
||||
|
||||
injectTargets(clicks: { normalizedX: number; normalizedY: number }[] | null) {
|
||||
if (clicks && this.screen.document) {
|
||||
this.clickMapOverlay?.remove();
|
||||
const overlay = document.createElement('canvas');
|
||||
const iframeSize = this.screen.iframeStylesRef;
|
||||
const scrollHeight = this.screen.document?.documentElement.scrollHeight || 0;
|
||||
const scrollWidth = this.screen.document?.documentElement.scrollWidth || 0;
|
||||
const scaleRatio = this.screen.getScale();
|
||||
Object.assign(
|
||||
overlay.style,
|
||||
clickmapStyles.overlayStyle({
|
||||
height: iframeSize.height,
|
||||
width: iframeSize.width,
|
||||
scale: scaleRatio,
|
||||
})
|
||||
);
|
||||
|
||||
injectTargets(
|
||||
selections: { selector: string, count: number, clickRage?: boolean }[] | null,
|
||||
onMarkerClick?: (selector: string, innerText: string) => void,
|
||||
) {
|
||||
if (selections) {
|
||||
const totalCount = selections.reduce((a, b) => {
|
||||
return a + b.count
|
||||
}, 0);
|
||||
this.clickMapOverlay = overlay;
|
||||
this.screen.getParentElement()?.appendChild(overlay);
|
||||
|
||||
this.clickMapOverlay?.remove()
|
||||
const overlay = document.createElement("div")
|
||||
const iframeSize = this.screen.iframeStylesRef
|
||||
const scaleRatio = this.screen.getScale()
|
||||
Object.assign(overlay.style, clickmapStyles.overlayStyle({ height: iframeSize.height, width: iframeSize.width, scale: scaleRatio }))
|
||||
const pointMap: Record<string, { times: number; data: number[], original: any }> = {};
|
||||
const ovWidth = parseInt(iframeSize.width);
|
||||
const ovHeight = parseInt(iframeSize.height);
|
||||
overlay.width = ovWidth;
|
||||
overlay.height = ovHeight;
|
||||
let maxIntensity = 0;
|
||||
|
||||
this.clickMapOverlay = overlay
|
||||
selections.forEach((s, i) => {
|
||||
const el = this.screen.getElementBySelector(s.selector);
|
||||
if (!el) return;
|
||||
|
||||
const bubbleContainer = document.createElement("div")
|
||||
const {top, left, width, height} = el.getBoundingClientRect()
|
||||
const totalClicks = document.createElement("div")
|
||||
totalClicks.innerHTML = `${s.count} ${s.count !== 1 ? 'Clicks' : 'Click'}`
|
||||
Object.assign(totalClicks.style, clickmapStyles.totalClicks)
|
||||
|
||||
const percent = document.createElement("div")
|
||||
percent.style.fontSize = "14px"
|
||||
percent.innerHTML = `${Math.round((s.count * 100) / totalCount)}% of the clicks recorded in this page`
|
||||
|
||||
bubbleContainer.appendChild(totalClicks)
|
||||
bubbleContainer.appendChild(percent)
|
||||
const containerId = `clickmap-bubble-${i}`
|
||||
bubbleContainer.id = containerId
|
||||
this.clickContainers.push(bubbleContainer)
|
||||
const frameWidth = iframeSize.width.replace('px', '')
|
||||
|
||||
// @ts-ignore
|
||||
Object.assign(bubbleContainer.style, clickmapStyles.bubbleContainer({ top, left: Math.max(100, frameWidth - left > 250 ? left : frameWidth - 220), height }))
|
||||
|
||||
const border = document.createElement("div")
|
||||
|
||||
let key = 0
|
||||
|
||||
if (width > 50) {
|
||||
let diff = widths[key] - width
|
||||
while (diff > 0) {
|
||||
key++
|
||||
diff = widths[key] - width
|
||||
}
|
||||
clicks.forEach((point) => {
|
||||
const key = `${point.normalizedY}-${point.normalizedX}`;
|
||||
if (pointMap[key]) {
|
||||
const times = pointMap[key].times + 1;
|
||||
maxIntensity = Math.max(maxIntensity, times);
|
||||
pointMap[key].times = times;
|
||||
} else {
|
||||
key = 3
|
||||
const clickData = [
|
||||
(point.normalizedX / 100) * scrollWidth,
|
||||
(point.normalizedY / 100) * scrollHeight,
|
||||
];
|
||||
pointMap[key] = { times: 1, data: clickData, original: point };
|
||||
}
|
||||
const borderZindex = zIndexMap[widths[key]]
|
||||
|
||||
Object.assign(border.style, clickmapStyles.highlight({ width, height, top, left, zIndex: borderZindex }))
|
||||
|
||||
const smallClicksBubble = document.createElement("div")
|
||||
smallClicksBubble.innerHTML = `${s.count}`
|
||||
const smallClicksId = containerId + '-small'
|
||||
smallClicksBubble.id = smallClicksId
|
||||
this.smallClicks.push(smallClicksBubble)
|
||||
|
||||
border.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
const innerText = el.innerText.length > 25 ? `${el.innerText.slice(0, 20)}...` : el.innerText
|
||||
onMarkerClick?.(s.selector, innerText)
|
||||
this.clickContainers.forEach(container => {
|
||||
if (container.id === containerId) {
|
||||
container.style.visibility = "visible"
|
||||
} else {
|
||||
container.style.visibility = "hidden"
|
||||
}
|
||||
})
|
||||
this.smallClicks.forEach(container => {
|
||||
if (container.id !== smallClicksId) {
|
||||
container.style.visibility = "visible"
|
||||
} else {
|
||||
container.style.visibility = "hidden"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
overlay.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
onMarkerClick?.('', '')
|
||||
this.clickContainers.forEach(container => {
|
||||
container.style.visibility = "hidden"
|
||||
})
|
||||
this.smallClicks.forEach(container => {
|
||||
container.style.visibility = "visible"
|
||||
})
|
||||
}
|
||||
|
||||
Object.assign(smallClicksBubble.style, clickmapStyles.clicks({ top, height, isRage: s.clickRage, left }))
|
||||
|
||||
border.appendChild(smallClicksBubble)
|
||||
overlay.appendChild(bubbleContainer)
|
||||
overlay.appendChild(border)
|
||||
});
|
||||
|
||||
this.screen.getParentElement()?.appendChild(overlay)
|
||||
const heatmapData: number[][] = [];
|
||||
for (const key in pointMap) {
|
||||
const { data, times } = pointMap[key];
|
||||
heatmapData.push([...data, times]);
|
||||
}
|
||||
|
||||
heatmapRenderer
|
||||
.setCanvas(overlay)
|
||||
.setData(heatmapData)
|
||||
.setRadius(15, 10)
|
||||
.setMax(maxIntensity)
|
||||
.resize()
|
||||
.draw();
|
||||
} else {
|
||||
this.store.update({ markedTargets: null });
|
||||
this.clickMapOverlay?.remove()
|
||||
this.clickMapOverlay = null
|
||||
this.smallClicks = []
|
||||
this.clickContainers = []
|
||||
this.clickMapOverlay?.remove();
|
||||
this.clickMapOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
162
frontend/app/player/web/addons/simpleHeatmap.ts
Normal file
162
frontend/app/player/web/addons/simpleHeatmap.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* modified version of simpleheat
|
||||
*
|
||||
* https://github.com/mourner/simpleheat
|
||||
* Copyright (c) 2015, Vladimir Agafonkin
|
||||
*
|
||||
* */
|
||||
|
||||
class SimpleHeatmap {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D | null;
|
||||
private width: number;
|
||||
private height: number;
|
||||
private max: number;
|
||||
private data: number[][];
|
||||
private circle: HTMLCanvasElement;
|
||||
private grad: Uint8ClampedArray;
|
||||
private r: number;
|
||||
private defaultRadius = 25;
|
||||
private defaultGradient = {
|
||||
0.4: 'blue',
|
||||
0.6: 'cyan',
|
||||
0.7: 'lime',
|
||||
0.8: 'yellow',
|
||||
1.0: 'red',
|
||||
};
|
||||
|
||||
setCanvas(canvas: HTMLCanvasElement): this {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.width = canvas.width;
|
||||
this.height = canvas.height;
|
||||
this.max = 1;
|
||||
this.data = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
setData(data: number[][]): this {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
setMax(max: number): this {
|
||||
this.max = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
add(point: number[]): this {
|
||||
this.data.push(point);
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): this {
|
||||
this.data = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
setRadius(r: number, blur: number = 15): this {
|
||||
const circle = this.createCanvas();
|
||||
const ctx = circle.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Canvas 2d context is not supported');
|
||||
}
|
||||
const r2 = r + blur;
|
||||
|
||||
circle.width = circle.height = r2 * 2;
|
||||
|
||||
ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2;
|
||||
ctx.shadowBlur = blur;
|
||||
ctx.shadowColor = 'black';
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(-r2, -r2, r, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
this.circle = circle;
|
||||
this.r = r2;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
checkReady(): boolean {
|
||||
return !!(this.canvas && this.ctx);
|
||||
}
|
||||
|
||||
resize(): this {
|
||||
this.width = this.canvas.width;
|
||||
this.height = this.canvas.height;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setGradient(grad: Record<string, string>): this {
|
||||
const canvas = this.createCanvas();
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Canvas 2d context is not supported');
|
||||
}
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
|
||||
|
||||
canvas.width = 1;
|
||||
canvas.height = 256;
|
||||
|
||||
for (const i in grad) {
|
||||
gradient.addColorStop(parseFloat(i), grad[i]);
|
||||
}
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 1, 256);
|
||||
|
||||
this.grad = ctx.getImageData(0, 0, 1, 256).data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
draw(minOpacity: number = 0.05): this {
|
||||
if (!this.circle) this.setRadius(this.defaultRadius);
|
||||
if (!this.grad) this.setGradient(this.defaultGradient);
|
||||
|
||||
const ctx = this.ctx;
|
||||
if (!ctx) {
|
||||
throw new Error('Canvas 2d context is not supported');
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, this.width, this.height);
|
||||
|
||||
this.data.forEach((p) => {
|
||||
ctx.globalAlpha = Math.min(Math.max(p[2] / this.max, minOpacity), 1);
|
||||
ctx.drawImage(this.circle, p[0] - this.r, p[1] - this.r);
|
||||
});
|
||||
|
||||
const colored = ctx.getImageData(0, 0, this.width, this.height);
|
||||
this.colorize(colored.data, this.grad);
|
||||
ctx.putImageData(colored, 0, 0);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private colorize(
|
||||
pixels: Uint8ClampedArray,
|
||||
gradient: Uint8ClampedArray
|
||||
): void {
|
||||
for (let i = 0, len = pixels.length; i < len; i += 4) {
|
||||
const j = pixels[i + 3] * 4;
|
||||
|
||||
if (j) {
|
||||
pixels[i] = gradient[j];
|
||||
pixels[i + 1] = gradient[j + 1];
|
||||
pixels[i + 2] = gradient[j + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createCanvas(): HTMLCanvasElement {
|
||||
return document.createElement('canvas');
|
||||
}
|
||||
}
|
||||
|
||||
const heatmapRenderer = new SimpleHeatmap();
|
||||
|
||||
export default heatmapRenderer;
|
||||
|
|
@ -32,7 +32,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const referrer = this.readString(); if (referrer === null) { return resetPointer() }
|
||||
const navigationStart = this.readUint(); if (navigationStart === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.SetPageLocation,
|
||||
tp: MType.SetPageLocationDeprecated,
|
||||
url,
|
||||
referrer,
|
||||
navigationStart,
|
||||
|
|
@ -515,13 +515,31 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
};
|
||||
}
|
||||
|
||||
case 68: {
|
||||
const id = this.readUint(); if (id === null) { return resetPointer() }
|
||||
const hesitationTime = this.readUint(); if (hesitationTime === null) { return resetPointer() }
|
||||
const label = this.readString(); if (label === null) { return resetPointer() }
|
||||
const selector = this.readString(); if (selector === null) { return resetPointer() }
|
||||
const normalizedX = this.readUint(); if (normalizedX === null) { return resetPointer() }
|
||||
const normalizedY = this.readUint(); if (normalizedY === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.MouseClick,
|
||||
id,
|
||||
hesitationTime,
|
||||
label,
|
||||
selector,
|
||||
normalizedX,
|
||||
normalizedY,
|
||||
};
|
||||
}
|
||||
|
||||
case 69: {
|
||||
const id = this.readUint(); if (id === null) { return resetPointer() }
|
||||
const hesitationTime = this.readUint(); if (hesitationTime === null) { return resetPointer() }
|
||||
const label = this.readString(); if (label === null) { return resetPointer() }
|
||||
const selector = this.readString(); if (selector === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.MouseClick,
|
||||
tp: MType.MouseClickDeprecated,
|
||||
id,
|
||||
hesitationTime,
|
||||
label,
|
||||
|
|
@ -763,13 +781,27 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
};
|
||||
}
|
||||
|
||||
case 122: {
|
||||
const url = this.readString(); if (url === null) { return resetPointer() }
|
||||
const referrer = this.readString(); if (referrer === null) { return resetPointer() }
|
||||
const navigationStart = this.readUint(); if (navigationStart === null) { return resetPointer() }
|
||||
const documentTitle = this.readString(); if (documentTitle === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.SetPageLocation,
|
||||
url,
|
||||
referrer,
|
||||
navigationStart,
|
||||
documentTitle,
|
||||
};
|
||||
}
|
||||
|
||||
case 93: {
|
||||
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
|
||||
const length = this.readUint(); if (length === null) { return resetPointer() }
|
||||
const name = this.readString(); if (name === null) { return resetPointer() }
|
||||
const payload = this.readString(); if (payload === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosEvent,
|
||||
tp: MType.MobileEvent,
|
||||
timestamp,
|
||||
length,
|
||||
name,
|
||||
|
|
@ -785,7 +817,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const width = this.readUint(); if (width === null) { return resetPointer() }
|
||||
const height = this.readUint(); if (height === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosScreenChanges,
|
||||
tp: MType.MobileScreenChanges,
|
||||
timestamp,
|
||||
length,
|
||||
x,
|
||||
|
|
@ -802,7 +834,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const x = this.readUint(); if (x === null) { return resetPointer() }
|
||||
const y = this.readUint(); if (y === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosClickEvent,
|
||||
tp: MType.MobileClickEvent,
|
||||
timestamp,
|
||||
length,
|
||||
label,
|
||||
|
|
@ -818,7 +850,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const valueMasked = this.readBoolean(); if (valueMasked === null) { return resetPointer() }
|
||||
const label = this.readString(); if (label === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosInputEvent,
|
||||
tp: MType.MobileInputEvent,
|
||||
timestamp,
|
||||
length,
|
||||
value,
|
||||
|
|
@ -833,7 +865,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const name = this.readString(); if (name === null) { return resetPointer() }
|
||||
const value = this.readUint(); if (value === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosPerformanceEvent,
|
||||
tp: MType.MobilePerformanceEvent,
|
||||
timestamp,
|
||||
length,
|
||||
name,
|
||||
|
|
@ -847,7 +879,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const severity = this.readString(); if (severity === null) { return resetPointer() }
|
||||
const content = this.readString(); if (content === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosLog,
|
||||
tp: MType.MobileLog,
|
||||
timestamp,
|
||||
length,
|
||||
severity,
|
||||
|
|
@ -860,7 +892,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const length = this.readUint(); if (length === null) { return resetPointer() }
|
||||
const content = this.readString(); if (content === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosInternalError,
|
||||
tp: MType.MobileInternalError,
|
||||
timestamp,
|
||||
length,
|
||||
content,
|
||||
|
|
@ -878,7 +910,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const status = this.readUint(); if (status === null) { return resetPointer() }
|
||||
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosNetworkCall,
|
||||
tp: MType.MobileNetworkCall,
|
||||
timestamp,
|
||||
length,
|
||||
type,
|
||||
|
|
@ -899,7 +931,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const y = this.readUint(); if (y === null) { return resetPointer() }
|
||||
const direction = this.readString(); if (direction === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosSwipeEvent,
|
||||
tp: MType.MobileSwipeEvent,
|
||||
timestamp,
|
||||
length,
|
||||
label,
|
||||
|
|
@ -916,7 +948,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
|||
const context = this.readString(); if (context === null) { return resetPointer() }
|
||||
const payload = this.readString(); if (payload === null) { return resetPointer() }
|
||||
return {
|
||||
tp: MType.IosIssueEvent,
|
||||
tp: MType.MobileIssueEvent,
|
||||
timestamp,
|
||||
type,
|
||||
contextString,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { MType } from './raw.gen'
|
||||
|
||||
const IOS_TYPES = [90,91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107,110,111]
|
||||
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,117,118,119]
|
||||
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,68,69,70,71,72,73,74,75,76,77,113,114,117,118,119,122]
|
||||
export function isDOMType(t: MType) {
|
||||
return DOM_TYPES.includes(t)
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import type { Timed } from './timed'
|
|||
import type { RawMessage } from './raw.gen'
|
||||
import type {
|
||||
RawTimestamp,
|
||||
RawSetPageLocation,
|
||||
RawSetPageLocationDeprecated,
|
||||
RawSetViewportSize,
|
||||
RawSetViewportScroll,
|
||||
RawCreateDocument,
|
||||
|
|
@ -46,6 +46,7 @@ import type {
|
|||
RawSetCssDataURLBased,
|
||||
RawCssInsertRuleURLBased,
|
||||
RawMouseClick,
|
||||
RawMouseClickDeprecated,
|
||||
RawCreateIFrameDocument,
|
||||
RawAdoptedSsReplaceURLBased,
|
||||
RawAdoptedSsReplace,
|
||||
|
|
@ -65,16 +66,17 @@ import type {
|
|||
RawCanvasNode,
|
||||
RawTagTrigger,
|
||||
RawRedux,
|
||||
RawIosEvent,
|
||||
RawIosScreenChanges,
|
||||
RawIosClickEvent,
|
||||
RawIosInputEvent,
|
||||
RawIosPerformanceEvent,
|
||||
RawIosLog,
|
||||
RawIosInternalError,
|
||||
RawIosNetworkCall,
|
||||
RawIosSwipeEvent,
|
||||
RawIosIssueEvent,
|
||||
RawSetPageLocation,
|
||||
RawMobileEvent,
|
||||
RawMobileScreenChanges,
|
||||
RawMobileClickEvent,
|
||||
RawMobileInputEvent,
|
||||
RawMobilePerformanceEvent,
|
||||
RawMobileLog,
|
||||
RawMobileInternalError,
|
||||
RawMobileNetworkCall,
|
||||
RawMobileSwipeEvent,
|
||||
RawMobileIssueEvent,
|
||||
} from './raw.gen'
|
||||
|
||||
export type Message = RawMessage & Timed
|
||||
|
|
@ -82,7 +84,7 @@ export type Message = RawMessage & Timed
|
|||
|
||||
export type Timestamp = RawTimestamp & Timed
|
||||
|
||||
export type SetPageLocation = RawSetPageLocation & Timed
|
||||
export type SetPageLocationDeprecated = RawSetPageLocationDeprecated & Timed
|
||||
|
||||
export type SetViewportSize = RawSetViewportSize & Timed
|
||||
|
||||
|
|
@ -164,6 +166,8 @@ export type CssInsertRuleURLBased = RawCssInsertRuleURLBased & Timed
|
|||
|
||||
export type MouseClick = RawMouseClick & Timed
|
||||
|
||||
export type MouseClickDeprecated = RawMouseClickDeprecated & Timed
|
||||
|
||||
export type CreateIFrameDocument = RawCreateIFrameDocument & Timed
|
||||
|
||||
export type AdoptedSsReplaceURLBased = RawAdoptedSsReplaceURLBased & Timed
|
||||
|
|
@ -202,23 +206,25 @@ export type TagTrigger = RawTagTrigger & Timed
|
|||
|
||||
export type Redux = RawRedux & Timed
|
||||
|
||||
export type IosEvent = RawIosEvent & Timed
|
||||
export type SetPageLocation = RawSetPageLocation & Timed
|
||||
|
||||
export type IosScreenChanges = RawIosScreenChanges & Timed
|
||||
export type MobileEvent = RawMobileEvent & Timed
|
||||
|
||||
export type IosClickEvent = RawIosClickEvent & Timed
|
||||
export type MobileScreenChanges = RawMobileScreenChanges & Timed
|
||||
|
||||
export type IosInputEvent = RawIosInputEvent & Timed
|
||||
export type MobileClickEvent = RawMobileClickEvent & Timed
|
||||
|
||||
export type IosPerformanceEvent = RawIosPerformanceEvent & Timed
|
||||
export type MobileInputEvent = RawMobileInputEvent & Timed
|
||||
|
||||
export type IosLog = RawIosLog & Timed
|
||||
export type MobilePerformanceEvent = RawMobilePerformanceEvent & Timed
|
||||
|
||||
export type IosInternalError = RawIosInternalError & Timed
|
||||
export type MobileLog = RawMobileLog & Timed
|
||||
|
||||
export type IosNetworkCall = RawIosNetworkCall & Timed
|
||||
export type MobileInternalError = RawMobileInternalError & Timed
|
||||
|
||||
export type IosSwipeEvent = RawIosSwipeEvent & Timed
|
||||
export type MobileNetworkCall = RawMobileNetworkCall & Timed
|
||||
|
||||
export type IosIssueEvent = RawIosIssueEvent & Timed
|
||||
export type MobileSwipeEvent = RawMobileSwipeEvent & Timed
|
||||
|
||||
export type MobileIssueEvent = RawMobileIssueEvent & Timed
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
export const enum MType {
|
||||
Timestamp = 0,
|
||||
SetPageLocation = 4,
|
||||
SetPageLocationDeprecated = 4,
|
||||
SetViewportSize = 5,
|
||||
SetViewportScroll = 6,
|
||||
CreateDocument = 7,
|
||||
|
|
@ -43,7 +43,8 @@ export const enum MType {
|
|||
SetNodeAttributeURLBased = 60,
|
||||
SetCssDataURLBased = 61,
|
||||
CssInsertRuleURLBased = 67,
|
||||
MouseClick = 69,
|
||||
MouseClick = 68,
|
||||
MouseClickDeprecated = 69,
|
||||
CreateIFrameDocument = 70,
|
||||
AdoptedSsReplaceURLBased = 71,
|
||||
AdoptedSsReplace = 72,
|
||||
|
|
@ -63,16 +64,17 @@ export const enum MType {
|
|||
CanvasNode = 119,
|
||||
TagTrigger = 120,
|
||||
Redux = 121,
|
||||
IosEvent = 93,
|
||||
IosScreenChanges = 96,
|
||||
IosClickEvent = 100,
|
||||
IosInputEvent = 101,
|
||||
IosPerformanceEvent = 102,
|
||||
IosLog = 103,
|
||||
IosInternalError = 104,
|
||||
IosNetworkCall = 105,
|
||||
IosSwipeEvent = 106,
|
||||
IosIssueEvent = 111,
|
||||
SetPageLocation = 122,
|
||||
MobileEvent = 93,
|
||||
MobileScreenChanges = 96,
|
||||
MobileClickEvent = 100,
|
||||
MobileInputEvent = 101,
|
||||
MobilePerformanceEvent = 102,
|
||||
MobileLog = 103,
|
||||
MobileInternalError = 104,
|
||||
MobileNetworkCall = 105,
|
||||
MobileSwipeEvent = 106,
|
||||
MobileIssueEvent = 111,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -81,8 +83,8 @@ export interface RawTimestamp {
|
|||
timestamp: number,
|
||||
}
|
||||
|
||||
export interface RawSetPageLocation {
|
||||
tp: MType.SetPageLocation,
|
||||
export interface RawSetPageLocationDeprecated {
|
||||
tp: MType.SetPageLocationDeprecated,
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
|
|
@ -371,6 +373,16 @@ export interface RawMouseClick {
|
|||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
normalizedX: number,
|
||||
normalizedY: number,
|
||||
}
|
||||
|
||||
export interface RawMouseClickDeprecated {
|
||||
tp: MType.MouseClickDeprecated,
|
||||
id: number,
|
||||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
}
|
||||
|
||||
export interface RawCreateIFrameDocument {
|
||||
|
|
@ -509,16 +521,24 @@ export interface RawRedux {
|
|||
actionTime: number,
|
||||
}
|
||||
|
||||
export interface RawIosEvent {
|
||||
tp: MType.IosEvent,
|
||||
export interface RawSetPageLocation {
|
||||
tp: MType.SetPageLocation,
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
documentTitle: string,
|
||||
}
|
||||
|
||||
export interface RawMobileEvent {
|
||||
tp: MType.MobileEvent,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
name: string,
|
||||
payload: string,
|
||||
}
|
||||
|
||||
export interface RawIosScreenChanges {
|
||||
tp: MType.IosScreenChanges,
|
||||
export interface RawMobileScreenChanges {
|
||||
tp: MType.MobileScreenChanges,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
x: number,
|
||||
|
|
@ -527,8 +547,8 @@ export interface RawIosScreenChanges {
|
|||
height: number,
|
||||
}
|
||||
|
||||
export interface RawIosClickEvent {
|
||||
tp: MType.IosClickEvent,
|
||||
export interface RawMobileClickEvent {
|
||||
tp: MType.MobileClickEvent,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
label: string,
|
||||
|
|
@ -536,8 +556,8 @@ export interface RawIosClickEvent {
|
|||
y: number,
|
||||
}
|
||||
|
||||
export interface RawIosInputEvent {
|
||||
tp: MType.IosInputEvent,
|
||||
export interface RawMobileInputEvent {
|
||||
tp: MType.MobileInputEvent,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
value: string,
|
||||
|
|
@ -545,31 +565,31 @@ export interface RawIosInputEvent {
|
|||
label: string,
|
||||
}
|
||||
|
||||
export interface RawIosPerformanceEvent {
|
||||
tp: MType.IosPerformanceEvent,
|
||||
export interface RawMobilePerformanceEvent {
|
||||
tp: MType.MobilePerformanceEvent,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
name: string,
|
||||
value: number,
|
||||
}
|
||||
|
||||
export interface RawIosLog {
|
||||
tp: MType.IosLog,
|
||||
export interface RawMobileLog {
|
||||
tp: MType.MobileLog,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
severity: string,
|
||||
content: string,
|
||||
}
|
||||
|
||||
export interface RawIosInternalError {
|
||||
tp: MType.IosInternalError,
|
||||
export interface RawMobileInternalError {
|
||||
tp: MType.MobileInternalError,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
content: string,
|
||||
}
|
||||
|
||||
export interface RawIosNetworkCall {
|
||||
tp: MType.IosNetworkCall,
|
||||
export interface RawMobileNetworkCall {
|
||||
tp: MType.MobileNetworkCall,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
type: string,
|
||||
|
|
@ -581,8 +601,8 @@ export interface RawIosNetworkCall {
|
|||
duration: number,
|
||||
}
|
||||
|
||||
export interface RawIosSwipeEvent {
|
||||
tp: MType.IosSwipeEvent,
|
||||
export interface RawMobileSwipeEvent {
|
||||
tp: MType.MobileSwipeEvent,
|
||||
timestamp: number,
|
||||
length: number,
|
||||
label: string,
|
||||
|
|
@ -591,8 +611,8 @@ export interface RawIosSwipeEvent {
|
|||
direction: string,
|
||||
}
|
||||
|
||||
export interface RawIosIssueEvent {
|
||||
tp: MType.IosIssueEvent,
|
||||
export interface RawMobileIssueEvent {
|
||||
tp: MType.MobileIssueEvent,
|
||||
timestamp: number,
|
||||
type: string,
|
||||
contextString: string,
|
||||
|
|
@ -601,4 +621,4 @@ export interface RawIosIssueEvent {
|
|||
}
|
||||
|
||||
|
||||
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawReduxDeprecated | 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 | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | RawRedux | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent;
|
||||
export type RawMessage = RawTimestamp | RawSetPageLocationDeprecated | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawReduxDeprecated | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawMouseClickDeprecated | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | RawRedux | RawSetPageLocation | RawMobileEvent | RawMobileScreenChanges | RawMobileClickEvent | RawMobileInputEvent | RawMobilePerformanceEvent | RawMobileLog | RawMobileInternalError | RawMobileNetworkCall | RawMobileSwipeEvent | RawMobileIssueEvent;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { MType } from './raw.gen'
|
|||
|
||||
export const TP_MAP = {
|
||||
0: MType.Timestamp,
|
||||
4: MType.SetPageLocation,
|
||||
4: MType.SetPageLocationDeprecated,
|
||||
5: MType.SetViewportSize,
|
||||
6: MType.SetViewportScroll,
|
||||
7: MType.CreateDocument,
|
||||
|
|
@ -44,7 +44,8 @@ export const TP_MAP = {
|
|||
60: MType.SetNodeAttributeURLBased,
|
||||
61: MType.SetCssDataURLBased,
|
||||
67: MType.CssInsertRuleURLBased,
|
||||
69: MType.MouseClick,
|
||||
68: MType.MouseClick,
|
||||
69: MType.MouseClickDeprecated,
|
||||
70: MType.CreateIFrameDocument,
|
||||
71: MType.AdoptedSsReplaceURLBased,
|
||||
72: MType.AdoptedSsReplace,
|
||||
|
|
@ -64,14 +65,15 @@ export const TP_MAP = {
|
|||
119: MType.CanvasNode,
|
||||
120: MType.TagTrigger,
|
||||
121: MType.Redux,
|
||||
93: MType.IosEvent,
|
||||
96: MType.IosScreenChanges,
|
||||
100: MType.IosClickEvent,
|
||||
101: MType.IosInputEvent,
|
||||
102: MType.IosPerformanceEvent,
|
||||
103: MType.IosLog,
|
||||
104: MType.IosInternalError,
|
||||
105: MType.IosNetworkCall,
|
||||
106: MType.IosSwipeEvent,
|
||||
111: MType.IosIssueEvent,
|
||||
122: MType.SetPageLocation,
|
||||
93: MType.MobileEvent,
|
||||
96: MType.MobileScreenChanges,
|
||||
100: MType.MobileClickEvent,
|
||||
101: MType.MobileInputEvent,
|
||||
102: MType.MobilePerformanceEvent,
|
||||
103: MType.MobileLog,
|
||||
104: MType.MobileInternalError,
|
||||
105: MType.MobileNetworkCall,
|
||||
106: MType.MobileSwipeEvent,
|
||||
111: MType.MobileIssueEvent,
|
||||
} as const
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ type TrTimestamp = [
|
|||
timestamp: number,
|
||||
]
|
||||
|
||||
type TrSetPageLocation = [
|
||||
type TrSetPageLocationDeprecated = [
|
||||
type: 4,
|
||||
url: string,
|
||||
referrer: string,
|
||||
|
|
@ -354,6 +354,16 @@ type TrCSSInsertRuleURLBased = [
|
|||
]
|
||||
|
||||
type TrMouseClick = [
|
||||
type: 68,
|
||||
id: number,
|
||||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
normalizedX: number,
|
||||
normalizedY: number,
|
||||
]
|
||||
|
||||
type TrMouseClickDeprecated = [
|
||||
type: 69,
|
||||
id: number,
|
||||
hesitationTime: number,
|
||||
|
|
@ -522,8 +532,16 @@ type TrRedux = [
|
|||
actionTime: number,
|
||||
]
|
||||
|
||||
type TrSetPageLocation = [
|
||||
type: 122,
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
documentTitle: string,
|
||||
]
|
||||
|
||||
export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrReduxDeprecated | 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 | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode | TrTagTrigger | TrRedux
|
||||
|
||||
export type TrackerMessage = TrTimestamp | TrSetPageLocationDeprecated | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrReduxDeprecated | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrMouseClickDeprecated | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode | TrTagTrigger | TrRedux | TrSetPageLocation
|
||||
|
||||
export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||
switch(tMsg[0]) {
|
||||
|
|
@ -537,7 +555,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
|||
|
||||
case 4: {
|
||||
return {
|
||||
tp: MType.SetPageLocation,
|
||||
tp: MType.SetPageLocationDeprecated,
|
||||
url: tMsg[1],
|
||||
referrer: tMsg[2],
|
||||
navigationStart: tMsg[3],
|
||||
|
|
@ -891,13 +909,25 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
|||
}
|
||||
}
|
||||
|
||||
case 69: {
|
||||
case 68: {
|
||||
return {
|
||||
tp: MType.MouseClick,
|
||||
id: tMsg[1],
|
||||
hesitationTime: tMsg[2],
|
||||
label: tMsg[3],
|
||||
selector: tMsg[4],
|
||||
normalizedX: tMsg[5],
|
||||
normalizedY: tMsg[6],
|
||||
}
|
||||
}
|
||||
|
||||
case 69: {
|
||||
return {
|
||||
tp: MType.MouseClickDeprecated,
|
||||
id: tMsg[1],
|
||||
hesitationTime: tMsg[2],
|
||||
label: tMsg[3],
|
||||
selector: tMsg[4],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1058,6 +1088,16 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
|||
}
|
||||
}
|
||||
|
||||
case 122: {
|
||||
return {
|
||||
tp: MType.SetPageLocation,
|
||||
url: tMsg[1],
|
||||
referrer: tMsg[2],
|
||||
navigationStart: tMsg[3],
|
||||
documentTitle: tMsg[4],
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ end
|
|||
message 3, 'SessionEndDeprecated', :tracker => false, :replayer => false do
|
||||
uint 'Timestamp'
|
||||
end
|
||||
message 4, 'SetPageLocation' do
|
||||
|
||||
# DEPRECATED since 14.0.0 -> goto 122
|
||||
message 4, 'SetPageLocationDeprecated' do
|
||||
string 'URL'
|
||||
string 'Referrer'
|
||||
uint 'NavigationStart'
|
||||
|
|
@ -360,8 +362,17 @@ message 67, 'CSSInsertRuleURLBased' do
|
|||
uint 'Index'
|
||||
string 'BaseURL'
|
||||
end
|
||||
## 68
|
||||
message 69, 'MouseClick' do
|
||||
|
||||
message 68, 'MouseClick' do
|
||||
uint 'ID'
|
||||
uint 'HesitationTime'
|
||||
string 'Label'
|
||||
string 'Selector'
|
||||
uint 'NormalizedX'
|
||||
uint 'NormalizedY'
|
||||
end
|
||||
|
||||
message 69, 'MouseClickDeprecated' do
|
||||
uint 'ID'
|
||||
uint 'HesitationTime'
|
||||
string 'Label'
|
||||
|
|
@ -529,6 +540,13 @@ message 121, 'Redux', :replayer => :devtools do
|
|||
uint 'ActionTime'
|
||||
end
|
||||
|
||||
message 122, 'SetPageLocation' do
|
||||
string 'URL'
|
||||
string 'Referrer'
|
||||
uint 'NavigationStart'
|
||||
string 'DocumentTitle'
|
||||
end
|
||||
|
||||
## Backend-only
|
||||
message 125, 'IssueEvent', :replayer => false, :tracker => false do
|
||||
uint 'MessageID'
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -31,7 +31,7 @@
|
|||
"dependencies": {
|
||||
"csstype": "^3.0.10",
|
||||
"fflate": "^0.8.2",
|
||||
"peerjs": "1.5.1",
|
||||
"peerjs": "1.5.4",
|
||||
"socket.io-client": "^4.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# 14.0.0
|
||||
|
||||
- titles for tabs
|
||||
- new `MouseClick` message to introduce heatmaps instead of clickmaps
|
||||
- crossdomain iframe tracking functionality
|
||||
|
||||
# 13.0.2
|
||||
|
||||
- more file extensions for canvas
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "13.0.2",
|
||||
"version": "14.0.1-6",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
export declare const enum Type {
|
||||
Timestamp = 0,
|
||||
SetPageLocation = 4,
|
||||
SetPageLocationDeprecated = 4,
|
||||
SetViewportSize = 5,
|
||||
SetViewportScroll = 6,
|
||||
CreateDocument = 7,
|
||||
|
|
@ -52,7 +52,8 @@ export declare const enum Type {
|
|||
TechnicalInfo = 63,
|
||||
CustomIssue = 64,
|
||||
CSSInsertRuleURLBased = 67,
|
||||
MouseClick = 69,
|
||||
MouseClick = 68,
|
||||
MouseClickDeprecated = 69,
|
||||
CreateIFrameDocument = 70,
|
||||
AdoptedSSReplaceURLBased = 71,
|
||||
AdoptedSSInsertRuleURLBased = 73,
|
||||
|
|
@ -75,6 +76,7 @@ export declare const enum Type {
|
|||
CanvasNode = 119,
|
||||
TagTrigger = 120,
|
||||
Redux = 121,
|
||||
SetPageLocation = 122,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -83,8 +85,8 @@ export type Timestamp = [
|
|||
/*timestamp:*/ number,
|
||||
]
|
||||
|
||||
export type SetPageLocation = [
|
||||
/*type:*/ Type.SetPageLocation,
|
||||
export type SetPageLocationDeprecated = [
|
||||
/*type:*/ Type.SetPageLocationDeprecated,
|
||||
/*url:*/ string,
|
||||
/*referrer:*/ string,
|
||||
/*navigationStart:*/ number,
|
||||
|
|
@ -432,6 +434,16 @@ export type MouseClick = [
|
|||
/*hesitationTime:*/ number,
|
||||
/*label:*/ string,
|
||||
/*selector:*/ string,
|
||||
/*normalizedX:*/ number,
|
||||
/*normalizedY:*/ number,
|
||||
]
|
||||
|
||||
export type MouseClickDeprecated = [
|
||||
/*type:*/ Type.MouseClickDeprecated,
|
||||
/*id:*/ number,
|
||||
/*hesitationTime:*/ number,
|
||||
/*label:*/ string,
|
||||
/*selector:*/ string,
|
||||
]
|
||||
|
||||
export type CreateIFrameDocument = [
|
||||
|
|
@ -595,6 +607,14 @@ export type Redux = [
|
|||
/*actionTime:*/ number,
|
||||
]
|
||||
|
||||
export type SetPageLocation = [
|
||||
/*type:*/ Type.SetPageLocation,
|
||||
/*url:*/ string,
|
||||
/*referrer:*/ string,
|
||||
/*navigationStart:*/ number,
|
||||
/*documentTitle:*/ string,
|
||||
]
|
||||
|
||||
type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | ReduxDeprecated | 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 | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux
|
||||
|
||||
type Message = Timestamp | SetPageLocationDeprecated | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | ReduxDeprecated | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | MouseClickDeprecated | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux | SetPageLocation
|
||||
export default Message
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,13 +14,13 @@ export function Timestamp(
|
|||
]
|
||||
}
|
||||
|
||||
export function SetPageLocation(
|
||||
export function SetPageLocationDeprecated(
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
): Messages.SetPageLocation {
|
||||
): Messages.SetPageLocationDeprecated {
|
||||
return [
|
||||
Messages.Type.SetPageLocation,
|
||||
Messages.Type.SetPageLocationDeprecated,
|
||||
url,
|
||||
referrer,
|
||||
navigationStart,
|
||||
|
|
@ -656,6 +656,8 @@ export function MouseClick(
|
|||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
normalizedX: number,
|
||||
normalizedY: number,
|
||||
): Messages.MouseClick {
|
||||
return [
|
||||
Messages.Type.MouseClick,
|
||||
|
|
@ -663,6 +665,23 @@ export function MouseClick(
|
|||
hesitationTime,
|
||||
label,
|
||||
selector,
|
||||
normalizedX,
|
||||
normalizedY,
|
||||
]
|
||||
}
|
||||
|
||||
export function MouseClickDeprecated(
|
||||
id: number,
|
||||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
): Messages.MouseClickDeprecated {
|
||||
return [
|
||||
Messages.Type.MouseClickDeprecated,
|
||||
id,
|
||||
hesitationTime,
|
||||
label,
|
||||
selector,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -966,3 +985,18 @@ export function Redux(
|
|||
]
|
||||
}
|
||||
|
||||
export function SetPageLocation(
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
documentTitle: string,
|
||||
): Messages.SetPageLocation {
|
||||
return [
|
||||
Messages.Type.SetPageLocation,
|
||||
url,
|
||||
referrer,
|
||||
navigationStart,
|
||||
documentTitle,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,22 @@ export default class Nodes {
|
|||
private totalNodeAmount = 0
|
||||
private readonly nodeCallbacks: Array<NodeCallback> = []
|
||||
private readonly elementListeners: Map<number, Array<ElementListener>> = new Map()
|
||||
private nextNodeId = 0
|
||||
|
||||
constructor(private readonly node_id: string) {}
|
||||
|
||||
syntheticMode(frameOrder: number) {
|
||||
const maxSafeNumber = 9007199254740900
|
||||
const placeholderSize = 99999999
|
||||
const nextFrameId = placeholderSize * frameOrder
|
||||
// I highly doubt that this will ever happen,
|
||||
// but it will be easier to debug if it does
|
||||
if (nextFrameId > maxSafeNumber) {
|
||||
throw new Error('Placeholder id overflow')
|
||||
}
|
||||
this.nextNodeId = nextFrameId
|
||||
}
|
||||
|
||||
// Attached once per Tracker instance
|
||||
attachNodeCallback(nodeCallback: NodeCallback): void {
|
||||
this.nodeCallbacks.push(nodeCallback)
|
||||
|
|
@ -38,8 +51,9 @@ export default class Nodes {
|
|||
let id: number = (node as any)[this.node_id]
|
||||
const isNew = id === undefined
|
||||
if (isNew) {
|
||||
id = this.nextNodeId
|
||||
this.totalNodeAmount++
|
||||
id = this.nodes.length
|
||||
this.nextNodeId++
|
||||
this.nodes[id] = node
|
||||
;(node as any)[this.node_id] = id
|
||||
}
|
||||
|
|
@ -102,6 +116,7 @@ export default class Nodes {
|
|||
}
|
||||
this.unregisterNode(node)
|
||||
}
|
||||
this.nextNodeId = 0
|
||||
this.nodes.length = 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,14 @@ export default class IFrameObserver extends Observer {
|
|||
this.app.send(CreateIFrameDocument(hostID, docID))
|
||||
})
|
||||
}
|
||||
|
||||
syntheticObserve(selfId: number, doc: Document) {
|
||||
this.observeRoot(doc, (docID) => {
|
||||
if (docID === undefined) {
|
||||
this.app.debug.log('OpenReplay: Iframe document not bound')
|
||||
return
|
||||
}
|
||||
this.app.send(CreateIFrameDocument(selfId, docID))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// Le truc - for defining an absolute offset for (nested) iframe documents. (To track mouse movments)
|
||||
|
||||
export type Offset = [/*left:*/ number, /*top: */ number]
|
||||
|
||||
type OffsetState = {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,21 @@ export default class TopObserver extends Observer {
|
|||
)
|
||||
}
|
||||
|
||||
crossdomainObserve(selfId: number, frameOder: number) {
|
||||
const observer = this
|
||||
Element.prototype.attachShadow = function () {
|
||||
// eslint-disable-next-line
|
||||
const shadow = attachShadowNativeFn.apply(this, arguments)
|
||||
observer.handleShadowRoot(shadow)
|
||||
return shadow
|
||||
}
|
||||
this.app.nodes.clear()
|
||||
this.app.nodes.syntheticMode(frameOder)
|
||||
const iframeObserver = new IFrameObserver(this.app)
|
||||
this.iframeObservers.push(iframeObserver)
|
||||
iframeObserver.syntheticObserve(selfId, window.document)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.iframeOffsets.clear()
|
||||
Element.prototype.attachShadow = attachShadowNativeFn
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ 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 { IN_BROWSER, deprecationWarn, DOCS_HOST, inIframe } from './utils.js'
|
||||
import FeatureFlags, { IFeatureFlag } from './modules/featureFlags.js'
|
||||
import type { Options as AppOptions } from './app/index.js'
|
||||
import type { Options as ConsoleOptions } from './modules/console.js'
|
||||
|
|
@ -99,8 +99,10 @@ export default class API {
|
|||
public featureFlags: FeatureFlags
|
||||
|
||||
private readonly app: App | null = null
|
||||
private readonly crossdomainMode: boolean = false
|
||||
|
||||
constructor(private readonly options: Options) {
|
||||
this.crossdomainMode = Boolean(inIframe() && options.crossdomain?.enabled)
|
||||
if (!IN_BROWSER || !processOptions(options)) {
|
||||
return
|
||||
}
|
||||
|
|
@ -158,25 +160,38 @@ export default class API {
|
|||
return
|
||||
}
|
||||
|
||||
const app = new App(options.projectKey, options.sessionToken, options, this.signalStartIssue)
|
||||
const app = new App(
|
||||
options.projectKey,
|
||||
options.sessionToken,
|
||||
options,
|
||||
this.signalStartIssue,
|
||||
this.crossdomainMode,
|
||||
)
|
||||
this.app = app
|
||||
Viewport(app)
|
||||
if (!this.crossdomainMode) {
|
||||
// no need to send iframe viewport data since its a node for us
|
||||
Viewport(app)
|
||||
// calculated in main window
|
||||
Connection(app)
|
||||
// while we can calculate it here, trying to compute it for all parts is hard
|
||||
Performance(app, options)
|
||||
// no tabs in iframes yet
|
||||
Tabs(app)
|
||||
}
|
||||
Mouse(app, options.mouse)
|
||||
// inside iframe, we ignore viewport scroll
|
||||
Scroll(app, this.crossdomainMode)
|
||||
CSSRules(app)
|
||||
ConstructedStyleSheets(app)
|
||||
Connection(app)
|
||||
Console(app, options)
|
||||
Exception(app, options)
|
||||
Img(app)
|
||||
Input(app, options)
|
||||
Mouse(app, options.mouse)
|
||||
Timing(app, options)
|
||||
Performance(app, options)
|
||||
Scroll(app)
|
||||
Focus(app)
|
||||
Fonts(app)
|
||||
Network(app, options.network)
|
||||
Selection(app)
|
||||
Tabs(app)
|
||||
;(window as any).__OPENREPLAY__ = this
|
||||
|
||||
if (options.flags && options.flags.onFlagsLoad) {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export interface MouseHandlerOptions {
|
|||
|
||||
export default function (app: App, options?: MouseHandlerOptions): void {
|
||||
const { disableClickmaps = false } = options || {}
|
||||
|
||||
function getTargetLabel(target: Element): string {
|
||||
const dl = getLabelAttribute(target)
|
||||
if (dl !== null) {
|
||||
|
|
@ -203,7 +204,6 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
mousePositionX = e.clientX + left
|
||||
mousePositionY = e.clientY + top
|
||||
mousePositionChanged = true
|
||||
|
||||
const nextDirection = Math.sign(e.movementX)
|
||||
distance += Math.abs(e.movementX) + Math.abs(e.movementY)
|
||||
|
||||
|
|
@ -221,6 +221,15 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
}
|
||||
const id = app.nodes.getID(target)
|
||||
if (id !== undefined) {
|
||||
const clickX = e.pageX
|
||||
const clickY = e.pageY
|
||||
|
||||
const contentWidth = document.documentElement.scrollWidth
|
||||
const contentHeight = document.documentElement.scrollHeight
|
||||
|
||||
const normalizedX = roundNumber(clickX / contentWidth)
|
||||
const normalizedY = roundNumber(clickY / contentHeight)
|
||||
|
||||
sendMouseMove()
|
||||
app.send(
|
||||
MouseClick(
|
||||
|
|
@ -228,6 +237,8 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0,
|
||||
getTargetLabel(target),
|
||||
isClickable(target) && !disableClickmaps ? getSelector(id, target, options) : '',
|
||||
normalizedX,
|
||||
normalizedY,
|
||||
),
|
||||
true,
|
||||
)
|
||||
|
|
@ -245,3 +256,11 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
|
||||
app.ticker.attach(sendMouseMove, options?.trackingOffset || 7)
|
||||
}
|
||||
|
||||
/**
|
||||
* we get 0 to 1 decimal number, convert and round it, then turn to %
|
||||
* 0.39643 => 396.43 => 396 => 39.6%
|
||||
* */
|
||||
function roundNumber(num: number) {
|
||||
return Math.round(num * 1e3) / 1e1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ import { isNode, isElementNode, isRootNode, isDocument } from '../app/guards.js'
|
|||
function getDocumentScroll(doc: Document): [number, number] {
|
||||
const win = doc.defaultView
|
||||
return [
|
||||
(win && win.pageXOffset) ||
|
||||
(win && win.scrollX) ||
|
||||
(doc.documentElement && doc.documentElement.scrollLeft) ||
|
||||
(doc.body && doc.body.scrollLeft) ||
|
||||
0,
|
||||
(win && win.pageYOffset) ||
|
||||
(win && win.scrollY) ||
|
||||
(doc.documentElement && doc.documentElement.scrollTop) ||
|
||||
(doc.body && doc.body.scrollTop) ||
|
||||
0,
|
||||
]
|
||||
}
|
||||
|
||||
export default function (app: App): void {
|
||||
export default function (app: App, insideIframe: boolean | null): void {
|
||||
let documentScroll = false
|
||||
const nodeScroll: Map<Node, [number, number]> = new Map()
|
||||
|
||||
|
|
@ -32,9 +32,12 @@ export default function (app: App): void {
|
|||
}
|
||||
}
|
||||
|
||||
const sendSetViewportScroll = app.safe((): void =>
|
||||
app.send(SetViewportScroll(...getDocumentScroll(document))),
|
||||
)
|
||||
const sendSetViewportScroll = app.safe((): void => {
|
||||
if (insideIframe) {
|
||||
return
|
||||
}
|
||||
app.send(SetViewportScroll(...getDocumentScroll(document)))
|
||||
})
|
||||
|
||||
const sendSetNodeScroll = app.safe((s: [number, number], node: Node): void => {
|
||||
const id = app.nodes.getID(node)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { getTimeOrigin } from '../utils.js'
|
|||
import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../app/messages.gen.js'
|
||||
|
||||
export default function (app: App): void {
|
||||
let url: string, width: number, height: number
|
||||
let url: string | null, width: number, height: number
|
||||
let navigationStart: number
|
||||
let referrer = document.referrer
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export default function (app: App): void {
|
|||
const { URL } = document
|
||||
if (URL !== url) {
|
||||
url = URL
|
||||
app.send(SetPageLocation(url, referrer, navigationStart))
|
||||
app.send(SetPageLocation(url, referrer, navigationStart, document.title))
|
||||
navigationStart = 0
|
||||
referrer = url
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export default function (app: App): void {
|
|||
: app.safe(() => app.send(SetPageVisibility(document.hidden)))
|
||||
|
||||
app.attachStartCallback(() => {
|
||||
url = ''
|
||||
url = null
|
||||
navigationStart = getTimeOrigin()
|
||||
width = height = -1
|
||||
sendSetPageLocation()
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ let timeOrigin: number = IN_BROWSER ? Date.now() - performance.now() : 0
|
|||
export function adjustTimeOrigin() {
|
||||
timeOrigin = Date.now() - performance.now()
|
||||
}
|
||||
|
||||
export function getTimeOrigin() {
|
||||
return timeOrigin
|
||||
}
|
||||
|
||||
export const now: () => number =
|
||||
IN_BROWSER && !!performance.now
|
||||
? () => Math.round(performance.now() + timeOrigin)
|
||||
|
|
@ -102,13 +104,17 @@ export function generateRandomId(len?: number) {
|
|||
// msCrypto = IE11
|
||||
// @ts-ignore
|
||||
const safeCrypto = window.crypto || window.msCrypto
|
||||
safeCrypto.getRandomValues(arr)
|
||||
return Array.from(arr, dec2hex).join('')
|
||||
if (safeCrypto) {
|
||||
safeCrypto.getRandomValues(arr)
|
||||
return Array.from(arr, dec2hex).join('')
|
||||
} else {
|
||||
return Array.from({ length: len || 40 }, () => dec2hex(Math.floor(Math.random() * 16))).join('')
|
||||
}
|
||||
}
|
||||
|
||||
export function inIframe() {
|
||||
try {
|
||||
return window.self !== window.top
|
||||
return window.self && window.top && window.self !== window.top
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
|
|
@ -141,9 +147,10 @@ export function createEventListener(
|
|||
try {
|
||||
target[safeAddEventListener](event, cb, capture)
|
||||
} catch (e) {
|
||||
const msg = e.message
|
||||
console.debug(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Openreplay: ${e.messages}; if this error is caused by an IframeObserver, ignore it`,
|
||||
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,14 +167,14 @@ export function deleteEventListener(
|
|||
try {
|
||||
target[safeRemoveEventListener](event, cb, capture)
|
||||
} catch (e) {
|
||||
const msg = e.message
|
||||
console.debug(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Openreplay: ${e.messages}; if this error is caused by an IframeObserver, ignore it`,
|
||||
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FIFOTaskScheduler {
|
||||
taskQueue: any[]
|
||||
isRunning: boolean
|
||||
|
|
@ -234,3 +241,27 @@ export function requestIdleCb(callback: () => void) {
|
|||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
export function simpleMerge<T>(defaultObj: T, givenObj: Partial<T>): T {
|
||||
const result = { ...defaultObj }
|
||||
|
||||
for (const key in givenObj) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (givenObj.hasOwnProperty(key)) {
|
||||
const userOptionValue = givenObj[key]
|
||||
const defaultOptionValue = defaultObj[key]
|
||||
|
||||
if (
|
||||
typeof userOptionValue === 'object' &&
|
||||
!Array.isArray(userOptionValue) &&
|
||||
userOptionValue !== null
|
||||
) {
|
||||
result[key] = simpleMerge(defaultOptionValue || {}, userOptionValue) as any
|
||||
} else {
|
||||
result[key] = userOptionValue as any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
113
tracker/tracker/src/tests/canvas.test.ts
Normal file
113
tracker/tracker/src/tests/canvas.test.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// @ts-nocheck
|
||||
import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals'
|
||||
import CanvasRecorder from '../main/app/canvas'
|
||||
import App from '../main/app/index.js'
|
||||
|
||||
describe('CanvasRecorder', () => {
|
||||
let appMock: jest.Mocked<App>
|
||||
let canvasRecorder: CanvasRecorder
|
||||
let nodeMock: Node
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
appMock = {}
|
||||
appMock.nodes = {
|
||||
scanTree: jest.fn(),
|
||||
attachNodeCallback: jest.fn(),
|
||||
getID: jest.fn().mockReturnValue(1),
|
||||
getNode: jest.fn(),
|
||||
}
|
||||
appMock.sanitizer = {
|
||||
isObscured: jest.fn().mockReturnValue(false),
|
||||
isHidden: jest.fn().mockReturnValue(false),
|
||||
}
|
||||
appMock.timestamp = jest.fn().mockReturnValue(1000)
|
||||
appMock.send = jest.fn()
|
||||
appMock.debug = {
|
||||
log: jest.fn(),
|
||||
error: jest.fn(),
|
||||
}
|
||||
appMock.session = {
|
||||
getSessionToken: jest.fn().mockReturnValue('token'),
|
||||
}
|
||||
appMock.options = {
|
||||
ingestPoint: 'http://example.com',
|
||||
}
|
||||
|
||||
canvasRecorder = new CanvasRecorder(appMock, { fps: 30, quality: 'medium' })
|
||||
nodeMock = document.createElement('canvas')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('startTracking', () => {
|
||||
test('scans tree and attaches node callback after timeout', () => {
|
||||
jest.useFakeTimers()
|
||||
canvasRecorder.startTracking()
|
||||
jest.advanceTimersByTime(500)
|
||||
expect(appMock.nodes.scanTree).toHaveBeenCalled()
|
||||
expect(appMock.nodes.attachNodeCallback).toHaveBeenCalled()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('restartTracking', () => {
|
||||
test('clears previous intervals and rescans the tree', () => {
|
||||
const clearSpy = jest.spyOn(canvasRecorder, 'clear')
|
||||
canvasRecorder.restartTracking()
|
||||
expect(clearSpy).toHaveBeenCalled()
|
||||
expect(appMock.nodes.scanTree).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('captureCanvas', () => {
|
||||
test('captures canvas and starts observing it', () => {
|
||||
const observeMock = jest.fn()
|
||||
window.IntersectionObserver = jest.fn().mockImplementation((callback) => {
|
||||
return {
|
||||
observe: observeMock,
|
||||
disconnect: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
canvasRecorder.captureCanvas(nodeMock)
|
||||
expect(observeMock).toHaveBeenCalledWith(nodeMock)
|
||||
})
|
||||
|
||||
test('does not capture canvas if it is obscured', () => {
|
||||
appMock.sanitizer.isObscured.mockReturnValue(true)
|
||||
const observeMock = jest.fn()
|
||||
window.IntersectionObserver = jest.fn().mockImplementation((callback) => {
|
||||
return {
|
||||
observe: observeMock,
|
||||
disconnect: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
canvasRecorder.captureCanvas(nodeMock)
|
||||
expect(observeMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('recordCanvas', () => {
|
||||
test('records canvas and sends snapshots at intervals', () => {
|
||||
jest.useFakeTimers()
|
||||
canvasRecorder.recordCanvas(nodeMock, 1)
|
||||
jest.advanceTimersByTime(1000 / 30)
|
||||
expect(appMock.send).toHaveBeenCalled()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('clear', () => {
|
||||
test('clears intervals and snapshots', () => {
|
||||
const clearIntervalSpy = jest.spyOn(global, 'clearInterval')
|
||||
canvasRecorder.recordCanvas(nodeMock, 1)
|
||||
canvasRecorder.clear()
|
||||
expect(clearIntervalSpy).toHaveBeenCalled()
|
||||
expect(canvasRecorder['snapshots']).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -5,8 +5,8 @@ import { describe, expect, jest, afterEach, beforeEach, test } from '@jest/globa
|
|||
const Type = {
|
||||
JSException: 78,
|
||||
CustomEvent: 27,
|
||||
MouseClick: 69,
|
||||
SetPageLocation: 4,
|
||||
MouseClick: 68,
|
||||
SetPageLocation: 122,
|
||||
NetworkRequest: 83,
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +142,7 @@ describe('ConditionsManager', () => {
|
|||
'https://example.com',
|
||||
'referrer',
|
||||
Date.now(),
|
||||
'this is a test title',
|
||||
]
|
||||
manager.processMessage(setPageLocationMessage)
|
||||
expect(manager.hasStarted).toBeTruthy()
|
||||
|
|
|
|||
42
tracker/tracker/src/tests/connection.test.ts
Normal file
42
tracker/tracker/src/tests/connection.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import connection from '../main/modules/connection'
|
||||
import { describe, beforeEach, afterEach, it, expect, jest } from '@jest/globals'
|
||||
|
||||
describe('Connection module', () => {
|
||||
const appMock = {
|
||||
send: jest.fn(),
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should send connection information', () => {
|
||||
const connectionInfo = {
|
||||
downlink: 1,
|
||||
type: 'wifi',
|
||||
addEventListener: jest.fn(),
|
||||
}
|
||||
Object.assign(navigator, { connection: connectionInfo })
|
||||
// @ts-ignore
|
||||
connection(appMock)
|
||||
expect(appMock.send).toHaveBeenCalledWith([54, 1000, 'wifi'])
|
||||
})
|
||||
|
||||
it('should send unknown connection type', () => {
|
||||
const connectionInfo = {
|
||||
downlink: 1,
|
||||
addEventListener: jest.fn(),
|
||||
}
|
||||
Object.assign(navigator, { connection: connectionInfo })
|
||||
// @ts-ignore
|
||||
connection(appMock)
|
||||
expect(appMock.send).toHaveBeenCalledWith([54, 1000, 'unknown'])
|
||||
})
|
||||
|
||||
it('should not send connection information if connection is undefined', () => {
|
||||
Object.assign(navigator, { connection: undefined })
|
||||
// @ts-ignore
|
||||
connection(appMock)
|
||||
expect(appMock.send).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
generateRandomId,
|
||||
ngSafeBrowserMethod,
|
||||
requestIdleCb,
|
||||
inIframe,
|
||||
} from '../main/utils.js'
|
||||
|
||||
describe('adjustTimeOrigin', () => {
|
||||
|
|
@ -185,6 +186,15 @@ describe('generateRandomId', () => {
|
|||
expect(id).toHaveLength(40)
|
||||
expect(/^[0-9a-f]+$/.test(id)).toBe(true)
|
||||
})
|
||||
|
||||
test('falls back to Math.random if crypto api is not available', () => {
|
||||
const originalCrypto = window.crypto
|
||||
// @ts-ignore
|
||||
window.crypto = undefined
|
||||
const id = generateRandomId(20)
|
||||
expect(id).toHaveLength(20)
|
||||
expect(/^[0-9a-f]+$/.test(id)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ngSafeBrowserMethod', () => {
|
||||
|
|
@ -207,7 +217,7 @@ describe('requestIdleCb', () => {
|
|||
test('testing FIFO scheduler', async () => {
|
||||
jest.useFakeTimers()
|
||||
// @ts-ignore
|
||||
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
|
||||
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb())
|
||||
const cb1 = jest.fn()
|
||||
const cb2 = jest.fn()
|
||||
|
||||
|
|
@ -221,3 +231,18 @@ describe('requestIdleCb', () => {
|
|||
expect(cb2).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('inIframe', () => {
|
||||
test('returns true if the code is running inside an iframe', () => {
|
||||
const originalSelf = window.self
|
||||
const originalTop = window.top
|
||||
|
||||
Object.defineProperty(window, 'self', { value: {} })
|
||||
Object.defineProperty(window, 'top', { value: {} })
|
||||
|
||||
expect(inIframe()).toBe(true)
|
||||
|
||||
Object.defineProperty(window, 'self', { value: originalSelf })
|
||||
Object.defineProperty(window, 'top', { value: originalTop })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
|||
return this.uint(msg[1])
|
||||
break
|
||||
|
||||
case Messages.Type.SetPageLocation:
|
||||
case Messages.Type.SetPageLocationDeprecated:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3])
|
||||
break
|
||||
|
||||
|
|
@ -211,6 +211,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
|||
break
|
||||
|
||||
case Messages.Type.MouseClick:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.uint(msg[5]) && this.uint(msg[6])
|
||||
break
|
||||
|
||||
case Messages.Type.MouseClickDeprecated:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4])
|
||||
break
|
||||
|
||||
|
|
@ -302,6 +306,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
|||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.uint(msg[4])
|
||||
break
|
||||
|
||||
case Messages.Type.SetPageLocation:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4])
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue