316 lines
8 KiB
Go
316 lines
8 KiB
Go
package builder
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
|
|
"log"
|
|
|
|
"openreplay/backend/pkg/intervals"
|
|
. "openreplay/backend/pkg/messages"
|
|
)
|
|
|
|
func getURLExtention(URL string) string {
|
|
u, err := url.Parse(URL)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
i := strings.LastIndex(u.Path, ".")
|
|
return u.Path[i+1:]
|
|
}
|
|
|
|
func getResourceType(initiator string, URL string) string {
|
|
switch initiator {
|
|
case "xmlhttprequest", "fetch":
|
|
return "fetch"
|
|
case "img":
|
|
return "img"
|
|
default:
|
|
switch getURLExtention(URL) {
|
|
case "css":
|
|
return "stylesheet"
|
|
case "js":
|
|
return "script"
|
|
case "png", "gif", "jpg", "jpeg", "svg":
|
|
return "img"
|
|
case "mp4", "mkv", "ogg", "webm", "avi", "mp3":
|
|
return "media"
|
|
default:
|
|
return "other"
|
|
}
|
|
}
|
|
}
|
|
|
|
type builder struct {
|
|
readyMsgs []Message
|
|
timestamp uint64
|
|
peBuilder *pageEventBuilder
|
|
ptaBuilder *performanceTrackAggrBuilder
|
|
ieBuilder *inputEventBuilder
|
|
ciFinder *cpuIssueFinder
|
|
miFinder *memoryIssueFinder
|
|
ddDetector *domDropDetector
|
|
crDetector *clickRageDetector
|
|
dcDetector *deadClickDetector
|
|
integrationsWaiting bool
|
|
|
|
|
|
sid uint64
|
|
}
|
|
|
|
func NewBuilder() *builder {
|
|
return &builder{
|
|
peBuilder: &pageEventBuilder{},
|
|
ptaBuilder: &performanceTrackAggrBuilder{},
|
|
ieBuilder: NewInputEventBuilder(),
|
|
ciFinder: &cpuIssueFinder{},
|
|
miFinder: &memoryIssueFinder{},
|
|
ddDetector: &domDropDetector{},
|
|
crDetector: &clickRageDetector{},
|
|
dcDetector: &deadClickDetector{},
|
|
integrationsWaiting: true,
|
|
}
|
|
}
|
|
|
|
func (b *builder) appendReadyMessage(msg Message) { // interface is never nil even if it holds nil value
|
|
b.readyMsgs = append(b.readyMsgs, msg)
|
|
}
|
|
|
|
func (b *builder) iterateReadyMessage(iter func(msg Message)) {
|
|
for _, readyMsg := range b.readyMsgs {
|
|
iter(readyMsg)
|
|
}
|
|
b.readyMsgs = nil
|
|
}
|
|
|
|
func (b *builder) buildSessionEnd() {
|
|
sessionEnd := &SessionEnd{
|
|
Timestamp: b.timestamp, // + delay?
|
|
}
|
|
b.appendReadyMessage(sessionEnd)
|
|
}
|
|
|
|
func (b *builder) buildPageEvent() {
|
|
if msg := b.peBuilder.Build(); msg != nil {
|
|
b.appendReadyMessage(msg)
|
|
}
|
|
}
|
|
func (b *builder) buildPerformanceTrackAggr() {
|
|
if msg := b.ptaBuilder.Build(); msg != nil {
|
|
b.appendReadyMessage(msg)
|
|
}
|
|
}
|
|
func (b *builder) buildInputEvent() {
|
|
if msg := b.ieBuilder.Build(); msg != nil {
|
|
b.appendReadyMessage(msg)
|
|
}
|
|
}
|
|
|
|
func (b *builder) handleMessage(message Message, messageID uint64) {
|
|
timestamp := uint64(message.Meta().Timestamp)
|
|
if b.timestamp <= timestamp {
|
|
b.timestamp = timestamp
|
|
}
|
|
// Start from the first timestamp.
|
|
switch msg := message.(type) {
|
|
case *SessionStart,
|
|
*Metadata,
|
|
*UserID,
|
|
*UserAnonymousID:
|
|
b.appendReadyMessage(msg)
|
|
}
|
|
if b.timestamp == 0 {
|
|
return
|
|
}
|
|
switch msg := message.(type) {
|
|
case *SetPageLocation:
|
|
if msg.NavigationStart == 0 {
|
|
b.appendReadyMessage(&PageEvent{
|
|
URL: msg.URL,
|
|
Referrer: msg.Referrer,
|
|
Loaded: false,
|
|
MessageID: messageID,
|
|
Timestamp: b.timestamp,
|
|
})
|
|
} else {
|
|
b.buildPageEvent()
|
|
b.buildInputEvent()
|
|
b.ieBuilder.ClearLabels()
|
|
b.peBuilder.HandleSetPageLocation(msg, messageID, b.timestamp)
|
|
b.miFinder.HandleSetPageLocation(msg)
|
|
b.ciFinder.HandleSetPageLocation(msg)
|
|
}
|
|
case *PageLoadTiming:
|
|
if rm := b.peBuilder.HandlePageLoadTiming(msg); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
case *PageRenderTiming:
|
|
if rm := b.peBuilder.HandlePageRenderTiming(msg); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
case *PerformanceTrack:
|
|
if rm := b.ptaBuilder.HandlePerformanceTrack(msg, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.ciFinder.HandlePerformanceTrack(msg, messageID, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.miFinder.HandlePerformanceTrack(msg, messageID, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
case *SetInputTarget:
|
|
if rm := b.ieBuilder.HandleSetInputTarget(msg); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
case *SetInputValue:
|
|
if rm := b.ieBuilder.HandleSetInputValue(msg, messageID, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
case *MouseClick:
|
|
b.buildInputEvent()
|
|
if rm := b.crDetector.HandleMouseClick(msg, messageID, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if msg.Label != "" {
|
|
b.appendReadyMessage(&ClickEvent{
|
|
MessageID: messageID,
|
|
Label: msg.Label,
|
|
HesitationTime: msg.HesitationTime,
|
|
Timestamp: b.timestamp,
|
|
})
|
|
}
|
|
case *RawErrorEvent:
|
|
b.appendReadyMessage(&ErrorEvent{
|
|
MessageID: messageID,
|
|
Timestamp: msg.Timestamp,
|
|
Source: msg.Source,
|
|
Name: msg.Name,
|
|
Message: msg.Message,
|
|
Payload: msg.Payload,
|
|
})
|
|
case *JSException:
|
|
b.appendReadyMessage(&ErrorEvent{
|
|
MessageID: messageID,
|
|
Timestamp: b.timestamp,
|
|
Source: "js_exception",
|
|
Name: msg.Name,
|
|
Message: msg.Message,
|
|
Payload: msg.Payload,
|
|
})
|
|
case *ResourceTiming:
|
|
tp := getResourceType(msg.Initiator, msg.URL)
|
|
success := msg.Duration != 0
|
|
b.appendReadyMessage(&ResourceEvent{
|
|
MessageID: messageID,
|
|
Timestamp: msg.Timestamp,
|
|
Duration: msg.Duration,
|
|
TTFB: msg.TTFB,
|
|
HeaderSize: msg.HeaderSize,
|
|
EncodedBodySize: msg.EncodedBodySize,
|
|
DecodedBodySize: msg.DecodedBodySize,
|
|
URL: msg.URL,
|
|
Type: tp,
|
|
Success: success,
|
|
})
|
|
if !success && tp == "fetch" {
|
|
b.appendReadyMessage(&IssueEvent{
|
|
Type: "bad_request",
|
|
MessageID: messageID,
|
|
Timestamp: msg.Timestamp,
|
|
ContextString: msg.URL,
|
|
Context: "",
|
|
Payload: "",
|
|
})
|
|
}
|
|
case *RawCustomEvent:
|
|
b.appendReadyMessage(&CustomEvent{
|
|
MessageID: messageID,
|
|
Timestamp: b.timestamp,
|
|
Name: msg.Name,
|
|
Payload: msg.Payload,
|
|
})
|
|
case *CustomIssue:
|
|
b.appendReadyMessage(&IssueEvent{
|
|
Type: "custom",
|
|
Timestamp: b.timestamp,
|
|
MessageID: messageID,
|
|
ContextString: msg.Name,
|
|
Payload: msg.Payload,
|
|
})
|
|
case *Fetch:
|
|
b.appendReadyMessage(&ResourceEvent{
|
|
MessageID: messageID,
|
|
Timestamp: msg.Timestamp,
|
|
Duration: msg.Duration,
|
|
URL: msg.URL,
|
|
Type: "fetch",
|
|
Success: msg.Status < 300,
|
|
Method: msg.Method,
|
|
Status: msg.Status,
|
|
})
|
|
case *StateAction:
|
|
b.appendReadyMessage(&StateActionEvent{
|
|
MessageID: messageID,
|
|
Timestamp: b.timestamp,
|
|
Type: msg.Type,
|
|
})
|
|
case *GraphQL:
|
|
b.appendReadyMessage(&GraphQLEvent{
|
|
MessageID: messageID,
|
|
Timestamp: b.timestamp,
|
|
Name: msg.OperationName,
|
|
})
|
|
case *CreateElementNode,
|
|
*CreateTextNode:
|
|
b.ddDetector.HandleNodeCreation()
|
|
case *RemoveNode:
|
|
b.ddDetector.HandleNodeRemoval(b.timestamp)
|
|
case *CreateDocument:
|
|
if rm := b.ddDetector.Build(); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
}
|
|
if rm := b.dcDetector.HandleMessage(message, messageID, b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
}
|
|
|
|
|
|
func (b *builder) checkTimeouts(ts int64) bool {
|
|
if b.timestamp == 0 {
|
|
return false // There was no timestamp events yet
|
|
}
|
|
|
|
if b.peBuilder.HasInstance() && int64(b.peBuilder.GetTimestamp())+intervals.EVENTS_PAGE_EVENT_TIMEOUT < ts {
|
|
b.buildPageEvent()
|
|
}
|
|
if b.ieBuilder.HasInstance() && int64(b.ieBuilder.GetTimestamp())+intervals.EVENTS_INPUT_EVENT_TIMEOUT < ts {
|
|
b.buildInputEvent()
|
|
}
|
|
if b.ptaBuilder.HasInstance() && int64(b.ptaBuilder.GetStartTimestamp())+intervals.EVENTS_PERFORMANCE_AGGREGATION_TIMEOUT < ts {
|
|
b.buildPerformanceTrackAggr()
|
|
}
|
|
|
|
lastTsGap := ts - int64(b.timestamp)
|
|
//log.Printf("checking timeouts for sess %v: %v now, %v sesstime; gap %v",b.sid, ts, b.timestamp, lastTsGap)
|
|
if lastTsGap > intervals.EVENTS_SESSION_END_TIMEOUT {
|
|
if rm := b.ddDetector.Build(); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.ciFinder.Build(); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.miFinder.Build(); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.crDetector.Build(); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
if rm := b.dcDetector.HandleReaction(b.timestamp); rm != nil {
|
|
b.appendReadyMessage(rm)
|
|
}
|
|
b.buildSessionEnd()
|
|
return true
|
|
}
|
|
return false
|
|
}
|