sesamy-go/measurementprotocol/v2/client.go
Kevin Franklin Kim ba2d68fd99
feat: copy headers
2024-03-08 14:51:45 +01:00

167 lines
4.0 KiB
Go

package v2
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
Client struct {
l *zap.Logger
url string
cookies []string
richsstsse string
trackingID string
measurementID string
protocolVersion string
httpClient *http.Client
middlewares []ClientMiddleware
}
ClientOption func(*Client)
ClientHandler func(r *http.Request, event *Event) error
ClientMiddleware func(next ClientHandler) ClientHandler
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func ClientWithHTTPClient(v *http.Client) ClientOption {
return func(o *Client) {
o.httpClient = v
}
}
func ClientWithCookies(v ...string) ClientOption {
return func(o *Client) {
o.cookies = append(o.cookies, v...)
}
}
func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
return func(o *Client) {
o.middlewares = append(o.middlewares, v...)
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Client {
inst := &Client{
l: l,
url: url,
cookies: []string{"gtm_auth", "gtm_debug", "gtm_preview"},
trackingID: trackingID,
protocolVersion: "2",
httpClient: http.DefaultClient,
}
for _, opt := range opts {
opt(inst)
}
return inst
}
// ------------------------------------------------------------------------------------------------
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (c *Client) HTTPClient() *http.Client {
return c.httpClient
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Client) Send(r *http.Request, event *Event) error {
yes := "1"
// set default values
event.TrackingID = &c.trackingID
event.Richsstsse = &c.richsstsse
event.ProtocolVersion = &c.protocolVersion
event.IgnoreReferrer = &yes
{ // set referrer parameter
if referrer, err := url.Parse(r.Referer()); err != nil {
c.l.With(zap.Error(err)).Warn("failed to parse referrer")
} else {
event.DocumentLocation = &referrer.Path
event.DocumentHostname = &referrer.Host
}
}
{ // TODO check
if value, _ := r.Cookie("gtm_debug"); value != nil {
event.IsDebug = &yes
}
}
{ // set client id
if value, _ := r.Cookie("_ga"); value != nil {
clientID := strings.TrimPrefix(value.Value, "GA1.1.")
event.ClientID = &clientID
}
}
next := c.SendRaw
for _, middleware := range c.middlewares {
next = middleware(next)
}
return next(r, event)
}
func (c *Client) SendRaw(r *http.Request, event *Event) error {
values, body, err := Encode(event)
if err != nil {
return errors.Wrap(err, "failed to marshall event")
}
req, err := http.NewRequestWithContext(
r.Context(),
http.MethodPost,
fmt.Sprintf("%s?%s", c.url, EncodeValues(values)),
body,
)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// 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")
}
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
}