feat: refactor event creation and move clients

This commit is contained in:
Kevin Franklin Kim 2024-05-25 21:55:30 +02:00
parent 5cb6719df0
commit a4fa86a8f3
No known key found for this signature in database
58 changed files with 468 additions and 229 deletions

View File

@ -8,9 +8,9 @@ import (
"github.com/pkg/errors"
)
func MessageHandler(handler func(payload *mpv2.Payload[any], msg *message.Message) error) func(msg *message.Message) ([]*message.Message, error) {
func MessageHandler(handler func(payload *mpv2.Payload[map[string]any], msg *message.Message) error) func(msg *message.Message) ([]*message.Message, error) {
return func(msg *message.Message) ([]*message.Message, error) {
var payload *mpv2.Payload[any]
var payload *mpv2.Payload[map[string]any]
// unmarshal payload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {

View File

@ -1,16 +1,17 @@
package gtag
package client
import (
"fmt"
"io"
"net/http"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
Client struct {
GTag struct {
l *zap.Logger
path string
host string
@ -18,37 +19,37 @@ type (
trackingID string
protocolVersion string
httpClient *http.Client
middlewares []ClientMiddleware
middlewares []GTagMiddleware
}
ClientOption func(*Client)
ClientHandler func(r *http.Request, event *Payload) error
ClientMiddleware func(next ClientHandler) ClientHandler
GTagOption func(*GTag)
GTagHandler func(r *http.Request, payload *gtag.Payload) error
GTagMiddleware func(next GTagHandler) GTagHandler
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func ClientWithHTTPClient(v *http.Client) ClientOption {
return func(o *Client) {
func GTagWithHTTPClient(v *http.Client) GTagOption {
return func(o *GTag) {
o.httpClient = v
}
}
func ClientWithPath(v string) ClientOption {
return func(o *Client) {
func GTagWithPath(v string) GTagOption {
return func(o *GTag) {
o.path = v
}
}
func ClientWithCookies(v ...string) ClientOption {
return func(o *Client) {
func GTagWithCookies(v ...string) GTagOption {
return func(o *GTag) {
o.cookies = append(o.cookies, v...)
}
}
func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
return func(o *Client) {
func GTagWithMiddlewares(v ...GTagMiddleware) GTagOption {
return func(o *GTag) {
o.middlewares = append(o.middlewares, v...)
}
}
@ -57,8 +58,8 @@ func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewClient(l *zap.Logger, host, trackingID string, opts ...ClientOption) *Client {
inst := &Client{
func NewGTag(l *zap.Logger, host, trackingID string, opts ...GTagOption) *GTag {
inst := &GTag{
l: l,
host: host,
path: "/g/collect",
@ -71,14 +72,12 @@ func NewClient(l *zap.Logger, host, trackingID string, opts ...ClientOption) *Cl
opt(inst)
}
inst.middlewares = append(inst.middlewares,
MiddlewareRichsstsse,
MiddlewareTrackingID(inst.trackingID),
// MiddlewarIgnoreReferrer("1"),
MiddlewarProtocolVersion("2"),
// MiddlewarDebug,
MiddlewarClientID,
MiddlewarSessionID(inst.trackingID),
MiddlewarDocument,
GTagMiddlewareRichsstsse,
GTagMiddlewareTrackingID(inst.trackingID),
GTagMiddlewarProtocolVersion("2"),
GTagMiddlewarIsDebug,
GTagMiddlewarClientID,
GTagMiddlewarSessionID(inst.trackingID),
)
return inst
}
@ -87,7 +86,7 @@ func NewClient(l *zap.Logger, host, trackingID string, opts ...ClientOption) *Cl
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (c *Client) HTTPClient() *http.Client {
func (c *GTag) HTTPClient() *http.Client {
return c.httpClient
}
@ -95,32 +94,24 @@ func (c *Client) HTTPClient() *http.Client {
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Client) Send(r *http.Request, event Marshler) error {
e, err := event.MarshalMPv2()
if err != nil {
return err
}
return c.SendEvent(r, e)
}
func (c *Client) SendEvent(r *http.Request, event *Payload) error {
next := c.SendRawEvent
func (c *GTag) Send(r *http.Request, payload *gtag.Payload) error {
next := c.SendRaw
for _, middleware := range c.middlewares {
next = middleware(next)
}
return next(r, event)
return next(r, payload)
}
func (c *Client) SendRawEvent(r *http.Request, event *Payload) error {
values, body, err := Encode(event)
func (c *GTag) SendRaw(r *http.Request, payload *gtag.Payload) error {
values, body, err := gtag.Encode(payload)
if err != nil {
return errors.Wrap(err, "failed to encode event")
return errors.Wrap(err, "failed to encode payload")
}
req, err := http.NewRequestWithContext(
r.Context(),
http.MethodPost,
fmt.Sprintf("%s%s?%s", c.host, c.path, EncodeValues(values)),
fmt.Sprintf("%s%s?%s", c.host, c.path, gtag.EncodeValues(values)),
body,
)
if err != nil {

35
pkg/client/gtag_test.go Normal file
View File

@ -0,0 +1,35 @@
package client_test
import (
"context"
"net/http"
"net/http/httptest"
"net/http/httputil"
"testing"
"github.com/foomo/sesamy-go/pkg/client"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/sesamy"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestNewGtag(t *testing.T) {
l := zaptest.NewLogger(t)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Helper()
out, err := httputil.DumpRequest(r, true)
require.NoError(t, err)
t.Log(string(out))
}))
c := client.NewGTag(l, s.URL, "GA-XXXXXX")
incomingReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "/foo/bar", nil)
require.NoError(t, err)
err = c.Send(incomingReq, &gtag.Payload{
EventName: gtag.Set(sesamy.EventName("page_view")),
})
require.NoError(t, err)
}

View File

@ -0,0 +1,75 @@
package client
import (
"net/http"
"strings"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/session"
"github.com/pkg/errors"
)
func GTagMiddlewareRichsstsse(next GTagHandler) GTagHandler {
v := ""
return func(r *http.Request, payload *gtag.Payload) error {
payload.Richsstsse = &v
return next(r, payload)
}
}
func GTagMiddlewareTrackingID(v string) GTagMiddleware {
return func(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
payload.TrackingID = &v
return next(r, payload)
}
}
}
func GTagMiddlewarProtocolVersion(v string) GTagMiddleware {
return func(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
payload.ProtocolVersion = &v
return next(r, payload)
}
}
}
func GTagMiddlewarIsDebug(next GTagHandler) GTagHandler {
v := "1"
return func(r *http.Request, payload *gtag.Payload) error {
if session.IsGTMDebug(r) {
payload.IsDebug = &v
}
return next(r, payload)
}
}
func GTagMiddlewarClientID(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
value, err := session.ParseGAClientID(r)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errors.Wrap(err, "failed to parse client cookie")
}
if value != "" {
payload.ClientID = &value
}
return next(r, payload)
}
}
func GTagMiddlewarSessionID(trackingID string) GTagMiddleware {
trackingID = strings.Split(trackingID, "-")[1]
return func(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
value, err := session.ParseGASessionID(r, trackingID)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errors.Wrap(err, "failed to parse session cookie")
}
if value != "" {
payload.SessionID = &value
}
return next(r, payload)
}
}
}

161
pkg/client/mpv2.go Normal file
View File

@ -0,0 +1,161 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"time"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/sesamy"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
MPv2 struct {
l *zap.Logger
path string
host string
cookies []string
protocolVersion string
httpClient *http.Client
middlewares []MPv2Middleware
}
MPv2Option func(*MPv2)
MPv2Handler func(r *http.Request, payload *mpv2.Payload[any]) error
MPv2Middleware func(next MPv2Handler) MPv2Handler
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func MPv2WithHTTPClient(v *http.Client) MPv2Option {
return func(o *MPv2) {
o.httpClient = v
}
}
func MPv2WithPath(v string) MPv2Option {
return func(o *MPv2) {
o.path = v
}
}
func MPv2WithCookies(v ...string) MPv2Option {
return func(o *MPv2) {
o.cookies = append(o.cookies, v...)
}
}
func MPv2WithMiddlewares(v ...MPv2Middleware) MPv2Option {
return func(o *MPv2) {
o.middlewares = append(o.middlewares, v...)
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewMPv2(l *zap.Logger, host string, opts ...MPv2Option) *MPv2 {
inst := &MPv2{
l: l,
host: host,
path: "/mp/collect",
cookies: []string{"gtm_auth", "gtm_debug", "gtm_preview"},
protocolVersion: "2",
httpClient: http.DefaultClient,
}
for _, opt := range opts {
opt(inst)
}
inst.middlewares = append(inst.middlewares,
MPv2MiddlewarClientID,
)
return inst
}
// ------------------------------------------------------------------------------------------------
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (c *MPv2) HTTPClient() *http.Client {
return c.httpClient
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *MPv2) Collect(r *http.Request, events ...sesamy.AnyEvent) error {
anyEvents := make([]sesamy.Event[any], len(events))
for i, event := range events {
fmt.Println("-----------")
t := reflect.TypeOf(event)
x := reflect.New(t)
fmt.Println(x)
fmt.Println("-----------")
anyEvents[i] = event.AnyEvent()
}
payload := &mpv2.Payload[any]{
Events: anyEvents,
TimestampMicros: time.Now().UnixMicro(),
}
next := c.SendRaw
for _, middleware := range c.middlewares {
next = middleware(next)
}
return next(r, payload)
}
func (c *MPv2) SendRaw(r *http.Request, payload *mpv2.Payload[any]) error {
jsonPayload, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "failed to encode payload")
}
req, err := http.NewRequestWithContext(
r.Context(),
http.MethodPost,
fmt.Sprintf("%s%s", c.host, c.path),
bytes.NewReader(jsonPayload),
)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// TODO valiate: copy headers
req.Header = r.Header.Clone()
// forward cookies
for _, cookie := range c.cookies {
if value, _ := r.Cookie(cookie); value != nil {
req.AddCookie(value)
}
}
resp, err := c.httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "failed to send request")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var body string
if out, err := io.ReadAll(resp.Body); err != nil {
c.l.With(zap.Error(err)).Warn(err.Error())
} else {
body = string(out)
}
return errors.Errorf("unexpected response status: %d (%s)", resp.StatusCode, body)
}
return nil
}

36
pkg/client/mpv2_test.go Normal file
View File

@ -0,0 +1,36 @@
package client_test
import (
"context"
"net/http"
"net/http/httptest"
"net/http/httputil"
"testing"
"github.com/foomo/sesamy-go/pkg/client"
"github.com/foomo/sesamy-go/pkg/event"
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestNewMPv2(t *testing.T) {
l := zaptest.NewLogger(t)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Helper()
out, err := httputil.DumpRequest(r, true)
require.NoError(t, err)
t.Log(string(out))
}))
c := client.NewMPv2(l, s.URL)
incomingReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "/foo/bar", nil)
require.NoError(t, err)
err = c.Collect(incomingReq, event.NewPageView(params.PageView{
PageTitle: "foo",
PageLocation: "bar",
}))
require.NoError(t, err)
}

View File

@ -0,0 +1,22 @@
package client
import (
"net/http"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/session"
"github.com/pkg/errors"
)
func MPv2MiddlewarClientID(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
value, err := session.ParseGAClientID(r)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errors.Wrap(err, "failed to parse client cookie")
}
if value != "" {
payload.ClientID = value
}
return next(r, payload)
}
}

View File

@ -29,12 +29,12 @@ func TestDecode(t *testing.T) {
{
name: "add_to_cart",
args: "v=2&tid=G-123456&gtm=45je42s0v9175354889z89175348963za200&_p=1709297934217&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709297934217&_s=8&cu=USD&sid=1709296380&sct=7&seg=1&dl=https%3A%2F%2Fsniffer.cloud.bestbytes.net%2F%3Fgtm_debug%3D1709297933868&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&tfd=15129&richsstsse",
want: `{"consent":{"google_consent_default":"13l3l3l3l1"},"campaign":{},"ecommerce":{"currency":"USD","items":[{"affiliation":"Google Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"5","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}]},"client_hints":{"screen_resolution":"3840x1600","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"14.3.1","user_agent_wow_64":"0","user_region":"DE-BY"},"protocol_version":"2","tracking_id":"G-123456","gtmhash_info":"45je42s0v9175354889z89175348963za200","client_id":"1220643501.1708014725","richsstsse":"","document_location":"https://sniffer.cloud.bestbytes.net/?gtm_debug=1709297933868","document_title":"Server Side Tracking Prototype (codename: sniffer)","document_referrer":"https://tagassistant.google.com/","event_name":"add_to_cart","event_parameter_number":{"value":"30.03"},"session_id":"1709296380","non_personalized_ads":"0","sst":{"etld":"google.de","gcsub":"region1","uc":"DE","tft":"1709297934217","gcd":"13l3l3l3l1"}}`,
want: `{"consent":{"google_consent_default":"13l3l3l3l1"},"campaign":{},"ecommerce":{"currency":"USD","items":[{"affiliation":"Google Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"5","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}]},"client_hints":{"screen_resolution":"3840x1600","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"14.3.1","user_agent_wow_64":"0","user_region":"DE-BY"},"protocol_version":"2","tracking_id":"G-123456","gtmhash_info":"45je42s0v9175354889z89175348963za200","is_debug":"1","client_id":"1220643501.1708014725","richsstsse":"","document_location":"https://sniffer.cloud.bestbytes.net/?gtm_debug=1709297933868","document_title":"Server Side Tracking Prototype (codename: sniffer)","document_referrer":"https://tagassistant.google.com/","event_name":"add_to_cart","event_parameter_number":{"value":"30.03"},"session_id":"1709296380","non_personalized_ads":"0","sst":{"etld":"google.de","gcsub":"region1","uc":"DE","tft":"1709297934217","gcd":"13l3l3l3l1"}}`,
},
{
name: "select_item",
args: "v=2&tid=G-F9XM71K45T&gtm=45he4580v9184715813z89184708445za200&_p=1715430403224&_dbg=1&gcd=13l3l3l2l1&npa=1&dma_cps=sypham&dma=1&cid=179294588.1715353601&ecid=251283723&ul=en-us&sr=3840x1600&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B124.0.6367.119%7CGoogle%2520Chrome%3B124.0.6367.119%7CNot-A.Brand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=14.4.1&uaw=0&are=1&frm=0&pscdl=noapi&sst.gcd=13l3l3l2l1&sst.tft=1715430403224&sst.ude=0&_s=3&sid=1715428762&sct=2&seg=1&dl=https%3A%2F%2Fsesamy.local.bestbytes.net%2F%3Fgtm_debug%3D1715430402906&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Home&en=select_item&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&ep.enable_page_views=false&ep.item_list_id=related_products&ep.item_list_name=Related%20products&_et=89&tfd=8618&richsstsse",
want: `{"consent":{"google_consent_default":"13l3l3l2l1"},"campaign":{},"ecommerce":{"items":[{"affiliation":"Google Merchandise Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related Products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"0","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}]},"client_hints":{"screen_resolution":"3840x1600","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;124.0.6367.119|Google%20Chrome;124.0.6367.119|Not-A.Brand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"14.4.1","user_agent_wow_64":"0","user_region":""},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he4580v9184715813z89184708445za200","client_id":"179294588.1715353601","richsstsse":"","document_location":"https://sesamy.local.bestbytes.net/?gtm_debug=1715430402906","document_title":"Home","document_referrer":"https://tagassistant.google.com/","event_name":"select_item","event_parameter":{"enable_page_views":"false","item_list_id":"related_products","item_list_name":"Related products"},"session_id":"1715428762","non_personalized_ads":"1","sst":{"tft":"1715430403224", "ude":"0", "gcd":"13l3l3l2l1"}}`,
want: `{"consent":{"google_consent_default":"13l3l3l2l1"},"campaign":{},"ecommerce":{"items":[{"affiliation":"Google Merchandise Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related Products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"0","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}]},"client_hints":{"screen_resolution":"3840x1600","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;124.0.6367.119|Google%20Chrome;124.0.6367.119|Not-A.Brand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"14.4.1","user_agent_wow_64":"0","user_region":""},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he4580v9184715813z89184708445za200","is_debug":"1","client_id":"179294588.1715353601","richsstsse":"","document_location":"https://sesamy.local.bestbytes.net/?gtm_debug=1715430402906","document_title":"Home","document_referrer":"https://tagassistant.google.com/","event_name":"select_item","event_parameter":{"enable_page_views":"false","item_list_id":"related_products","item_list_name":"Related products"},"session_id":"1715428762","non_personalized_ads":"1","sst":{"tft":"1715430403224", "ude":"0", "gcd":"13l3l3l2l1"}}`,
},
}
for _, tt := range tests {

View File

@ -1,5 +0,0 @@
package gtag
type Marshler interface {
MarshalMPv2() (*Payload, error)
}

View File

@ -1,89 +0,0 @@
package gtag
import (
"net/http"
"net/url"
"strings"
)
func MiddlewareRichsstsse(next ClientHandler) ClientHandler {
v := ""
return func(r *http.Request, event *Payload) error {
event.Richsstsse = &v
return next(r, event)
}
}
func MiddlewareTrackingID(v string) ClientMiddleware {
return func(next ClientHandler) ClientHandler {
return func(r *http.Request, event *Payload) error {
event.TrackingID = &v
return next(r, event)
}
}
}
func MiddlewarProtocolVersion(v string) ClientMiddleware {
return func(next ClientHandler) ClientHandler {
return func(r *http.Request, event *Payload) error {
event.ProtocolVersion = &v
return next(r, event)
}
}
}
// func MiddlewarIgnoreReferrer(v string) ClientMiddleware {
// return func(next ClientHandler) ClientHandler {
// return func(r *http.Request, event *Event) error {
// event.IgnoreReferrer = &v
// return next(r, event)
// }
// }
// }
// func MiddlewarDebug(next ClientHandler) ClientHandler {
// v := "1"
// return func(r *http.Request, event *Event) error {
// if value, _ := r.Cookie("gtm_debug"); value != nil {
// event.IsDebug = &v
// }
// return next(r, event)
// }
// }
func MiddlewarClientID(next ClientHandler) ClientHandler {
return func(r *http.Request, event *Payload) error {
if value, _ := r.Cookie("_ga"); value != nil {
clientID := strings.TrimPrefix(value.Value, "GA1.1.")
event.ClientID = &clientID
}
return next(r, event)
}
}
func MiddlewarSessionID(trackingID string) ClientMiddleware {
trackingID = strings.Split(trackingID, "-")[1]
return func(next ClientHandler) ClientHandler {
return func(r *http.Request, event *Payload) error {
if value, _ := r.Cookie("_ga_" + trackingID); value != nil {
if value := strings.Split(value.Value, "."); len(value) > 3 {
event.SessionID = &value[2]
}
}
return next(r, event)
}
}
}
func MiddlewarDocument(next ClientHandler) ClientHandler {
return func(r *http.Request, event *Payload) error {
if referrer, err := url.Parse(r.Referer()); err != nil {
return err
} else {
location := referrer.String()
event.DocumentLocation = &location
// event.DocumentHostname = &referrer.Host
}
return next(r, event)
}
}

View File

@ -56,7 +56,7 @@ type Payload struct {
// EventDebugID *string `json:"event_debug_id,omitempty" gtag:"edid,omitempty"`
// If an event contains this parameters it won't be processed and it will show on on the debug View in GA4
// Example: 1
// IsDebug *string `json:"is_debug,omitempty" gtag:"_dbg,omitempty"`
IsDebug *string `json:"is_debug,omitempty" gtag:"_dbg,omitempty"`
// If the current request has a referrer, it will be ignored at processing level
// Example: 1
// IgnoreReferrer *string `json:"ignore_referrer,omitempty" gtag:"ir,omitempty"`

View File

@ -7,6 +7,6 @@ import (
type AddPaymentInfo sesamy.Event[params.AddPaymentInfo[params.Item]]
func NewAddPaymentInfo(p params.AddPaymentInfo[params.Item]) AddPaymentInfo {
return AddPaymentInfo(sesamy.NewEvent(sesamy.EventNameAddPaymentInfo, p))
func NewAddPaymentInfo(p params.AddPaymentInfo[params.Item]) sesamy.Event[params.AddPaymentInfo[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddPaymentInfo, p)
}

View File

@ -7,6 +7,6 @@ import (
type AddShippingInfo sesamy.Event[params.AddShippingInfo[params.Item]]
func NewAddShippingInfo(p params.AddShippingInfo[params.Item]) AddShippingInfo {
return AddShippingInfo(sesamy.NewEvent(sesamy.EventNameAddShippingInfo, p))
func NewAddShippingInfo(p params.AddShippingInfo[params.Item]) sesamy.Event[params.AddShippingInfo[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddShippingInfo, p)
}

View File

@ -7,6 +7,6 @@ import (
type AddToCart sesamy.Event[params.AddToCart[params.Item]]
func NewAddToCart(p params.AddToCart[params.Item]) AddToCart {
return AddToCart(sesamy.NewEvent(sesamy.EventNameAddToCart, p))
func NewAddToCart(p params.AddToCart[params.Item]) sesamy.Event[params.AddToCart[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddToCart, p)
}

View File

@ -7,6 +7,6 @@ import (
type AddToWishlist sesamy.Event[params.AddToWishlist[params.Item]]
func NewAddToWishlist(p params.AddToWishlist[params.Item]) AddToWishlist {
return AddToWishlist(sesamy.NewEvent(sesamy.EventNameAddToWishlist, p))
func NewAddToWishlist(p params.AddToWishlist[params.Item]) sesamy.Event[params.AddToWishlist[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddToWishlist, p)
}

View File

@ -7,6 +7,6 @@ import (
type AdImpression sesamy.Event[params.AdImpression]
func NewAdImpression(p params.AdImpression) AdImpression {
return AdImpression(sesamy.NewEvent(sesamy.EventNameAdImpression, p))
func NewAdImpression(p params.AdImpression) sesamy.Event[params.AdImpression] {
return sesamy.NewEvent(sesamy.EventNameAdImpression, p)
}

View File

@ -7,6 +7,6 @@ import (
type BeginCheckout sesamy.Event[params.BeginCheckout[params.Item]]
func NewBeginCheckout(p params.BeginCheckout[params.Item]) BeginCheckout {
return BeginCheckout(sesamy.NewEvent(sesamy.EventNameBeginCheckout, p))
func NewBeginCheckout(p params.BeginCheckout[params.Item]) sesamy.Event[params.BeginCheckout[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameBeginCheckout, p)
}

View File

@ -7,6 +7,6 @@ import (
type CampaignDetails sesamy.Event[params.CampaignDetails]
func NewCampaignDetails(p params.CampaignDetails) CampaignDetails {
return CampaignDetails(sesamy.NewEvent(sesamy.EventNameCampaignDetails, p))
func NewCampaignDetails(p params.CampaignDetails) sesamy.Event[params.CampaignDetails] {
return sesamy.NewEvent(sesamy.EventNameCampaignDetails, p)
}

View File

@ -7,6 +7,6 @@ import (
type Click sesamy.Event[params.Click]
func NewClick(p params.Click) Click {
return Click(sesamy.NewEvent(sesamy.EventNameClick, p))
func NewClick(p params.Click) sesamy.Event[params.Click] {
return sesamy.NewEvent(sesamy.EventNameClick, p)
}

View File

@ -7,6 +7,6 @@ import (
type EarnVirtualMoney sesamy.Event[params.EarnVirtualMoney]
func NewEarnVirtualMoney(p params.EarnVirtualMoney) EarnVirtualMoney {
return EarnVirtualMoney(sesamy.NewEvent(sesamy.EventNameEarnVirtualMoney, p))
func NewEarnVirtualMoney(p params.EarnVirtualMoney) sesamy.Event[params.EarnVirtualMoney] {
return sesamy.NewEvent(sesamy.EventNameEarnVirtualMoney, p)
}

View File

@ -7,6 +7,6 @@ import (
type FileDownload sesamy.Event[params.FileDownload]
func NewFileDownload(p params.FileDownload) FileDownload {
return FileDownload(sesamy.NewEvent(sesamy.EventNameFileDownload, p))
func NewFileDownload(p params.FileDownload) sesamy.Event[params.FileDownload] {
return sesamy.NewEvent(sesamy.EventNameFileDownload, p)
}

View File

@ -7,6 +7,6 @@ import (
type FormStart sesamy.Event[params.FormStart]
func NewFormStart(p params.FormStart) FormStart {
return FormStart(sesamy.NewEvent(sesamy.EventNameFormStart, p))
func NewFormStart(p params.FormStart) sesamy.Event[params.FormStart] {
return sesamy.NewEvent(sesamy.EventNameFormStart, p)
}

View File

@ -7,6 +7,6 @@ import (
type FormSubmit sesamy.Event[params.FormSubmit]
func NewFormSubmit(p params.FormSubmit) FormSubmit {
return FormSubmit(sesamy.NewEvent(sesamy.EventNameFormSubmit, p))
func NewFormSubmit(p params.FormSubmit) sesamy.Event[params.FormSubmit] {
return sesamy.NewEvent(sesamy.EventNameFormSubmit, p)
}

View File

@ -7,6 +7,6 @@ import (
type GenerateLead sesamy.Event[params.GenerateLead]
func NewGenerateLead(p params.GenerateLead) GenerateLead {
return GenerateLead(sesamy.NewEvent(sesamy.EventNameGenerateLead, p))
func NewGenerateLead(p params.GenerateLead) sesamy.Event[params.GenerateLead] {
return sesamy.NewEvent(sesamy.EventNameGenerateLead, p)
}

View File

@ -7,6 +7,6 @@ import (
type JoinGroup sesamy.Event[params.JoinGroup]
func NewJoinGroup(p params.JoinGroup) JoinGroup {
return JoinGroup(sesamy.NewEvent(sesamy.EventNameJoinGroup, p))
func NewJoinGroup(p params.JoinGroup) sesamy.Event[params.JoinGroup] {
return sesamy.NewEvent(sesamy.EventNameJoinGroup, p)
}

View File

@ -7,6 +7,6 @@ import (
type LevelEnd sesamy.Event[params.LevelEnd]
func NewLevelEnd(p params.LevelEnd) LevelEnd {
return LevelEnd(sesamy.NewEvent(sesamy.EventNameLevelEnd, p))
func NewLevelEnd(p params.LevelEnd) sesamy.Event[params.LevelEnd] {
return sesamy.NewEvent(sesamy.EventNameLevelEnd, p)
}

View File

@ -7,6 +7,6 @@ import (
type LevelStart sesamy.Event[params.LevelStart]
func NewLevelStart(p params.LevelStart) LevelStart {
return LevelStart(sesamy.NewEvent(sesamy.EventNameLevelStart, p))
func NewLevelStart(p params.LevelStart) sesamy.Event[params.LevelStart] {
return sesamy.NewEvent(sesamy.EventNameLevelStart, p)
}

View File

@ -7,6 +7,6 @@ import (
type LevelUp sesamy.Event[params.LevelUp]
func NewLevelUp(p params.LevelUp) LevelUp {
return LevelUp(sesamy.NewEvent(sesamy.EventNameLevelUp, p))
func NewLevelUp(p params.LevelUp) sesamy.Event[params.LevelUp] {
return sesamy.NewEvent(sesamy.EventNameLevelUp, p)
}

View File

@ -7,6 +7,6 @@ import (
type Login sesamy.Event[params.Login]
func NewLogin(p params.Login) Login {
return Login(sesamy.NewEvent(sesamy.EventNameLogin, p))
func NewLogin(p params.Login) sesamy.Event[params.Login] {
return sesamy.NewEvent(sesamy.EventNameLogin, p)
}

View File

@ -7,6 +7,6 @@ import (
type PageView sesamy.Event[params.PageView]
func NewPageView(p params.PageView) PageView {
return PageView(sesamy.NewEvent(sesamy.EventNamePageView, p))
func NewPageView(p params.PageView) sesamy.Event[params.PageView] {
return sesamy.NewEvent(sesamy.EventNamePageView, p)
}

View File

@ -7,6 +7,6 @@ import (
type PostScore sesamy.Event[params.PostScore]
func NewPostScore(p params.PostScore) PostScore {
return PostScore(sesamy.NewEvent(sesamy.EventNamePostScore, p))
func NewPostScore(p params.PostScore) sesamy.Event[params.PostScore] {
return sesamy.NewEvent(sesamy.EventNamePostScore, p)
}

View File

@ -7,6 +7,6 @@ import (
type Purchase sesamy.Event[params.Purchase[params.Item]]
func NewPurchase(p params.Purchase[params.Item]) Purchase {
return Purchase(sesamy.NewEvent(sesamy.EventNamePurchase, p))
func NewPurchase(p params.Purchase[params.Item]) sesamy.Event[params.Purchase[params.Item]] {
return sesamy.NewEvent(sesamy.EventNamePurchase, p)
}

View File

@ -7,6 +7,6 @@ import (
type Refund sesamy.Event[params.Refund[params.Item]]
func NewRefund(p params.Refund[params.Item]) Refund {
return Refund(sesamy.NewEvent(sesamy.EventNameRefund, p))
func NewRefund(p params.Refund[params.Item]) sesamy.Event[params.Refund[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameRefund, p)
}

View File

@ -7,6 +7,6 @@ import (
type RemoveFromCart sesamy.Event[params.RemoveFromCart[params.Item]]
func NewRemoveFromCart(p params.RemoveFromCart[params.Item]) RemoveFromCart {
return RemoveFromCart(sesamy.NewEvent(sesamy.EventNameRemoveFromCart, p))
func NewRemoveFromCart(p params.RemoveFromCart[params.Item]) sesamy.Event[params.RemoveFromCart[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameRemoveFromCart, p)
}

View File

@ -7,6 +7,6 @@ import (
type ScreenView sesamy.Event[params.ScreenView]
func NewScreenView(p params.ScreenView) ScreenView {
return ScreenView(sesamy.NewEvent(sesamy.EventNameScreenView, p))
func NewScreenView(p params.ScreenView) sesamy.Event[params.ScreenView] {
return sesamy.NewEvent(sesamy.EventNameScreenView, p)
}

View File

@ -7,6 +7,6 @@ import (
type Scroll sesamy.Event[params.Scroll]
func NewScroll(p params.Scroll) Scroll {
return Scroll(sesamy.NewEvent(sesamy.EventNameScroll, p))
func NewScroll(p params.Scroll) sesamy.Event[params.Scroll] {
return sesamy.NewEvent(sesamy.EventNameScroll, p)
}

View File

@ -7,6 +7,6 @@ import (
type Search sesamy.Event[params.Search]
func NewSearch(p params.Search) Search {
return Search(sesamy.NewEvent(sesamy.EventNameSearch, p))
func NewSearch(p params.Search) sesamy.Event[params.Search] {
return sesamy.NewEvent(sesamy.EventNameSearch, p)
}

View File

@ -7,6 +7,6 @@ import (
type SelectContent sesamy.Event[params.SelectContent]
func NewSelectContent(p params.SelectContent) SelectContent {
return SelectContent(sesamy.NewEvent(sesamy.EventNameSelectContent, p))
func NewSelectContent(p params.SelectContent) sesamy.Event[params.SelectContent] {
return sesamy.NewEvent(sesamy.EventNameSelectContent, p)
}

View File

@ -7,6 +7,6 @@ import (
type SelectItem sesamy.Event[params.SelectItem[params.Item]]
func NewSelectItem(p params.SelectItem[params.Item]) SelectItem {
return SelectItem(sesamy.NewEvent(sesamy.EventNameSelectItem, p))
func NewSelectItem(p params.SelectItem[params.Item]) sesamy.Event[params.SelectItem[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameSelectItem, p)
}

View File

@ -7,6 +7,6 @@ import (
type SelectPromotion sesamy.Event[params.SelectPromotion[params.Item]]
func NewSelectPromotion(p params.SelectPromotion[params.Item]) SelectPromotion {
return SelectPromotion(sesamy.NewEvent(sesamy.EventNameSelectPromotion, p))
func NewSelectPromotion(p params.SelectPromotion[params.Item]) sesamy.Event[params.SelectPromotion[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameSelectPromotion, p)
}

View File

@ -7,6 +7,6 @@ import (
type SessionStart sesamy.Event[params.SessionStart]
func NewSessionStart(p params.SessionStart) SessionStart {
return SessionStart(sesamy.NewEvent(sesamy.EventNameSessionStart, p))
func NewSessionStart(p params.SessionStart) sesamy.Event[params.SessionStart] {
return sesamy.NewEvent(sesamy.EventNameSessionStart, p)
}

View File

@ -7,6 +7,6 @@ import (
type Share sesamy.Event[params.Share]
func NewShare(p params.Share) Share {
return Share(sesamy.NewEvent(sesamy.EventNameShare, p))
func NewShare(p params.Share) sesamy.Event[params.Share] {
return sesamy.NewEvent(sesamy.EventNameShare, p)
}

View File

@ -7,6 +7,6 @@ import (
type SignUp sesamy.Event[params.SignUp]
func NewSignUp(p params.SignUp) SignUp {
return SignUp(sesamy.NewEvent(sesamy.EventNameSignUp, p))
func NewSignUp(p params.SignUp) sesamy.Event[params.SignUp] {
return sesamy.NewEvent(sesamy.EventNameSignUp, p)
}

View File

@ -7,6 +7,6 @@ import (
type SpendVirtualCurrency sesamy.Event[params.SpendVirtualCurrency]
func NewSpendVirtualCurrency(p params.SpendVirtualCurrency) SpendVirtualCurrency {
return SpendVirtualCurrency(sesamy.NewEvent(sesamy.EventNameSpendVirtualCurrency, p))
func NewSpendVirtualCurrency(p params.SpendVirtualCurrency) sesamy.Event[params.SpendVirtualCurrency] {
return sesamy.NewEvent(sesamy.EventNameSpendVirtualCurrency, p)
}

View File

@ -7,6 +7,6 @@ import (
type TutorialBegin sesamy.Event[params.TutorialBegin]
func NewTutorialBegin(p params.TutorialBegin) TutorialBegin {
return TutorialBegin(sesamy.NewEvent(sesamy.EventNameTutorialBegin, p))
func NewTutorialBegin(p params.TutorialBegin) sesamy.Event[params.TutorialBegin] {
return sesamy.NewEvent(sesamy.EventNameTutorialBegin, p)
}

View File

@ -7,6 +7,6 @@ import (
type TutorialComplete sesamy.Event[params.TutorialComplete]
func NewTutorialComplete(p params.TutorialComplete) TutorialComplete {
return TutorialComplete(sesamy.NewEvent(sesamy.EventNameTutorialComplete, p))
func NewTutorialComplete(p params.TutorialComplete) sesamy.Event[params.TutorialComplete] {
return sesamy.NewEvent(sesamy.EventNameTutorialComplete, p)
}

View File

@ -7,6 +7,6 @@ import (
type UnlockAchievement sesamy.Event[params.UnlockAchievement]
func NewUnlockArchievement(p params.UnlockAchievement) UnlockAchievement {
return UnlockAchievement(sesamy.NewEvent(sesamy.EventNameUnlockAchievement, p))
func NewUnlockAchievement(p params.UnlockAchievement) sesamy.Event[params.UnlockAchievement] {
return sesamy.NewEvent(sesamy.EventNameUnlockAchievement, p)
}

View File

@ -7,6 +7,6 @@ import (
type UserEngagement sesamy.Event[params.UserEngagement]
func NewUserEngagement(p params.UserEngagement) UserEngagement {
return UserEngagement(sesamy.NewEvent(sesamy.EventNameUserEngagement, p))
func NewUserEngagement(p params.UserEngagement) sesamy.Event[params.UserEngagement] {
return sesamy.NewEvent(sesamy.EventNameUserEngagement, p)
}

View File

@ -7,6 +7,6 @@ import (
type VideoComplete sesamy.Event[params.VideoComplete]
func NewVideoComplete(p params.VideoComplete) VideoComplete {
return VideoComplete(sesamy.NewEvent(sesamy.EventNameVideoComplete, p))
func NewVideoComplete(p params.VideoComplete) sesamy.Event[params.VideoComplete] {
return sesamy.NewEvent(sesamy.EventNameVideoComplete, p)
}

View File

@ -7,6 +7,6 @@ import (
type VideoProgress sesamy.Event[params.VideoProgress]
func NewVideoProgress(p params.VideoProgress) VideoProgress {
return VideoProgress(sesamy.NewEvent(sesamy.EventNameVideoProgress, p))
func NewVideoProgress(p params.VideoProgress) sesamy.Event[params.VideoProgress] {
return sesamy.NewEvent(sesamy.EventNameVideoProgress, p)
}

View File

@ -7,6 +7,6 @@ import (
type VideoStart sesamy.Event[params.VideoStart]
func NewVideoStart(p params.VideoStart) VideoStart {
return VideoStart(sesamy.NewEvent(sesamy.EventNameVideoStart, p))
func NewVideoStart(p params.VideoStart) sesamy.Event[params.VideoStart] {
return sesamy.NewEvent(sesamy.EventNameVideoStart, p)
}

View File

@ -7,6 +7,6 @@ import (
type ViewCart sesamy.Event[params.ViewCart[params.Item]]
func NewViewCart(p params.ViewCart[params.Item]) ViewCart {
return ViewCart(sesamy.NewEvent(sesamy.EventNameViewCart, p))
func NewViewCart(p params.ViewCart[params.Item]) sesamy.Event[params.ViewCart[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameViewCart, p)
}

View File

@ -7,6 +7,6 @@ import (
type ViewItem sesamy.Event[params.ViewItem[params.Item]]
func NewViewItem(p params.ViewItem[params.Item]) ViewItem {
return ViewItem(sesamy.NewEvent(sesamy.EventNameViewItem, p))
func NewViewItem(p params.ViewItem[params.Item]) sesamy.Event[params.ViewItem[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameViewItem, p)
}

View File

@ -7,6 +7,6 @@ import (
type ViewItemList sesamy.Event[params.ViewItemList[params.Item]]
func NewViewItemList(p params.ViewItemList[params.Item]) ViewItemList {
return ViewItemList(sesamy.NewEvent(sesamy.EventNameViewItemList, p))
func NewViewItemList(p params.ViewItemList[params.Item]) sesamy.Event[params.ViewItemList[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameViewItemList, p)
}

View File

@ -7,6 +7,6 @@ import (
type ViewPromotion sesamy.Event[params.ViewPromotion[params.Item]]
func NewViewPromotion(p params.ViewPromotion[params.Item]) ViewPromotion {
return ViewPromotion(sesamy.NewEvent(sesamy.EventNameViewPromotion, p))
func NewViewPromotion(p params.ViewPromotion[params.Item]) sesamy.Event[params.ViewPromotion[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameViewPromotion, p)
}

View File

@ -7,6 +7,6 @@ import (
type ViewSearchResults sesamy.Event[params.ViewSearchResults]
func NewViewSearchResults(p params.ViewSearchResults) ViewSearchResults {
return ViewSearchResults(sesamy.NewEvent(sesamy.EventNameViewSearchResults, p))
func NewViewSearchResults(p params.ViewSearchResults) sesamy.Event[params.ViewSearchResults] {
return sesamy.NewEvent(sesamy.EventNameViewSearchResults, p)
}

6
pkg/sesamy/anyevent.go Normal file
View File

@ -0,0 +1,6 @@
package sesamy
// AnyEvent casting is required as castings like Event[any](pageView) do not work.
type AnyEvent interface {
AnyEvent() Event[any]
}

View File

@ -13,3 +13,10 @@ func NewEvent[P any](name EventName, params P) Event[P] {
Params: params,
}
}
func (e Event[P]) AnyEvent() Event[any] {
return Event[any]{
Name: e.Name,
Params: e.Params,
}
}