From f64da1ab89eb2841592bf6631840d95ea8ad5738 Mon Sep 17 00:00:00 2001 From: Ayuhito Date: Sun, 1 Sep 2024 22:47:55 +0300 Subject: [PATCH] fix: remove unload page property feature --- core/api/oas_json_gen.go | 195 +------------------ core/api/oas_schemas_gen.go | 173 ---------------- core/db/db.go | 2 +- core/db/duckdb/event.go | 4 +- core/model/errors.go | 4 +- core/openapi.yaml | 11 -- core/services/event.go | 157 +++++++-------- dashboard/app/api/types.d.ts | 6 - tracker/README.md | 2 +- tracker/dist/click-events.js | 6 +- tracker/dist/click-events.page-events.js | 30 +-- tracker/dist/click-events.page-events.min.js | 2 +- tracker/dist/default.js | 6 +- tracker/dist/page-events.js | 30 +-- tracker/dist/page-events.min.js | 2 +- tracker/src/tracker.js | 34 +--- tracker/tests/fixtures/history/index.html | 4 +- tracker/tests/fixtures/simple/about.html | 4 +- tracker/tests/fixtures/simple/contact.html | 4 +- tracker/tests/fixtures/simple/index.html | 4 +- 20 files changed, 125 insertions(+), 555 deletions(-) diff --git a/core/api/oas_json_gen.go b/core/api/oas_json_gen.go index 66b123a..b92293c 100644 --- a/core/api/oas_json_gen.go +++ b/core/api/oas_json_gen.go @@ -865,18 +865,6 @@ func (s EventHit) encodeFields(e *jx.Encoder) { e.FieldStart("m") e.Int(s.M) } - { - if s.G.Set { - e.FieldStart("g") - s.G.Encode(e) - } - } - { - if s.D.Set { - e.FieldStart("d") - s.D.Encode(e) - } - } } } } @@ -1286,25 +1274,11 @@ func (s *EventUnload) encodeFields(e *jx.Encoder) { e.FieldStart("m") e.Int(s.M) } - { - if s.G.Set { - e.FieldStart("g") - s.G.Encode(e) - } - } - { - if s.D.Set { - e.FieldStart("d") - s.D.Encode(e) - } - } } -var jsonFieldsNameOfEventUnload = [4]string{ +var jsonFieldsNameOfEventUnload = [2]string{ 0: "b", 1: "m", - 2: "g", - 3: "d", } // Decode decodes EventUnload from json. @@ -1340,26 +1314,6 @@ func (s *EventUnload) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"m\"") } - case "g": - if err := func() error { - s.G.Reset() - if err := s.G.Decode(d); err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"g\"") - } - case "d": - if err := func() error { - s.D.Reset() - if err := s.D.Decode(d); err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"d\"") - } default: return d.Skip() } @@ -1416,119 +1370,6 @@ func (s *EventUnload) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode implements json.Marshaler. -func (s EventUnloadD) Encode(e *jx.Encoder) { - e.ObjStart() - s.encodeFields(e) - e.ObjEnd() -} - -// encodeFields implements json.Marshaler. -func (s EventUnloadD) encodeFields(e *jx.Encoder) { - for k, elem := range s { - e.FieldStart(k) - - elem.Encode(e) - } -} - -// Decode decodes EventUnloadD from json. -func (s *EventUnloadD) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode EventUnloadD to nil") - } - m := s.init() - if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { - var elem EventUnloadDItem - if err := func() error { - if err := elem.Decode(d); err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrapf(err, "decode field %q", k) - } - m[string(k)] = elem - return nil - }); err != nil { - return errors.Wrap(err, "decode EventUnloadD") - } - - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s EventUnloadD) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *EventUnloadD) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - -// Encode encodes EventUnloadDItem as json. -func (s EventUnloadDItem) Encode(e *jx.Encoder) { - switch s.Type { - case StringEventUnloadDItem: - e.Str(s.String) - case IntEventUnloadDItem: - e.Int(s.Int) - case BoolEventUnloadDItem: - e.Bool(s.Bool) - } -} - -// Decode decodes EventUnloadDItem from json. -func (s *EventUnloadDItem) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode EventUnloadDItem to nil") - } - // Sum type type_discriminator. - switch t := d.Next(); t { - case jx.Bool: - v, err := d.Bool() - s.Bool = bool(v) - if err != nil { - return err - } - s.Type = BoolEventUnloadDItem - case jx.Number: - v, err := d.Int() - s.Int = int(v) - if err != nil { - return err - } - s.Type = IntEventUnloadDItem - case jx.String: - v, err := d.Str() - s.String = string(v) - if err != nil { - return err - } - s.Type = StringEventUnloadDItem - default: - return errors.Errorf("unexpected json type %q", t) - } - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s EventUnloadDItem) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *EventUnloadDItem) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode implements json.Marshaler. func (s *ForbiddenError) Encode(e *jx.Encoder) { e.ObjStart() @@ -2237,40 +2078,6 @@ func (s *OptEventLoadD) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode encodes EventUnloadD as json. -func (o OptEventUnloadD) Encode(e *jx.Encoder) { - if !o.Set { - return - } - o.Value.Encode(e) -} - -// Decode decodes EventUnloadD from json. -func (o *OptEventUnloadD) Decode(d *jx.Decoder) error { - if o == nil { - return errors.New("invalid: unable to decode OptEventUnloadD to nil") - } - o.Set = true - o.Value = make(EventUnloadD) - if err := o.Value.Decode(d); err != nil { - return err - } - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s OptEventUnloadD) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *OptEventUnloadD) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode encodes float32 as json. func (o OptFloat32) Encode(e *jx.Encoder) { if !o.Set { diff --git a/core/api/oas_schemas_gen.go b/core/api/oas_schemas_gen.go index d6a4ab9..05987b8 100644 --- a/core/api/oas_schemas_gen.go +++ b/core/api/oas_schemas_gen.go @@ -600,11 +600,6 @@ type EventUnload struct { B string `json:"b"` // Time spent on page in milliseconds. M int `json:"m"` - // Group name of events. This must be set if passing custom event properties. Currently, only the - // hostname is supported. - G OptString `json:"g"` - // Custom event properties. - D OptEventUnloadD `json:"d"` } // GetB returns the value of B. @@ -617,16 +612,6 @@ func (s *EventUnload) GetM() int { return s.M } -// GetG returns the value of G. -func (s *EventUnload) GetG() OptString { - return s.G -} - -// GetD returns the value of D. -func (s *EventUnload) GetD() OptEventUnloadD { - return s.D -} - // SetB sets the value of B. func (s *EventUnload) SetB(val string) { s.B = val @@ -637,118 +622,6 @@ func (s *EventUnload) SetM(val int) { s.M = val } -// SetG sets the value of G. -func (s *EventUnload) SetG(val OptString) { - s.G = val -} - -// SetD sets the value of D. -func (s *EventUnload) SetD(val OptEventUnloadD) { - s.D = val -} - -// Custom event properties. -type EventUnloadD map[string]EventUnloadDItem - -func (s *EventUnloadD) init() EventUnloadD { - m := *s - if m == nil { - m = map[string]EventUnloadDItem{} - *s = m - } - return m -} - -// EventUnloadDItem represents sum type. -type EventUnloadDItem struct { - Type EventUnloadDItemType // switch on this field - String string - Int int - Bool bool -} - -// EventUnloadDItemType is oneOf type of EventUnloadDItem. -type EventUnloadDItemType string - -// Possible values for EventUnloadDItemType. -const ( - StringEventUnloadDItem EventUnloadDItemType = "string" - IntEventUnloadDItem EventUnloadDItemType = "int" - BoolEventUnloadDItem EventUnloadDItemType = "bool" -) - -// IsString reports whether EventUnloadDItem is string. -func (s EventUnloadDItem) IsString() bool { return s.Type == StringEventUnloadDItem } - -// IsInt reports whether EventUnloadDItem is int. -func (s EventUnloadDItem) IsInt() bool { return s.Type == IntEventUnloadDItem } - -// IsBool reports whether EventUnloadDItem is bool. -func (s EventUnloadDItem) IsBool() bool { return s.Type == BoolEventUnloadDItem } - -// SetString sets EventUnloadDItem to string. -func (s *EventUnloadDItem) SetString(v string) { - s.Type = StringEventUnloadDItem - s.String = v -} - -// GetString returns string and true boolean if EventUnloadDItem is string. -func (s EventUnloadDItem) GetString() (v string, ok bool) { - if !s.IsString() { - return v, false - } - return s.String, true -} - -// NewStringEventUnloadDItem returns new EventUnloadDItem from string. -func NewStringEventUnloadDItem(v string) EventUnloadDItem { - var s EventUnloadDItem - s.SetString(v) - return s -} - -// SetInt sets EventUnloadDItem to int. -func (s *EventUnloadDItem) SetInt(v int) { - s.Type = IntEventUnloadDItem - s.Int = v -} - -// GetInt returns int and true boolean if EventUnloadDItem is int. -func (s EventUnloadDItem) GetInt() (v int, ok bool) { - if !s.IsInt() { - return v, false - } - return s.Int, true -} - -// NewIntEventUnloadDItem returns new EventUnloadDItem from int. -func NewIntEventUnloadDItem(v int) EventUnloadDItem { - var s EventUnloadDItem - s.SetInt(v) - return s -} - -// SetBool sets EventUnloadDItem to bool. -func (s *EventUnloadDItem) SetBool(v bool) { - s.Type = BoolEventUnloadDItem - s.Bool = v -} - -// GetBool returns bool and true boolean if EventUnloadDItem is bool. -func (s EventUnloadDItem) GetBool() (v bool, ok bool) { - if !s.IsBool() { - return v, false - } - return s.Bool, true -} - -// NewBoolEventUnloadDItem returns new EventUnloadDItem from bool. -func NewBoolEventUnloadDItem(v bool) EventUnloadDItem { - var s EventUnloadDItem - s.SetBool(v) - return s -} - // Ref: #/components/schemas/FilterString type FilterString struct { // Equal to. @@ -1313,52 +1186,6 @@ func (o OptEventLoadD) Or(d EventLoadD) EventLoadD { return d } -// NewOptEventUnloadD returns new OptEventUnloadD with value set to v. -func NewOptEventUnloadD(v EventUnloadD) OptEventUnloadD { - return OptEventUnloadD{ - Value: v, - Set: true, - } -} - -// OptEventUnloadD is optional EventUnloadD. -type OptEventUnloadD struct { - Value EventUnloadD - Set bool -} - -// IsSet returns true if OptEventUnloadD was set. -func (o OptEventUnloadD) IsSet() bool { return o.Set } - -// Reset unsets value. -func (o *OptEventUnloadD) Reset() { - var v EventUnloadD - o.Value = v - o.Set = false -} - -// SetTo sets value to v. -func (o *OptEventUnloadD) SetTo(v EventUnloadD) { - o.Set = true - o.Value = v -} - -// Get returns value and boolean that denotes whether value was set. -func (o OptEventUnloadD) Get() (v EventUnloadD, ok bool) { - if !o.Set { - return v, false - } - return o.Value, true -} - -// Or returns value if set, or given parameter if does not. -func (o OptEventUnloadD) Or(d EventUnloadD) EventUnloadD { - if v, ok := o.Get(); ok { - return v - } - return d -} - // NewOptFilterString returns new OptFilterString with value set to v. func NewOptFilterString(v FilterString) OptFilterString { return OptFilterString{ diff --git a/core/db/db.go b/core/db/db.go index 4756d71..c04f025 100644 --- a/core/db/db.go +++ b/core/db/db.go @@ -53,7 +53,7 @@ type AnalyticsClient interface { // Events AddEvents(ctx context.Context, event *[]model.EventHit) error AddPageView(ctx context.Context, event *model.PageViewHit, events *[]model.EventHit) error - UpdatePageView(ctx context.Context, event *model.PageViewDuration, events *[]model.EventHit) error + UpdatePageView(ctx context.Context, event *model.PageViewDuration) error // Pages GetWebsitePages(ctx context.Context, filter *Filters) ([]*model.StatsPages, error) GetWebsitePagesSummary(ctx context.Context, filter *Filters) ([]*model.StatsPagesSummary, error) diff --git a/core/db/duckdb/event.go b/core/db/duckdb/event.go index aea62fb..b23d365 100644 --- a/core/db/duckdb/event.go +++ b/core/db/duckdb/event.go @@ -99,7 +99,7 @@ const updatePageViewStmt = `--sql UPDATE views SET duration_ms = ? WHERE bid = ?` // UpdatePageView updates a page view in the database. -func (c *Client) UpdatePageView(ctx context.Context, event *model.PageViewDuration, events *[]model.EventHit) error { +func (c *Client) UpdatePageView(ctx context.Context, event *model.PageViewDuration) error { return c.executeInTransaction(ctx, func(tx *sqlx.Tx) error { stmt, err := c.GetPreparedStmt(ctx, updatePageViewName, updatePageViewStmt) if err != nil { @@ -112,7 +112,7 @@ func (c *Client) UpdatePageView(ctx context.Context, event *model.PageViewDurati return errors.Wrap(err, "duckdb: execute statement") } - return c.addEventsWithinTransaction(ctx, tx, events) + return nil }) } diff --git a/core/model/errors.go b/core/model/errors.go index 10ab985..54b7d53 100644 --- a/core/model/errors.go +++ b/core/model/errors.go @@ -20,8 +20,8 @@ var ( ErrSessionNotFound = errors.New("session not found") // Events - // ErrInvalidScreenSize is returned when a screen size is invalid. - ErrInvalidScreenSize = errors.New("screen height or width is too large") + // ErrInvalidProperties is returned when a given custom property is invalid. + ErrInvalidProperties = errors.New("invalid custom property") // ErrInvalidTimezone is returned when a given timezone is invalid. ErrInvalidTimezone = errors.New("invalid country code") // ErrInvalidTrackerEvent is returned when a given tracker event is invalid. diff --git a/core/openapi.yaml b/core/openapi.yaml index 597767d..94c6f59 100644 --- a/core/openapi.yaml +++ b/core/openapi.yaml @@ -1426,17 +1426,6 @@ components: description: Time spent on page in milliseconds. minimum: 0 exclusiveMinimum: true - g: - type: string - description: Group name of events. This must be set if passing custom event properties. Currently, only the hostname is supported. - d: - type: object - description: Custom event properties. - additionalProperties: - oneOf: - - type: string - - type: integer - - type: boolean required: - b - m diff --git a/core/services/event.go b/core/services/event.go index 7d9bcef..43bd966 100644 --- a/core/services/event.go +++ b/core/services/event.go @@ -261,19 +261,45 @@ func (h *Handler) PostEventHit(ctx context.Context, req api.EventHit, _params ap Str("device_type", event.DeviceType). Logger() - if req.EventUnload.D.IsSet() { - events, err := customEventToEventHit(event.BID, hostname, req.EventCustom.D) + if req.EventLoad.D.IsSet() { + // Generate batch ID to group all the properties of the same event. + batchIDType, err := typeid.WithPrefix("event") if err != nil { - log.Error().Msg("hit: " + err.Error()) - return ErrBadRequest(model.ErrInvalidTrackerEvent), nil + return nil, errors.Wrap(err, "typeid custom event") + } + batchID := batchIDType.String() + + events := make([]model.EventHit, 0, len(req.EventLoad.D.Value)) + + for name, item := range req.EventLoad.D.Value { + var value string + + switch item.Type { + case api.StringEventLoadDItem: + value = item.String + case api.IntEventLoadDItem: + value = strconv.Itoa(item.Int) + case api.BoolEventLoadDItem: + value = strconv.FormatBool(item.Bool) + default: + return nil, errors.New("invalid custom event property type: " + string(item.Type)) + } + + events = append(events, model.EventHit{ + BID: event.BID, + BatchID: batchID, + Group: hostname, + Name: name, + Value: value, + }) } log = log.With(). Str("event_type", string(req.Type)). - Int("event_count", len(*events)). + Int("event_count", len(events)). Logger() - err = h.analyticsDB.AddPageView(ctx, event, events) + err = h.analyticsDB.AddPageView(ctx, event, &events) if err != nil { log.Error().Err(err).Msg("hit: failed to add page view") return ErrInternalServerError(err), nil @@ -300,45 +326,20 @@ func (h *Handler) PostEventHit(ctx context.Context, req api.EventHit, _params ap Int("duration_ms", event.DurationMs). Logger() - if req.EventUnload.D.IsSet() && req.EventUnload.G.IsSet() { - group := req.EventUnload.G.Value - log = log.With().Str("group_name", group).Logger() - - // Verify hostname exists as hostname is used as the group name. - if !h.hostnames.Has(group) { - log.Warn().Msg("hit: website not found") - return ErrNotFound(model.ErrWebsiteNotFound), nil - } - - events, err := customEventToEventHit(req.EventCustom.B.Or(""), group, req.EventCustom.D) - if err != nil { - log.Error().Msg("hit: " + err.Error()) - return ErrBadRequest(model.ErrInvalidTrackerEvent), nil - } - - log = log.With(). - Str("event_type", string(req.Type)). - Str("group", group). - Int("event_count", len(*events)). - Logger() - - err = h.analyticsDB.UpdatePageView(ctx, event, events) - if err != nil { - log.Error().Err(err).Msg("hit: failed to update page view") - return ErrInternalServerError(err), nil - } - } else { - err := h.analyticsDB.UpdatePageView(ctx, event, nil) - if err != nil { - log.Error().Err(err).Msg("hit: failed to update page view") - return ErrInternalServerError(err), nil - } + err := h.analyticsDB.UpdatePageView(ctx, event) + if err != nil { + log.Error().Err(err).Msg("hit: failed to update page view") + return ErrInternalServerError(err), nil } // Log success log.Debug().Msg("hit: updated page view") case api.EventCustomEventHit: + if (len(req.EventCustom.D)) == 0 { + return ErrBadRequest(model.ErrInvalidProperties), nil + } + group := req.EventCustom.G log = log.With().Str("group_name", group).Logger() @@ -348,7 +349,38 @@ func (h *Handler) PostEventHit(ctx context.Context, req api.EventHit, _params ap return ErrNotFound(model.ErrWebsiteNotFound), nil } - events, err := customEventToEventHit(req.EventCustom.B.Or(""), group, req.EventCustom.D) + // Generate batch ID to group all the properties of the same event. + batchIDType, err := typeid.WithPrefix("event") + if err != nil { + return nil, errors.Wrap(err, "typeid custom event") + } + batchID := batchIDType.String() + + events := make([]model.EventHit, 0, len(req.EventCustom.D)) + + for name, item := range req.EventCustom.D { + var value string + + switch item.Type { + case api.StringEventCustomDItem: + value = item.String + case api.IntEventCustomDItem: + value = strconv.Itoa(item.Int) + case api.BoolEventCustomDItem: + value = strconv.FormatBool(item.Bool) + default: + return nil, errors.New("invalid custom event property type: " + string(item.Type)) + } + + events = append(events, model.EventHit{ + BID: req.EventCustom.B.Or(""), + BatchID: batchID, + Group: group, + Name: name, + Value: value, + }) + } + if err != nil { log.Error().Msg("hit: " + err.Error()) return ErrBadRequest(model.ErrInvalidTrackerEvent), nil @@ -357,10 +389,10 @@ func (h *Handler) PostEventHit(ctx context.Context, req api.EventHit, _params ap log = log.With(). Str("event_type", string(req.Type)). Str("group", group). - Int("event_count", len(*events)). + Int("event_count", len(events)). Logger() - err = h.analyticsDB.AddEvents(ctx, events) + err = h.analyticsDB.AddEvents(ctx, &events) if err != nil { log.Error().Err(err).Msg("hit: failed to add event") return ErrInternalServerError(err), nil @@ -374,44 +406,3 @@ func (h *Handler) PostEventHit(ctx context.Context, req api.EventHit, _params ap return &api.PostEventHitNoContent{}, nil } - -func customEventToEventHit(bid string, group string, items api.EventCustomD) (*[]model.EventHit, error) { - if len(items) == 0 { - //nolint: nilnil // It saves us an extra error check. - return nil, nil - } - - // Generate batch ID to group all the properties of the same event. - batchIDType, err := typeid.WithPrefix("event") - if err != nil { - return nil, errors.Wrap(err, "typeid custom event") - } - batchID := batchIDType.String() - - events := make([]model.EventHit, 0, len(items)) - - for name, item := range items { - var value string - - switch item.Type { - case api.StringEventCustomDItem: - value = item.String - case api.IntEventCustomDItem: - value = strconv.Itoa(item.Int) - case api.BoolEventCustomDItem: - value = strconv.FormatBool(item.Bool) - default: - return nil, errors.New("invalid custom event property type: " + string(item.Type)) - } - - events = append(events, model.EventHit{ - BID: bid, - BatchID: batchID, - Group: group, - Name: name, - Value: value, - }) - } - - return &events, nil -} diff --git a/dashboard/app/api/types.d.ts b/dashboard/app/api/types.d.ts index 84411e7..08f1616 100644 --- a/dashboard/app/api/types.d.ts +++ b/dashboard/app/api/types.d.ts @@ -496,12 +496,6 @@ export interface components { b: string; /** @description Time spent on page in milliseconds. */ m: number; - /** @description Group name of events. This must be set if passing custom event properties. Currently, only the hostname is supported. */ - g?: string; - /** @description Custom event properties. */ - d?: { - [key: string]: string | number | boolean; - }; /** * @description discriminator enum property added by openapi-typescript * @enum {string} diff --git a/tracker/README.md b/tracker/README.md index 41f961b..42a22d4 100644 --- a/tracker/README.md +++ b/tracker/README.md @@ -11,7 +11,7 @@ Our tracker is designed with compression in mind, given that web traffic is usua | File | Size | Compressed (gzip) | Compressed (brotli) | | --------------------- | -------------------- | -------------------- | ------------------- | | `default.min.js` | 1574 bytes (1.54 KB) | 792 bytes (0.77 KB) | 639 bytes (0.62 KB) | -| `page-events.min.js` | 1845 bytes (1.80 KB) | 935 bytes (0.91 KB) | 770 bytes (0.75 KB) | +| `page-events.min.js` | 1812 bytes (1.77 KB) | 916 bytes (0.89 KB) | 751 bytes (0.73 KB) | | `click-events.min.js` | 2053 bytes (2.00 KB) | 1003 bytes (0.98 KB) | 810 bytes (0.79 KB) | The listed sizes only show the size of the tracker itself with one specific feature. When combining multiple features, the size of the tracker will relatively increase (although some features may share code with each other). diff --git a/tracker/dist/click-events.js b/tracker/dist/click-events.js index c96b2f8..c17980e 100644 --- a/tracker/dist/click-events.js +++ b/tracker/dist/click-events.js @@ -17,7 +17,6 @@ * @property {string} b Beacon ID. * @property {'unload'} e Event type. * @property {number} m Time spent on page. - * @property {Object=} d Event custom properties. */ /** @@ -133,12 +132,12 @@ /** * @this {History} * @param {*} _state - The state object. - * @param {string} _unused - The title (unused). + * @param {string} _title - The title. * @param {(string | URL)=} url - The URL to navigate to. * @returns {void} */ ) => - function (_state, _unused, url) { + function (_state, _title, url) { if (url && location.pathname !== new URL(url, location.href).pathname) { sendUnloadBeacon(); // If the event is a history change, then we need to reset the id and timers @@ -168,7 +167,6 @@ return acc; }, {}); - /** * Ping the server with the cache endpoint and read the last modified header to determine * if the user is unique or not. diff --git a/tracker/dist/click-events.page-events.js b/tracker/dist/click-events.page-events.js index 57265db..72ede39 100644 --- a/tracker/dist/click-events.page-events.js +++ b/tracker/dist/click-events.page-events.js @@ -17,7 +17,6 @@ * @property {string} b Beacon ID. * @property {'unload'} e Event type. * @property {number} m Time spent on page. - * @property {Object=} d Event custom properties. */ /** @@ -133,12 +132,12 @@ /** * @this {History} * @param {*} _state - The state object. - * @param {string} _unused - The title (unused). + * @param {string} _title - The title. * @param {(string | URL)=} url - The URL to navigate to. * @returns {void} */ ) => - function (_state, _unused, url) { + function (_state, _title, url) { if (url && location.pathname !== new URL(url, location.href).pathname) { sendUnloadBeacon(); // If the event is a history change, then we need to reset the id and timers @@ -168,21 +167,6 @@ return acc; }, {}); - /** - * Helper function to extract data attributes and merge them. - * @param {string} eventType - * @returns {Object} - */ - const extractAndMergeDataAttributes = (eventType) => { - return [...document.querySelectorAll(`[data-m\\:${eventType}]`)].reduce( - (acc, elem) => ({ - ...acc, - ...extractDataAttributes(elem, eventType), - }), - {}, - ); - }; - /** * Ping the server with the cache endpoint and read the last modified header to determine * if the user is unique or not. @@ -242,7 +226,14 @@ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#return_value */ "t": Intl.DateTimeFormat().resolvedOptions().timeZone, - "d": extractAndMergeDataAttributes('load'), + // Helper function to extract data attributes and merge them. + "d": [...document.querySelectorAll('[data-m\\:load]')].reduce( + (acc, elem) => ({ + ...acc, + ...extractDataAttributes(elem, 'load'), + }), + {}, + ), }), ), // Will make the response opaque, but we don't need it. @@ -277,7 +268,6 @@ "b": uid, "e": "unload", "m": Date.now() - hiddenTotalTime, - "d": extractAndMergeDataAttributes('unload'), }), ), ); diff --git a/tracker/dist/click-events.page-events.min.js b/tracker/dist/click-events.page-events.min.js index b270afe..50b334e 100644 --- a/tracker/dist/click-events.page-events.min.js +++ b/tracker/dist/click-events.page-events.min.js @@ -1 +1 @@ -!function(){if(!document)return;const t=document.currentScript,e=t.getAttribute("data-api")?`${location.protocol}//${t.getAttribute("data-api")}`:t.src.replace(/[^\/]+$/,"api/"),n=()=>Date.now().toString(36)+Math.random().toString(36).substr(2);let o=n(),a=!0,i=0,r=Date.now(),c=!1;const d=history.pushState,s=history.replaceState,l=()=>{a=!1,o=n(),i=0,r=Date.now(),c=!1},p=t=>function(e,n,o){o&&location.pathname!==new URL(o,location.href).pathname?(y(),l(),t.apply(this,arguments),g()):t.apply(this,arguments)},u=(t,e)=>(t.getAttribute("data-m:"+e)||"").split(";").reduce(((t,e)=>{const[n,o]=e.split("=").map((t=>t.trim()));return n&&o&&(t[n]=o),t}),{}),h=t=>[...document.querySelectorAll(`[data-m\\:${t}]`)].reduce(((e,n)=>({...e,...u(n,t)})),{}),m=t=>new Promise((e=>{const n=new XMLHttpRequest;n.onload=()=>{e(0==n.responseText)},n.open("GET",t),n.setRequestHeader("Content-Type","text/plain"),n.send()})),g=async()=>{m(e+"event/ping?u="+encodeURIComponent(location.host+location.pathname)).then((t=>{fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:o,e:"load",u:location.href,r:document.referrer,p:a,q:t,t:Intl.DateTimeFormat().resolvedOptions().timeZone,d:h("load")}),mode:"no-cors"})}))},y=()=>{c||navigator.sendBeacon(e+"event/hit",JSON.stringify({b:o,e:"unload",m:Date.now()-r,d:h("unload")})),c=!0},f=t=>{if(t.button>1||!(t.target instanceof HTMLElement))return;const n=u(t.target,"click");Object.keys(n).length>0&&fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:o,e:"custom",g:location.hostname,d:n}),mode:"no-cors"})};for(const t of document.querySelectorAll("[data-m\\:click]"))t.addEventListener("click",f),t.addEventListener("auxclick",f);"onpagehide"in self?addEventListener("pagehide",y,{capture:!0}):(addEventListener("beforeunload",y,{capture:!0}),addEventListener("unload",y,{capture:!0})),addEventListener("visibilitychange",(()=>{"hidden"==document.visibilityState?i=Date.now():(r+=Date.now()-i,i=0)}),{capture:!0}),m(e+"event/ping?root").then((e=>{a=e,g(),t.getAttribute("data-hash")?addEventListener("hashchange",g,{capture:!0}):(history.pushState=p(d),history.replaceState=p(s),addEventListener("popstate",(()=>{y(),l(),g()}),{capture:!0}))}))}(); \ No newline at end of file +!function(){if(!document)return;const t=document.currentScript,e=t.getAttribute("data-api")?`${location.protocol}//${t.getAttribute("data-api")}`:t.src.replace(/[^\/]+$/,"api/"),n=()=>Date.now().toString(36)+Math.random().toString(36).substr(2);let o=n(),a=!0,i=0,r=Date.now(),c=!1;const d=history.pushState,s=history.replaceState,l=()=>{a=!1,o=n(),i=0,r=Date.now(),c=!1},p=t=>function(e,n,o){o&&location.pathname!==new URL(o,location.href).pathname?(g(),l(),t.apply(this,arguments),m()):t.apply(this,arguments)},u=(t,e)=>(t.getAttribute("data-m:"+e)||"").split(";").reduce(((t,e)=>{const[n,o]=e.split("=").map((t=>t.trim()));return n&&o&&(t[n]=o),t}),{}),h=t=>new Promise((e=>{const n=new XMLHttpRequest;n.onload=()=>{e(0==n.responseText)},n.open("GET",t),n.setRequestHeader("Content-Type","text/plain"),n.send()})),m=async()=>{h(e+"event/ping?u="+encodeURIComponent(location.host+location.pathname)).then((t=>{fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:o,e:"load",u:location.href,r:document.referrer,p:a,q:t,t:Intl.DateTimeFormat().resolvedOptions().timeZone,d:[...document.querySelectorAll("[data-m\\:load]")].reduce(((t,e)=>({...t,...u(e,"load")})),{})}),mode:"no-cors"})}))},g=()=>{c||navigator.sendBeacon(e+"event/hit",JSON.stringify({b:o,e:"unload",m:Date.now()-r})),c=!0},y=t=>{if(t.button>1||!(t.target instanceof HTMLElement))return;const n=u(t.target,"click");Object.keys(n).length>0&&fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:o,e:"custom",g:location.hostname,d:n}),mode:"no-cors"})};for(const t of document.querySelectorAll("[data-m\\:click]"))t.addEventListener("click",y),t.addEventListener("auxclick",y);"onpagehide"in self?addEventListener("pagehide",g,{capture:!0}):(addEventListener("beforeunload",g,{capture:!0}),addEventListener("unload",g,{capture:!0})),addEventListener("visibilitychange",(()=>{"hidden"==document.visibilityState?i=Date.now():(r+=Date.now()-i,i=0)}),{capture:!0}),h(e+"event/ping?root").then((e=>{a=e,m(),t.getAttribute("data-hash")?addEventListener("hashchange",m,{capture:!0}):(history.pushState=p(d),history.replaceState=p(s),addEventListener("popstate",(()=>{g(),l(),m()}),{capture:!0}))}))}(); \ No newline at end of file diff --git a/tracker/dist/default.js b/tracker/dist/default.js index 3e991ee..249b153 100644 --- a/tracker/dist/default.js +++ b/tracker/dist/default.js @@ -17,7 +17,6 @@ * @property {string} b Beacon ID. * @property {'unload'} e Event type. * @property {number} m Time spent on page. - * @property {Object=} d Event custom properties. */ /** @@ -133,12 +132,12 @@ /** * @this {History} * @param {*} _state - The state object. - * @param {string} _unused - The title (unused). + * @param {string} _title - The title. * @param {(string | URL)=} url - The URL to navigate to. * @returns {void} */ ) => - function (_state, _unused, url) { + function (_state, _title, url) { if (url && location.pathname !== new URL(url, location.href).pathname) { sendUnloadBeacon(); // If the event is a history change, then we need to reset the id and timers @@ -152,7 +151,6 @@ }; - /** * Ping the server with the cache endpoint and read the last modified header to determine * if the user is unique or not. diff --git a/tracker/dist/page-events.js b/tracker/dist/page-events.js index cebcbdb..4583137 100644 --- a/tracker/dist/page-events.js +++ b/tracker/dist/page-events.js @@ -17,7 +17,6 @@ * @property {string} b Beacon ID. * @property {'unload'} e Event type. * @property {number} m Time spent on page. - * @property {Object=} d Event custom properties. */ /** @@ -133,12 +132,12 @@ /** * @this {History} * @param {*} _state - The state object. - * @param {string} _unused - The title (unused). + * @param {string} _title - The title. * @param {(string | URL)=} url - The URL to navigate to. * @returns {void} */ ) => - function (_state, _unused, url) { + function (_state, _title, url) { if (url && location.pathname !== new URL(url, location.href).pathname) { sendUnloadBeacon(); // If the event is a history change, then we need to reset the id and timers @@ -168,21 +167,6 @@ return acc; }, {}); - /** - * Helper function to extract data attributes and merge them. - * @param {string} eventType - * @returns {Object} - */ - const extractAndMergeDataAttributes = (eventType) => { - return [...document.querySelectorAll(`[data-m\\:${eventType}]`)].reduce( - (acc, elem) => ({ - ...acc, - ...extractDataAttributes(elem, eventType), - }), - {}, - ); - }; - /** * Ping the server with the cache endpoint and read the last modified header to determine * if the user is unique or not. @@ -242,7 +226,14 @@ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#return_value */ "t": Intl.DateTimeFormat().resolvedOptions().timeZone, - "d": extractAndMergeDataAttributes('load'), + // Helper function to extract data attributes and merge them. + "d": [...document.querySelectorAll('[data-m\\:load]')].reduce( + (acc, elem) => ({ + ...acc, + ...extractDataAttributes(elem, 'load'), + }), + {}, + ), }), ), // Will make the response opaque, but we don't need it. @@ -277,7 +268,6 @@ "b": uid, "e": "unload", "m": Date.now() - hiddenTotalTime, - "d": extractAndMergeDataAttributes('unload'), }), ), ); diff --git a/tracker/dist/page-events.min.js b/tracker/dist/page-events.min.js index 552c814..5610255 100644 --- a/tracker/dist/page-events.min.js +++ b/tracker/dist/page-events.min.js @@ -1 +1 @@ -!function(){if(!document)return;const t=document.currentScript,e=t.getAttribute("data-api")?`${location.protocol}//${t.getAttribute("data-api")}`:t.src.replace(/[^\/]+$/,"api/"),n=()=>Date.now().toString(36)+Math.random().toString(36).substr(2);let a=n(),o=!0,r=0,i=Date.now(),d=!1;const s=history.pushState,c=history.replaceState,p=()=>{o=!1,a=n(),r=0,i=Date.now(),d=!1},u=t=>function(e,n,a){a&&location.pathname!==new URL(a,location.href).pathname?(g(),p(),t.apply(this,arguments),m()):t.apply(this,arguments)},l=t=>[...document.querySelectorAll(`[data-m\\:${t}]`)].reduce(((e,n)=>{return{...e,...(a=n,o=t,(a.getAttribute("data-m:"+o)||"").split(";").reduce(((t,e)=>{const[n,a]=e.split("=").map((t=>t.trim()));return n&&a&&(t[n]=a),t}),{}))};var a,o}),{}),h=t=>new Promise((e=>{const n=new XMLHttpRequest;n.onload=()=>{e(0==n.responseText)},n.open("GET",t),n.setRequestHeader("Content-Type","text/plain"),n.send()})),m=async()=>{h(e+"event/ping?u="+encodeURIComponent(location.host+location.pathname)).then((t=>{fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:a,e:"load",u:location.href,r:document.referrer,p:o,q:t,t:Intl.DateTimeFormat().resolvedOptions().timeZone,d:l("load")}),mode:"no-cors"})}))},g=()=>{d||navigator.sendBeacon(e+"event/hit",JSON.stringify({b:a,e:"unload",m:Date.now()-i,d:l("unload")})),d=!0};"onpagehide"in self?addEventListener("pagehide",g,{capture:!0}):(addEventListener("beforeunload",g,{capture:!0}),addEventListener("unload",g,{capture:!0})),addEventListener("visibilitychange",(()=>{"hidden"==document.visibilityState?r=Date.now():(i+=Date.now()-r,r=0)}),{capture:!0}),h(e+"event/ping?root").then((e=>{o=e,m(),t.getAttribute("data-hash")?addEventListener("hashchange",m,{capture:!0}):(history.pushState=u(s),history.replaceState=u(c),addEventListener("popstate",(()=>{g(),p(),m()}),{capture:!0}))}))}(); \ No newline at end of file +!function(){if(!document)return;const t=document.currentScript,e=t.getAttribute("data-api")?`${location.protocol}//${t.getAttribute("data-api")}`:t.src.replace(/[^\/]+$/,"api/"),n=()=>Date.now().toString(36)+Math.random().toString(36).substr(2);let a=n(),o=!0,r=0,i=Date.now(),d=!1;const s=history.pushState,c=history.replaceState,p=()=>{o=!1,a=n(),r=0,i=Date.now(),d=!1},u=t=>function(e,n,a){a&&location.pathname!==new URL(a,location.href).pathname?(m(),p(),t.apply(this,arguments),h()):t.apply(this,arguments)},l=t=>new Promise((e=>{const n=new XMLHttpRequest;n.onload=()=>{e(0==n.responseText)},n.open("GET",t),n.setRequestHeader("Content-Type","text/plain"),n.send()})),h=async()=>{l(e+"event/ping?u="+encodeURIComponent(location.host+location.pathname)).then((t=>{fetch(e+"event/hit",{method:"POST",body:JSON.stringify({b:a,e:"load",u:location.href,r:document.referrer,p:o,q:t,t:Intl.DateTimeFormat().resolvedOptions().timeZone,d:[...document.querySelectorAll("[data-m\\:load]")].reduce(((t,e)=>{return{...t,...(n=e,(n.getAttribute("data-m:load")||"").split(";").reduce(((t,e)=>{const[n,a]=e.split("=").map((t=>t.trim()));return n&&a&&(t[n]=a),t}),{}))};var n}),{})}),mode:"no-cors"})}))},m=()=>{d||navigator.sendBeacon(e+"event/hit",JSON.stringify({b:a,e:"unload",m:Date.now()-i})),d=!0};"onpagehide"in self?addEventListener("pagehide",m,{capture:!0}):(addEventListener("beforeunload",m,{capture:!0}),addEventListener("unload",m,{capture:!0})),addEventListener("visibilitychange",(()=>{"hidden"==document.visibilityState?r=Date.now():(i+=Date.now()-r,r=0)}),{capture:!0}),l(e+"event/ping?root").then((e=>{o=e,h(),t.getAttribute("data-hash")?addEventListener("hashchange",h,{capture:!0}):(history.pushState=u(s),history.replaceState=u(c),addEventListener("popstate",(()=>{m(),p(),h()}),{capture:!0}))}))}(); \ No newline at end of file diff --git a/tracker/src/tracker.js b/tracker/src/tracker.js index a7fed4b..209aeae 100644 --- a/tracker/src/tracker.js +++ b/tracker/src/tracker.js @@ -17,7 +17,6 @@ * @property {string} b Beacon ID. * @property {'unload'} e Event type. * @property {number} m Time spent on page. - * @property {Object=} d Event custom properties. */ /** @@ -133,12 +132,12 @@ /** * @this {History} * @param {*} _state - The state object. - * @param {string} _unused - The title (unused). + * @param {string} _title - The title. * @param {(string | URL)=} url - The URL to navigate to. * @returns {void} */ ) => - function (_state, _unused, url) { + function (_state, _title, url) { if (url && location.pathname !== new URL(url, location.href).pathname) { sendUnloadBeacon(); // If the event is a history change, then we need to reset the id and timers @@ -170,23 +169,6 @@ }, {}); // @endif - // @ifdef PAGE_EVENTS - /** - * Helper function to extract data attributes and merge them. - * @param {string} eventType - * @returns {Object} - */ - const extractAndMergeDataAttributes = (eventType) => { - return [...document.querySelectorAll(`[data-m\\:${eventType}]`)].reduce( - (acc, elem) => ({ - ...acc, - ...extractDataAttributes(elem, eventType), - }), - {}, - ); - }; - // @endif - /** * Ping the server with the cache endpoint and read the last modified header to determine * if the user is unique or not. @@ -247,7 +229,14 @@ */ "t": Intl.DateTimeFormat().resolvedOptions().timeZone, // @ifdef PAGE_EVENTS - "d": extractAndMergeDataAttributes('load'), + // Helper function to extract data attributes and merge them. + "d": [...document.querySelectorAll('[data-m\\:load]')].reduce( + (acc, elem) => ({ + ...acc, + ...extractDataAttributes(elem, 'load'), + }), + {}, + ), // @endif }), ), @@ -283,9 +272,6 @@ "b": uid, "e": "unload", "m": Date.now() - hiddenTotalTime, - // @ifdef PAGE_EVENTS - "d": extractAndMergeDataAttributes('unload'), - // @endif }), ), ); diff --git a/tracker/tests/fixtures/history/index.html b/tracker/tests/fixtures/history/index.html index 860ecff..574fabe 100644 --- a/tracker/tests/fixtures/history/index.html +++ b/tracker/tests/fixtures/history/index.html @@ -7,7 +7,7 @@ -

History API Router

+

History API Router