Compare commits

...

202 Commits

Author SHA1 Message Date
Kevin Franklin Kim
54e0166cab
Merge pull request #62 from foomo/fix/gtag-space-encoding
Fix: gtag space encoding
2025-06-06 14:54:21 +02:00
Kevin Franklin Kim
596615b287
revert: go bump 2025-06-06 11:25:55 +02:00
Kevin Franklin Kim
1f968d33b5
feat: bump deps 2025-06-06 11:21:39 +02:00
Kevin Franklin Kim
171b14f620
fix: typo 2025-06-06 11:21:30 +02:00
Kevin Franklin Kim
c9aee967cb
fix: add missing params 2025-06-06 11:21:20 +02:00
Kevin Franklin Kim
3f0b3fca75
fix: handle empty query 2025-06-06 11:21:08 +02:00
Kevin Franklin Kim
5b50febce6
test: use parallel test 2025-06-06 11:20:57 +02:00
Kevin Franklin Kim
fcf4229980
fix: space encoding 2025-06-06 11:20:33 +02:00
Kevin Franklin Kim
0a45237051
Merge pull request #61 from foomo/fix/unnecessary-url-unescape
fix: remove unnecessary url unescape
2025-05-26 16:28:29 +02:00
Kevin Franklin Kim
3af21626d8
revert: gracefully try unescape 2025-05-26 16:19:08 +02:00
Kevin Franklin Kim
7884aecdae
style: remove comment 2025-05-26 16:08:23 +02:00
Kevin Franklin Kim
f8a36b73ff
fix: remove unnecessary url unescape 2025-05-26 16:06:28 +02:00
Kevin Franklin Kim
42b76dc10a
Merge pull request #51 from foomo/dependabot/go_modules/gomod-security-50d30f88de
chore(deps): bump github.com/redis/go-redis/v9 from 9.7.0 to 9.7.3 in the gomod-security group
2025-05-25 21:59:51 +02:00
Kevin Franklin Kim
3dcf81f8f1
Merge branch 'main' into dependabot/go_modules/gomod-security-50d30f88de 2025-05-25 21:59:30 +02:00
Kevin Franklin Kim
1bd8089d85
Merge pull request #57 from foomo/dependabot/github_actions/github-actions-afd9e15987
chore(deps): bump golangci/golangci-lint-action from 7 to 8 in the github-actions group
2025-05-25 21:59:12 +02:00
Kevin Franklin Kim
dc018262f9
Merge pull request #60 from foomo/extend-events
feat: extend events
2025-05-25 21:58:02 +02:00
Kevin Franklin Kim
256789a9e5
feat: bump deps 2025-05-25 21:54:23 +02:00
Kevin Franklin Kim
6be359cbbb
feat: extend events 2025-05-25 21:50:03 +02:00
dependabot[bot]
e0d941a111
chore(deps): bump golangci/golangci-lint-action
Bumps the github-actions group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `golangci/golangci-lint-action` from 7 to 8
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v7...v8)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-11 10:52:02 +00:00
dependabot[bot]
410d2c5959
chore(deps): bump github.com/redis/go-redis/v9
Bumps the gomod-security group with 1 update: [github.com/redis/go-redis/v9](https://github.com/redis/go-redis).


Updates `github.com/redis/go-redis/v9` from 9.7.0 to 9.7.3
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.7.0...v9.7.3)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: indirect
  dependency-group: gomod-security
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-11 10:10:49 +00:00
Kevin Franklin Kim
3e1c057496
Merge pull request #56 from foomo/fix/bump-deps
fix: bump deps
2025-05-07 09:17:18 +02:00
Kevin Franklin Kim
69189a891f
fix: bump deps 2025-05-07 09:13:49 +02:00
Kevin Franklin Kim
0ef8d2ab7e
Merge pull request #55 from foomo/fix/ga-session-parsing
fix: support gs1 and gs2 session parsing
2025-05-07 08:58:54 +02:00
Kevin Franklin Kim
8b3a763010
fix: support gs1 and gs2 session parsing 2025-05-06 23:54:17 +02:00
Kevin Franklin Kim
6fc85b0f92
Merge pull request #54 from foomo/feature/without-cancel-client-middleware
feat: add without cancel client middleware
2025-04-25 15:00:02 +02:00
Kevin Franklin Kim
074b137958
chore: bump golangcli lint action 2025-04-25 14:56:45 +02:00
Kevin Franklin Kim
845d93bddd
chore: update golangci lint 2025-04-25 14:53:01 +02:00
Kevin Franklin Kim
07b186d846
feat: add middleware for handling requests without cancel context
Introduced `GTagMiddlewarWithoutCancel` and `MPv2MiddlewarWithoutCancel` to allow handling of requests with contexts where cancellation signals are ignored. This improves flexibility for specific use cases where cancel propagation is not desired.
2025-04-25 14:52:23 +02:00
Kevin Franklin Kim
b2764d77d7
Merge pull request #49 from foomo/feature/go-1.24.1
feat: go 1.24.1
2025-03-13 08:02:48 +01:00
Kevin Franklin Kim
2634da8453
chore: update gitignore 2025-03-13 07:58:52 +01:00
Kevin Franklin Kim
24e6f55ea9
feat: remove watermill 2025-03-13 07:58:41 +01:00
Kevin Franklin Kim
9823bd6eed
feat: go 1.24.1 2025-03-13 07:58:26 +01:00
Kevin Franklin Kim
0af53a0141
docs: add community files 2025-03-13 07:58:02 +01:00
Kevin Franklin Kim
6466f0ef79
Merge pull request #44 from foomo/v0.9.0
feat(emarsys): change events
2025-02-17 09:49:27 +01:00
Kevin Franklin Kim
af1b60c7c5
revert(emarsys): filename 2025-02-17 09:46:25 +01:00
Kevin Franklin Kim
6f300d90d5
revert(emarsys): remove dlv 2025-02-17 09:45:54 +01:00
Kevin Franklin Kim
54545c6e06
revert(emarsys): use event names 2025-02-17 09:44:46 +01:00
Kevin Franklin Kim
819f40b501
Merge branch 'main' of github.com:foomo/sesamy-go into v0.9.0 2025-02-17 09:41:05 +01:00
Kevin Franklin Kim
28805c1eb3
chore: update lint settings 2025-02-17 09:40:48 +01:00
Kevin Franklin Kim
2317e8c6fd
Merge pull request #46 from foomo/dependabot/go_modules/gomod-security-fce19947a0
chore(deps): bump the gomod-security group across 1 directory with 2 updates
2025-02-17 09:31:17 +01:00
dependabot[bot]
2fc77d0c65
chore(deps): bump the gomod-security group across 1 directory with 2 updates
Bumps the gomod-security group with 2 updates in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto) and [golang.org/x/net](https://github.com/golang/net).


Updates `golang.org/x/crypto` from 0.32.0 to 0.33.0
- [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.33.0)

Updates `golang.org/x/net` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/net/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
  dependency-group: gomod-security
- dependency-name: golang.org/x/net
  dependency-type: indirect
  dependency-group: gomod-security
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 08:27:47 +00:00
Kevin Franklin Kim
6534d72815
Merge pull request #45 from foomo/dependabot/go_modules/gomod-update-6f4265763a
chore(deps): bump the gomod-update group across 1 directory with 5 updates
2025-02-17 09:26:22 +01:00
dependabot[bot]
73435d4490
chore(deps): bump the gomod-update group across 1 directory with 5 updates
Bumps the gomod-update group with 3 updates in the / directory: [github.com/ThreeDotsLabs/watermill](https://github.com/ThreeDotsLabs/watermill), [github.com/grafana/loki/v3](https://github.com/grafana/loki) and [github.com/prometheus/common](https://github.com/prometheus/common).


Updates `github.com/ThreeDotsLabs/watermill` from 1.4.1 to 1.4.4
- [Release notes](https://github.com/ThreeDotsLabs/watermill/releases)
- [Changelog](https://github.com/ThreeDotsLabs/watermill/blob/master/RELEASE-PROCEDURE.md)
- [Commits](https://github.com/ThreeDotsLabs/watermill/compare/v1.4.1...v1.4.4)

Updates `github.com/grafana/loki/v3` from 3.3.1 to 3.4.2
- [Release notes](https://github.com/grafana/loki/releases)
- [Changelog](https://github.com/grafana/loki/blob/main/CHANGELOG.md)
- [Commits](https://github.com/grafana/loki/compare/v3.3.1...v3.4.2)

Updates `github.com/mitchellh/mapstructure` from 1.5.0 to 1.5.1-0.20220423185008-bf980b35cac4
- [Changelog](https://github.com/mitchellh/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mitchellh/mapstructure/commits)

Updates `github.com/prometheus/common` from 0.61.0 to 0.62.0
- [Release notes](https://github.com/prometheus/common/releases)
- [Changelog](https://github.com/prometheus/common/blob/main/RELEASE.md)
- [Commits](https://github.com/prometheus/common/compare/v0.61.0...v0.62.0)

Updates `go.opentelemetry.io/otel/trace` from 1.32.0 to 1.34.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.32.0...v1.34.0)

---
updated-dependencies:
- dependency-name: github.com/ThreeDotsLabs/watermill
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gomod-update
- dependency-name: github.com/grafana/loki/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/mitchellh/mapstructure
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gomod-update
- dependency-name: github.com/prometheus/common
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-16 10:21:20 +00:00
Kevin Franklin Kim
b6be99c280
feat(emarsys): change events 2025-02-14 11:55:04 +01:00
Kevin Franklin Kim
19235dbc52
Merge pull request #42 from foomo/v0.8.0
v0.8.0
2025-02-12 11:46:04 +01:00
Kevin Franklin Kim
f4b1a3c77b
fix: check empty 2025-02-12 11:37:58 +01:00
Kevin Franklin Kim
08579227f8
fix: set values back 2025-02-12 10:25:03 +01:00
Kevin Franklin Kim
79e6f67409
refactor: use single 2025-02-12 09:29:42 +01:00
Kevin Franklin Kim
12c8d87600
feat: add engagement time middleware 2025-02-12 09:29:27 +01:00
Kevin Franklin Kim
8c170e83ad
fix: remove dump 2025-02-12 09:29:07 +01:00
Kevin Franklin Kim
69594d99fa
feat: add event session id and number 2025-02-10 11:45:54 +01:00
Kevin Franklin Kim
79ce1d81e4
chore: skip mke 2025-02-10 11:32:35 +01:00
Kevin Franklin Kim
4900b61684
fix: set debug mode on every event 2025-02-10 11:20:24 +01:00
Kevin Franklin Kim
778bb0f199
Merge pull request #37 from foomo/sesamy-go-0.7.1
fix: set content type
2024-12-10 08:50:45 +01:00
Kevin Franklin Kim
f5d0d00b78
fix: set content type 2024-12-10 08:45:46 +01:00
Kevin Franklin Kim
d5a849611e
Merge pull request #36 from foomo/sesamy-go-0.7.0
feat: go 1.23.0 & WithTimeout middlewares
2024-12-09 14:42:19 +01:00
Kevin Franklin Kim
d1967008a8
feat: bump go 1.23.0 2024-12-09 12:33:33 +01:00
Kevin Franklin Kim
69fc1ac71d
feat: add with timeout middleware 2024-12-09 12:32:55 +01:00
dependabot[bot]
603cc9e45b
chore(deps): bump the gomod-update group across 1 directory with 3 updates
Bumps the gomod-update group with 2 updates in the / directory: [github.com/grafana/loki/v3](https://github.com/grafana/loki) and [github.com/prometheus/common](https://github.com/prometheus/common).


Updates `github.com/grafana/loki/v3` from 3.2.1 to 3.3.1
- [Release notes](https://github.com/grafana/loki/releases)
- [Changelog](https://github.com/grafana/loki/blob/main/CHANGELOG.md)
- [Commits](https://github.com/grafana/loki/compare/v3.2.1...v3.3.1)

Updates `github.com/prometheus/common` from 0.60.1 to 0.61.0
- [Release notes](https://github.com/prometheus/common/releases)
- [Changelog](https://github.com/prometheus/common/blob/main/RELEASE.md)
- [Commits](https://github.com/prometheus/common/compare/v0.60.1...v0.61.0)

Updates `github.com/stretchr/testify` from 1.9.0 to 1.10.0
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/grafana/loki/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/prometheus/common
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 10:52:34 +00:00
Kevin Franklin Kim
bc2e2e0957
Merge pull request #34 from foomo/fix/collect-response-header
fix: copy headers to response
2024-11-26 16:16:18 +01:00
Kevin Franklin Kim
67a12676eb
fix: copy headers to response 2024-11-26 15:49:21 +01:00
Kevin Franklin Kim
0853b5153d
fix: copy headers to response 2024-11-26 15:26:48 +01:00
Kevin Franklin Kim
ad3a673307
Merge pull request #31 from foomo/sesamy-go-0.4.2
feat: collect
2024-11-25 15:49:30 +01:00
Kevin Franklin Kim
6680fbb169
feat: add sendpayload 2024-11-25 13:41:46 +01:00
Kevin Franklin Kim
478b678a6e
feat: add decode 2024-11-21 13:56:10 +01:00
Kevin Franklin Kim
e801701b58
fix: middleware name 2024-11-21 13:20:13 +01:00
Kevin Franklin Kim
2a125cee30
fix: middleware name 2024-11-21 12:55:07 +01:00
Kevin Franklin Kim
e7f9e2af67
feat: add middlewares 2024-11-21 12:52:25 +01:00
Kevin Franklin Kim
28573f3798
feat: add middlewares 2024-11-21 12:38:11 +01:00
Kevin Franklin Kim
757a4f63e5
feat: add middlewares 2024-11-21 12:36:05 +01:00
Kevin Franklin Kim
9cca227cd3
fix: loki next 2024-11-20 17:08:28 +01:00
Kevin Franklin Kim
0c1db3cad2
wip: use new request 2024-11-20 17:05:26 +01:00
Kevin Franklin Kim
9af3ced441
wip: use new request 2024-11-20 16:59:59 +01:00
Kevin Franklin Kim
84f5a2f76b
wip: debug 2024-11-20 16:17:42 +01:00
Kevin Franklin Kim
deadd984ad
fix: nil check 2024-11-20 16:07:58 +01:00
Kevin Franklin Kim
3f644f8c52
refactor: add event handler middleware 2024-11-20 15:41:56 +01:00
Kevin Franklin Kim
a58dfafaef
feat: add collect 2024-11-20 13:48:00 +01:00
Kevin Franklin Kim
615b57f387
feat: add consent 2024-11-20 09:30:12 +01:00
Kevin Franklin Kim
fe5b8c9f0f
fix: don't use tft timestamp 2024-11-20 08:22:57 +01:00
Kevin Franklin Kim
2770d4f32f
feat: transform document params 2024-11-18 17:32:55 +01:00
Kevin Franklin Kim
c5248e77c2
revert: internal otel 2024-11-18 14:28:26 +01:00
Kevin Franklin Kim
4a7ba15e5f
docs: update README 2024-11-18 09:43:41 +01:00
Kevin Franklin Kim
0e9c5d04a5
Merge pull request #30 from foomo/sesamy-go-0.4.2
Sesamy go 0.5.0
2024-11-16 22:59:30 +01:00
Kevin Franklin Kim
1787ffa90f
feat: bump deps 2024-11-16 22:53:39 +01:00
Kevin Franklin Kim
f0480f02ea
feat: make client id optional 2024-11-16 22:51:40 +01:00
Kevin Franklin Kim
25620e06f0
Merge pull request #28 from foomo/dependabot/go_modules/gomod-update-a1dfd81af6
chore(deps): bump the gomod-update group across 1 directory with 4 updates
2024-11-16 22:49:22 +01:00
Kevin Franklin Kim
92ed44c56e
fix: go mod tidy 2024-11-16 22:38:35 +01:00
Kevin Franklin Kim
d457eca621
Merge branch 'main' of github.com:foomo/sesamy-go into dependabot/go_modules/gomod-update-a1dfd81af6 2024-11-16 22:36:17 +01:00
Kevin Franklin Kim
46d78687b8
fix: remove toolchain 2024-11-16 22:35:31 +01:00
Kevin Franklin Kim
c975593c5c
fix: don't error on missing cookie 2024-11-16 22:34:40 +01:00
Kevin Franklin Kim
e8e7722960
Merge pull request #29 from foomo/sesamy-go-0.4.1
fix: only log on ignore
2024-11-12 13:35:18 +01:00
Kevin Franklin Kim
d289d04298
fix: only log on ignore 2024-11-12 10:31:30 +01:00
dependabot[bot]
e96f667d0e
chore(deps): bump the gomod-update group across 1 directory with 4 updates
Bumps the gomod-update group with 4 updates in the / directory: [github.com/ThreeDotsLabs/watermill](https://github.com/ThreeDotsLabs/watermill), [github.com/foomo/gostandards](https://github.com/foomo/gostandards), [github.com/grafana/loki/v3](https://github.com/grafana/loki) and [github.com/prometheus/common](https://github.com/prometheus/common).


Updates `github.com/ThreeDotsLabs/watermill` from 1.3.5 to 1.4.1
- [Release notes](https://github.com/ThreeDotsLabs/watermill/releases)
- [Changelog](https://github.com/ThreeDotsLabs/watermill/blob/master/RELEASE-PROCEDURE.md)
- [Commits](https://github.com/ThreeDotsLabs/watermill/compare/v1.3.5...v1.4.1)

Updates `github.com/foomo/gostandards` from 0.1.0 to 0.2.0
- [Release notes](https://github.com/foomo/gostandards/releases)
- [Commits](https://github.com/foomo/gostandards/compare/v0.1.0...v0.2.0)

Updates `github.com/grafana/loki/v3` from 3.1.1 to 3.2.1
- [Release notes](https://github.com/grafana/loki/releases)
- [Changelog](https://github.com/grafana/loki/blob/main/CHANGELOG.md)
- [Commits](https://github.com/grafana/loki/compare/v3.1.1...v3.2.1)

Updates `github.com/prometheus/common` from 0.55.0 to 0.60.1
- [Release notes](https://github.com/prometheus/common/releases)
- [Changelog](https://github.com/prometheus/common/blob/main/RELEASE.md)
- [Commits](https://github.com/prometheus/common/compare/v0.55.0...v0.60.1)

---
updated-dependencies:
- dependency-name: github.com/ThreeDotsLabs/watermill
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/foomo/gostandards
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/grafana/loki/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
- dependency-name: github.com/prometheus/common
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod-update
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-03 10:20:43 +00:00
Kevin Franklin Kim
dae9b3ee29
Merge pull request #23 from foomo/tracify
feat: tracify & cookiebot
2024-10-01 12:55:13 +02:00
Kevin Franklin Kim
a95ea0147b
feat: implement keel service 2024-10-01 12:34:04 +02:00
Kevin Franklin Kim
d8443c212b
feat: implement service interface 2024-10-01 12:28:33 +02:00
Kevin Franklin Kim
499010ae1c
feat: add message handler 2024-10-01 12:16:20 +02:00
Kevin Franklin Kim
beb6082670
feat: refactor to PublisherMiddlewareEventParams 2024-10-01 08:14:36 +02:00
Kevin Franklin Kim
38715a58b6
feat: add inline mpv2 2024-09-30 18:01:20 +02:00
Kevin Franklin Kim
ee153c3504
feat: add middleware 2024-09-30 11:19:35 +02:00
Kevin Franklin Kim
519267b962
feat: pass through query 2024-09-30 10:12:33 +02:00
Kevin Franklin Kim
c22b610038
feat: add session id middleware 2024-09-28 21:59:27 +02:00
Kevin Franklin Kim
20e55ad210
feat: add session id middleware 2024-09-28 21:55:53 +02:00
Kevin Franklin Kim
f6a379429b
wip: add payload vars 2024-09-28 21:09:44 +02:00
Kevin Franklin Kim
dbc3217726
feat: add consent 2024-09-26 22:09:35 +02:00
Kevin Franklin Kim
a44d43422a
feat: add consent 2024-09-26 22:01:50 +02:00
Kevin Franklin Kim
9673d35bc3
wip: debug 2024-09-26 21:53:34 +02:00
Kevin Franklin Kim
56b9e9b67b
wip: debug 2024-09-26 21:41:10 +02:00
Kevin Franklin Kim
8a91ffb81b
wip: debug 2024-09-26 21:35:02 +02:00
Kevin Franklin Kim
d6e52d620d
wip: debug 2024-09-26 21:32:19 +02:00
Kevin Franklin Kim
0bc536f6db
wip: debug 2024-09-26 21:31:26 +02:00
Kevin Franklin Kim
adbf3046ef
wip: debug 2024-09-26 21:28:16 +02:00
Kevin Franklin Kim
5fadcd731c
wip: debug 2024-09-26 21:21:29 +02:00
Kevin Franklin Kim
fcb18a0799
wip: debug 2024-09-26 21:17:50 +02:00
Kevin Franklin Kim
7468cd8508
feat: add helper method 2024-09-26 21:11:09 +02:00
Kevin Franklin Kim
4ebeac6af7
feat: add tracify provider 2024-09-26 21:10:58 +02:00
Kevin Franklin Kim
7249491e46
Merge pull request #19 from foomo/emarsys-provider
feat: add emarsys provider events
2024-08-22 14:48:32 +02:00
Kevin Franklin Kim
a44e4b1e4c
fix: typo 2024-08-22 12:27:11 +02:00
Kevin Franklin Kim
199b141f1f
feat: use items 2024-08-22 12:24:15 +02:00
Kevin Franklin Kim
0b0a8000aa
fix: event name 2024-08-21 15:52:55 +02:00
Kevin Franklin Kim
032a5177bb
refactor: change names 2024-08-21 14:48:57 +02:00
Kevin Franklin Kim
fc05804b18
feat: add emarsys provider events 2024-08-21 11:23:49 +02:00
Kevin Franklin Kim
bea1de4daa
Merge pull request #17 from foomo/fix/mpv2encode
fix: use any
2024-07-15 08:19:04 +02:00
Kevin Franklin Kim
45bd571282
feat: add recommended events 2024-07-13 18:27:42 +02:00
Kevin Franklin Kim
56ab1ada09
fix: type checking 2024-07-12 08:16:48 +02:00
Kevin Franklin Kim
8ad1c6cee5
fix: try float 2024-07-11 23:36:03 +02:00
Kevin Franklin Kim
cfe639e0e7
fix: use any 2024-07-11 23:28:46 +02:00
Kevin Franklin Kim
85dd9bb9fd
Merge pull request #12 from foomo/dependabot/github_actions/github-actions-fcf4e41b5c
chore(deps): bump goreleaser/goreleaser-action from 5 to 6 in the github-actions group
2024-07-10 22:25:32 +02:00
Kevin Franklin Kim
d3ffb1977b
chore: add version 2024-07-10 22:23:42 +02:00
Kevin Franklin Kim
96f9728f1c
Merge branch 'main' of github.com:foomo/sesamy-go into dependabot/github_actions/github-actions-fcf4e41b5c 2024-07-10 22:23:08 +02:00
Kevin Franklin Kim
e184d4d467
Merge pull request #16 from foomo/fix/decode-event-params
fix: decode event params
2024-07-10 22:21:17 +02:00
Kevin Franklin Kim
3b8c2a55ae
refactor: use message func 2024-07-10 11:48:57 +02:00
Kevin Franklin Kim
70b8a970c5
feat: add metadata func option 2024-07-10 11:39:35 +02:00
Kevin Franklin Kim
3a414aba79
feat: use request context 2024-07-10 10:58:02 +02:00
Kevin Franklin Kim
d88146a2e9
fix: nil pointer access 2024-07-10 08:55:29 +02:00
Kevin Franklin Kim
e5f48dcad8
fix: nil pointer access 2024-07-10 08:42:25 +02:00
Kevin Franklin Kim
b2891c12d1
fix: add percent scrolled 2024-07-10 08:42:04 +02:00
Kevin Franklin Kim
9331d10e0d
feat: pass through cookies 2024-07-09 08:18:53 +02:00
Kevin Franklin Kim
f8fcacca2a
fix: transform debug mode 2024-07-09 07:57:20 +02:00
Kevin Franklin Kim
dced802d6a
feat: bump deps 2024-07-09 07:18:29 +02:00
Kevin Franklin Kim
598ab3b8fb
fix: decode params using json tags 2024-07-09 07:12:41 +02:00
dependabot[bot]
54e2519b4d
chore(deps): bump goreleaser/goreleaser-action
Bumps the github-actions group with 1 update: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action).


Updates `goreleaser/goreleaser-action` from 5 to 6
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-09 10:12:44 +00:00
Kevin Franklin Kim
9629047b99
Merge pull request #8 from foomo/ga4
v0.2.x
2024-05-30 15:10:22 +02:00
Kevin Franklin Kim
3c143f5d60
feat: switch log level 2024-05-30 15:07:08 +02:00
Kevin Franklin Kim
738ddd94b5
wip: fix batch 2024-05-30 12:19:55 +02:00
Kevin Franklin Kim
81d4d84868
wip: debug 2024-05-30 12:13:46 +02:00
Kevin Franklin Kim
9d0aa01bb1
feat: add log messages 2024-05-30 12:02:09 +02:00
Kevin Franklin Kim
9c4ec4a25b
feat: add no publish handler 2024-05-30 10:30:23 +02:00
Kevin Franklin Kim
4921daa17e
feat: remove ts 2024-05-29 21:44:54 +02:00
Kevin Franklin Kim
ec7166f059
feat: add loki 2024-05-29 21:37:46 +02:00
Kevin Franklin Kim
31f2ea9f8f
feat: use json 2024-05-27 14:25:37 +02:00
Kevin Franklin Kim
f021853848
feat: fix ecommerce encoding 2024-05-27 13:28:27 +02:00
Kevin Franklin Kim
3bd292ec56
feat: add decode params 2024-05-27 12:51:53 +02:00
Kevin Franklin Kim
d9cee831e8
fix: lint issues 2024-05-27 11:18:22 +02:00
Kevin Franklin Kim
337824cd85
chore: bump version 2024-05-27 11:18:12 +02:00
Kevin Franklin Kim
08b4b5fd40
feat: remove debug 2024-05-27 11:12:59 +02:00
Kevin Franklin Kim
8385dbe7df
wip: set debug mode 2024-05-27 11:04:57 +02:00
Kevin Franklin Kim
8fada67bcd
wip: add debug mode middleware 2024-05-27 10:57:39 +02:00
Kevin Franklin Kim
9a32adcadb
revert: switch back to any 2024-05-25 22:18:46 +02:00
Kevin Franklin Kim
a4fa86a8f3
feat: refactor event creation and move clients 2024-05-25 21:55:30 +02:00
Kevin Franklin Kim
5cb6719df0
feat: add message handler 2024-05-22 11:59:08 +02:00
Kevin Franklin Kim
8419d07ae4
feat: refactor watermill subscriber and add middlewares 2024-05-22 11:33:31 +02:00
Kevin Franklin Kim
a7746844d0
fix: switch to json to decode 2024-05-22 09:17:36 +02:00
Kevin Franklin Kim
de0c44a649
fix: import alias 2024-05-21 10:50:43 +02:00
Kevin Franklin Kim
4ead285d4c
refactor: structure 2024-05-21 07:26:53 +02:00
Kevin Franklin Kim
a0b7aabcfa
refactor: change I 2024-05-21 06:58:09 +02:00
Kevin Franklin Kim
d18231b3b2
fix: annotations 2024-05-20 20:07:55 +02:00
Kevin Franklin Kim
e9853efff8
feat: make event name required 2024-05-20 17:46:19 +02:00
Kevin Franklin Kim
5a1a2f9ea1
fix: fix annotation 2024-05-20 17:27:04 +02:00
Kevin Franklin Kim
df15984d9e
fix: price type 2024-05-20 17:22:12 +02:00
Kevin Franklin Kim
71fc83a0eb
fix: typos 2024-05-20 15:47:33 +02:00
Kevin Franklin Kim
453e5159a1
feat: add events 2024-05-15 13:17:45 +02:00
Kevin Franklin Kim
ce5bd23d48
refactor: move packages 2024-05-14 15:37:46 +02:00
Kevin Franklin Kim
9117669802
refactor: change packages 2024-05-13 22:14:30 +02:00
Kevin Franklin Kim
64c50a6f57
refactor: implement mpv2 2024-05-12 20:31:23 +02:00
Kevin Franklin Kim
4ed27c190e
chore: update dependabot 2024-05-12 20:30:25 +02:00
Kevin Franklin Kim
e12d083f85
feat(deps): bump action 2024-04-29 14:28:09 +02:00
Kevin Franklin Kim
2f4880f505
feat: add click, page_view and scroll events 2024-04-29 14:24:50 +02:00
Kevin Franklin Kim
ce0e8e6d9d
feat: remove comment 2024-03-14 16:03:01 +01:00
Kevin Franklin Kim
de5f5f0dd0
feat: add EventNameSelectPromotion 2024-03-13 16:30:48 +01:00
Kevin Franklin Kim
f928679030
Merge pull request #4 from foomo/add-item-props
feat: add item props
2024-03-13 16:24:52 +01:00
Kevin Franklin Kim
6e2275fb93
feat: add item props 2024-03-13 16:23:56 +01:00
Kevin Franklin Kim
6246b38719
feat: use full location 2024-03-12 15:36:53 +01:00
Kevin Franklin Kim
5125125257
feat: add path 2024-03-12 13:42:04 +01:00
Kevin Franklin Kim
391f82e2f0
feat: update publisher 2024-03-12 13:40:13 +01:00
Kevin Franklin Kim
67a90c8a8d
fix: session id 2024-03-12 09:16:37 +01:00
Kevin Franklin Kim
f8908a6dd1
fix: session id 2024-03-12 09:13:38 +01:00
Kevin Franklin Kim
0c0f3be0a2
fix: session id 2024-03-12 09:10:15 +01:00
Kevin Franklin Kim
131c7f3f87
feat: add session id 2024-03-12 09:03:26 +01:00
Kevin Franklin Kim
14ce26b622
fix: middleware 2024-03-12 08:46:25 +01:00
Kevin Franklin Kim
2c68979504
refactor: update client api 2024-03-12 08:43:09 +01:00
Kevin Franklin Kim
202a816cb2
feat: split into middlewares 2024-03-11 16:02:15 +01:00
Kevin Franklin Kim
c1221f870e
fix: case 2024-03-11 15:26:50 +01:00
Kevin Franklin Kim
30550f16cc
feat: change id case 2024-03-11 15:25:16 +01:00
Kevin Franklin Kim
5cfffd2d63
feat: add sendType 2024-03-11 15:25:02 +01:00
Kevin Franklin Kim
bbf3c33612
wip: add todo 2024-03-08 14:55:18 +01:00
Kevin Franklin Kim
ba2d68fd99
feat: copy headers 2024-03-08 14:51:45 +01:00
Kevin Franklin Kim
1c4f38e552
feat: handle cookies 2024-03-08 14:41:55 +01:00
Kevin Franklin Kim
18754b0f1a
feat: handle cookies 2024-03-08 14:18:46 +01:00
Kevin Franklin Kim
ffd63f3b42
revert: add cookies 2024-03-08 14:04:30 +01:00
Kevin Franklin Kim
f6488fb269
feat: add cookies 2024-03-08 13:45:18 +01:00
Kevin Franklin Kim
a527a59197
feat: add cookies 2024-03-08 13:31:42 +01:00
228 changed files with 5917 additions and 2320 deletions

View File

@ -1,3 +1,4 @@
# Contributor Covenant Code of Conduct
## Our Pledge
@ -6,8 +7,8 @@ We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
@ -22,17 +23,17 @@ community include:
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
@ -52,7 +53,7 @@ decisions when appropriate.
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
@ -60,7 +61,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
info@bestbytes.de.
info@bestbytes.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@ -82,15 +83,15 @@ behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
@ -109,20 +110,24 @@ Violating these terms may lead to a permanent ban.
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

21
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,21 @@
# Contributing
If you want to submit a pull request to fix a bug or enhance an existing
feature, please first open an issue and link to that issue when you
submit your pull request.
If you have any questions about a possible submission, feel free to open
an issue too.
### Pull request process
1. Fork this repository
2. Create a branch in your fork to implement the changes. We recommend using
the issue number as part of your branch name, e.g. `1234-fixes`
3. Ensure that any documentation is updated with the changes that are required
by your fix.
4. Ensure that any samples are updated if the base image has been changed.
5. Submit the pull request. *Do not leave the pull request blank*. Explain exactly
what your changes are meant to do and provide simple steps on how to validate
your changes. Ensure that you reference the issue you created as well.
The pull request will be review before it is merged.

17
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,17 @@
---
name: Bug Report
about: Report a bug you encountered
labels: bug
---
**What happened**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
**Environment**:
- Affected Version:
- OS (e.g: `cat /etc/os-release`):
- Others:

8
.github/ISSUE_TEMPLATE/enhancement.md vendored Normal file
View File

@ -0,0 +1,8 @@
---
name: Enhancement Request
about: Suggest an enhancement
labels: enhancement
---
**What would you like to be added**:
**Why is this needed**:

19
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,19 @@
### Type of Change
- [ ] New feature
- [ ] Bug fix
- [ ] Documentation update
- [ ] Refactoring
- [ ] Hotfix
- [ ] Security patch
### Description
_[Provide a detailed explanation of the changes you have made. Include the reasons behind these changes and any relevant context. Link any related issues.]_
### Related Issues
_[If this pull request addresses an issue, please link to it here (e.g., Fixes #123).]_
### Checklist
- [ ] My code adheres to the coding and style guidelines of the project.
- [ ] I have performed a self-review of my own code.
- [ ] I have commented my code, particularly in hard-to-understand areas.
- [ ] I have made corresponding changes to the documentation.

45
.github/SECURITY.md vendored Normal file
View File

@ -0,0 +1,45 @@
# Security Guidelines
## How security is managed on this project
The foomo team and community take security seriously and wants to ensure that
we maintain a secure environment and provide secure solutions for the open
source community. To help us achieve these goals, please note the
following before using this software:
- Review the software license to understand the contributor's obligations in
terms of warranties and suitability for purpose
- For any questions or concerns about security, you can
[create an issue][new-issue] or [report a vulnerability][new-sec-issue]
- We request that you work with our security team and opt for
responsible disclosure using the guidelines below
- All security related issues and pull requests you make should be tagged with
"security" for easy identification
- Please monitor this repository and update your environment in a timely manner
as we release patches and updates
## Responsibly Disclosing Security Bugs
If you find a security bug in this repository, please work with contributors
following responsible disclosure principles and these guidelines:
- Do not submit a normal issue or pull request in our public repository, instead
[report it directly][new-sec-issue].
- We will review your submission and may follow up for additional details
- If you have a patch, we will review it and approve it privately; once approved
for release you can submit it as a pull request publicly in the repository (we
give credit where credit is due)
- We will keep you informed during our investigation, feel free to check in for
a status update
- We will release the fix and publicly disclose the issue as soon as possible,
but want to ensure we due properly due diligence before releasing
- Please do not publicly blog or post about the security issue until after we
have updated the public repo so that other downstream users have an opportunity
to patch
## Contact / Misc
If you have any questions, please reach out directly by [creating an issue][new-issue].
[new-issue]: https://github.com/foomo/sesamy-go/issues/new/choose
[new-sec-issue]: https://github.com/foomo/sesamy-go/security/advisories/new

BIN
.github/assets/sesamy.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1,15 +1,25 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/"
day: 'sunday'
interval: 'weekly'
groups:
github-actions:
patterns:
- '*'
- package-ecosystem: 'gomod'
directory: '/'
schedule:
interval: "weekly"
day: 'sunday'
interval: 'weekly'
groups:
gomod-security:
applies-to: security-updates
patterns: ['*']
gomod-update:
applies-to: version-updates
patterns: ['*']

View File

@ -17,13 +17,12 @@ jobs:
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: 'stable'
check-latest: true
go-version-file: go.mod
- uses: goreleaser/goreleaser-action@v5
- uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean

View File

@ -4,6 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
merge_group:
workflow_dispatch:
concurrency:
@ -18,18 +19,20 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version: 'stable'
check-latest: true
go-version-file: 'go.mod'
- uses: gotesttools/gotestfmt-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: golangci/golangci-lint-action@v4
- uses: golangci/golangci-lint-action@v8
with:
version: latest
args: --timeout=5m
- run: make test
- name: Run tests
run: GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
- uses: coverallsapp/github-action@v2
with:

26
.gitignore vendored
View File

@ -1,13 +1,25 @@
.*
*.log
*.zip
*.tar
*.out
*.log
/bin/
/tmp/
!.github/
!.husky/
!.editorconfig
## Git
!.gitkeep
!.gitignore
## GitHub
!.github/
## Editorconfig
!.editorconfig
## Husky
!.husky/
!.husky.yaml
## Golang
!.golangci.yml
!.goreleaser.yml
!.husky.yaml
go.work
go.work.sum

View File

@ -1,89 +1,215 @@
version: "2"
run:
skip-dirs:
- tmp
linters-settings:
# https://golangci-lint.run/usage/linters/#revive
revive:
rules:
- name: indent-error-flow
disabled: true
- name: exported
disabled: true
# https://golangci-lint.run/usage/linters/#gocritic
gocritic:
enabled-tags:
- diagnostic
- performance
- style
disabled-tags:
- experimental
- opinionated
disabled-checks:
- ifElseChain
settings:
hugeParam:
sizeThreshold: 512
# https://golangci-lint.run/usage/linters/#gosec
gosec:
config:
G306: "0700"
excludes:
- G101 # Potential hardcoded credentials
- G102 # Bind to all interfaces
- G112 # Potential slowloris attack
- G401 # Detect the usage of DES, RC4, MD5 or SHA1
- G402 # Look for bad TLS connection settings
- G404 # Insecure random number source (rand)
- G501 # Import blocklist: crypto/md5
- G505 # Import blocklist: crypto/sha1
go: 1.24.1
build-tags: [ safe ]
modules-download-mode: readonly
linters:
default: none
enable:
# Enabled by default linters:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
## Default linters
- errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
- govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false]
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
- staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false]
- unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
# Disabled by default linters:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
## Recommended linters
- asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false]
- asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false]
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
- canonicalheader # checks whether net/http.Header uses canonical header [fast: false, auto-fix: false]
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false]
- contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false]
- copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false]
- decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false]
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false]
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. [auto-fix]
- fatcontext # detects nested contexts in loops and function literals [fast: false, auto-fix: false]
#- forbidigo # Forbids identifiers [fast: false, auto-fix: false]
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false]
- gochecksumtype # Run exhaustiveness checks on Go "sum types" [fast: false, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false]
- goimports # In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true]
#- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: true]
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: true]
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
- goprintffuncname # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
- gosec # (gas): Inspects source code for security problems [fast: false, auto-fix: false]
- grouper # An analyzer to analyze expression groups. [fast: true, auto-fix: false]
- goprintffuncname # Checks that printf-like functions are named with `f` at the end. [fast: true, auto-fix: false]
- gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false]
- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false]
- grouper # Analyze expression groups. [fast: true, auto-fix: false]
- iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. [fast: false, auto-fix: false]
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
- inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false]
- loggercheck # (logrlint) Checks key value pairs for common logger libraries (kitlog,klog,logr,zap). [fast: false, auto-fix: false]
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
- mirror # reports wrong mirror patterns of bytes/strings usage [fast: false, auto-fix: true]
- misspell # Finds commonly misspelled English words [fast: true, auto-fix: true]
- musttag # enforce field tags in (un)marshaled structs [fast: false, auto-fix: false]
- nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). [fast: true, auto-fix: false]
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
- nilnesserr # Reports constructs that checks for err != nil, but returns a different nil value error.
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
- noctx # noctx finds sending http request without context.Context [fast: false, auto-fix: false]
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
- noctx # Finds sending http request without context.Context [fast: false, auto-fix: false]
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: true]
- nonamedreturns # Reports all named returns [fast: false, auto-fix: false]
- nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false]
- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
- paralleltest # Detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false]
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
- promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false]
- reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false]
- recvcheck # checks for receiver type consistency [fast: false, auto-fix: false]
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
- rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false]
- spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false]
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. [fast: false, auto-fix: false]
- testableexamples # linter checks if examples are testable (have an expected output) [fast: true, auto-fix: false]
- testifylint # Checks usage of github.com/stretchr/testify. [fast: false, auto-fix: false]
- testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false]
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
- thelper # thelper detects tests helpers which is not start with t.Helper() method. [fast: false, auto-fix: false]
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false]
- unparam # Reports unused function parameters [fast: false, auto-fix: false]
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false]
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
- whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
- usetesting # Reports uses of functions with replacement inside the testing package. [auto-fix]
- wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false]
- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true]
## Discouraged linters
#- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
#- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
#- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false]
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
#- err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false]
#- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false]
#- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
#- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false]
#- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false]
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
#- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
#- godot # Check if comments end in a period [fast: true, auto-fix: true]
#- godox # Tool for detection of comment keywords [fast: true, auto-fix: false]
#- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false]
#- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false]
#- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
#- lll # Reports long lines [fast: true, auto-fix: false]
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
#- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
#- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
#- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false]
#- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
#- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true]
#- sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false]
#- tagalign # check that struct tags are well aligned [fast: true, auto-fix: true]
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
#- unparam # Reports unused function parameters [fast: false, auto-fix: false]
#- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
#- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
#- wsl # add or remove empty lines [fast: true, auto-fix: false]
#- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false]
settings:
exhaustive:
default-signifies-exhaustive: true
gocritic:
disabled-checks:
- ifElseChain
- commentFormatting
gomoddirectives:
replace-local: true
gosec:
confidence: medium
importas:
no-unaliased: true
misspell:
mode: restricted
predeclared:
ignore:
- new
- error
revive:
enable-all-rules: true
rules:
- name: line-length-limit
disabled: true
- name: cognitive-complexity
disabled: true
- name: unused-parameter
disabled: true
- name: add-constant
disabled: true
- name: cyclomatic
disabled: true
- name: function-length
disabled: true
- name: function-result-limit
disabled: true
- name: flag-parameter
disabled: true
- name: unused-receiver
disabled: true
- name: argument-limit
disabled: true
- name: max-control-nesting
disabled: true
- name: comment-spacings
disabled: true
- name: max-public-structs
disabled: true
- name: struct-tag
arguments:
- json,inline
- name: unhandled-error
arguments:
- fmt.Println
- io.Writer.Write
- strings.Builder.WriteString
- name: deep-exit
disabled: true
- name: if-return
disabled: true
- name: empty-block
disabled: true
- name: indent-error-flow
disabled: true
testifylint:
disable:
- unused
- float-compare
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- forbidigo
- forcetypeassert
path: _test\.go
- linters:
- asasalint
path: (.+)_test\.go
paths:
- tmp
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- tmp
- third_party$
- builtin$
- examples$

View File

@ -1,5 +1,10 @@
version: 2
builds:
- skip: true
release:
prerelease: auto
changelog:
use: github-native

View File

@ -1,6 +1,6 @@
hooks:
pre-commit:
- golangci-lint run --fast
- golangci-lint run --fast-only
- husky lint-staged
commit-msg:
# only execute if not in a merge
@ -8,7 +8,7 @@ hooks:
lint-staged:
'*.go':
- goimports -l -w
- golangci-lint fmt
lint-commit:
types: '^(feat|fix|build|chore|docs|perf|refactor|revert|style|test|wip)$'

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) Foomo web framework
Copyright (c) foomo by bestbytes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -21,13 +21,14 @@
.PHONY: doc
## Run tests
doc:
@open "http://localhost:6060/pkg/github.com/foomo/keel/"
@open "http://localhost:6060/pkg/github.com/foomo/sesamy-go/"
@godoc -http=localhost:6060 -play
.PHONY: test
## Run tests
test:
@go test -coverprofile=coverage.out -race -json ./... | gotestfmt
@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race ./...
#@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
.PHONY: lint
## Run linter

View File

@ -1,15 +1,26 @@
# Sesamy Go SDK
[![Build Status](https://github.com/foomo/sesamy-go/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/foomo/sesamy-go/actions/workflows/test.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/foomo/sesamy-go)](https://goreportcard.com/report/github.com/foomo/sesamy-go)
[![GoDoc](https://godoc.org/github.com/foomo/sesamy-go?status.svg)](https://godoc.org/github.com/foomo/sesamy-go)
<p align="center">
<img alt="sesamy" src=".github/assets/sesamy.png"/>
</p>
# Sesamy Go SDK
> **Se**rver **S**ide T**a**g **M**anagement **S**ystem
## References
- [Event naming rules](https://support.google.com/analytics/answer/13316687)
## How to Contribute
Make a pull request...
Please refer to the [CONTRIBUTING](.github/CONTRIBUTING.md) details and follow the [CODE_OF_CONDUCT](.github/CODE_OF_CONDUCT.md) and [SECURITY](.github/SECURITY.md) guidelines.
## License
Distributed under MIT License, please see license file within the code for more details.
_Made with ♥ [foomo](https://www.foomo.org) by [bestbytes](https://www.bestbytes.com)_

172
go.mod
View File

@ -1,35 +1,161 @@
module github.com/foomo/sesamy-go
go 1.21
go 1.24.1
require (
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/foomo/keel v0.17.3
github.com/mitchellh/mapstructure v1.5.0
github.com/foomo/go v0.0.3
github.com/foomo/gostandards v0.2.0
github.com/gogo/protobuf v1.3.2
github.com/golang/snappy v1.0.0
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec
github.com/grafana/loki/v3 v3.5.1
github.com/json-iterator/go v1.1.12
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
github.com/prometheus/common v0.64.0
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel/trace v1.36.0
go.uber.org/zap v1.27.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fbiville/markdown-table-formatter v0.3.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/go-logr/logr v1.2.4 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
go.opentelemetry.io/otel/metric v0.30.0 // indirect
go.opentelemetry.io/otel/trace v1.7.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
github.com/go-redsync/redsync/v4 v4.13.0 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/status v1.1.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // indirect
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675 // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hashicorp/consul/api v1.31.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/memberlist v0.5.3 // indirect
github.com/hashicorp/serf v0.10.2 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/opentracing-contrib/go-grpc v0.1.1 // indirect
github.com/opentracing-contrib/go-stdlib v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/exporter-toolkit v0.14.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/prometheus v0.303.1 // indirect
github.com/redis/go-redis/v9 v9.8.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/sercand/kuberesolver/v6 v6.0.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sony/gobreaker/v2 v2.1.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tjhop/slog-gokit v0.1.4 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/pdata v1.28.1 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 // indirect
go.opentelemetry.io/otel/log v0.12.2 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

736
go.sum
View File

@ -1,102 +1,708 @@
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4=
github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNYvwXQMMMIKNZop2SSho=
github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY=
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foomo/keel v0.17.3 h1:ldsTk4ANhiFgtEAYUlacSXqfisX2UkUvuFVy4FF+o0g=
github.com/foomo/keel v0.17.3/go.mod h1:lwlWZ5ZvuX0PCxliabY69AmxQZnU9nIc10wWuavvKtM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk=
github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio=
github.com/foomo/gostandards v0.2.0 h1:Ryd7TI9yV3Xk5B84DcUDB7KcL3LzQ8NS+TVOrFxTYfA=
github.com/foomo/gostandards v0.2.0/go.mod h1:XQx7Ur6vyvxaIe2cQvAthuhPYDe+d2soibqVcXDXOh4=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg=
github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7 h1:O9Evoqc8/4KaWrMshUG23P417UOjvwIee9jRe5JzHAs=
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E=
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl6LhqBdbyAQ9YFhExwsj4bjh5vwMNRZY=
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw=
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675 h1:U94jQ2TQr1m3HNyE8efSdyaBbDrdPaWImXyenuKZ/nw=
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675/go.mod h1:796sq+UcONnSlzA3RtlBZ+b/hrerkZXiEmO8oMjyRwY=
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec h1:hRxt5+O/vCCkAMfPGbqEm3kehrvr8GqEQLad+/emQFg=
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec/go.mod h1:jFooqvfqy5p+fhJIVTiNvw4+fhOLjpVhYJbEUZfMVA8=
github.com/grafana/loki/v3 v3.5.1 h1:muG+UNmUuLBcWIRtankVPRyee58VSUVkZg0NMUPGP7M=
github.com/grafana/loki/v3 v3.5.1/go.mod h1:BQiwDJF7fXVjFuKsN9U2uojfA2wE3sWOXbRFdN4y0mQ=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/consul/api v1.31.2 h1:NicObVJHcCmyOIl7Z9iHPvvFrocgTYo9cITSGg0/7pw=
github.com/hashicorp/consul/api v1.31.2/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0=
github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/memberlist v0.5.3 h1:tQ1jOCypD0WvMemw/ZhhtH+PWpzcftQvgCorLu0hndk=
github.com/hashicorp/memberlist v0.5.3/go.mod h1:h60o12SZn/ua/j0B6iKAZezA4eDaGsIuPO70eOaJ6WE=
github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc=
github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY=
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/opentracing-contrib/go-grpc v0.1.1 h1:Ws7IN1zyiL1DFqKQPhRXuKe5pLYzMfdxnC1qtajE2PE=
github.com/opentracing-contrib/go-grpc v0.1.1/go.mod h1:Nu6sz+4zzgxXu8rvKfnwjBEmHsuhTigxRwV2RhELrS8=
github.com/opentracing-contrib/go-stdlib v1.1.0 h1:cZBWc4pA4e65tqTJddbflK435S0tDImj6c9BMvkdUH0=
github.com/opentracing-contrib/go-stdlib v1.1.0/go.mod h1:S0p+X9p6dcBkoMTL+Qq2VOvxKs9ys5PpYWXWqlCS0bQ=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY=
github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck=
github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8=
github.com/prometheus/sigv4 v0.1.2/go.mod h1:GF9fwrvLgkQwDdQ5BXeV9XUSCH/IPNqzvAoaohfjqMU=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sercand/kuberesolver/v6 v6.0.0 h1:ScvS2Ga9snVkpOahln/BCLySr3/iBAHJf25u66DweZ0=
github.com/sercand/kuberesolver/v6 v6.0.0/go.mod h1:Dxkqms3OJadP5zirIBPLi9FV8Qpys3T3w40XPEcVsu0=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sony/gobreaker/v2 v2.1.0 h1:av2BnjtRmVPWBvy5gSFPytm1J8BmN5AGhq875FfGKDM=
github.com/sony/gobreaker/v2 v2.1.0/go.mod h1:dO3Q/nCzxZj6ICjH6J/gM0r4oAwBMVLY8YAQf+NTtUg=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 h1:mac9BKRqwaX6zxHPDe3pvmWpwuuIM0vuXv2juCnQevE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0/go.mod h1:5eCOqeGphOyz6TsY3ZDNjE33SM/TFAK3RGuCL2naTgY=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/metric v0.30.0 h1:Hs8eQZ8aQgs0U49diZoaS6Uaxw3+bBE3lcMUKBFIk3c=
go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
github.com/tjhop/slog-gokit v0.1.4 h1:uj/vbDt3HaF0Py8bHPV4ti/s0utnO0miRbO277FLBKM=
github.com/tjhop/slog-gokit v0.1.4/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/collector/pdata v1.28.1 h1:ORl5WLpQJvjzBVpHu12lqKMdcf/qDBwRXMcUubhybiQ=
go.opentelemetry.io/collector/pdata v1.28.1/go.mod h1:asKE8MD/4SOKz1mCrGdAz4VO2U2HUNg8A6094uK7pq0=
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M=
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY=
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI=
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 h1:UIrZgRBHUrYRlJ4V419lVb4rs2ar0wFzKNAebaP05XU=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc=
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8=
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0=
go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY=
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0=
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s=
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

25
integration/loki/line.go Normal file
View File

@ -0,0 +1,25 @@
package loki
import (
"encoding/json"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type Line struct {
Name sesamy.EventName `json:"name"`
Params any `json:"params,omitempty"`
ClientID string `json:"client_id,omitempty"`
UserID string `json:"user_id,omitempty"`
UserProperties map[string]any `json:"user_properties,omitempty"`
Consent *mpv2.ConsentData `json:"consent,omitempty"`
UserData *mpv2.UserData `json:"user_data,omitempty"`
DebugMode bool `json:"debug_mode,omitempty"`
SessionID string `json:"session_id,omitempty"`
EngagementTimeMSec int64 `json:"engagement_time_msec,omitempty"`
}
func (l *Line) Marshal() ([]byte, error) {
return json.Marshal(l)
}

265
integration/loki/loki.go Normal file
View File

@ -0,0 +1,265 @@
package loki
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"net/http"
"time"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/utils"
"github.com/gogo/protobuf/proto"
"github.com/golang/snappy"
"github.com/grafana/dskit/backoff"
"github.com/grafana/loki/pkg/push"
"github.com/grafana/loki/v3/pkg/logproto"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"go.uber.org/zap"
)
const (
pushEndpoint = "/loki/api/v1/push"
defaultContentType = "application/x-protobuf"
defaultMaxReponseBufferLen = 1024
)
type (
Loki struct {
l *zap.Logger
endpoint string
// channel for incoming logs
entries chan logproto.Entry
// shutdown
cancel context.CancelFunc
// options
backoff *backoff.Config
batchSize int
bufferSize int
userAgent string
httpClient *http.Client
}
Option func(*Loki)
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func WithHTTPClient(v *http.Client) Option {
return func(l *Loki) {
l.httpClient = v
}
}
func WithBackoffConfig(v *backoff.Config) Option {
return func(l *Loki) {
l.backoff = v
}
}
func WithUserAgent(v string) Option {
return func(l *Loki) {
l.userAgent = v
}
}
func WithBatchSize(v int) Option {
return func(l *Loki) {
l.batchSize = v
}
}
func WithBufferSize(v int) Option {
return func(l *Loki) {
l.bufferSize = v
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
// New creates an instance of `Push` which writes logs directly to given `lokiAddr`
func New(l *zap.Logger, addr string, opts ...Option) *Loki {
inst := &Loki{
l: l,
endpoint: addr + pushEndpoint,
httpClient: http.DefaultClient,
userAgent: "sesamy",
batchSize: 10,
bufferSize: 50,
backoff: &backoff.Config{
MinBackoff: 500 * time.Millisecond,
MaxBackoff: 5 * time.Minute,
MaxRetries: 10,
},
}
for _, opt := range opts {
if opt != nil {
opt(inst)
}
}
inst.entries = make(chan logproto.Entry, inst.bufferSize) // Use a buffered channel so we can retry failed pushes without blocking WriteEntry
return inst
}
// Start pulls lines out of the channel and sends them to Loki
func (l *Loki) Start(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
l.cancel = cancel
utils.Batch(ctx, l.entries, l.batchSize, l.process)
}
func (l *Loki) Write(payload mpv2.Payload[any]) {
for _, event := range payload.Events {
// sanity check
if event.Name == "" {
l.l.Warn("received event without event name")
continue
}
line := Line{
Name: event.Name,
Params: event.Params,
UserID: payload.UserID,
Consent: payload.Consent,
UserData: payload.UserData,
ClientID: payload.ClientID,
UserProperties: payload.UserProperties,
DebugMode: payload.DebugMode,
SessionID: payload.SessionID,
EngagementTimeMSec: payload.EngagementTimeMSec,
}
lineBytes, err := line.Marshal()
if err != nil {
l.l.Warn("failed to marshal line", zap.Error(err))
continue
}
if len(l.entries) == l.bufferSize {
l.l.Warn("buffer size reached", zap.Int("size", l.bufferSize))
}
var timestamp time.Time
if payload.TimestampMicros > 0 {
timestamp = time.UnixMicro(payload.TimestampMicros)
} else {
timestamp = time.Now()
}
l.entries <- logproto.Entry{
Line: string(lineBytes),
Timestamp: timestamp,
StructuredMetadata: push.LabelsAdapter{
{
Name: "event_name",
Value: event.Name.String(),
},
},
}
}
}
// Stop will cancel any ongoing requests and stop the goroutine listening for requests
func (l *Loki) Stop() {
if l.cancel != nil {
l.cancel()
l.cancel = nil
}
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (l *Loki) process(entries []logproto.Entry) {
l.l.Debug("processing entries batch", zap.Int("num", len(entries)))
labels := model.LabelSet{
"name": "events",
"stream": "sesamy",
}
request, err := proto.Marshal(&logproto.PushRequest{
Streams: []logproto.Stream{
{
Labels: labels.String(),
Entries: entries,
Hash: uint64(labels.Fingerprint()),
},
},
})
if err != nil {
l.l.Error("failed to marshal payload to json", zap.Error(err))
return
}
payload := snappy.Encode(nil, request)
// We will use a timeout within each attempt to send
back := backoff.New(context.Background(), *l.backoff)
// send log with retry
for {
var status int
status, err = l.send(context.Background(), payload)
if err == nil {
break
}
if status > 0 && status != 429 && status/100 != 5 {
l.l.Error("failed to send entries, server rejected push with a non-retryable status code", zap.Error(err), zap.Int("status", status))
break
}
if !back.Ongoing() {
l.l.Error("failed to send entries, retries exhausted, entries will be dropped", zap.Error(err), zap.Int("status", status))
break
}
l.l.Warn("failed to send entries, retrying", zap.Error(err), zap.Int("status", status))
back.Wait()
}
}
// send makes one attempt to send the payload to Loki
func (l *Loki) send(ctx context.Context, payload []byte) (int, error) {
// Set a timeout for the request
ctx, cancel := context.WithTimeout(ctx, l.httpClient.Timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, l.endpoint, bytes.NewReader(payload))
if err != nil {
return -1, errors.Wrap(err, "failed to create request")
}
req.Header.Set("Content-Type", defaultContentType)
req.Header.Set("User-Agent", l.userAgent)
resp, err := l.httpClient.Do(req)
if err != nil {
return -1, errors.Wrap(err, "failed to send payload")
}
status := resp.StatusCode
if status/100 != 2 {
scanner := bufio.NewScanner(io.LimitReader(resp.Body, defaultMaxReponseBufferLen))
line := ""
if scanner.Scan() {
line = scanner.Text()
}
err = fmt.Errorf("server returned HTTP status %s (%d): %s", resp.Status, status, line)
}
if err := resp.Body.Close(); err != nil {
l.l.Error("failed to close response body", zap.Error(err))
}
return status, err
}

View File

@ -0,0 +1,42 @@
package loki
import (
"net/http"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
gtaghttp "github.com/foomo/sesamy-go/pkg/http/gtag"
mpv2http "github.com/foomo/sesamy-go/pkg/http/mpv2"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func GTagMiddleware(loki *Loki) gtaghttp.Middleware {
return func(next gtaghttp.MiddlewareHandler) gtaghttp.MiddlewareHandler {
return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
err := next(l, w, r, payload)
if err != nil {
// encode to mpv2
var mpv2Payload mpv2.Payload[any]
if err := gtagencode.MPv2(*payload, &mpv2Payload); err != nil {
return errors.Wrap(err, "failed to encode gtag to mpv2")
}
loki.Write(mpv2Payload)
}
return err
}
}
}
func MPv2Middleware(loki *Loki) mpv2http.Middleware {
return func(next mpv2http.MiddlewareHandler) mpv2http.MiddlewareHandler {
return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *mpv2.Payload[any]) error {
err := next(l, w, r, payload)
if err != nil {
loki.Write(*payload)
}
return err
}
}
}

View File

@ -0,0 +1,29 @@
package loki
import (
"context"
)
type Service struct {
loki *Loki
}
func NewService(loki *Loki) *Service {
return &Service{loki: loki}
}
func (s *Service) Name() string {
return "loki"
}
// Start pulls lines out of the channel and sends them to Loki
func (s *Service) Start(ctx context.Context) error {
s.loki.Start(ctx)
return nil
}
// Close will cancel any ongoing requests and stop the goroutine listening for requests
func (s *Service) Close(ctx context.Context) error {
s.loki.Stop()
return nil
}

View File

@ -1,6 +0,0 @@
package gtm
const (
HeaderUUID = "Message-Uuid"
ProviderName = "gtm"
)

View File

@ -1,157 +0,0 @@
package gtm
import (
"encoding/json"
"fmt"
"net/http"
"github.com/ThreeDotsLabs/watermill/message"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/pkg/errors"
)
var (
ErrErrorResponse = errors.New("server responded with error status")
ErrPublisherClosed = errors.New("publisher is closed")
)
type (
Publisher struct {
url string
client *http.Client
marshalMessageFunc MarshalMessageFunc
closed bool
}
PublisherOption func(*Publisher)
)
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewPublisher(url string, opts ...PublisherOption) *Publisher {
inst := &Publisher{
url: url,
client: http.DefaultClient,
}
for _, opt := range opts {
opt(inst)
}
return inst
}
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func PublisherWithClient(v *http.Client) PublisherOption {
return func(o *Publisher) {
o.client = v
}
}
func PublisherWithMarshalMessageFunc(v MarshalMessageFunc) PublisherOption {
return func(o *Publisher) {
o.marshalMessageFunc = v
}
}
// ------------------------------------------------------------------------------------------------
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (p *Publisher) Client() *http.Client {
return p.client
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (p *Publisher) Publish(topic string, messages ...*message.Message) error {
if p.closed {
return ErrPublisherClosed
}
for _, msg := range messages {
var event *mpv2.Event
if err := json.Unmarshal(msg.Payload, &event); err != nil {
return err
}
values, body, err := mpv2.Encode(event)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(msg.Context(), http.MethodPost, fmt.Sprintf("%s?%s", p.url, mpv2.EncodeValues(values)), body)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
for s, s2 := range msg.Metadata {
req.Header.Set(s, s2)
}
// logFields := watermill.LogFields{
// "uuid": msg.UUID,
// "provider": ProviderName,
// }
// p.l.Trace("Publishing message", logFields)
resp, err := p.client.Do(req)
if err != nil {
return errors.Wrapf(err, "failed to publish message: %s", msg.UUID)
}
if err = p.handleResponseBody(resp); err != nil {
return err
}
if resp.StatusCode >= http.StatusBadRequest {
return errors.Wrap(ErrErrorResponse, resp.Status)
}
// p.l.Trace("Message published", logFields)
}
return nil
}
func (p *Publisher) Close() error {
if p.closed {
return nil
}
p.closed = true
return nil
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (p *Publisher) handleResponseBody(resp *http.Response) error {
defer resp.Body.Close()
if resp.StatusCode < http.StatusBadRequest {
return nil
}
// body, err := io.ReadAll(resp.Body)
// if err != nil {
// return errors.Wrap(err, "could not read response body")
// }
// logFields = logFields.Add(watermill.LogFields{
// "http_status": resp.StatusCode,
// "http_response": string(body),
// })
// p.l.Info("Server responded with error", logFields)
return nil
}
// MarshalMessageFunc transforms the message into a HTTP request to be sent to the specified url.
type MarshalMessageFunc func(url string, msg *message.Message) (*http.Request, error)

View File

@ -1,5 +0,0 @@
package keel
const (
MetadataEventName = "X-Event-Name"
)

View File

@ -1,217 +0,0 @@
package keel
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
keellog "github.com/foomo/keel/net/http/log"
keelhttputils "github.com/foomo/keel/utils/net/http"
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/pkg/errors"
"go.uber.org/zap"
)
var (
ErrMissingEventName = errors.New("missing event name")
ErrContextCanceled = errors.New("request stopped without ACK received")
ErrMessageNacked = errors.New("message nacked")
ErrClosed = errors.New("subscriber already closed")
)
type (
Subscriber struct {
l *zap.Logger
uuidFunc func() string
messages chan *message.Message
middlewares []SubscriberMiddleware
closed bool
}
SubscriberOption func(*Subscriber)
SubscriberHandler func(l *zap.Logger, r *http.Request, event *mpv2.Event) error
SubscriberMiddleware func(next SubscriberHandler) SubscriberHandler
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func SubscriberWithUUIDFunc(v func() string) SubscriberOption {
return func(o *Subscriber) {
o.uuidFunc = v
}
}
func SubscriberWithMiddlewares(v ...SubscriberMiddleware) SubscriberOption {
return func(o *Subscriber) {
o.middlewares = append(o.middlewares, v...)
}
}
func SubscriberWithLogger(fields ...zap.Field) SubscriberOption {
return func(o *Subscriber) {
o.middlewares = append(o.middlewares, func(next SubscriberHandler) SubscriberHandler {
return func(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
fields := append(fields, zap.String("event_name", mp.GetDefault(event.EventName, "-").String()))
if labeler, ok := keellog.LabelerFromRequest(r); ok {
labeler.Add(fields...)
}
return next(l.With(fields...), r, event)
}
})
}
}
func SubscriberWithRequireEventName() SubscriberOption {
return func(o *Subscriber) {
o.middlewares = append(o.middlewares, func(next SubscriberHandler) SubscriberHandler {
return func(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
if event.EventName == nil {
return ErrMissingEventName
}
return next(l, r, event)
}
})
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewSubscriber(l *zap.Logger, opts ...SubscriberOption) *Subscriber {
inst := &Subscriber{
l: l,
uuidFunc: watermill.NewUUID,
messages: make(chan *message.Message),
}
for _, opt := range opts {
opt(inst)
}
return inst
}
func (s *Subscriber) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var values url.Values
switch r.Method {
case http.MethodGet:
values = r.URL.Query()
case http.MethodPost:
values = r.URL.Query()
// read request body
out, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("failed to read body: %s", err.Error()), http.StatusInternalServerError)
return
}
defer r.Body.Close()
// append request body to query
if len(out) > 0 {
v, err := url.ParseQuery(string(out))
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse extended url: %s", err.Error()), http.StatusInternalServerError)
return
}
for s2, i := range v {
values.Set(s2, i[0])
}
} else {
values = r.URL.Query()
}
default:
keelhttputils.ServerError(s.l, w, r, http.StatusMethodNotAllowed, errors.New(http.StatusText(http.StatusMethodNotAllowed)))
return
}
// unmarshal event
var event *mpv2.Event
if err := mpv2.Decode(values, &event); err != nil {
keelhttputils.InternalServerError(s.l, w, r, errors.Wrap(err, "failed to marshal url values"))
return
}
// compose middlewares
next := s.handle
for _, middleware := range s.middlewares {
next = middleware(next)
}
// run handler
if err := next(s.l, r, event); err != nil {
keelhttputils.InternalServerError(s.l, w, r, err)
return
}
}
func (s *Subscriber) handle(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
// marshal message payload
payload, err := json.Marshal(event)
if err != nil {
return errors.Wrap(err, "failed to marshal payload")
}
msg := message.NewMessage(s.uuidFunc(), payload)
l = l.With(zap.String("message_id", msg.UUID))
if labeler, ok := keellog.LabelerFromRequest(r); ok {
labeler.Add(zap.String("message_id", msg.UUID))
}
if event.EventName != nil {
msg.Metadata.Set(MetadataEventName, mp.Get(event.EventName).String())
}
// TODO filter headers?
for name, headers := range r.Header {
msg.Metadata.Set(name, strings.Join(headers, ","))
}
for k, v := range msg.Metadata {
l = l.With(zap.String(k, v))
}
// TODO different context?
ctx, cancelCtx := context.WithCancel(r.Context())
msg.SetContext(ctx)
defer cancelCtx()
// send message
s.messages <- msg
// wait for ACK
select {
case <-msg.Acked():
l.Debug("message acked")
return nil
case <-msg.Nacked():
l.Debug("message nacked")
return ErrMessageNacked
case <-r.Context().Done():
l.Debug("message cancled")
return ErrContextCanceled
}
}
func (s *Subscriber) Subscribe(ctx context.Context, topic string) (<-chan *message.Message, error) {
return s.messages, nil
}
// Close closes all subscriptions with their output channels and flush offsets etc. when needed.
func (s *Subscriber) Close() error {
if s.closed {
return ErrClosed
}
s.closed = true
close(s.messages)
return nil
}

View File

@ -1,30 +0,0 @@
package v2
import (
"encoding/json"
"github.com/ThreeDotsLabs/watermill/message"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/pkg/errors"
)
func EventHandler(eventHandler func(event *mpv2.Event, msg *message.Message) error) func(msg *message.Message) ([]*message.Message, error) {
return func(msg *message.Message) ([]*message.Message, error) {
var event *mpv2.Event
if err := json.Unmarshal(msg.Payload, &event); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal event")
}
if err := eventHandler(event, msg); err != nil {
return nil, err
}
b, err := json.Marshal(event)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal event")
}
msg.Payload = b
return []*message.Message{msg}, nil
}
}

View File

@ -1,104 +0,0 @@
package measurementprotocol
import (
"fmt"
)
func Set[T any](v T) *T {
return &v
}
func SetInt(v int) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetUInt(v uint) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetInt32(v int32) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetUInt32(v uint32) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetInt64(v int64) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetUInt64(v uint64) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%d", v))
}
func SetFloat32(v float32) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%f", v))
}
func SetFloat64(v float64) *string {
if v == 0 {
return nil
}
return Set(fmt.Sprintf("%f", v))
}
func SetString(v string) *string {
if v == "" {
return nil
}
return Set(v)
}
func SetBool(v bool) *string {
if !v {
return nil
}
return Set("1")
}
func SetStringMap(v map[string]string) map[string]string {
if len(v) == 0 {
return nil
}
return v
}
func AddStringMap(t map[string]string, k string, v *string) {
if v == nil {
return
}
t[k] = *v
}
func Get[T any](v *T) T {
return *v
}
func GetDefault[T any](v *T, fallback T) T {
if v == nil {
return fallback
}
return *v
}

View File

@ -1,98 +0,0 @@
package v2
import (
"net/url"
"regexp"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func Decode(input url.Values, output interface{}) error {
data := Data{}
// decode values
for k, v := range input {
// handle maps
if ok, err := DecodeMapValue(k, v, data); err != nil {
return err
} else if ok {
continue
}
// handle slices
if ok, err := DecodeRegexValue(k, v, RegexProduct, data, ParameterItem); err != nil {
return err
} else if ok {
continue
}
// default
v, err := url.QueryUnescape(v[0])
if err != nil {
return err
}
data[k] = v
}
if err := mapstructure.WeakDecode(data, output); err != nil {
return errors.Wrap(err, "failed to weakly decode query")
}
return nil
}
func DecodeMapValue(k string, v []string, data Data) (bool, error) {
if strings.Contains(k, ".") {
parts := strings.Split(k, ".")
if _, ok := data[parts[0]]; !ok {
data[parts[0]] = map[string]any{}
}
if value, ok := data[parts[0]].(map[string]any); ok {
v, err := url.QueryUnescape(v[0])
if err != nil {
return false, err
}
value[strings.Join(parts[1:], ".")] = v
}
return true, nil
}
return false, nil
}
// DecodeRegexValue e.g. `pr1=idSKU_123456` = map["pr"][]map["id"]="SKU_123456"
func DecodeRegexValue(k string, v []string, r *regexp.Regexp, data Data, key string) (bool, error) {
if r.MatchString(k) {
value, err := DecodeObjectValue(v[0])
if err != nil {
return false, err
}
if value != nil {
v, ok := data[key].([]map[string]any)
if !ok {
v = []map[string]any{}
}
v = append(v, value)
data[key] = v
return true, nil
}
}
return false, nil
}
// DecodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
func DecodeObjectValue(s string) (map[string]any, error) {
if len(s) == 0 {
return nil, nil //nolint:nilnil
}
ret := map[string]any{}
for _, part := range strings.Split(s, "~") {
v, err := url.QueryUnescape(part[2:])
if err != nil {
return nil, err
}
ret[part[0:2]] = v
}
return ret, nil
}

View File

@ -1,134 +0,0 @@
package v2_test
import (
"net/url"
"reflect"
"testing"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
tests := []struct {
name string
args string
want error
}{
{
name: "page_view",
args: "v=2&tid=G-123456&gtm=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
want: nil,
},
{
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: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.ParseQuery(tt.args)
require.NoError(t, err)
e := &mpv2.Event{}
if err := mpv2.Decode(u, e); !assert.ErrorIs(t, err, tt.want) {
t.Errorf("Decode() = %v, want %v", err, tt.want)
t.Log(e)
}
})
}
}
func TestDecodeMapValue(t *testing.T) {
tests := []struct {
name string
args string
want bool
}{
{
name: "v=2",
want: false,
},
{
name: "ep.foo=bar",
want: true,
},
{
name: "ep.foo.bar=bar",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := mpv2.Data{}
values, err := url.ParseQuery(tt.name)
require.NoError(t, err)
for k, v := range values {
if got, err := mpv2.DecodeMapValue(k, v, d); assert.NoError(t, err) && got != tt.want {
t.Errorf("DecodeMapValue() = %v, want %v", got, tt.want)
t.Log(d)
}
}
})
}
}
func TestDecodeProductValue(t *testing.T) {
tests := []struct {
name string
args string
want bool
}{
{
name: "v=2",
want: false,
},
{
name: "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~pr9.99~qt1",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := mpv2.Data{}
values, err := url.ParseQuery(tt.name)
require.NoError(t, err)
for k, v := range values {
if got, err := mpv2.DecodeRegexValue(k, v, mpv2.RegexProduct, d, "pr"); assert.NoError(t, err) && got != tt.want {
t.Errorf("decodeMapValue() = %v, want %v", got, tt.want)
t.Log(d)
}
}
})
}
}
func TestDecodeObjectValue(t *testing.T) {
tests := []struct {
name string
args string
want map[string]any
}{
{
name: "",
want: nil,
},
{
name: "idSKU_12345~nmStan%20and%20Friends%20Tee~qt1",
want: map[string]any{
"id": "SKU_12345",
"nm": "Stan and Friends Tee",
"qt": "1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, err := mpv2.DecodeObjectValue(tt.name); assert.NoError(t, err) && !reflect.DeepEqual(got, tt.want) {
t.Errorf("decodeObjectValue() = %v, want %v", got, tt.want)
t.Log(got)
}
})
}
}

View File

@ -1,43 +0,0 @@
package v2_test
import (
"net/url"
"testing"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEncode(t *testing.T) {
tests := []struct {
name string
args string
want error
}{
{
name: "page_view",
args: "v=2&tid=G-123456&gtm=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
want: nil,
},
// {
// 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: nil,
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.ParseQuery(tt.args)
require.NoError(t, err)
e := &mpv2.Event{}
require.NoError(t, mpv2.Decode(u, e))
if !assert.Empty(t, e.Unknown) {
t.Errorf("Encode() = %v, want %v", e.Unknown, nil)
}
if out, _, err := mpv2.Encode(e); assert.NoError(t, err) {
assert.EqualValues(t, u, out)
}
})
}
}

View File

@ -1,263 +0,0 @@
package v2
// See https://www.thyngster.com/ga4-measurement-protocol-cheatsheet/
type Event struct {
// --- Request parameters ---
// Defines que current protocol version being used.
// Example: 2
ProtocolVersion *string `json:"v,omitempty" mapstructure:"v,omitempty"`
// Current Stream ID / Measurement ID
// Example: G-THYNGSTER
TrackingID *string `json:"tid,omitempty" mapstructure:"tid,omitempty"`
// If the current hit is coming was generated from GTM, it will contain a hash of current GTM/GTAG config
// Example: 2oear0
GTMHashInfo *string `json:"gtm,omitempty" mapstructure:"gtm,omitempty"`
// Is a random hash generated on the page load.
// Examole: 456193680
RandomPageLoadHash *string `json:"_p,omitempty" mapstructure:"_p,omitempty"`
// Browser screen resolution in format width x height
// Example: 2560x1440
ScreenResolution *string `json:"sr,omitempty" mapstructure:"sr,omitempty"`
// Browser active locale.
// Example: es-es
UserLanguage *string `json:"ul,omitempty" mapstructure:"ul,omitempty"`
// Current Document Hostname
// Exampple: www.analytics-debugger.com
DocumentHostname *string `json:"dh,omitempty" mapstructure:"dh,omitempty"`
// Google Analytics Client Id
// Example: 281344611.1635634925
ClientID *string `json:"cid,omitempty" mapstructure:"cid,omitempty"`
// Current hits counter for the current page load
// Example: 1
HitCounter *string `json:"_s,omitempty" mapstructure:"_s,omitempty"`
// This is supposed to be to enrich the GA4 hits to send data to SGTM, at this point is always set as an empty value...
Richsstsse *string `json:"richsstsse,omitempty" mapstructure:"richsstsse,omitempty"`
// --- Client Hints ---
// Example: x86
UserAgentArchitecture *string `json:"uaa,omitempty" mapstructure:"uaa,omitempty"`
// The "bitness" of the user-agent's underlying CPU architecture. This is the size in bits of an integer or memory address—typically 64 or 32 bits.
// Example: 32 | 64
UserAgentBitness *string `json:"uab,omitempty" mapstructure:"uab,omitempty"`
// The brand and full version information for each brand associated with the browser, in a comma-separated list
// Example: Google Chrome;105.0.5195.127|Not)A;Brand;8.0.0.0|Chromium;105.0.5195.127
UserAgentFullVersionList *string `json:"uafvl,omitempty" mapstructure:"uafvl,omitempty"`
// Indicates whether the browser is on a mobile device
// Example: 1
UserAgentMobile *string `json:"uamb,omitempty" mapstructure:"uamb,omitempty"`
// The device model on which the browser is running. Will likely be empty for desktop browsers
// Example: Nexus 6
UserAgentModel *string `json:"uam,omitempty" mapstructure:"uam,omitempty"`
// The platform or operating system on which the user agent is running
// Example: Chromium OS | macOS | Android | iOS
UserAgentPlatform *string `json:"uap,omitempty" mapstructure:"uap,omitempty"`
// The version of the operating system on which the user agent is running
// Example: 14.0.0
UserAgentPlatformVersion *string `json:"uapv,omitempty" mapstructure:"uapv,omitempty"`
// Whatever Windows On Windows 64 Bit is supported. Used by "WoW64-ness" sites. ( running 32bits app on 64bits windows)
// Example: 1
UserAgentWOW64 *string `json:"uaw,omitempty" mapstructure:"uaw,omitempty"`
// --- Shared ---
// Actual page's Pathname. It does not include the hostname, quertyString or Fragment
// Example: /hire-me
DocumentLocation *string `json:"dl,omitempty" mapstructure:"dl,omitempty"`
// Actual page's Title
// Example: Hire Me
DocumentTitle *string `json:"dt,omitempty" mapstructure:"dt,omitempty"`
// Actual page's Referrer
// Example:
DocumentReferrer *string `json:"dr,omitempty" mapstructure:"dr,omitempty"`
// Unknown. Value ccd.{{HASH}}. The hash in based on various internal parameters. Some kind of usage hash.
// Example: ccd.AAB
Z *string `json:"_z,omitempty" mapstructure:"_z,omitempty"`
// This is added when an event is generated from rules (from the admin). Actually is hash of the "GA4_EVENT" string
// Example: Q
EventUsage *string `json:"_eu,omitempty" mapstructure:"_eu,omitempty"`
// Unknown
// Example:
EventDebugID *string `json:"edid,omitempty" mapstructure:"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:"_dbg,omitempty" mapstructure:"_dbg,omitempty"`
// If the current request has a referrer, it will be ignored at processing level
// Example: 1
IgnoreReferrer *string `json:"ir,omitempty" mapstructure:"ir,omitempty"`
// Traffic Type
// Example: 1
TrafficType *string `json:"tt,omitempty" mapstructure:"tt,omitempty"`
// Current Google Consent Status. Format 'G1'+'AdsStorageBoolStatus'`+'AnalyticsStorageBoolStatus'
// Example: G101
GoogleConsentStatus *string `json:"gcs,omitempty" mapstructure:"gcs,omitempty"`
// Will be added with the value "1" if the Google Consent has just been updated (wait_for_update setting on GTAG)
// Example: 1
GoogleConsentUpdate *string `json:"gcu,omitempty" mapstructure:"gcu,omitempty"`
// Documented values, 1 or 2, no more info on the meaning
// Example: 2
GoogleConsentUpdateType *string `json:"gcut,omitempty" mapstructure:"gcut,omitempty"`
// Will be added with the value "1" if the Google Consent had a default value before getting an update
// Example: G111
GoogleConsentDefault *string `json:"gcd,omitempty" mapstructure:"gcd,omitempty"`
// Will be set to 1 is the current page has a linker and this last one is valid
// Example: 1
IsGoogleLinkerValid *string `json:"_glv,omitempty" mapstructure:"_glv,omitempty"`
// --- Campaign Attributes ---
// Campaign Medium ( utm_medium ), this will override the current values read from the url
// Example: cpc
CampaignMedium *string `json:"cm,omitempty" mapstructure:"cm,omitempty"`
// Campaign Source ( utm_source ), this will override the current values read from the url
// Example: google
CampaignSource *string `json:"cs,omitempty" mapstructure:"cs,omitempty"`
// Campaign Name ( utm_campaign ), this will override the current values read from the url
// Example: cpc
CampaignName *string `json:"cn,omitempty" mapstructure:"cn,omitempty"`
// Campaign Content ( utm_content ), this will override the current values read from the url
// Example: big banner
CampaignContent *string `json:"cc,omitempty" mapstructure:"cc,omitempty"`
// Campaign Term ( utm_term ), this will override the current values read from the url
// Example: summer
CampaignTerm *string `json:"ck,omitempty" mapstructure:"ck,omitempty"`
// Campaign Creative Format ( utm_creative_format ), this will override the current values read from the url
// Example: native
CampaignCreativeFormat *string `json:"ccf,omitempty" mapstructure:"ccf,omitempty"`
// Campaign Marketing Tactic ( utm_marketing_tactic ), this will override the current values read from the url
// Example: prospecting
CampaignMarketingTactic *string `json:"cmt,omitempty" mapstructure:"cmt,omitempty"`
// Random Number used to Dedupe gclid
// Example: 342342343
GclidDeduper *string `json:"_rnd,omitempty" mapstructure:"_rnd,omitempty"`
// --- Event Parameters ---
// Current Event Name.
// Example: page_view
EventName *EventName `json:"en,omitempty" mapstructure:"en,omitempty"`
// It's the total engagement time in milliseconds since the last event. The engagement time is measured only when the current page is visible and active ( ie: the browser window/tab must be active and visible ), for this GA4 uses the window.events: focus, blur, pageshow, pagehide and the document:visibilitychange, these will determine when the timer starts and pauses
// Example: 1234
EngagementTime *string `json:"_et,omitempty" mapstructure:"_et,omitempty"`
// Defines a parameter for the current Event
// Example: ep.page_type: checkout
EventParameter map[string]string `json:"ep,omitempty" mapstructure:"ep,omitempty"`
// Defines a parameter for the current Event
// Example: epn.plays_count: 42
EventParameterNumber map[string]string `json:"epn,omitempty" mapstructure:"epn,omitempty"`
// If the current event is set as a conversion on the admin interacted the evfent will have this value present
// Example: 1
IsConversion *string `json:"_c,omitempty" mapstructure:"_c,omitempty"`
// External Event
ExternalEvent *string `json:"_ee,omitempty" mapstructure:"_ee,omitempty"`
// --- Session / User Related ---
// Current User ID
// Example: 1635691016
UserID *string `json:"uid,omitempty" mapstructure:"uid,omitempty"`
// Current Firebase ID
// Example: HASHSAH
FirebaseID *string `json:"_fid,omitempty" mapstructure:"_fid,omitempty"`
// GA4 Session Id. This comes from the GA4 Cookie. It may be different for each Stream ID Configured on the site
// Example: 1635691016
SessionID *string `json:"sid,omitempty" mapstructure:"sid,omitempty"`
// Count of sessions recorded by GA4. This value increases by one each time a new session is detected ( when the session expires )
// Example: 10
SessionCount *string `json:"sct,omitempty" mapstructure:"sct,omitempty"`
// If the current user is engaged in any way, this value will be 1
// Example:
SessionEngagment *string `json:"seg,omitempty" mapstructure:"seg,omitempty"`
// Defines an user Propery for the current Measurement ID
// Example: up.is_premium_user: yes
UserProperty map[string]string `json:"up,omitempty" mapstructure:"up,omitempty"`
// Defines an user Propery for the current Measurement ID
// Example:
UserPropertyNumber map[string]string `json:"upn,omitempty" mapstructure:"upn,omitempty"`
// If the "_ga_THYNGSTER" cookie is not set, the first event will have this value present. This will internally create a new "first_visit" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
// Example: 1|2
FirstVisit *string `json:"_fv,omitempty" mapstructure:"_fv,omitempty"`
// If the "_ga_THYNGSTER" cookie last session time value is older than 1800 seconds, the current event will have this value present. This will internally create a new "session_start" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
// Example: 1|2
SessionStart *string `json:"_ss,omitempty" mapstructure:"_ss,omitempty"`
// This seems to be related to the ServerSide hits, it's 0 if the FPLC Cookie is not present and to the current value if it's coming from a Cross Domain linker
// Example: bVhVicbfiSXaGNxeawKaPlDQc9QXPD6bKcsn36Elden6wZNb7Q5X1iXlkTVP5iP3H3y76cgM3UIgHCaRsYfPoyLGlbiIYMPRjvnUU7KWbdWLagodzxjrlPnvaRZJkw
FirstPartyLinkerCookie *string `json:"_fplc,omitempty" mapstructure:"_fplc,omitempty"`
// If the current user has a GA4 session cookie, but not a GA (_ga) client id cookie, this parameter will be added to the hit
// Example: 1
NewSessionID *string `json:"_nsi,omitempty" mapstructure:"_nsi,omitempty"`
// You may find this parameter if using some vendor plugin o platform ( ie: using shopify integration or a prestashop plugin )
// Example: jdhsd87
GoogleDeveloperID *string `json:"gdid,omitempty" mapstructure:"gdid,omitempty"`
// Added to report the current country for the user under some circumstanced. To be documented.
// Example: ES
UserCountry *string `json:"_uc,omitempty" mapstructure:"_uc,omitempty"`
// Example: DE-BY
UserRegion *string `json:"ur,omitempty" mapstructure:"ur,omitempty"`
// --- eCommerce ---
// Currency Code. ISO 4217
// Example: JPY
Currency *string `json:"cu,omitempty" mapstructure:"cu,omitempty"`
// Example:
Items []*Item `json:"pr,omitempty" mapstructure:"pr,omitempty"`
// Promotion Impression/Click Tracking. Promotion Id
// Example: summer-offer
PromotionID *string `json:"pi,omitempty" mapstructure:"pi,omitempty"`
// Promotion Impression/Click Tracking. Promotion Name
// Example: summer-offer
PromotionName *string `json:"pn,omitempty" mapstructure:"pn,omitempty"`
// Promotion Impression/Click Tracking. Creative Name
// Example: red-car
// CreativeName *string `json:"cn,omitempty" mapstructure:"cn,omitempty"`
// Promotion Impression/Click Tracking. Promotion Slot / Position
// Example: slide-3
// CreativeSlot *string `json:"cs,omitempty" mapstructure:"cs,omitempty"`
// Google Place ID: Refer to: https://developers.google.com/maps/documentation/places/web-service/place-id . Seems to be inherited from Firebase, not sure about the current use on GA4
// Example: ChIJiyj437sx3YAR9kUWC8QkLzQ
LocationID *string `json:"lo,omitempty" mapstructure:"lo,omitempty"`
// --- Uncategorized / Missing Info ---
// Example: 1
GTMUp *string `json:"gtm_up,omitempty" mapstructure:"gtm_up,omitempty"`
// Documented values, 1,2,3: Not sure when it's added.
EuropeanConsentModeEnabledID *string `json:"_ecid,omitempty" mapstructure:"_ecid,omitempty"`
// Example:
UEI *string `json:"_uei,omitempty" mapstructure:"_uei,omitempty"`
// It's set when a Google Join is created/imported. Google Signals
// Example: 1
CreateGoogleJoin *string `json:"_gaz,omitempty" mapstructure:"_gaz,omitempty"`
// Example: Redact Device Info. Need Investigation about functionality
RedactDeviceInfo *string `json:"_rdi,omitempty" mapstructure:"_rdi,omitempty"`
// Geo Granularity. Need Investigation about functionality
GeoGranularity *string `json:"_geo,omitempty" mapstructure:"_geo,omitempty"`
// Sent on sites that implement the US Privacy User Signal Mechanism, sent if window.__uspapi is present and returning a value.
// Example: 1YNY
USPrivacySignal *string `json:"us_privacy,omitempty" mapstructure:"us_privacy,omitempty"`
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
// Example: 1
GDPR *string `json:"gdpr,omitempty" mapstructure:"gdpr,omitempty"`
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
// Example: CPfPdAAPfPdAAAHABBENCgCsAP_AAAAAAAAAI_tf_X__b3_j-_5___t0eY1f9_7__-0zjhfdl-8N3f_X_L8X_2M7vF36tq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbeydYGH9_n1_z-ZKY7_____77__-____3_____-_f___5_3____f_V__97fn9_____9_P___9v__9__________3___gAAAJJQAYAAgj-GgAwABBH8VABgACCP5SADAAEEfx0AGAAII_kIAMAAQR_CQAYAAgj-IgAwABBH8ZABgACCP4A.f_gAAAAAAAAA
GDPRConsent *string `json:"gdpr_consent,omitempty" mapstructure:"gdpr_consent,omitempty"`
// Example: sypham
NonPersonalizedAds *string `json:"npa,omitempty" mapstructure:"npa,omitempty"`
// Example: 1
ARE *string `json:"are,omitempty" mapstructure:"are,omitempty"`
// Example: 1
DigitalMarketAct *string `json:"dma,omitempty" mapstructure:"dma,omitempty"`
// Example: sypham
DigitalMarketActParameters *string `json:"dma_cps,omitempty" mapstructure:"dma_cps,omitempty"`
// Example: noapi | denied
PrivacySandboxCookieDeprecationLabel *string `json:"pscdl,omitempty" mapstructure:"pscdl,omitempty"`
// A timestamp measuring the difference between the moment this parameter gets populated and the moment the navigation started on that particular page.
TFD *string `json:"tfd,omitempty" mapstructure:"tfd,omitempty"`
SST *SST `json:"sst,omitempty" mapstructure:"sst,omitempty"`
PAE *string `json:"pae,omitempty" mapstructure:"pae,omitempty"`
// --- Unresolved ---
Unknown map[string]any `json:"-" mapstructure:",remain"`
}

View File

@ -1,67 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
AddPaymentInfo https://developers.google.com/tag-platform/gtagjs/reference/events#add_payment_info
gtag('event', 'add_payment_info', {
currency: 'USD',
value: 30.03,
coupon: 'SUMMER_FUN',
payment_type: 'Credit Card',
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-123456&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=5&cu=USD&sid=1709445696&sct=8&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_payment_info&_ss=1&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&ep.coupon=SUMMER_FUN&ep.payment_type=Credit%20Card&tfd=120434230&richsstsse
*/
type AddPaymentInfo struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Coupon string `json:"coupon,omitempty"`
PaymentType string `json:"payment_type,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *AddPaymentInfo) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
mp.AddStringMap(eventParameter, mpv2.EventParameterPaymentType.String(), mp.SetString(e.PaymentType))
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameAddPaymentInfo),
Currency: mp.SetString(e.Currency),
EventParameter: mp.SetStringMap(eventParameter),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,67 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
AddShippingInfo https://developers.google.com/tag-platform/gtagjs/reference/events#add_shipping_info
gtag('event', 'add_payment_info', {
currency: 'USD',
value: 30.03,
coupon: 'SUMMER_FUN',
shipping_tier: 'Ground',
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45je42t1v9177778896z89175355532za220&_p=1709357665402&_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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709357665402&_s=10&cu=USD&sid=1709529821&sct=9&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709357665301&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_payment_info&_ss=1&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&ep.coupon=SUMMER_FUN&ep.shipping_tier=Ground&tfd=137238301&richsstsse
*/
type AddShippingInfo struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Coupon string `json:"coupon,omitempty"`
ShippingTier string `json:"shipping_tier,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *AddShippingInfo) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
mp.AddStringMap(eventParameter, mpv2.EventParameterShippingTier.String(), mp.SetString(e.ShippingTier))
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameAddShippingInfo),
Currency: mp.SetString(e.Currency),
EventParameter: mp.SetStringMap(eventParameter),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,60 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
AddToCart https://developers.google.com/tag-platform/gtagjs/reference/events#add_to_cart
gtag('event', 'add_to_cart', {
currency: 'USD',
value: 30.03,
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%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&_et=3187&tfd=11387&richsstsse
*/
type AddToCart struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *AddToCart) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameAddToCart),
Currency: mp.SetString(e.Currency),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
Richsstsse: mp.Set(""),
}, nil
}

View File

@ -1,59 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
AddToWishlist https://developers.google.com/tag-platform/gtagjs/reference/events#add_to_wishlist
gtag('event', 'add_to_wishlist', {
currency: 'USD',
value: 30.03,
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_wishlist&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&_et=3187&tfd=11387&richsstsse
*/
type AddToWishlist struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *AddToWishlist) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameAddToWishlist),
Currency: mp.SetString(e.Currency),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,64 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
BeginCheckout https://developers.google.com/tag-platform/gtagjs/reference/events#begin_checkout
gtag('event', 'begin_checkout', {
currency: 'USD',
value: 30.03,
coupon: 'SUMMER_FUN',
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-123456&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=5&cu=USD&sid=1709445696&sct=8&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=begin_checkout&_ss=1&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&ep.coupon=SUMMER_FUN&tfd=120434230&richsstsse
*/
type BeginCheckout struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Coupon string `json:"coupon,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *BeginCheckout) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameBeginCheckout),
Currency: mp.SetString(e.Currency),
EventParameter: mp.SetStringMap(eventParameter),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,50 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
type Item struct {
Affiliation string `json:"affiliation,omitempty"`
Coupon string `json:"coupon,omitempty"`
Discount float64 `json:"discount,omitempty"`
Index int `json:"index,omitempty"`
ItemBrand string `json:"item_brand,omitempty"`
ItemCategory string `json:"item_category,omitempty"`
ItemCategory2 string `json:"item_category2,omitempty"`
ItemCategory3 string `json:"item_category3,omitempty"`
ItemCategory4 string `json:"item_category4,omitempty"`
ItemCategory5 string `json:"item_category5,omitempty"`
ItemID string `json:"item_id,omitempty"`
ItemListName string `json:"item_list_name,omitempty"`
ItemName string `json:"item_name,omitempty"`
ItemVariant string `json:"item_variant,omitempty"`
ItemListID string `json:"listId,omitempty"`
LocationID string `json:"location_id,omitempty"`
Price string `json:"price,omitempty"`
Quantity float64 `json:"quantity,omitempty"`
}
func (e *Item) MarshalMPv2() *mpv2.Item {
return &mpv2.Item{
ID: mp.SetString(e.ItemID),
Name: mp.SetString(e.ItemName),
Brand: mp.SetString(e.ItemBrand),
CategoryHierarchy1: mp.SetString(e.ItemCategory),
CategoryHierarchy2: mp.SetString(e.ItemCategory2),
CategoryHierarchy3: mp.SetString(e.ItemCategory3),
CategoryHierarchy4: mp.SetString(e.ItemCategory4),
CategoryHierarchy5: mp.SetString(e.ItemCategory5),
Price: mp.SetString(e.Price),
Quantity: mp.SetFloat64(e.Quantity),
Variant: mp.SetString(e.ItemVariant),
Coupon: mp.SetString(e.Coupon),
Discount: mp.SetFloat64(e.Discount),
ListName: mp.SetString(e.ItemListName),
ListID: mp.SetString(e.ItemListID),
ListPosition: mp.SetInt(e.Index),
Affiliation: mp.SetString(e.Affiliation),
LocationID: mp.SetString(e.LocationID),
}
}

View File

@ -1,28 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
Login - Send this event to signify that a user has logged in to your website or app.
gtag('event', 'login', {
method: 'Google'
});
Reference: https://developers.google.com/tag-platform/gtagjs/reference/events#login
*/
type Login struct {
Method string `json:"method,omitempty"`
}
func (e *Login) MarshalMPv2() (*mpv2.Event, error) {
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterMethod.String(), mp.SetString(e.Method))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameLogin),
EventParameter: mp.SetStringMap(eventParameter),
}, nil
}

View File

@ -1,73 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
Purchase https://developers.google.com/tag-platform/gtagjs/reference/events#purchase
gtag('event', 'purchase', {
currency: 'USD',
value: 30.03,
transaction_id: 'T_12345',
coupon: 'SUMMER_FUN',
shipping: 3.33,
tax: 1.11,
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45he42t1v9177778896z89175355532za220&_p=1709537217366&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=&sst.gcd=13l3l3l3l1&sst.tft=1709537217366&_s=6&cu=USD&sid=1709534872&sct=10&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709537217296&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=purchase&_c=1&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&ep.transaction_id=T_12345&ep.coupon=SUMMER_FUN&epn.shipping=3.33&epn.tax=1.11&tfd=184622&richsstsse
*/
type Purchase struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
TransactionID string `json:"transaction_id,omitempty"`
Coupon string `json:"coupon,omitempty"`
Shipping float64 `json:"shipping,omitempty"`
Tax float64 `json:"tax,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *Purchase) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterTransactionID.String(), mp.SetString(e.TransactionID))
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberShipping.String(), mp.SetFloat64(e.Shipping))
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberTax.String(), mp.SetFloat64(e.Tax))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNamePurchase),
Currency: mp.SetString(e.Currency),
EventParameter: mp.SetStringMap(eventParameter),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,73 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
Refund https://developers.google.com/tag-platform/gtagjs/reference/events#refund
gtag('event', 'refund', {
currency: 'USD',
value: 30.03,
transaction_id: 'T_12345',
coupon: 'SUMMER_FUN',
shipping: 3.33,
tax: 1.11,
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45he42t1v9177778896z89175355532za220&_p=1709537217366&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=&sst.gcd=13l3l3l3l1&sst.tft=1709537217366&_s=6&cu=USD&sid=1709534872&sct=10&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709537217296&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=refund&_c=1&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&ep.transaction_id=T_12345&ep.coupon=SUMMER_FUN&epn.shipping=3.33&epn.tax=1.11&tfd=184622&richsstsse
*/
type Refund struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
TransactionID string `json:"transaction_id,omitempty"`
Coupon string `json:"coupon,omitempty"`
Shipping float64 `json:"shipping,omitempty"`
Tax float64 `json:"tax,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *Refund) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterTransactionID.String(), mp.SetString(e.TransactionID))
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberShipping.String(), mp.SetFloat64(e.Shipping))
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberTax.String(), mp.SetFloat64(e.Tax))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameRefund),
Currency: mp.SetString(e.Currency),
EventParameter: mp.SetStringMap(eventParameter),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,59 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
RemoveFromCart https://developers.google.com/tag-platform/gtagjs/reference/events#remove_from_cart
gtag('event', 'remove_from_cart', {
currency: 'USD',
value: 30.03,
items: [
{
item_id: 'SKU_12345',
item_name: 'Stan and Friends Tee',
affiliation: 'Google Store',
coupon: 'SUMMER_FUN',
discount: 2.22,
index: 5,
item_brand: 'Google',
item_category: 'Apparel',
item_category2: 'Adult',
item_category3: 'Shirts',
item_category4: 'Crew',
item_category5: 'Short sleeve',
item_list_id: 'related_products',
item_list_name: 'Related products',
item_variant: 'green',
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
price: 10.01,
quantity: 3
}
]
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=remove_from_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&_et=3187&tfd=11387&richsstsse
*/
type RemoveFromCart struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Items []*Item `json:"items,omitempty"`
}
func (e *RemoveFromCart) MarshalMPv2() (*mpv2.Event, error) {
items := make([]*mpv2.Item, len(e.Items))
for i, item := range e.Items {
items[i] = item.MarshalMPv2()
}
eventParameterNumber := map[string]string{}
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameRemoveFromCart),
Currency: mp.SetString(e.Currency),
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
Items: items,
}, nil
}

View File

@ -1,28 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
Search https://developers.google.com/tag-platform/gtagjs/reference/events#search
gtag('event', 'search', {
search_term: 't-shirts',
});
Query: v=2&tid=G-PZ5ELRCR31&gtm=45je42t1v9177778896z89175355532za200&_p=1709325262551&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%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&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=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=search&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&_et=3187&tfd=11387&richsstsse
*/
type Search struct {
SearchTerm string `json:"search_term,omitempty"`
}
func (e *Search) MarshalMPv2() (*mpv2.Event, error) {
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterSearchTerm.String(), mp.SetString(e.SearchTerm))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameSearch),
EventParameter: mp.SetStringMap(eventParameter),
}, nil
}

View File

@ -1,28 +0,0 @@
package event
import (
mp "github.com/foomo/sesamy-go/measurementprotocol"
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
)
/*
SignUp - Send this event to signify that a user has logged in to your website or app.
gtag('event', 'sign_up', {
method: 'Google'
});
Reference: https://developers.google.com/tag-platform/gtagjs/reference/events#sign_up
*/
type SignUp struct {
Method string `json:"method,omitempty"`
}
func (e *SignUp) MarshalMPv2() (*mpv2.Event, error) {
eventParameter := map[string]string{}
mp.AddStringMap(eventParameter, mpv2.EventParameterMethod.String(), mp.SetString(e.Method))
return &mpv2.Event{
EventName: mp.Set(mpv2.EventNameSignUp),
EventParameter: mp.SetStringMap(eventParameter),
}, nil
}

View File

@ -1,39 +0,0 @@
package v2
type EventName string
const (
EventNameAddPaymentInfo EventName = "add_payment_info"
EventNameAddShippingInfo EventName = "add_shipping_info"
EventNameAddToCart EventName = "add_to_cart"
EventNameAddToWishlist EventName = "add_to_wishlit"
EventNameBeginCheckout EventName = "begin_checkout"
EventNameEarnVirtualMoney EventName = "earn_virtual_money"
EventNameGenerateLead EventName = "generate_lead"
EventNameJoinGroup EventName = "join_group"
EventNameLevelEnd EventName = "level_end"
EventNameLevelStart EventName = "level_start"
EventNameLevelUp EventName = "level_up"
EventNameLogin EventName = "login"
EventNamePostScore EventName = "post_score"
EventNamePurchase EventName = "purchase"
EventNameRefund EventName = "refund"
EventNameRemoveFromCart EventName = "remove_from_cart"
EventNameSearch EventName = "search"
EventNameSelectContent EventName = "select_content"
EventNameSelectItem EventName = "select_item"
EventNameShare EventName = "share"
EventNameSignUp EventName = "sign_up"
EventNameSpendVirtualMoney EventName = "spend_virtual_money"
EventNameTutorialBegin EventName = "tutorial_begin"
EventNameTuturialComplete EventName = "tutorial_complete"
EventNameUnlockArchievement EventName = "unlock_archievement"
EventNameViewCart EventName = "view_cart"
EventNameViewItem EventName = "view_item"
EventNameViewItemList EventName = "view_item_list"
EventNameViewPromotion EventName = "view_promotion"
)
func (s EventName) String() string {
return string(s)
}

View File

@ -1,164 +0,0 @@
package v2
// EventParameter as string
// See https://support.google.com/analytics/table/13594742?sjid=7861230991468479976-EU
type EventParameter string
const (
// EventParameterAppVersion The mobile app's versionName (Android) or short bundle version (iOS) in which an event occurred
EventParameterAppVersion EventParameter = "app_version"
// EventParameterCancellationReason The reason a cancellation occurred
EventParameterCancellationReason EventParameter = "cancellation_reason"
// EventParameterFirebaseErrorValue Additional details corresponding to the error code reported by the Firebase SDK
EventParameterFirebaseErrorValue EventParameter = "firebase_error_value"
// EventParameterFirebaseScreen The current screens name, as provided by the developer
EventParameterFirebaseScreen EventParameter = "firebase_screen"
// EventParameterFirebaseScreenClass The current screens class name
EventParameterFirebaseScreenClass EventParameter = "firebase_screen_class"
// EventParameterFirebaseScreenId A random identifier for the current screen
EventParameterFirebaseScreenId EventParameter = "firebase_screen_id"
// EventParameterMessageId The Firebase Cloud Messaging or Firebase In-App Messaging message identifier, which is unique per message campaign
EventParameterMessageId EventParameter = "message_id"
// EventParameterMessageName The Firebase Cloud Messaging or Firebase In-App Messaging message name
EventParameterMessageName EventParameter = "message_name"
// EventParameterMessageType The Firebase Cloud Messaging message notification type
EventParameterMessageType EventParameter = "message_type"
// EventParameterPreviousAppVersion For the app_update event, the parameter signifies the previous application version
EventParameterPreviousAppVersion EventParameter = "previous_app_version"
// EventParameterPreviousOsVersion For the os_update event, the parameter signifies the previous OS version
EventParameterPreviousOsVersion EventParameter = "previous_os_version"
// EventParameterCampaignContent The ad content that was associated with the start of a session
EventParameterCampaignContent EventParameter = "campaign_content"
// EventParameterCampaignMedium The method for acquiring users to your website or application
EventParameterCampaignMedium EventParameter = "campaign_medium"
// EventParameterCampaignSource A representation of the publisher or inventory source from which traffic originated. For example, users who return to your website from Google Search show as "google" in the Session source dimension
EventParameterCampaignSource EventParameter = "campaign_source"
// EventParameterCampaignTerm The term that was associated with the start of a session
EventParameterCampaignTerm EventParameter = "campaign_term"
// EventParameterCoupon The coupon name or code associated with an event
EventParameterCoupon EventParameter = "coupon"
// EventParameterCurrency The currency used in an event, in 3-letter ISO 4217 format. For example, the currency used in a purchase
EventParameterCurrency EventParameter = "currency"
// EventParameterShippingTier The shipping tier selected for delivery of a purchased item
EventParameterShippingTier EventParameter = "shipping_tier"
// EventParameterTransactionId The unique identifier of a transaction
EventParameterTransactionId EventParameter = "transaction_id"
// EventParameterValue The monetary value of the event
EventParameterValue EventParameter = "value"
// EventParameterAffiliation A product affiliation to designate a supplying company or brick and mortar store location
EventParameterAffiliation EventParameter = "affiliation"
// EventParameterCreativeName The name of a creative used in a promotion. Example value: summer_banner
EventParameterCreativeName EventParameter = "creative_name"
// EventParameterCreativeSlot The name of the promotional creative slot associated with an event. Example value: featured_app_1
EventParameterCreativeSlot EventParameter = "creative_slot"
// EventParameterDiscount The value of a discount value associated with a purchased item
EventParameterDiscount EventParameter = "discount"
// EventParameterIndex The index of the item in a list
EventParameterIndex EventParameter = "index"
// EventParameterItemBrand The brand of an item
EventParameterItemBrand EventParameter = "item_brand"
// EventParameterItemCategory The category of an item. If used as part of a category hierarchy or taxonomy, then this is the first category
EventParameterItemCategory EventParameter = "item_category"
// EventParameterItemCategory2 The second hierarchical category in which you classified an item
EventParameterItemCategory2 EventParameter = "item_category2"
// EventParameterItemCategory3 The third hierarchical category in which you classified an item
EventParameterItemCategory3 EventParameter = "item_category3"
// EventParameterItemCategory4 The fourth hierarchical category in which you classified an item
EventParameterItemCategory4 EventParameter = "Item_category4"
// EventParameterItemCategory5 The fifth hierarchical category in which you classified an item
EventParameterItemCategory5 EventParameter = "Item_category5"
// EventParameterItemId The ID that you specify for an item
EventParameterItemId EventParameter = "item_id"
// EventParameterItemListId The name of the list in which an item was presented to a user
EventParameterItemListId EventParameter = "item_list_id"
// EventParameterItemListName The ID of the list in which an item was presented to a user
EventParameterItemListName EventParameter = "item_list_name"
// EventParameterItemName The name of the event that contains the parameter group
EventParameterItemName EventParameter = "item_name"
// EventParameterItemVariant The item variant or unique code or description (e.g., XS, S, M, L for size; Red, Blue, Green, Black for color) for additional item details or options
EventParameterItemVariant EventParameter = "item_variant"
// EventParameterLocationId The physical location associated with the item (e.g. the physical store location)
EventParameterLocationId EventParameter = "location_id"
// EventParameterPromotionId The ID of the promotion associated with an event
EventParameterPromotionId EventParameter = "promotion_id"
// EventParameterPromotionName The name of the promotion associated with an event
EventParameterPromotionName EventParameter = "promotion_name"
// EventParameterAchievementId The ID of an achievement that was unlocked in a game
EventParameterAchievementId EventParameter = "achievement_id"
// EventParameterCharacter The name of a character in a game
EventParameterCharacter EventParameter = "character"
// EventParameterLevelName The name of the level in a game
EventParameterLevelName EventParameter = "level_name"
// EventParameterVirtualCurrencyName The name of a virtual currency
EventParameterVirtualCurrencyName EventParameter = "virtual_currency_name"
// EventParameterFileExtension The extension of a file download
EventParameterFileExtension EventParameter = "file_extension"
// EventParameterFileName The page path of a file download
EventParameterFileName EventParameter = "file_name"
// EventParameterFormDestination The URL to which a form is being submitted
EventParameterFormDestination EventParameter = "form_destination"
// EventParameterFormId The HTML id attribution of the <form> DOM element
EventParameterFormId EventParameter = "form_id"
// EventParameterFormName The HTML name attribute of the <form> DOM element
EventParameterFormName EventParameter = "form_name"
// EventParameterFormSubmitText The text of the submit button, if present
EventParameterFormSubmitText EventParameter = "form_submit_text"
// EventParameterGroupId The ID of a group
EventParameterGroupId EventParameter = "group_id"
// EventParameterLanguage The language setting of a users browser or device, displayed as the ISO 639 language code
EventParameterLanguage EventParameter = "language"
// EventParameterPercentScrolled The percentage down the page that the user scrolled
EventParameterPercentScrolled EventParameter = "percent_scrolled"
// EventParameterSearchTerm The strings or keywords used in a search
EventParameterSearchTerm EventParameter = "search_term"
// EventParameterLinkClasses The HTML class attribute for an outbound link or file download
EventParameterLinkClasses EventParameter = "link_classes"
// EventParameterLinkDomain The destination domain of an outbound link or file download
EventParameterLinkDomain EventParameter = "link_domain"
// EventParameterLinkId The ID for an outbound link or file download
EventParameterLinkId EventParameter = "link_id"
// EventParameterLinkUrl The full URL for an outbound link or file download
EventParameterLinkUrl EventParameter = "link_url"
// EventParameterOutbound Indicates whether a click was on an outbound link
EventParameterOutbound EventParameter = "outbound"
// EventParameterContentGroup The content group associated with a page or screen
EventParameterContentGroup EventParameter = "content_group"
// EventParameterContentId An ID for an article of content that a user interacted with
EventParameterContentId EventParameter = "content_id"
// EventParameterContentType The type of content that a user interacted with
EventParameterContentType EventParameter = "content_type"
// EventParameterPageLocation The complete URL of the webpage that someone visited on your website
EventParameterPageLocation EventParameter = "page_location"
// EventParameterPageReferrer The referring URL, which is the user's previous URL and can be your website's domain or other domains
EventParameterPageReferrer EventParameter = "page_referrer"
// EventParameterPageTitle The HTML page title that you set on your website
EventParameterPageTitle EventParameter = "page_title"
// EventParameterScreenResolution The resolution of a device, in the format (Width)x(Height)
EventParameterScreenResolution EventParameter = "screen_resolution"
// EventParameterAdFormat The format of an advertisement in an app
EventParameterAdFormat EventParameter = "ad_format"
// EventParameterAdPlatform The platform used to surface an advertisement in an app
EventParameterAdPlatform EventParameter = "ad_platform"
// EventParameterAdSource The source network that served an advertisement
EventParameterAdSource EventParameter = "ad_source"
// EventParameterAdUnitId The unique identifier for an ad unit
EventParameterAdUnitId EventParameter = "ad_unit_id"
// EventParameterAdUnitName The name you choose for an ad unit
EventParameterAdUnitName EventParameter = "ad_unit_name"
// EventParameterVideoProvider The source of an embedded video
EventParameterVideoProvider EventParameter = "video_provider"
// EventParameterVideoTitle The title of an embedded video
EventParameterVideoTitle EventParameter = "video_title"
// EventParameterVideoUrl The url of an embedded video
EventParameterVideoUrl EventParameter = "video_url"
// Additional undocumented parameters
EventParameterMethod EventParameter = "method"
EventParameterPaymentType EventParameter = "payment_type"
EventParameterTransactionID EventParameter = "transaction_id"
)
func (s EventParameter) String() string {
return string(s)
}

View File

@ -1,25 +0,0 @@
package v2
// EventParameterNumber as number
// See https://support.google.com/analytics/table/13594742?sjid=7861230991468479976-EU
type EventParameterNumber string
const (
EventParameterNumberValue EventParameterNumber = "value"
// EventParameterNumberFirebaseError The error code reported by the Firebase SDK.
EventParameterNumberFirebaseError EventParameterNumber = "firebase_error"
// EventParameterNumberFreeTrial Signifies that an in-app purchase is a free trial
EventParameterNumberFreeTrial EventParameterNumber = "free_trial"
// EventParameterNumberMessageDeviceTime The Firebase Cloud Messaging or Firebase In-App Messaging delivery epoch timestamp in UTC. (None)
EventParameterNumberMessageDeviceTime EventParameterNumber = "message_device_time"
// EventParameterNumberMessageTime The Firebase Cloud Messaging message notification epoch timestamp in UTC. (None)
EventParameterNumberMessageTime EventParameterNumber = "message_time"
// EventParameterNumberShipping The shipping cost associated with a transaction
EventParameterNumberShipping EventParameterNumber = "shipping"
// EventParameterNumberTax The tax cost associated with a transaction
EventParameterNumberTax EventParameterNumber = "tax"
)
func (s EventParameterNumber) String() string {
return string(s)
}

View File

@ -1,48 +0,0 @@
package v2
/*
*
promotion_id: "pi",
promotion_name: "pn",
creative_name: "cn",
creative_slot: "cs",
*/
type Item struct {
// Example: 12345
ID *string `json:"id,omitempty" mapstructure:"id,omitempty"`
// Example: Stan and Friends Tee
Name *string `json:"nm,omitempty" mapstructure:"nm,omitempty"`
// Example: Google
Brand *string `json:"br,omitempty" mapstructure:"br,omitempty"`
// Example: men
CategoryHierarchy1 *string `json:"ca,omitempty" mapstructure:"ca,omitempty"`
// Example: t-shirts
CategoryHierarchy2 *string `json:"c2,omitempty" mapstructure:"c2,omitempty"`
// Example: men
CategoryHierarchy3 *string `json:"c3,omitempty" mapstructure:"c3,omitempty"`
// Example: men
CategoryHierarchy4 *string `json:"c4,omitempty" mapstructure:"c4,omitempty"`
// Example: men
CategoryHierarchy5 *string `json:"c5,omitempty" mapstructure:"c5,omitempty"`
// Example: Yellow
Variant *string `json:"va,omitempty" mapstructure:"va,omitempty"`
// Example: 123.45
Price *string `json:"pr,omitempty" mapstructure:"pr,omitempty"`
// Example: 1
Quantity *string `json:"qt,omitempty" mapstructure:"qt,omitempty"`
// Example: 50%OFF
Coupon *string `json:"cp,omitempty" mapstructure:"cp,omitempty"`
// Example: cross-selling: mens
ListName *string `json:"ln,omitempty" mapstructure:"ln,omitempty"`
// Example: 10
ListPosition *string `json:"lp,omitempty" mapstructure:"lp,omitempty"`
// Example: id-mens-123
ListID *string `json:"li,omitempty" mapstructure:"li,omitempty"`
// Example: 10.00
Discount *string `json:"ds,omitempty" mapstructure:"ds,omitempty"`
// Example: Foo Marketplace
Affiliation *string `json:"af,omitempty" mapstructure:"af,omitempty"`
// Example: ChIJIQBpAG2ahYAR_6128GcTUEo
LocationID *string `json:"lo,omitempty" mapstructure:"lo,omitempty"`
}

View File

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

View File

@ -1,20 +0,0 @@
package v2
type SST struct {
// Example: 1
ADR *string `json:"adr,omitempty" mapstructure:"adr,omitempty"`
// Example: 1---
USPrivacy *string `json:"us_privacy,omitempty" mapstructure:"us_privacy,omitempty"`
// Example: 542231386.1709295522
RND *string `json:"rnd,omitempty" mapstructure:"rnd,omitempty"`
// Example: google.de
ETLD *string `json:"etld,omitempty" mapstructure:"etld,omitempty"`
// Example: region1
GCSub *string `json:"gcsub,omitempty" mapstructure:"gcsub,omitempty"`
// Example: DE
UC *string `json:"uc,omitempty" mapstructure:"uc,omitempty"`
// Example: 1708250245344
TFT *string `json:"tft,omitempty" mapstructure:"tft,omitempty"`
// Example: 13l3l3l3l1
GCD *string `json:"gcd,omitempty" mapstructure:"gcd,omitempty"`
}

View File

@ -1,5 +0,0 @@
package v2
type Unmarshler interface {
UnmarshalMPv2(*Event) error
}

View File

@ -1,51 +1,55 @@
package v2
package client
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
Client struct {
GTag struct {
l *zap.Logger
url string
path string
host string
cookies []string
richsstsse string
trackingID string
measurementID string
protocolVersion string
httpClient *http.Client
middlewares []ClientMiddleware
middlewares []GTagMiddleware
}
ClientOption func(*Client)
ClientHandler func(r *http.Request, event *Event) 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 ClientWithCookies(v ...string) ClientOption {
return func(o *Client) {
func GTagWithPath(v string) GTagOption {
return func(o *GTag) {
o.path = v
}
}
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...)
}
}
@ -54,10 +58,11 @@ func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Client {
inst := &Client{
func NewGTag(l *zap.Logger, host, trackingID string, opts ...GTagOption) *GTag {
inst := &GTag{
l: l,
url: url,
host: host,
path: "/g/collect",
cookies: []string{"gtm_auth", "gtm_debug", "gtm_preview"},
trackingID: trackingID,
protocolVersion: "2",
@ -66,6 +71,14 @@ func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Cli
for _, opt := range opts {
opt(inst)
}
inst.middlewares = append(inst.middlewares,
GTagMiddlewareRichsstsse,
GTagMiddlewareTrackingID(inst.trackingID),
GTagMiddlewarProtocolVersion("2"),
GTagMiddlewarIsDebug,
GTagMiddlewarClientID,
GTagMiddlewarSessionID(inst.trackingID),
)
return inst
}
@ -73,7 +86,7 @@ func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Cli
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (c *Client) HTTPClient() *http.Client {
func (c *GTag) HTTPClient() *http.Client {
return c.httpClient
}
@ -81,74 +94,45 @@ func (c *Client) HTTPClient() *http.Client {
// ~ 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
}
}
{ // pass through cookies
for _, value := range c.cookies {
if cookie, _ := r.Cookie(value); cookie != nil {
r.Header.Add("Cookie", cookie.String())
}
}
}
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) SendRaw(r *http.Request, event *Event) 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 marshall event")
return errors.Wrap(err, "failed to encode payload")
}
req, err := http.NewRequestWithContext(
r.Context(),
http.MethodPost,
fmt.Sprintf("%s?%s", c.url, EncodeValues(values)),
fmt.Sprintf("%s%s?%s", c.host, c.path, gtag.EncodeValues(values)),
body,
)
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

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

@ -0,0 +1,37 @@
package client_test
import (
"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/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestNewGtag(t *testing.T) {
t.Parallel()
l := zaptest.NewLogger(t)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Helper()
out, err := httputil.DumpRequest(r, true)
if assert.NoError(t, err) {
t.Log(string(out))
}
}))
c := client.NewGTag(l, s.URL, "GA-XXXXXX")
incomingReq, err := http.NewRequestWithContext(t.Context(), 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,82 @@
package client
import (
"context"
"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 GTagMiddlewarWithoutCancel(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
return next(r.WithContext(context.WithoutCancel(r.Context())), payload)
}
}
func GTagMiddlewarSessionID(measurementID string) GTagMiddleware {
measurementID = strings.Split(measurementID, "-")[1]
return func(next GTagHandler) GTagHandler {
return func(r *http.Request, payload *gtag.Payload) error {
value, err := session.ParseGASessionID(r, measurementID)
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)
}
}
}

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

@ -0,0 +1,181 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"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
// To create a new secret, navigate in the Google Analytics UI to:
// Admin > Data Streams > choose your stream > Measurement Protocol > Create
apiSecret string
// Measurement ID. The identifier for a Data Stream. Found in the Google Analytics UI under:
// Admin > Data Streams > choose your stream > Measurement ID
measurementID 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 MPv2WithAPISecret(v string) MPv2Option {
return func(o *MPv2) {
o.apiSecret = v
}
}
func MPv2WithMeasurementID(v string) MPv2Option {
return func(o *MPv2) {
o.measurementID = 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 {
payload := mpv2.NewPayload[any]()
for _, event := range events {
payload.Events = append(payload.Events, event.AnyEvent())
}
return c.SendPayload(r, payload)
}
func (c *MPv2) SendPayload(r *http.Request, payload *mpv2.Payload[any]) error {
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")
}
// query
qry := req.URL.Query()
if len(c.apiSecret) > 0 {
qry.Add("api_secret", c.apiSecret)
}
if len(c.measurementID) > 0 {
qry.Add("measurement_id", c.measurementID)
}
req.URL.RawQuery = qry.Encode()
// TODO valiate: copy headers
req.Header = r.Header.Clone()
req.Header.Set("Content-Type", "application/json")
// 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
}

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

@ -0,0 +1,38 @@
package client_test
import (
"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/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestNewMPv2(t *testing.T) {
t.Parallel()
l := zaptest.NewLogger(t)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Helper()
out, err := httputil.DumpRequest(r, true)
if assert.NoError(t, err) {
t.Log(string(out))
}
}))
c := client.NewMPv2(l, s.URL)
incomingReq, err := http.NewRequestWithContext(t.Context(), 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,70 @@
package client
import (
"context"
"net/http"
"strings"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/session"
"github.com/pkg/errors"
)
func MPv2MiddlewarSessionID(measurementID string) MPv2Middleware {
measurementID = strings.Split(measurementID, "-")[1]
return func(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
if payload.SessionID == "" {
value, err := session.ParseGASessionID(r, measurementID)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errors.Wrap(err, "failed to parse client cookie")
}
payload.SessionID = value
}
return next(r, payload)
}
}
}
func MPv2MiddlewarClientID(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
if payload.ClientID == "" {
value, err := session.ParseGAClientID(r)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errors.Wrap(err, "failed to parse client cookie")
}
payload.ClientID = value
}
return next(r, payload)
}
}
func MPv2MiddlewarDebugMode(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
if !payload.DebugMode {
payload.DebugMode = session.IsGTMDebug(r)
}
return next(r, payload)
}
}
func MPv2MiddlewarWithoutCancel(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
return next(r.WithContext(context.WithoutCancel(r.Context())), payload)
}
}
func MPv2MiddlewareUserID(cookieName string) MPv2Middleware {
return func(next MPv2Handler) MPv2Handler {
return func(r *http.Request, payload *mpv2.Payload[any]) error {
if payload.UserID == "" {
value, err := r.Cookie(cookieName)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return err
}
payload.UserID = value.Value
}
return next(r, payload)
}
}
}

196
pkg/collect/collect.go Normal file
View File

@ -0,0 +1,196 @@
package collect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
gtaghttp "github.com/foomo/sesamy-go/pkg/http/gtag"
mpv2http "github.com/foomo/sesamy-go/pkg/http/mpv2"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
Collect struct {
l *zap.Logger
taggingURL string
taggingClient *http.Client
gtagHTTPMiddlewares []gtaghttp.Middleware
mpv2HTTPMiddlewares []mpv2http.Middleware
}
Option func(*Collect) error
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func WithTagging(v string) Option {
return func(c *Collect) error {
c.taggingURL = v
return nil
}
}
func WithTaggingClient(v *http.Client) Option {
return func(c *Collect) error {
c.taggingClient = v
return nil
}
}
func WithGTagHTTPMiddlewares(v ...gtaghttp.Middleware) Option {
return func(c *Collect) error {
c.gtagHTTPMiddlewares = append(c.gtagHTTPMiddlewares, v...)
return nil
}
}
func WithMPv2HTTPMiddlewares(v ...mpv2http.Middleware) Option {
return func(c *Collect) error {
c.mpv2HTTPMiddlewares = append(c.mpv2HTTPMiddlewares, v...)
return nil
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func New(l *zap.Logger, opts ...Option) (*Collect, error) {
inst := &Collect{
l: l,
taggingClient: http.DefaultClient,
}
for _, opt := range opts {
if opt != nil {
if err := opt(inst); err != nil {
return nil, err
}
}
}
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Collect) GTagHTTPHandler(w http.ResponseWriter, r *http.Request) {
// retrieve payload
payload := gtaghttp.Handler(w, r)
if payload == nil {
http.Error(w, "failed to decode payload", http.StatusBadRequest)
return
}
// compose middlewares
next := c.gtagHandler
for _, middleware := range c.gtagHTTPMiddlewares {
next = middleware(next)
}
// run handler
if err := next(c.l, w, r, payload); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (c *Collect) MPv2HTTPHandler(w http.ResponseWriter, r *http.Request) {
// retrieve payload
payload := mpv2http.Handler(w, r)
// compose middlewares
next := c.mpv2Handler
for _, middleware := range c.mpv2HTTPMiddlewares {
next = middleware(next)
}
// run handler
if err := next(c.l, w, r, payload); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (c *Collect) gtagHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
values, body, err := gtag.Encode(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s?%s", c.taggingURL, "/g/collect", gtag.EncodeValues(values)), body)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// copy headers
req.Header = r.Header.Clone()
resp, err := c.taggingClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// copy headers
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
return nil
}
func (c *Collect) mpv2Handler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *mpv2.Payload[any]) error {
body, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s", c.taggingURL, "/mp/collect"), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// copy headers
req.Header = r.Header.Clone()
// copy raw query
req.URL.RawQuery = r.URL.RawQuery
resp, err := c.taggingClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// copy headers
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
return nil
}

154
pkg/collect/collect_test.go Normal file
View File

@ -0,0 +1,154 @@
package collect_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/foomo/sesamy-go/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestNew(t *testing.T) {
t.Parallel()
tests := []struct {
name string
opts []collect.Option
wantErr bool
}{
{
name: "create with default options",
opts: nil,
wantErr: false,
},
{
name: "create with custom tagging URL",
opts: []collect.Option{
collect.WithTagging("https://example.com"),
},
wantErr: false,
},
{
name: "create with custom HTTP client",
opts: []collect.Option{
collect.WithTaggingClient(http.DefaultClient),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
l := zaptest.NewLogger(t)
c, err := collect.New(l, tt.opts...)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.NotNil(t, c)
})
}
}
func TestCollect_GTagHTTPHandler(t *testing.T) {
t.Parallel()
tests := []struct {
name string
query string
setupMockServer func(query string) *httptest.Server
expectedStatus int
}{
{
name: "successful request",
query: "v=2&tid=G-C5FH0JEWES&gtm=45he5641h1v9208481823z89208852519za204zb9208852519&_p=1749197326339&_dbg=1&gcs=G111&gcd=13t3t3t2t5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&gdid=dMWZhNz&cid=1897509477.1748871081&ecid=1224659014&ul=en-us&sr=1728x1117&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AAAAAAQ&sst.rnd=602677853.1749197331&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.us_privacy=1YNY&sst.tft=1749197326339&sst.lpc=223090308&sst.navt=r&sst.ude=0&sst.sw_exp=1&_s=1&sid=1749197325&sct=4&seg=1&dl=https%3A%2F%2Fnode-b.stage.geschenkidee.ch%2Fsets-geschenkkoerbe&dr=https%3A%2F%2Fnode-b.stage.geschenkidee.ch%2Fgeschenkgutscheine-2&dt=Geschenksets%202025%20%E2%80%93%20Kreative%20Sets%20f%C3%BCr%20jeden%20Anlass%20%7C%20geschenkidee.ch&_tu=DA&en=page_view&epn.emarsys_page_view_id=1550965197&tfd=4880&richsstsse",
setupMockServer: func(query string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/g/collect", r.URL.Path)
if !assert.Len(t, r.URL.RawQuery, len(query)) {
t.Logf("expected: %s", query)
t.Logf("actual: %s", r.URL.RawQuery)
}
w.WriteHeader(http.StatusOK)
}))
},
expectedStatus: http.StatusOK,
},
{
name: "server error",
query: "",
setupMockServer: func(query string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
},
expectedStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
l := zaptest.NewLogger(t)
// Setup mock server
mockServer := tt.setupMockServer(tt.query)
defer mockServer.Close()
// Create collector with mock server URL
c, err := collect.New(l, collect.WithTagging(mockServer.URL))
require.NoError(t, err)
// Create test request
req := httptest.NewRequest(http.MethodPost, "/collect?"+tt.query, nil)
req.Header.Set("User-Agent", "test-agent")
// Create response recorder
w := httptest.NewRecorder()
// Call the handler
c.GTagHTTPHandler(w, req)
// Assert response
assert.Equal(t, tt.expectedStatus, w.Code)
})
}
}
//
// func TestCollect_WithMiddleware(t *testing.T) {
// logger := zap.NewNop()
//
// // Create a test middleware
// testMiddleware := func(next gtaghttp.HandlerFunc) gtaghttp.HandlerFunc {
// return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
// // Modify the payload or do something before passing to next handler
// payload.ClientID = "test-client"
// return next(l, w, r, payload)
// }
// }
//
// mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// assert.Contains(t, r.URL.String(), "cid=test-client")
// w.WriteHeader(http.StatusOK)
// }))
// defer mockServer.Close()
//
// c, err := New(logger,
// WithTagging(mockServer.URL),
// WithGTagHTTPMiddlewares(testMiddleware),
// )
// require.NoError(t, err)
//
// // Create test request
// req := httptest.NewRequest(http.MethodPost, "/collect", nil)
// w := httptest.NewRecorder()
//
// // Call the handler
// c.GTagHTTPHandler(w, req)
//
// assert.Equal(t, http.StatusOK, w.Code)
// }

View File

@ -0,0 +1,28 @@
package gtag
type Campaign struct {
// Campaign Medium ( utm_medium ), this will override the current values read from the url
// Example: cpc
CampaignMedium *string `json:"campaign_medium,omitempty" gtag:"cm,omitempty"`
// Campaign Source ( utm_source ), this will override the current values read from the url
// Example: google
CampaignSource *string `json:"campaign_source,omitempty" gtag:"cs,omitempty"`
// Campaign Name ( utm_campaign ), this will override the current values read from the url
// Example: cpc
CampaignName *string `json:"campaign_name,omitempty" gtag:"cn,omitempty"`
// Campaign Content ( utm_content ), this will override the current values read from the url
// Example: big banner
CampaignContent *string `json:"campaign_content,omitempty" gtag:"cc,omitempty"`
// Campaign Term ( utm_term ), this will override the current values read from the url
// Example: summer
CampaignTerm *string `json:"campaign_term,omitempty" gtag:"ck,omitempty"`
// Campaign Creative Format ( utm_creative_format ), this will override the current values read from the url
// Example: native
CampaignCreativeFormat *string `json:"campaign_creative_format,omitempty" gtag:"ccf,omitempty"`
// Campaign Marketing Tactic ( utm_marketing_tactic ), this will override the current values read from the url
// Example: prospecting
CampaignMarketingTactic *string `json:"campaign_marketing_tactic,omitempty" gtag:"cmt,omitempty"`
// Random Number used to Dedupe gclid
// Example: 342342343
// GclidDeduper *string `json:"gclid_deduper,omitempty" gtag:"_rnd,omitempty"`
}

View File

@ -0,0 +1,41 @@
package gtag
type ClientHints struct {
// Is a random hash generated on the page load.
// Examole: 456193680
// RandomPageLoadHash *string `json:"random_page_load_hash,omitempty" gtag:"_p,omitempty"`
// Browser screen resolution in format width x height
// Example: 2560x1440
ScreenResolution *string `json:"screen_resolution,omitempty" gtag:"sr,omitempty"`
// Browser active locale.
// Example: es-es
UserLanguage *string `json:"user_language,omitempty" gtag:"ul,omitempty"`
// Example: x86
UserAgentArchitecture *string `json:"user_agent_architecture,omitempty" gtag:"uaa,omitempty"`
// The "bitness" of the user-agent's underlying CPU architecture. This is the size in bits of an integer or memory address—typically 64 or 32 bits.
// Example: 32 | 64
UserAgentBitness *string `json:"user_agent_bitness,omitempty" gtag:"uab,omitempty"`
// The brand and full version information for each brand associated with the browser, in a comma-separated list
// Example: Google Chrome;105.0.5195.127|Not)A;Brand;8.0.0.0|Chromium;105.0.5195.127
UserAgentFullVersionList *string `json:"user_agent_full_version_list,omitempty" gtag:"uafvl,omitempty"`
// Indicates whether the browser is on a mobile device
// Example: 1
UserAgentMobile *string `json:"user_agent_mobile,omitempty" gtag:"uamb,omitempty"`
// The device model on which the browser is running. Will likely be empty for desktop browsers
// Example: Nexus 6
UserAgentModel *string `json:"user_agent_model,omitempty" gtag:"uam,omitempty"`
// The platform or operating system on which the user agent is running
// Example: Chromium OS | macOS | Android | iOS
UserAgentPlatform *string `json:"user_agent_platform,omitempty" gtag:"uap,omitempty"`
// The version of the operating system on which the user agent is running
// Example: 14.0.0
UserAgentPlatformVersion *string `json:"user_agent_platform_version,omitempty" gtag:"uapv,omitempty"`
// Whatever Windows On Windows 64 Bit is supported. Used by "WoW64-ness" sites. ( running 32bits app on 64bits windows)
// Example: 1
UserAgentWOW64 *string `json:"user_agent_wow_64,omitempty" gtag:"uaw,omitempty"`
// Added to report the current country for the user under some circumstanced. To be documented.
// Example: ES
UserCountry *string `json:"user_country,omitempty" gtag:"_uc,omitempty"`
// Example: DE-BY
UserRegion *string `json:"user_region,omitempty" gtag:"ur,omitempty"`
}

View File

@ -0,0 +1,85 @@
package gtag
import (
"slices"
"strings"
)
// See https://developers.google.com/tag-platform/security/concepts/consent-mode
type Consent struct {
// Current Google Consent Status. Format 'G1'+'AdsStorageBoolStatus'`+'AnalyticsStorageBoolStatus'
// Example: G101
GoogleConsentStatus *string `json:"google_consent_status,omitempty" gtag:"gcs,omitempty"`
// Will be added with the value "1" if the Google Consent has just been updated (wait_for_update setting on GTAG)
// Example: 1
GoogleConsentUpdate *string `json:"google_consent_update,omitempty" gtag:"gcu,omitempty"`
// Documented values, 1 or 2, no more info on the meaning
// Example: 2
GoogleConsentUpdateType *string `json:"google_consent_update_type,omitempty" gtag:"gcut,omitempty"`
// Will be added with the value "1" if the Google Consent had a default value before getting an update
// Example: G111
GoogleConsentDefault *string `json:"google_consent_default,omitempty" gtag:"gcd,omitempty"`
// Example: 1
// DigitalMarketAct *string `json:"digital_market_act,omitempty" gtag:"dma,omitempty"`
// Example: sypham
// DigitalMarketActParameters *string `json:"digital_market_act_parameters,omitempty" gtag:"dma_cps,omitempty"`
// Example: noapi | denied
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c Consent) AdStorage() bool {
if c.GoogleConsentDefault != nil {
gcd := strings.Split(*c.GoogleConsentDefault, "")
if len(gcd) > 3 {
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[2])
}
}
if c.GoogleConsentUpdate != nil {
gcs := *c.GoogleConsentUpdate
if strings.HasPrefix(gcs, "G1") && len(gcs) == 4 {
return gcs[2:3] == "1"
}
return false
}
return true
}
func (c Consent) AnalyticsStorage() bool {
if c.GoogleConsentDefault != nil {
gcd := strings.Split(*c.GoogleConsentDefault, "")
if len(gcd) > 5 {
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[4])
}
}
if c.GoogleConsentUpdate != nil {
gcs := *c.GoogleConsentUpdate
if strings.HasPrefix(gcs, "G1") && len(gcs) == 4 {
return gcs[3:4] == "1"
}
return false
}
return true
}
func (c Consent) AdUserData() bool {
if c.GoogleConsentDefault != nil {
gcd := strings.Split(*c.GoogleConsentDefault, "")
if len(gcd) > 7 {
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[6])
}
}
return c.AdStorage()
}
func (c Consent) AdPersonalization() bool {
if c.GoogleConsentDefault != nil {
gcd := strings.Split(*c.GoogleConsentDefault, "")
if len(gcd) > 9 {
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[8])
}
}
return c.AdStorage()
}

View File

@ -1,4 +1,4 @@
package v2
package gtag
import (
"regexp"

View File

@ -1,3 +1,3 @@
package v2
package gtag
type Data map[string]any

124
pkg/encoding/gtag/decode.go Normal file
View File

@ -0,0 +1,124 @@
package gtag
import (
"net/http"
"net/url"
"regexp"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func DecodeRequest(r http.Request, target any) error {
u, err := url.ParseRequestURI(r.RequestURI)
if err != nil {
return errors.Wrap(err, "failed to parse request URI")
}
return Decode(u.Query(), target)
}
func DecodeQuery(input string, tarteg any) error {
values, err := url.ParseQuery(input)
if err != nil {
return errors.Wrap(err, "failed to parse query")
}
return Decode(values, tarteg)
}
// Decode an incoming request into an Payload
func Decode(values url.Values, target any) error {
data := Data{}
// decode values
for key, value := range values {
// handle maps
if ok, err := DecodeMapValue(key, value, data); err != nil {
return errors.Wrap(err, "failed to decode map value")
} else if ok {
continue
}
// handle slices
if ok, err := DecodeRegexValue(key, value, RegexProduct, data, ParameterItem); err != nil {
return errors.Wrap(err, "failed to decode regex value")
} else if ok {
continue
}
// default
data[key] = value[0]
}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: &target,
TagName: "gtag",
IgnoreUntaggedFields: true,
Squash: true,
})
if err != nil {
return errors.Wrap(err, "failed to weakly decode query")
}
if err := decoder.Decode(data); err != nil {
return errors.Wrap(err, "failed to weakly decode query")
}
return nil
}
func DecodeMapValue(k string, v []string, data Data) (bool, error) {
if strings.Contains(k, ".") {
parts := strings.Split(k, ".")
if _, ok := data[parts[0]]; !ok {
data[parts[0]] = map[string]any{}
}
if value, ok := data[parts[0]].(map[string]any); ok && len(v) > 0 {
val := v[0]
// gracefully try to unescape value
if out, err := url.QueryUnescape(val); err == nil {
val = out
}
value[strings.Join(parts[1:], ".")] = val
}
return true, nil
}
return false, nil
}
// DecodeRegexValue e.g. `pr1=idSKU_123456` = map["pr"][]map["id"]="SKU_123456"
func DecodeRegexValue(k string, v []string, r *regexp.Regexp, data Data, key string) (bool, error) {
if r.MatchString(k) {
value, err := DecodeObjectValue(v[0])
if err != nil {
return false, err
}
if value != nil {
v, ok := data[key].([]map[string]any)
if !ok {
v = []map[string]any{}
}
v = append(v, value)
data[key] = v
return true, nil
}
}
return false, nil
}
// DecodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
func DecodeObjectValue(s string) (map[string]any, error) {
if len(s) == 0 {
return nil, nil //nolint:nilnil
}
ret := map[string]any{}
for _, part := range strings.Split(s, "~") {
val := part[2:]
// gracefully try to unescape value
if out, err := url.QueryUnescape(val); err == nil {
val = out
}
ret[part[0:2]] = val
}
return ret, nil
}

View File

@ -0,0 +1,155 @@
package gtag_test
import (
"encoding/json"
"net/url"
"reflect"
"testing"
testingx "github.com/foomo/go/testing"
tagx "github.com/foomo/go/testing/tag"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
t.Parallel()
testingx.Tags(t, tagx.Short)
tests := []struct {
name string
args string
want string
}{
{
name: "page_view",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&ec_mode=a&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=13&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dt=Sesamy%20Demo%20Page&sid=1749196701&sct=41&seg=1&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&_tu=DA&en=page_view&_et=12397&tfd=285810&richsstsse",
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"campaign":{},"ecommerce":{},"client_hints":{"screen_resolution":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"page_view","session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
},
{
name: "add_to_cart",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&ec_mode=a&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=14&cu=USD&sid=1749196701&sct=41&seg=1&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy%20Demo%20Page&_tu=DA&en=add_to_cart&_c=1&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&epn.value=30.03&_et=3110&tfd=395426&richsstsse",
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"campaign":{},"ecommerce":{"currency":"USD","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"}],"is_conversion":"1"},"client_hints":{"screen_resolution":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"add_to_cart","event_parameter_number":{"value":"30.03"},"session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
},
{
name: "select_item",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=15&sid=1749196701&sct=41&seg=1&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy%20Demo%20Page&_tu=DA&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.item_list_id=related_products&ep.item_list_name=Related%20products&_et=4363&tfd=436740&richsstsse",
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"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":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"select_item","event_parameter":{"item_list_id":"related_products","item_list_name":"Related products"},"session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var event gtag.Payload
require.NoError(t, gtag.DecodeQuery(tt.args, &event))
out, err := json.Marshal(event)
require.NoError(t, err)
if !assert.JSONEq(t, tt.want, string(out)) {
t.Log(string(out))
}
})
}
}
func TestDecodeMapValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args string
want bool
}{
{
name: "v=2",
want: false,
},
{
name: "ep.foo=bar",
want: true,
},
{
name: "ep.foo.bar=bar",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
d := gtag.Data{}
values, err := url.ParseQuery(tt.name)
require.NoError(t, err)
for k, v := range values {
if got, err := gtag.DecodeMapValue(k, v, d); assert.NoError(t, err) && got != tt.want {
t.Errorf("DecodeMapValue() = %v, want %v", got, tt.want)
t.Log(d)
}
}
})
}
}
func TestDecodeProductValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args string
want bool
}{
{
name: "v=2",
want: false,
},
{
name: "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~pr9.99~qt1",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
d := gtag.Data{}
values, err := url.ParseQuery(tt.name)
require.NoError(t, err)
for k, v := range values {
if got, err := gtag.DecodeRegexValue(k, v, gtag.RegexProduct, d, "pr"); assert.NoError(t, err) && got != tt.want {
t.Errorf("decodeMapValue() = %v, want %v", got, tt.want)
t.Log(d)
}
}
})
}
}
func TestDecodeObjectValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args string
want map[string]any
}{
{
name: "",
want: nil,
},
{
name: "idSKU_12345~nmStan%20and%20Friends%20Tee~qt1",
want: map[string]any{
"id": "SKU_12345",
"nm": "Stan and Friends Tee",
"qt": "1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got, err := gtag.DecodeObjectValue(tt.name); assert.NoError(t, err) && !reflect.DeepEqual(got, tt.want) {
t.Errorf("decodeObjectValue() = %v, want %v", got, tt.want)
t.Log(got)
}
})
}
}

View File

@ -0,0 +1,31 @@
package gtag
import (
"github.com/foomo/gostandards/iso4217"
)
type ECommerce struct {
// Currency Code. ISO 4217
// Example: JPY
Currency *iso4217.Currency `json:"currency,omitempty" gtag:"cu,omitempty"`
// Example:
Items []*Item `json:"items,omitempty" gtag:"pr,omitempty"`
// Promotion Impression/Click Tracking. Promotion Id
// Example: summer-offer
PromotionID *string `json:"promotion_id,omitempty" gtag:"pi,omitempty"`
// Promotion Impression/Click Tracking. Promotion Name
// Example: summer-offer
PromotionName *string `json:"promotion_name,omitempty" gtag:"pn,omitempty"`
// Promotion Impression/Click Tracking. Creative Name
// Example: red-car
// CreativeName *string `json:"//,omitempty" gtag:"cn,omitempty"`
// Promotion Impression/Click Tracking. Promotion Slot / Position
// Example: slide-3
// CreativeSlot *string `json:"//,omitempty" gtag:"cs,omitempty"`
// Google Place ID: Refer to: https://developers.google.com/maps/documentation/places/web-service/place-id . Seems to be inherited from Firebase, not sure about the current use on GA4
// Example: ChIJiyj437sx3YAR9kUWC8QkLzQ
LocationID *string `json:"location_id,omitempty" gtag:"lo,omitempty"`
// If the current event is set as a conversion on the admin interacted the evfent will have this value present
// Example: 1
IsConversion *string `json:"is_conversion,omitempty" gtag:"_c,omitempty"`
}

View File

@ -1,42 +1,55 @@
package v2
package gtag
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"sort"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
func Encode(input *Event) (url.Values, io.Reader, error) {
func Encode(payload *Payload) (url.Values, io.Reader, error) {
var richsstsse bool
// NOTE: `richsstsse` seems to be last parameter in the query to let's ensure it stays that way
if input.Richsstsse != nil {
if payload.Richsstsse != nil {
richsstsse = true
input.Richsstsse = nil
payload.Richsstsse = nil
}
a, err := json.Marshal(input)
if err != nil {
return nil, nil, err
}
remain := payload.Remain
payload.Remain = nil
data := Data{}
if err := json.Unmarshal(a, &data); err != nil {
return nil, nil, errors.Wrap(err, "failed to decode into map")
var json = jsoniter.Config{
TagKey: "gtag",
EscapeHTML: false,
MarshalFloatWith6Digits: true, // will lose precession
ObjectFieldMustBeSimpleString: true, // do not unescape object field
}.Froze()
jsonBytes, err := json.Marshal(&payload)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to marshall payload")
}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshall payload")
}
for s, a := range remain {
data[s] = a
}
ret := url.Values{}
for k, v := range data {
switch t := v.(type) {
case []interface{}:
case []any:
for key, value := range t {
switch tt := value.(type) {
case map[string]interface{}:
case map[string]any:
ret[fmt.Sprintf("%s%d", k, key+1)] = []string{EncodeObjectValue(tt)}
default:
panic("unhandled")
@ -46,7 +59,7 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
for key, value := range t {
ret[fmt.Sprintf("%s.%s", k, key)] = []string{value}
}
case map[string]interface{}:
case map[string]any:
for key, value := range t {
ret[fmt.Sprintf("%s.%s", k, key)] = []string{fmt.Sprintf("%v", value)}
}
@ -54,6 +67,10 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
ret[k] = []string{*t}
case string:
ret[k] = []string{t}
case interface{ String() string }:
ret[k] = []string{t.String()}
case nil:
continue
default:
panic("unhandled")
}
@ -61,7 +78,7 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
var body []string
var reader io.Reader
maxQueryLength := 2048 //
maxQueryLength := 2048
if richsstsse {
maxQueryLength -= len("&richsstsse")
}

View File

@ -0,0 +1,53 @@
package gtag_test
import (
"net/url"
"testing"
testingx "github.com/foomo/go/testing"
tagx "github.com/foomo/go/testing/tag"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEncode(t *testing.T) {
t.Parallel()
testingx.Tags(t, tagx.Short)
tests := []struct {
name string
args string
}{
{
name: "page_view",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&gtm_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=3&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=scroll&_et=NaN&epn.percent_scrolled=90&tfd=8412&richsstsse",
},
{
name: "add_to_cart",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&gtm_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&ec_mode=a&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=4&cu=USD&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=add_to_cart&_c=1&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&epn.value=30.03&_et=8715&tfd=53322&richsstsse",
},
{
name: "purchase",
args: "v=2&tid=G-F9XM71K45T&gtm=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&gtm_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&ec_mode=a&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=4&cu=USD&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=add_to_cart&_c=1&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&epn.value=30.03&_et=8715&tfd=53322&richsstsse",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
values, err := url.ParseQuery(tt.args)
require.NoError(t, err)
var event gtag.Payload
require.NoError(t, gtag.Decode(values, &event))
assert.NotEmpty(t, event.Remain)
if actual, _, err := gtag.Encode(&event); assert.NoError(t, err) {
if !assert.Len(t, actual.Encode(), len(values.Encode())) {
t.Logf("expected: %s", values.Encode())
t.Logf("actual: %s", actual.Encode())
}
}
})
}
}

View File

@ -0,0 +1,16 @@
package gtag
func Set[T any](v T) *T {
return &v
}
func Get[T any](v *T) T {
return *v
}
func GetDefault[T any](v *T, fallback T) T {
if v == nil {
return fallback
}
return *v
}

52
pkg/encoding/gtag/item.go Normal file
View File

@ -0,0 +1,52 @@
package gtag
type Item struct {
// Example: Foo Marketplace
Affiliation *string `json:"affiliation,omitempty" gtag:"af,omitempty"`
// Example: 50%OFF
Coupon *string `json:"coupon,omitempty" gtag:"cp,omitempty"`
// The name of the promotional creative.
// Example: summer_banner2
CreativeName *string `json:"creative_name,omitempty" gtag:"cn,omitempty"`
// The name of the promotional creative slot associated with the item.
// Example: featured_app_1
CreativeSlot *string `json:"creative_slot,omitempty" gtag:"cs,omitempty"`
// Example: 10.00
Discount *string `json:"discount,omitempty" gtag:"ds,omitempty"`
// Example: Google
ItemBrand *string `json:"item_brand,omitempty" gtag:"br,omitempty"`
// Example: men
ItemCategory *string `json:"item_category,omitempty" gtag:"ca,omitempty"`
// Example: t-shirts
ItemCategory2 *string `json:"item_category2,omitempty" gtag:"c2,omitempty"`
// Example: men
ItemCategory3 *string `json:"item_category3,omitempty" gtag:"c3,omitempty"`
// Example: men
ItemCategory4 *string `json:"item_category4,omitempty" gtag:"c4,omitempty"`
// Example: men
ItemCategory5 *string `json:"item_category5,omitempty" gtag:"c5,omitempty"`
// Example: 12345
ItemID *string `json:"item_id,omitempty" gtag:"id,omitempty"`
// Example: id-mens-123
ItemListID *string `json:"item_list_id,omitempty" gtag:"li,omitempty"`
// Example: cross-selling: mens
ItemListName *string `json:"item_list_name,omitempty" gtag:"ln,omitempty"`
// Example: Stan and Friends Tee
ItemName *string `json:"item_name,omitempty" gtag:"nm,omitempty"`
// Example: Yellow
ItemVariant *string `json:"item_variant,omitempty" gtag:"va,omitempty"`
// Example: 10 FIXME
ItemListPosition *string `json:"item_list_position,omitempty" gtag:"lp,omitempty"`
// Example: ChIJIQBpAG2ahYAR_6128GcTUEo
LocationID *string `json:"location_id,omitempty" gtag:"lo,omitempty"`
// Example: 123.45
Price *string `json:"price,omitempty" gtag:"pr,omitempty"`
// The ID of the promotion associated with the item.
// Example: P_12345
PromotionID *string `json:"promotion_id,omitempty" gtag:"pi,omitempty"`
// The name of the promotion associated with the item.
// Example: Summer Sale
PromotionName *string `json:"promotion_name,omitempty" gtag:"pn,omitempty"`
// Example: 1
Quantity *string `json:"quantity,omitempty" gtag:"qt,omitempty"`
}

View File

@ -0,0 +1,163 @@
package gtag
import (
"github.com/foomo/sesamy-go/pkg/sesamy"
)
// Payload https://www.thyngster.com/ga4-measurement-protocol-cheatsheet/
type Payload struct {
Consent `json:"consent" gtag:",inline,squash"`
Campaign `json:"campaign" gtag:",inline,squash"`
ECommerce `json:"ecommerce" gtag:",inline,squash"`
ClientHints `json:"client_hints" gtag:",inline,squash"`
// --- Request parameters ---
// Defines que current protocol version being used.
// Example: 2
ProtocolVersion *string `json:"protocol_version,omitempty" gtag:"v,omitempty"`
// Current Stream ID / Measurement ID
// Example: G-THYNGSTER
TrackingID *string `json:"tracking_id,omitempty" gtag:"tid,omitempty"`
// If the current hit is coming was generated from GTM, it will contain a hash of current GTM/GTAG config
// Example: 2oear0
GTMHashInfo *string `json:"gtmhash_info,omitempty" gtag:"gtm,omitempty"`
// Current Document Hostname
// Exampple: www.analytics-debugger.com
// DocumentHostname *string `json:"document_hostname,omitempty" gtag:"dh,omitempty"`
// Google Analytics Client Id
// Example: 281344611.1635634925
ClientID *string `json:"client_id,omitempty" gtag:"cid,omitempty"`
// Current hits counter for the current page load
// Example: 1
// HitCounter *string `json:"hit_counter,omitempty" gtag:"_s,omitempty"`
// This is supposed to be to enrich the GA4 hits to send data to SGTM, at this point is always set as an empty value...
Richsstsse *string `json:"richsstsse,omitempty" gtag:"richsstsse,omitempty"`
// --- Shared ---
// Actual page's Pathname. It does not include the hostname, quertyString or Fragment
// Example: /hire-me
DocumentLocation *string `json:"document_location,omitempty" gtag:"dl,omitempty"`
// Actual page's Title
// Example: Hire Me
DocumentTitle *string `json:"document_title,omitempty" gtag:"dt,omitempty"`
// Actual page's Referrer
// Example:
DocumentReferrer *string `json:"document_referrer,omitempty" gtag:"dr,omitempty"`
// Unknown. Value ccd.{{HASH}}. The hash in based on various internal parameters. Some kind of usage hash.
// Example: ccd.AAB
// Z *string `json:"z,omitempty" gtag:"_z,omitempty"`
// This is added when an event is generated from rules (from the admin). Actually is hash of the "GA4_EVENT" string
// Example: Q
// EventUsage *string `json:"event_usage,omitempty" gtag:"_eu,omitempty"`
// Unknown
// Example:
// 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"`
// 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"`
// Traffic Type
// Example: 1
// TrafficType *string `json:"traffic_type,omitempty" gtag:"tt,omitempty"`
// Will be set to 1 is the current page has a linker and this last one is valid
// Example: 1
// IsGoogleLinkerValid *string `json:"is_google_linker_valid,omitempty" gtag:"_glv,omitempty"`
// --- Event Parameters ---
// Current Payload Name.
// Example: page_view
EventName *sesamy.EventName `json:"event_name,omitempty" gtag:"en,omitempty"`
// It's the total engagement time in milliseconds since the last event. The engagement time is measured only when the current page is visible and active ( ie: the browser window/tab must be active and visible ), for this GA4 uses the window.events: focus, blur, pageshow, pagehide and the document:visibilitychange, these will determine when the timer starts and pauses
// Example: 1234
// EngagementTime *string `json:"engagement_time,omitempty" gtag:"_et,omitempty"`
// Defines a parameter for the current Payload
// Example: ep.page_type: checkout
EventParameter map[string]string `json:"event_parameter,omitempty" gtag:"ep,omitempty"`
// Defines a parameter for the current Payload
// Example: epn.plays_count: 42
EventParameterNumber map[string]string `json:"event_parameter_number,omitempty" gtag:"epn,omitempty"`
// External Event
// ExternalEvent *string `json:"external_event,omitempty" gtag:"_ee,omitempty"`
// --- Session / User Related ---
// Current User ID
// Example: 1635691016
UserID *string `json:"user_id,omitempty" gtag:"uid,omitempty"`
// Current Firebase ID
// Example: HASHSAH
// FirebaseID *string `json:"firebase_id,omitempty" gtag:"_fid,omitempty"`
// GA4 Session Id. This comes from the GA4 Cookie. It may be different for each Stream ID Configured on the site
// Example: 1635691016
SessionID *string `json:"session_id,omitempty" gtag:"sid,omitempty"`
// Count of sessions recorded by GA4. This value increases by one each time a new session is detected ( when the session expires )
// Example: 10
// SessionCount *string `json:"session_count,omitempty" gtag:"sct,omitempty"`
// If the current user is engaged in any way, this value will be 1
// Example:
// SessionEngagment *string `json:"session_engagement,omitempty" gtag:"seg,omitempty"`
// Defines an user Propery for the current Measurement ID
// Example: up.is_premium_user: yes
UserProperty map[string]string `json:"user_property,omitempty" gtag:"up,omitempty"`
// Defines an user Propery for the current Measurement ID
// Example:
UserPropertyNumber map[string]string `json:"user_property_number,omitempty" gtag:"upn,omitempty"`
// If the "_ga_THYNGSTER" cookie is not set, the first event will have this value present. This will internally create a new "first_visit" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
// Example: 1|2
// FirstVisit *string `json:"first_visit,omitempty" gtag:"_fv,omitempty"`
// If the "_ga_THYNGSTER" cookie last session time value is older than 1800 seconds, the current event will have this value present. This will internally create a new "session_start" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
// Example: 1|2
// SessionStart *string `json:"session_start,omitempty" gtag:"_ss,omitempty"`
// This seems to be related to the ServerSide hits, it's 0 if the FPLC Cookie is not present and to the current value if it's coming from a Cross Domain linker
// Example: bVhVicbfiSXaGNxeawKaPlDQc9QXPD6bKcsn36Elden6wZNb7Q5X1iXlkTVP5iP3H3y76cgM3UIgHCaRsYfPoyLGlbiIYMPRjvnUU7KWbdWLagodzxjrlPnvaRZJkw
// FirstPartyLinkerCookie *string `json:"first_party_linker_cookie,omitempty" gtag:"_fplc,omitempty"`
// If the current user has a GA4 session cookie, but not a GA (_ga) client id cookie, this parameter will be added to the hit
// Example: 1
// NewSessionID *string `json:"new_session_id,omitempty" gtag:"_nsi,omitempty"`
// You may find this parameter if using some vendor plugin o platform ( ie: using shopify integration or a prestashop plugin )
// Example: jdhsd87
// GoogleDeveloperID *string `json:"google_developer_id,omitempty" gtag:"gdid,omitempty"`
// --- Uncategorized / Missing Info ---
// Example: 1
// GTMUp *string `json:"gtmup,omitempty" gtag:"gtm_up,omitempty"`
// Documented values, 1,2,3: Not sure when it's added.
// EuropeanConsentModeEnabledID *string `json:"european_consent_mode_enabled_id,omitempty" gtag:"_ecid,omitempty"`
// Example:
// UEI *string `json:"uei,omitempty" gtag:"_uei,omitempty"`
// It's set when a Google Join is created/imported. Google Signals
// Example: 1
// CreateGoogleJoin *string `json:"create_google_join,omitempty" gtag:"_gaz,omitempty"`
// Example: Redact Device Info. Need Investigation about functionality
// RedactDeviceInfo *string `json:"redact_device_info,omitempty" gtag:"_rdi,omitempty"`
// Geo Granularity. Need Investigation about functionality
// GeoGranularity *string `json:"geo_granularity,omitempty" gtag:"_geo,omitempty"`
// Sent on sites that implement the US Privacy User Signal Mechanism, sent if window.__uspapi is present and returning a value.
// Example: 1YNY
// USPrivacySignal *string `json:"usprivacy_signal,omitempty" gtag:"us_privacy,omitempty"`
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
// Example: 1
// GDPR *string `json:"gdpr,omitempty" gtag:"gdpr,omitempty"`
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
// Example: CPfPdAAPfPdAAAHABBENCgCsAP_AAAAAAAAAI_tf_X__b3_j-_5___t0eY1f9_7__-0zjhfdl-8N3f_X_L8X_2M7vF36tq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbeydYGH9_n1_z-ZKY7_____77__-____3_____-_f___5_3____f_V__97fn9_____9_P___9v__9__________3___gAAAJJQAYAAgj-GgAwABBH8VABgACCP5SADAAEEfx0AGAAII_kIAMAAQR_CQAYAAgj-IgAwABBH8ZABgACCP4A.f_gAAAAAAAAA
// GDPRConsent *string `json:"gdprconsent,omitempty" gtag:"gdpr_consent,omitempty"`
// Example: sypham
NonPersonalizedAds *string `json:"non_personalized_ads,omitempty" gtag:"npa,omitempty"`
// Example: 1
// ARE *string `json:"are,omitempty" gtag:"are,omitempty"`
// PrivacySandboxCookieDeprecationLabel *string `json:"privacy_sandbox_cookie_deprecation_label,omitempty" gtag:"pscdl,omitempty"`
// A timestamp measuring the difference between the moment this parameter gets populated and the moment the navigation started on that particular page.
// TFD *string `json:"tfd,omitempty" gtag:"tfd,omitempty"`
SST *SST `json:"sst,omitempty" gtag:"sst,omitempty"`
// PAE *string `json:"pae,omitempty" gtag:"pae,omitempty"`
// --- Unresolved ---
Remain map[string]any `json:"-" gtag:"-,omitempy,remain"`
}

28
pkg/encoding/gtag/sst.go Normal file
View File

@ -0,0 +1,28 @@
package gtag
type SST struct {
// Example: 1
ADR *string `json:"adr,omitempty" gtag:"adr,omitempty"`
// Example: 1---
USPrivacy *string `json:"us_privacy,omitempty" gtag:"us_privacy,omitempty"`
// Example: 542231386.1709295522
RND *string `json:"rnd,omitempty" gtag:"rnd,omitempty"`
// Example: google.de
ETLD *string `json:"etld,omitempty" gtag:"etld,omitempty"`
// Example: region1
GCSub *string `json:"gcsub,omitempty" gtag:"gcsub,omitempty"`
// Example: DE
UC *string `json:"uc,omitempty" gtag:"uc,omitempty"`
// Session start time, time first seen. Example: 1708250245344
TFT *string `json:"tft,omitempty" gtag:"tft,omitempty"`
// Example: 13l3l3l3l1
GCD *string `json:"gcd,omitempty" gtag:"gcd,omitempty"`
// Example: 0
UDE *string `json:"ude,omitempty" gtag:"ude,omitempty"`
// Example: 223090308
LPC *string `json:"lpc,omitempty" gtag:"lpc,omitempty"`
// Example: 223090308
NAVT *string `json:"navt,omitempty" gtag:"navt,omitempty"`
// Example: 1
SWExp *string `json:"sw_exp,omitempty" gtag:"sw_exp,omitempty"`
}

View File

@ -1,7 +1,8 @@
package v2
package gtag
import (
"net/url"
"strings"
)
// EncodeValues
@ -13,7 +14,7 @@ func EncodeValues(values url.Values) string {
richsstsse = true
}
ret := values.Encode()
ret := strings.ReplaceAll(values.Encode(), "+", "%20")
if richsstsse {
ret += "&richsstsse"

View File

@ -0,0 +1,108 @@
package gtagencode
import (
"encoding/json"
"fmt"
"maps"
"strconv"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func MPv2(source gtag.Payload, target any) error {
var sourceData map[string]any
out, err := json.Marshal(source)
if err != nil {
return errors.Wrap(err, "failed to marshal source")
}
if err = json.Unmarshal(out, &sourceData); err != nil {
return errors.Wrap(err, "failed to unmarshal source")
}
// transform map to match mpv2 format
targetData := map[string]any{
"client_id": source.ClientID,
"user_id": source.UserID,
"session_id": source.SessionID,
"non_personalized_ads": source.NonPersonalizedAds,
"debug_mode": source.IsDebug,
}
// consent
targetConsentData := map[string]any{
"ad_storage": mpv2.ConsentText(source.AdStorage()),
"ad_user_data": mpv2.ConsentText(source.AdUserData()),
"ad_personalization": mpv2.ConsentText(source.AdPersonalization()),
"analytics_storage": mpv2.ConsentText(source.AnalyticsStorage()),
}
targetData["consent"] = targetConsentData
// combine user properties
targetUserProperties := map[string]any{}
if node, ok := sourceData["user_property"].(map[string]string); ok {
for s, s2 := range node {
targetUserProperties[s] = s2
}
}
if node, ok := sourceData["user_property_number"].(map[string]string); ok {
for s, s2 := range node {
targetUserProperties[s] = s2
}
}
targetData["user_properties"] = targetUserProperties
// transform event
targetEventData := map[string]any{
"name": source.EventName,
}
targetEventDataParams := map[string]any{}
if value, ok := sourceData["document_title"]; ok {
targetEventDataParams["page_title"] = value
}
if value, ok := sourceData["document_referrer"]; ok {
targetEventDataParams["page_referrer"] = value
}
if value, ok := sourceData["document_location"]; ok {
targetEventDataParams["page_location"] = value
}
if node, ok := sourceData["ecommerce"].(map[string]any); ok {
maps.Copy(targetEventDataParams, node)
}
if node, ok := sourceData["event_parameter"].(map[string]any); ok {
for s, s2 := range node {
targetEventDataParams[s] = s2
}
}
if node, ok := sourceData["event_parameter_number"].(map[string]any); ok {
for s, s2 := range node {
if value, err := strconv.ParseFloat(fmt.Sprintf("%s", s2), 64); err == nil {
targetEventDataParams[s] = value
} else {
targetEventDataParams[s] = s2
}
}
}
targetEventData["params"] = targetEventDataParams
targetData["events"] = []any{targetEventData}
// encode map to target entity
enc, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: target,
TagName: "json",
WeaklyTypedInput: true,
IgnoreUntaggedFields: true,
})
if err != nil {
return errors.Wrap(err, "failed to create event encoder")
}
if err := enc.Decode(targetData); err != nil {
return errors.Wrap(err, "failed to encode event")
}
return nil
}

View File

@ -0,0 +1,76 @@
package gtagencode_test
import (
"testing"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/sesamy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMPv2(t *testing.T) {
t.Parallel()
tests := []struct {
name string
source gtag.Payload
want mpv2.Payload[any]
wantErr bool
}{
{
name: "basic conversion",
source: gtag.Payload{
ClientID: gtag.Set("test-client"),
UserID: gtag.Set("test-user"),
SessionID: gtag.Set("test-session"),
NonPersonalizedAds: gtag.Set("1"),
IsDebug: gtag.Set("true"),
EventName: gtag.Set(sesamy.EventName("page_view")),
DocumentTitle: gtag.Set("Test Page"),
DocumentLocation: gtag.Set("https://test.com"),
DocumentReferrer: gtag.Set("https://referrer.com"),
},
want: mpv2.Payload[any]{
ClientID: "test-client",
UserID: "test-user",
SessionID: "test-session",
DebugMode: true,
UserProperties: map[string]any{},
Events: []sesamy.Event[any]{
{
Name: "page_view",
Params: map[string]any{
"page_title": "Test Page",
"page_location": "https://test.com",
"page_referrer": "https://referrer.com",
},
},
},
Consent: &mpv2.ConsentData{
AdStorage: gtag.Set(mpv2.ConsentGranted),
AdUserData: gtag.Set(mpv2.ConsentGranted),
AdPersonalization: gtag.Set(mpv2.ConsentGranted),
AnalyticsStorage: gtag.Set(mpv2.ConsentGranted),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var got mpv2.Payload[any]
err := gtagencode.MPv2(tt.source, &got)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -0,0 +1,15 @@
package mpv2
type Consent string
const (
ConsentDenied Consent = "DENIED"
ConsentGranted Consent = "GRANTED"
)
func ConsentText(v bool) Consent {
if v {
return ConsentGranted
}
return ConsentDenied
}

View File

@ -0,0 +1,11 @@
package mpv2
type ConsentData struct {
AdStorage *Consent `json:"ad_storage,omitempty"`
AdUserData *Consent `json:"ad_user_data,omitempty"`
AdPersonalization *Consent `json:"ad_personalization,omitempty"`
AnalyticsStorage *Consent `json:"analytics_storage,omitempty"`
FunctionalityStorage *Consent `json:"functionality_storage,omitempty"`
PersonalizationStorage *Consent `json:"personalization_storage,omitempty"`
SecurityStorage *Consent `json:"security_storage,omitempty"`
}

6
pkg/encoding/mpv2/doc.go Normal file
View File

@ -0,0 +1,6 @@
package mpv2
// Google Analytics (GA4) Measurement Protocol
//
// Reference: https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference
// Changelog: https://developers.google.com/analytics/devguides/collection/protocol/ga4/changelog

View File

@ -0,0 +1,27 @@
package mpv2
import (
"time"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#payload_post_body
type Payload[P any] struct {
ClientID string `json:"client_id,omitempty"`
UserID string `json:"user_id,omitempty"`
TimestampMicros int64 `json:"timestamp_micros,omitempty"`
UserProperties map[string]any `json:"user_properties,omitempty"`
Consent *ConsentData `json:"consent,omitempty"`
Events []sesamy.Event[P] `json:"events,omitempty"`
UserData *UserData `json:"user_data,omitempty"`
DebugMode bool `json:"debug_mode,omitempty"`
SessionID string `json:"session_id,omitempty"`
EngagementTimeMSec int64 `json:"engagement_time_msec,omitempty"`
}
func NewPayload[P any]() *Payload[P] {
return &Payload[P]{
TimestampMicros: time.Now().UnixMicro(),
}
}

View File

@ -0,0 +1,44 @@
package mpv2_test
import (
"encoding/json"
"testing"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/event"
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPayload(t *testing.T) {
t.Parallel()
v := mpv2.Payload[params.PageView]{
ClientID: "C123456",
UserID: "U123456",
TimestampMicros: 1727701064057701,
UserProperties: nil,
Consent: nil,
Events: []sesamy.Event[params.PageView]{
event.NewPageView(params.PageView{
PageTitle: "Home",
PageLocation: "https://foomo.org",
}),
},
UserData: nil,
DebugMode: true,
SessionID: "S123456",
EngagementTimeMSec: 100,
}
out, err := json.Marshal(v)
require.NoError(t, err)
expected := `{"debug_mode":true,"session_id":"S123456","engagement_time_msec":100,"client_id":"C123456","user_id":"U123456","timestamp_micros":1727701064057701,"events":[{"name":"page_view","params":{"page_title":"Home","page_location":"https://foomo.org"}}]}`
assert.JSONEq(t, expected, string(out))
var in mpv2.Payload[params.PageView]
err = json.Unmarshal(out, &in)
require.NoError(t, err)
assert.Equal(t, v, in)
}

View File

@ -0,0 +1,19 @@
package mpv2
import (
"crypto/sha256"
"encoding/hex"
"strings"
)
type SHA256Hash string
func (s SHA256Hash) String() string {
return string(s)
}
func NewSHA256Hash(s string) SHA256Hash {
h := sha256.New()
h.Write([]byte(strings.TrimSpace(strings.ToLower(s))))
return SHA256Hash(hex.EncodeToString(h.Sum(nil)))
}

View File

@ -0,0 +1,8 @@
package mpv2
// UserData https://developers.google.com/analytics/devguides/collection/ga4/uid-data
type UserData struct {
SHA256EmailAddress SHA256Hash `json:"sha256_email_address,omitempty"`
SHA256PhoneNumber SHA256Hash `json:"sha256_phone_number,omitempty"`
Address UserDataAddress `json:"address,omitempty"`
}

View File

@ -0,0 +1,12 @@
package mpv2
// UserDataAddress https://developers.google.com/analytics/devguides/collection/ga4/uid-data
type UserDataAddress struct {
SHA256FirstName SHA256Hash `json:"sha256_first_name,omitempty"`
SHA256LastName SHA256Hash `json:"sha256_last_name,omitempty"`
SHA256Street SHA256Hash `json:"sha256_street,omitempty"`
City string `json:"city,omitempty"`
Region string `json:"region,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
Country string `json:"country,omitempty"`
}

View File

@ -0,0 +1,109 @@
package mpv2encode
import (
"encoding/json"
"fmt"
"strconv"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func GTag[P any](source mpv2.Payload[P], target any) error {
targetData := map[string]any{
"client_id": source.ClientID,
"user_id": source.UserID,
}
{ // user_property
targetUserProperty := map[string]any{}
targetUserPropertyNumber := map[string]any{}
for k, v := range source.UserProperties {
if s, ok := v.(string); ok {
if f, err := strconv.ParseFloat(s, 64); err == nil {
targetUserPropertyNumber[k] = f
} else {
targetUserProperty[k] = v
}
} else {
targetUserProperty[k] = fmt.Sprintf("%s", v)
}
}
targetData["user_property"] = targetUserProperty
targetData["user_property_number"] = targetUserPropertyNumber
}
sourceData := map[string]any{}
out, err := json.Marshal(source.Events[0])
if err != nil {
return errors.Wrap(err, "failed to marshal event")
}
if err = json.Unmarshal(out, &sourceData); err != nil {
return errors.Wrap(err, "failed to unmarshal source events")
}
{ // ecommerce
targetData["event_name"] = sourceData["name"]
if params, ok := sourceData["params"].(map[string]any); ok {
targetData["document_title"] = params["page_title"]
delete(params, "page_title")
targetData["document_referrer"] = params["page_referrer"]
delete(params, "page_referrer")
targetData["document_location"] = params["page_location"]
delete(params, "page_location")
targetData["currency"] = params["currency"]
delete(params, "currency")
targetData["promotion_id"] = params["promotion_id"]
delete(params, "promotion_id")
targetData["promotion_name"] = params["promotion_name"]
delete(params, "promotion_name")
targetData["location_id"] = params["location_id"]
delete(params, "location_id")
targetData["is_conversion"] = params["is_conversion"]
delete(params, "is_conversion")
targetData["items"] = params["items"]
delete(params, "items")
{ // user_property
targetEventProperty := map[string]any{}
targetEventPropertyNumber := map[string]any{}
for k, v := range params {
switch t := v.(type) {
case float64:
targetEventPropertyNumber[k] = v
case string:
if f, err := strconv.ParseFloat(t, 64); err == nil {
targetEventPropertyNumber[k] = f
} else {
targetEventProperty[k] = v
}
default:
targetEventProperty[k] = fmt.Sprintf("%s", v)
}
}
targetData["event_parameter"] = targetEventProperty
targetData["event_parameter_number"] = targetEventPropertyNumber
}
}
}
// encode gtag event to map
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Squash: true,
Result: target,
TagName: "json",
IgnoreUntaggedFields: true,
})
if err != nil {
return errors.Wrap(err, "failed to create event decoder")
}
if err := dec.Decode(targetData); err != nil {
return errors.Wrap(err, "failed to decode event")
}
return nil
}

View File

@ -0,0 +1,75 @@
package mpv2encode_test
import (
"encoding/json"
"fmt"
"net/url"
"testing"
"github.com/foomo/gostandards/iso4217"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2encode"
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSelectItem(t *testing.T) {
t.Parallel()
query := "v=2&tid=G-F9XM71K45T&gtm=45he45m0v9184715813z89184708445za200zb9184708445&_p=1716795486104&_dbg=1&gcd=13l3l3l2l1&npa=1&dma_cps=sypham&dma=1&cid=179294588.1715353601&ecid=2065234266&ul=en-us&sr=2056x1329&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B124.0.6367.201%7CGoogle%2520Chrome%3B124.0.6367.201%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=1716795486104&sst.ude=0&_s=18&cu=USD&sid=1716807360&sct=16&seg=1&dl=https%3A%2F%2Fsesamy.local.bestbytes.net%2F%3Fgtm_debug%3D1716795486020&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Home&en=remove_from_cart&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&epn.value=30.03&_et=10086&tfd=9253808&richsstsse"
values, err := url.ParseQuery(query)
require.NoError(t, err)
var incoming gtag.Payload
err = gtag.Decode(values, &incoming)
require.NoError(t, err)
var intermediate *mpv2.Payload[params.RemoveFromCart[params.Item]]
err = gtagencode.MPv2(incoming, &intermediate)
require.NoError(t, err)
assert.Equal(t, iso4217.USD, intermediate.Events[0].Params.Currency)
intermediate.Events[0].Params.Currency = iso4217.EUR
{
out, err := json.MarshalIndent(intermediate, "", " ")
require.NoError(t, err)
fmt.Println(string(out))
}
var outgoing gtag.Payload
err = mpv2encode.GTag(*intermediate, &outgoing)
require.NoError(t, err)
assert.Equal(t, iso4217.EUR, gtag.Get(outgoing.Currency))
}
func TestSelectItem_Pointer(t *testing.T) {
t.Parallel()
query := "v=2&tid=G-F9XM71K45T&gtm=45he45m0v9184715813z89184708445za200zb9184708445&_p=1716795486104&_dbg=1&gcd=13l3l3l2l1&npa=1&dma_cps=sypham&dma=1&cid=179294588.1715353601&ecid=2065234266&ul=en-us&sr=2056x1329&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B124.0.6367.201%7CGoogle%2520Chrome%3B124.0.6367.201%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=1716795486104&sst.ude=0&_s=18&cu=USD&sid=1716807360&sct=16&seg=1&dl=https%3A%2F%2Fsesamy.local.bestbytes.net%2F%3Fgtm_debug%3D1716795486020&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Home&en=remove_from_cart&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&epn.value=30.03&_et=10086&tfd=9253808&richsstsse"
values, err := url.ParseQuery(query)
require.NoError(t, err)
var incoming *gtag.Payload
err = gtag.Decode(values, &incoming)
require.NoError(t, err)
var intermediate *mpv2.Payload[params.RemoveFromCart[params.Item]]
err = gtagencode.MPv2(*incoming, &intermediate)
require.NoError(t, err)
assert.Equal(t, iso4217.USD, intermediate.Events[0].Params.Currency)
// override value
intermediate.Events[0].Params.Currency = iso4217.EUR
// {
// out, err := json.MarshalIndent(intermediate, "", " ")
// require.NoError(t, err)
// fmt.Println(string(out))
// }
err = mpv2encode.GTag(*intermediate, &incoming)
require.NoError(t, err)
assert.Equal(t, iso4217.EUR, gtag.Get(incoming.Currency))
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type AddPaymentInfo sesamy.Event[params.AddPaymentInfo[params.Item]]
func NewAddPaymentInfo(p params.AddPaymentInfo[params.Item]) sesamy.Event[params.AddPaymentInfo[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddPaymentInfo, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type AddShippingInfo sesamy.Event[params.AddShippingInfo[params.Item]]
func NewAddShippingInfo(p params.AddShippingInfo[params.Item]) sesamy.Event[params.AddShippingInfo[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddShippingInfo, p)
}

12
pkg/event/addtocart.go Normal file
View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type AddToCart sesamy.Event[params.AddToCart[params.Item]]
func NewAddToCart(p params.AddToCart[params.Item]) sesamy.Event[params.AddToCart[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddToCart, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type AddToWishlist sesamy.Event[params.AddToWishlist[params.Item]]
func NewAddToWishlist(p params.AddToWishlist[params.Item]) sesamy.Event[params.AddToWishlist[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameAddToWishlist, p)
}

12
pkg/event/adimpression.go Normal file
View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type AdImpression sesamy.Event[params.AdImpression]
func NewAdImpression(p params.AdImpression) sesamy.Event[params.AdImpression] {
return sesamy.NewEvent(sesamy.EventNameAdImpression, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type BeginCheckout sesamy.Event[params.BeginCheckout[params.Item]]
func NewBeginCheckout(p params.BeginCheckout[params.Item]) sesamy.Event[params.BeginCheckout[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameBeginCheckout, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type CampaignDetails sesamy.Event[params.CampaignDetails]
func NewCampaignDetails(p params.CampaignDetails) sesamy.Event[params.CampaignDetails] {
return sesamy.NewEvent(sesamy.EventNameCampaignDetails, p)
}

12
pkg/event/click.go Normal file
View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type Click sesamy.Event[params.Click]
func NewClick(p params.Click) sesamy.Event[params.Click] {
return sesamy.NewEvent(sesamy.EventNameClick, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type CloseConvertLead sesamy.Event[params.CloseConvertLead[params.Item]]
func NewCloseConvertLead(p params.CloseConvertLead[params.Item]) sesamy.Event[params.CloseConvertLead[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameCloseConvertLead, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type CloseUnconvertLead sesamy.Event[params.CloseUnconvertLead[params.Item]]
func NewCloseUnconvertLead(p params.CloseUnconvertLead[params.Item]) sesamy.Event[params.CloseUnconvertLead[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameCloseUnconvertLead, p)
}

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type DisqualifyLead sesamy.Event[params.DisqualifyLead[params.Item]]
func NewDisqualifyLead(p params.DisqualifyLead[params.Item]) sesamy.Event[params.DisqualifyLead[params.Item]] {
return sesamy.NewEvent(sesamy.EventNameDisqualifyLead, p)
}

5
pkg/event/doc.go Normal file
View File

@ -0,0 +1,5 @@
package event
// Google Analytics (GA4) Measurement Protocol events
//
// Reference: https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events

View File

@ -0,0 +1,12 @@
package event
import (
"github.com/foomo/sesamy-go/pkg/event/params"
"github.com/foomo/sesamy-go/pkg/sesamy"
)
type EarnVirtualMoney sesamy.Event[params.EarnVirtualMoney]
func NewEarnVirtualMoney(p params.EarnVirtualMoney) sesamy.Event[params.EarnVirtualMoney] {
return sesamy.NewEvent(sesamy.EventNameEarnVirtualMoney, p)
}

Some files were not shown because too many files have changed in this diff Show More