diff --git a/go.mod b/go.mod index 124bf1f0..ad72b547 100644 --- a/go.mod +++ b/go.mod @@ -6,43 +6,44 @@ require ( connectrpc.com/connect v1.19.1 firebase.google.com/go/v4 v4.15.2 github.com/golang-migrate/migrate/v4 v4.19.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 - github.com/jackc/pgx/v5 v5.7.5 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 + github.com/jackc/pgx/v5 v5.9.1 github.com/jessevdk/go-flags v1.6.1 - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.12.0 github.com/pkg/errors v0.9.1 github.com/sideshow/apns2 v0.25.0 - github.com/stretchr/testify v1.10.0 - go.uber.org/zap v1.27.0 - golang.org/x/net v0.39.0 + github.com/stretchr/testify v1.11.1 + github.com/xmtp/xmtpd v1.3.0 + go.uber.org/zap v1.27.1 + golang.org/x/net v0.52.0 google.golang.org/api v0.231.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 - google.golang.org/grpc v1.72.0 - google.golang.org/protobuf v1.36.9 + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.11 ) require ( - cel.dev/expr v0.23.1 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.121.0 // indirect cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/firestore v1.18.0 // indirect cloud.google.com/go/iam v1.5.2 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/storage v1.52.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -57,28 +58,27 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/zeebo/errs v1.4.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.24.0 // indirect - golang.org/x/time v0.11.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/genproto v0.0.0-20250428153025-10db94c68c34 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 508c64df..c0c0501b 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= -cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg= cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q= cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= 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= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/firestore v1.18.0 h1:cuydCaLS7Vl2SatAeivXyhbhDEIR8BDmtn4egDhIn2s= cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= @@ -26,62 +26,144 @@ connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= firebase.google.com/go/v4 v4.15.2 h1:KJtV4rAfO2CVCp40hBfVk+mqUqg7+jQKx7yOgFDnXBg= firebase.google.com/go/v4 v4.15.2/go.mod h1:qkD/HtSumrPMTLs0ahQrje5gTw2WKFKrzVFoqy4SbKA= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/DataDog/appsec-internal-go v1.11.2 h1:Q00pPMQzqMIw7jT2ObaORIxBzSly+deS0Ely9OZ/Bj0= +github.com/DataDog/appsec-internal-go v1.11.2/go.mod h1:9YppRCpElfGX+emXOKruShFYsdPq7WEPq/Fen4tYYpk= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.76.2 h1:dnkA7bPC4NL7s3aWNRff8SHdaLMdaUW/yXRx+uEX7g4= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.76.2/go.mod h1:EiRMrLmaSQmE4dLlBewlhbqg2J0n2QVhUWrWe/1MHMw= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.76.2 h1:KkLQGQlk3HjEQjs2XlK9fEabysO+c6YGmqZsLmpw2yk= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.76.2/go.mod h1:Q1B0h6Gyk8twdN1huri/UkQvLbUooVmEsooE49cqEFk= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.76.2 h1:XDKuOvZEIvoOWXFaSoEdViBVrbj1boNPgVxRwwzPT7U= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.76.2/go.mod h1:5dbYePTSJxpBbCA02rB1TzS/NgcJvblIhrrcUat1vi8= +github.com/DataDog/datadog-agent/pkg/proto v0.76.2 h1:Rf+y+5LM5txmaIbPQoAHcaPC8CXhpf8gte/8zKZtN/A= +github.com/DataDog/datadog-agent/pkg/proto v0.76.2/go.mod h1:RzHSkeIpR6OBEmwGy4fUPtQ1hPgt7ljwYlJmKxMHSX4= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.76.2 h1:wE9N1onlBC4NFabRqY8bD5u0LZPRzaU1Pufbqxubk8Q= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.76.2/go.mod h1:MCaZbLoaLD7kUUmSd5jlIvQ5qPAMM8GI7OZ+0FGWi1E= +github.com/DataDog/datadog-agent/pkg/template v0.76.2 h1:ogc1Vz5i5b4DH9nsTYqIOthG5OOEYF8s1v56AfADgC4= +github.com/DataDog/datadog-agent/pkg/template v0.76.2/go.mod h1:mpV3MbF/us0LdM3tvVHDztjApy3VWGeu5RuS/MpGVHQ= +github.com/DataDog/datadog-agent/pkg/trace v0.76.2 h1:HgknNcNY4Iymx/xMwBgXF6SCSjVBsmWdFaj7HHF4b/U= +github.com/DataDog/datadog-agent/pkg/trace v0.76.2/go.mod h1:gg8R4AkYyRJAc5oSSpcwZwvY/hCEMK0gsfRo9WCufFE= +github.com/DataDog/datadog-agent/pkg/trace/log v0.77.0 h1:2VY1byEA2XnYVg7+eLQSTgX2f76ZCf/AwpCBJDXCiDc= +github.com/DataDog/datadog-agent/pkg/trace/log v0.77.0/go.mod h1:thnxBOGfMU9uRlFUClXud6J7DdI8qWtElELSds5jqts= +github.com/DataDog/datadog-agent/pkg/trace/otel v0.76.2 h1:2AdHsMyL1v680U3JkUIXBSSFl2EVSAzKFYWixqrvMZY= +github.com/DataDog/datadog-agent/pkg/trace/otel v0.76.2/go.mod h1:E4FYEUnCneDDPEzN8s+0le1oIrV4I4Nm1oeEvqC2rUA= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.76.2 h1:U5k2kA3xvsf5HLmGQUilu7+DOgF3m7CLO/ZZbok41pc= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.76.2/go.mod h1:hKGPJA7eGRZCTKNIYzE+8OlpIbNJasgVOtegVaALqVs= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.76.2 h1:rJx1fhqLLYvmtt0Zoomob4eLsodgMn5T8KPuMKgC9E8= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.76.2/go.mod h1:YiflIHg6cxNehFkqMubUlfmRt41/afuXr2aNDWzjIDU= +github.com/DataDog/datadog-agent/pkg/util/log v0.76.2 h1:DVmZTMJ6RRROPkbCs5ZybQsf8tpoSuvZa8Q69XQ70cw= +github.com/DataDog/datadog-agent/pkg/util/log v0.76.2/go.mod h1:ed4N2Q4BbWAGqX9Kg5ej3IAKZZWFuxb8ccgIzLdVPxk= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.76.2 h1:beRSQsRlX/tf0SviHANKVmGjUhPWOoS2kP0J22ypXis= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.76.2/go.mod h1:/GvOF3H0meWMxBWGzSkh9t7aqlAxIEbeQSeaGk711Ho= +github.com/DataDog/datadog-agent/pkg/version v0.76.2 h1:KkIY/OF6/yuJ7o/VKY7j/toLWzDYLft3rh6uuIbmuvc= +github.com/DataDog/datadog-agent/pkg/version v0.76.2/go.mod h1:4I4x0IqhIr0fXr90G8Qauz9iID0xhonjUB2lAZH9qyI= +github.com/DataDog/datadog-go/v5 v5.8.2 h1:9IEfH1Mw9AjWwhAMqCAkhbxjuJeMxm2ARX2VdgL+ols= +github.com/DataDog/datadog-go/v5 v5.8.2/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DataDog/dd-trace-go/v2 v2.0.0 h1:cHMEzD0Wcgtu+Rec9d1GuVgpIN5f+4vCaNzuFHJ0v+Y= +github.com/DataDog/dd-trace-go/v2 v2.0.0/go.mod h1:WBtf7TA9bWr5uA8DjOyw1qlSKe3bw9gN5nc0Ta9dHFE= +github.com/DataDog/go-libddwaf/v3 v3.5.4 h1:cLV5lmGhrUBnHG50EUXdqPQAlJdVCp9n3aQ5bDWJEAg= +github.com/DataDog/go-libddwaf/v3 v3.5.4/go.mod h1:HoLUHdj0NybsPBth/UppTcg8/DKA4g+AXuk8cZ6nuoo= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250319104955-81009b9bad14 h1:tc5aVw7OcMyfVmJnrY4IOeiV1RTSaBuJBqF14BXxzIo= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250319104955-81009b9bad14/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs= +github.com/DataDog/go-sqllexer v0.1.12 h1:2eI/Mbs+fcHzXp4iwGz60V6mAWQNFOD/aa98ZluJJ7c= +github.com/DataDog/go-sqllexer v0.1.12/go.mod h1:vOw7Ia7z+z6nl3zGZlLIZe0vQlPtCPR906WIPBJadxc= +github.com/DataDog/go-tuf v1.1.1-0.5.2 h1:YWvghV4ZvrQsPcUw8IOUMSDpqc3W5ruOIC+KJxPknv0= +github.com/DataDog/go-tuf v1.1.1-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= +github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +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/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= -github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= +github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= +github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= 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/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/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= -github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +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/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= +github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kRlAVxes= +github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o= +github.com/exaring/otelpgx v0.10.0 h1:NGGegdoBQM3jNZDKG8ENhigUcgBN7d7943L0YlcIpZc= +github.com/exaring/otelpgx v0.10.0/go.mod h1:R5/M5LWsPPBZc1SrRE5e0DiU48bI78C1/GPTWs6I66U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -104,149 +186,228 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU 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/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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= 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-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +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 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= +github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= +github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -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/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/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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= +github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= +github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/sideshow/apns2 v0.25.0 h1:XOzanncO9MQxkb03T/2uU2KcdVjYiIf0TMLzec0FTW4= github.com/sideshow/apns2 v0.25.0/go.mod h1:7Fceu+sL0XscxrfLSkAoH6UtvKefq3Kq1n4W3ayQZqE= -github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/xmtp/xmtpd v1.3.0 h1:yeiwbgB+y77y+q1+xqwH46SAYr1FXX2puTVldSGTBKc= +github.com/xmtp/xmtpd v1.3.0/go.mod h1:e4SHxnHr9v58tL4mdez6UzVSbi250YXF9Pu0tY+l3PU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -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/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= -go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/collector/component v1.50.0 h1:AvIhCc/J7tXlKZDETPDMDp6g6pwa3FBD6c0Q8h2u3xA= +go.opentelemetry.io/collector/component v1.50.0/go.mod h1:S0p+mq0ZvEEN67BKWt0atC5cHn2Km8vBeeIZuYzD0XU= +go.opentelemetry.io/collector/featuregate v1.50.0 h1:nROGw8VpLuc2/PExnL6ammUpr2y7pozpbwgae6zU4s0= +go.opentelemetry.io/collector/featuregate v1.50.0/go.mod h1:/1bclXgP91pISaEeNulRxzzmzMTm4I5Xih2SnI4HRSo= +go.opentelemetry.io/collector/pdata v1.50.0 h1:vES5c9jT9HzOhHEg1OIjPxk4qKIjA+Dao8dxU3oePU0= +go.opentelemetry.io/collector/pdata v1.50.0/go.mod h1:G18lFpQYh4473PiEPqLd7BKfc8a/j+Fl4EfHWy1Ylx8= +go.opentelemetry.io/collector/pdata/pprofile v0.144.0 h1:jzgIl+Hhjr5sfJDals+6Zl0IS1EUtZBChvv+j05Ih44= +go.opentelemetry.io/collector/pdata/pprofile v0.144.0/go.mod h1:mipJI/T20uy/+iD3QrzmRUPGenJRhBJj8qGXDpLWoQs= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= 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/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/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +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.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/crypto v0.0.0-20170512130425-ab89591268e0/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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= 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-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20211216021012-1d35b9e2eb4e/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.231.0 h1:LbUD5FUl0C4qwia2bjXhCMH65yz1MLPzA/0OYEsYY7Q= google.golang.org/api v0.231.0/go.mod h1:H52180fPI/QQlUc0F4xWfGZILdv09GCWKt2bcsn164A= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= google.golang.org/genproto v0.0.0-20250428153025-10db94c68c34 h1:oklGWmm0ZiCw4efmdYZo5MF9t6nRvGzM5+0klSjOmGM= google.golang.org/genproto v0.0.0-20250428153025-10db94c68c34/go.mod h1:hiH/EqX5GBdTyIpkqMqDGUHDiBniln8b4FCw+NzPxQY= -google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE= -google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/DataDog/dd-trace-go.v1 v1.74.0 h1:wScziU1ff6Bnyr8MEyxATPSLJdnLxKz3p6RsA8FUaek= +gopkg.in/DataDog/dd-trace-go.v1 v1.74.0/go.mod h1:ReNBsNfnsjVC7GsCe80zRcykL/n+nxvsNrg3NbjuleM= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/integration/package-lock.json b/integration/package-lock.json index 1b8d6f5a..e4c1360e 100644 --- a/integration/package-lock.json +++ b/integration/package-lock.json @@ -10,7 +10,7 @@ "@connectrpc/connect": "^2.0.0", "@connectrpc/connect-web": "^2.0.0", "@koa/bodyparser": "^5", - "@xmtp/node-sdk": "5.3.0", + "@xmtp/node-sdk": "6.0.0", "koa": "^2", "viem": "^2.22.2" }, @@ -1252,18 +1252,22 @@ } }, "node_modules/@xmtp/node-bindings": { - "version": "1.9.1", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@xmtp/node-bindings/-/node-bindings-1.10.0.tgz", + "integrity": "sha512-US/YvMP7RL1DkNeiZ75ZjEyvMwLIrEPn12dBj5h7lAesM2N5snwnstJ2NNk3LpW3kCNDdzwhe/G0ZSb0PhwNeg==", "license": "MIT", "engines": { "node": ">=22" } }, "node_modules/@xmtp/node-sdk": { - "version": "5.3.0", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xmtp/node-sdk/-/node-sdk-6.0.0.tgz", + "integrity": "sha512-UMClg1+WkC3pfeHffJoZMYzhqsUa2wZpSJtIrpvBqUD+n1VYVQTfhTqGyaavVIPgITxjHBi/uMS/K18E4qDbPA==", "license": "MIT", "dependencies": { "@xmtp/content-type-primitives": "3.0.0", - "@xmtp/node-bindings": "1.9.1" + "@xmtp/node-bindings": "1.10.0" }, "engines": { "node": ">=22" @@ -2660,6 +2664,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/integration/package.json b/integration/package.json index b5107215..cf78a2a6 100644 --- a/integration/package.json +++ b/integration/package.json @@ -14,7 +14,7 @@ "@connectrpc/connect": "^2.0.0", "@connectrpc/connect-web": "^2.0.0", "@koa/bodyparser": "^5", - "@xmtp/node-sdk": "5.3.0", + "@xmtp/node-sdk": "6.0.0", "koa": "^2", "viem": "^2.22.2" } diff --git a/integration/src/gen/notifications/v1/service_pb.ts b/integration/src/gen/notifications/v1/service_pb.ts index 5645d91f..aec64df3 100644 --- a/integration/src/gen/notifications/v1/service_pb.ts +++ b/integration/src/gen/notifications/v1/service_pb.ts @@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file notifications/v1/service.proto. */ export const file_notifications_v1_service: GenFile = /*@__PURE__*/ - fileDesc("Ch5ub3RpZmljYXRpb25zL3YxL3NlcnZpY2UucHJvdG8SEG5vdGlmaWNhdGlvbnMudjEihAEKEURlbGl2ZXJ5TWVjaGFuaXNtEhsKEWFwbnNfZGV2aWNlX3Rva2VuGAEgASgJSAASHwoVZmlyZWJhc2VfZGV2aWNlX3Rva2VuGAIgASgJSAASFgoMY3VzdG9tX3Rva2VuGAMgASgJSABCGQoXZGVsaXZlcnlfbWVjaGFuaXNtX3R5cGUidwobUmVnaXN0ZXJJbnN0YWxsYXRpb25SZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI/ChJkZWxpdmVyeV9tZWNoYW5pc20YAiABKAsyIy5ub3RpZmljYXRpb25zLnYxLkRlbGl2ZXJ5TWVjaGFuaXNtIkwKHFJlZ2lzdGVySW5zdGFsbGF0aW9uUmVzcG9uc2USFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJEhMKC3ZhbGlkX3VudGlsGAIgASgEIjQKGURlbGV0ZUluc3RhbGxhdGlvblJlcXVlc3QSFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJIqsBCgxTdWJzY3JpcHRpb24SDQoFdG9waWMYASABKAkSOQoJaG1hY19rZXlzGAIgAygLMiYubm90aWZpY2F0aW9ucy52MS5TdWJzY3JpcHRpb24uSG1hY0tleRIRCglpc19zaWxlbnQYAyABKAgaPgoHSG1hY0tleRImCh50aGlydHlfZGF5X3BlcmlvZHNfc2luY2VfZXBvY2gYASABKA0SCwoDa2V5GAIgASgMIm4KHFN1YnNjcmliZVdpdGhNZXRhZGF0YVJlcXVlc3QSFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJEjUKDXN1YnNjcmlwdGlvbnMYAiADKAsyHi5ub3RpZmljYXRpb25zLnYxLlN1YnNjcmlwdGlvbiI7ChBTdWJzY3JpYmVSZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRIOCgZ0b3BpY3MYAiADKAkiPQoSVW5zdWJzY3JpYmVSZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRIOCgZ0b3BpY3MYAiADKAky2AMKDU5vdGlmaWNhdGlvbnMSdQoUUmVnaXN0ZXJJbnN0YWxsYXRpb24SLS5ub3RpZmljYXRpb25zLnYxLlJlZ2lzdGVySW5zdGFsbGF0aW9uUmVxdWVzdBouLm5vdGlmaWNhdGlvbnMudjEuUmVnaXN0ZXJJbnN0YWxsYXRpb25SZXNwb25zZRJZChJEZWxldGVJbnN0YWxsYXRpb24SKy5ub3RpZmljYXRpb25zLnYxLkRlbGV0ZUluc3RhbGxhdGlvblJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkSRwoJU3Vic2NyaWJlEiIubm90aWZpY2F0aW9ucy52MS5TdWJzY3JpYmVSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5El8KFVN1YnNjcmliZVdpdGhNZXRhZGF0YRIuLm5vdGlmaWNhdGlvbnMudjEuU3Vic2NyaWJlV2l0aE1ldGFkYXRhUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRJLCgtVbnN1YnNjcmliZRIkLm5vdGlmaWNhdGlvbnMudjEuVW5zdWJzY3JpYmVSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5QuABChRjb20ubm90aWZpY2F0aW9ucy52MUIMU2VydmljZVByb3RvUAFaWWdpdGh1Yi5jb20veG10cC9leGFtcGxlLW5vdGlmaWNhdGlvbi1zZXJ2ZXItZ28vcGtnL3Byb3RvL25vdGlmaWNhdGlvbnMvdjE7bm90aWZpY2F0aW9uc3YxogIDTlhYqgIQTm90aWZpY2F0aW9ucy5WMcoCEE5vdGlmaWNhdGlvbnNcVjHiAhxOb3RpZmljYXRpb25zXFYxXEdQQk1ldGFkYXRh6gIRTm90aWZpY2F0aW9uczo6VjFiBnByb3RvMw", [file_google_protobuf_empty]); + fileDesc("Ch5ub3RpZmljYXRpb25zL3YxL3NlcnZpY2UucHJvdG8SEG5vdGlmaWNhdGlvbnMudjEihAEKEURlbGl2ZXJ5TWVjaGFuaXNtEhsKEWFwbnNfZGV2aWNlX3Rva2VuGAEgASgJSAASHwoVZmlyZWJhc2VfZGV2aWNlX3Rva2VuGAIgASgJSAASFgoMY3VzdG9tX3Rva2VuGAMgASgJSABCGQoXZGVsaXZlcnlfbWVjaGFuaXNtX3R5cGUidwobUmVnaXN0ZXJJbnN0YWxsYXRpb25SZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI/ChJkZWxpdmVyeV9tZWNoYW5pc20YAiABKAsyIy5ub3RpZmljYXRpb25zLnYxLkRlbGl2ZXJ5TWVjaGFuaXNtIkwKHFJlZ2lzdGVySW5zdGFsbGF0aW9uUmVzcG9uc2USFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJEhMKC3ZhbGlkX3VudGlsGAIgASgEIjQKGURlbGV0ZUluc3RhbGxhdGlvblJlcXVlc3QSFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJIsABCgxTdWJzY3JpcHRpb24SDQoFdG9waWMYASABKAkSOQoJaG1hY19rZXlzGAIgAygLMiYubm90aWZpY2F0aW9ucy52MS5TdWJzY3JpcHRpb24uSG1hY0tleRIRCglpc19zaWxlbnQYAyABKAgSEwoLdG9waWNfYnl0ZXMYBCABKAwaPgoHSG1hY0tleRImCh50aGlydHlfZGF5X3BlcmlvZHNfc2luY2VfZXBvY2gYASABKA0SCwoDa2V5GAIgASgMIm4KHFN1YnNjcmliZVdpdGhNZXRhZGF0YVJlcXVlc3QSFwoPaW5zdGFsbGF0aW9uX2lkGAEgASgJEjUKDXN1YnNjcmlwdGlvbnMYAiADKAsyHi5ub3RpZmljYXRpb25zLnYxLlN1YnNjcmlwdGlvbiJRChBTdWJzY3JpYmVSZXF1ZXN0EhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRIOCgZ0b3BpY3MYAiADKAkSFAoMdG9waWNzX2J5dGVzGAMgAygMIlMKElVuc3Vic2NyaWJlUmVxdWVzdBIXCg9pbnN0YWxsYXRpb25faWQYASABKAkSDgoGdG9waWNzGAIgAygJEhQKDHRvcGljc19ieXRlcxgDIAMoDDLYAwoNTm90aWZpY2F0aW9ucxJ1ChRSZWdpc3Rlckluc3RhbGxhdGlvbhItLm5vdGlmaWNhdGlvbnMudjEuUmVnaXN0ZXJJbnN0YWxsYXRpb25SZXF1ZXN0Gi4ubm90aWZpY2F0aW9ucy52MS5SZWdpc3Rlckluc3RhbGxhdGlvblJlc3BvbnNlElkKEkRlbGV0ZUluc3RhbGxhdGlvbhIrLm5vdGlmaWNhdGlvbnMudjEuRGVsZXRlSW5zdGFsbGF0aW9uUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRJHCglTdWJzY3JpYmUSIi5ub3RpZmljYXRpb25zLnYxLlN1YnNjcmliZVJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkSXwoVU3Vic2NyaWJlV2l0aE1ldGFkYXRhEi4ubm90aWZpY2F0aW9ucy52MS5TdWJzY3JpYmVXaXRoTWV0YWRhdGFSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5EksKC1Vuc3Vic2NyaWJlEiQubm90aWZpY2F0aW9ucy52MS5VbnN1YnNjcmliZVJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHlC4AEKFGNvbS5ub3RpZmljYXRpb25zLnYxQgxTZXJ2aWNlUHJvdG9QAVpZZ2l0aHViLmNvbS94bXRwL2V4YW1wbGUtbm90aWZpY2F0aW9uLXNlcnZlci1nby9wa2cvcHJvdG8vbm90aWZpY2F0aW9ucy92MTtub3RpZmljYXRpb25zdjGiAgNOWFiqAhBOb3RpZmljYXRpb25zLlYxygIQTm90aWZpY2F0aW9uc1xWMeICHE5vdGlmaWNhdGlvbnNcVjFcR1BCTWV0YWRhdGHqAhFOb3RpZmljYXRpb25zOjpWMWIGcHJvdG8z", [file_google_protobuf_empty]); /** * An union of possible delibery mechanisms @@ -138,6 +138,13 @@ export type Subscription = Message<"notifications.v1.Subscription"> & { * @generated from field: bool is_silent = 3; */ isSilent: boolean; + + /** + * Binary topic (V4 format). If both topic and topic_bytes are set, topic_bytes takes precedence. + * + * @generated from field: bytes topic_bytes = 4; + */ + topicBytes: Uint8Array; }; /** @@ -208,6 +215,13 @@ export type SubscribeRequest = Message<"notifications.v1.SubscribeRequest"> & { * @generated from field: repeated string topics = 2; */ topics: string[]; + + /** + * Binary topics (V4 format). Merged with string topics if both are set. + * + * @generated from field: repeated bytes topics_bytes = 3; + */ + topicsBytes: Uint8Array[]; }; /** @@ -232,6 +246,13 @@ export type UnsubscribeRequest = Message<"notifications.v1.UnsubscribeRequest"> * @generated from field: repeated string topics = 2; */ topics: string[]; + + /** + * Binary topics (V4 format). Merged with string topics if both are set. + * + * @generated from field: repeated bytes topics_bytes = 3; + */ + topicsBytes: Uint8Array[]; }; /** diff --git a/integration/src/index.test.ts b/integration/src/index.test.ts index 84b3008f..1a0812aa 100644 --- a/integration/src/index.test.ts +++ b/integration/src/index.test.ts @@ -24,7 +24,7 @@ describe("notifications", () => { }); const waitForNextRequest = ( - timeoutMs: number + timeoutMs: number, ): Promise => new Promise((resolve, reject) => { onRequest = (body) => resolve(body); @@ -101,7 +101,7 @@ describe("notifications", () => { thirtyDayPeriodsSinceEpoch: Number(v.epoch), key: Uint8Array.from(v.key), })); - const topic = `/xmtp/mls/1/g-${alixGroup.id}/proto`; + const topic = alixGroup.topic; await alixNotificationClient.subscribeWithMetadata({ installationId: alix.installationId, subscriptions: [ diff --git a/integration/src/index.ts b/integration/src/index.ts index a3b1978e..1f0552f7 100644 --- a/integration/src/index.ts +++ b/integration/src/index.ts @@ -1,8 +1,17 @@ -import { Client, type Signer, IdentifierKind } from "@xmtp/node-sdk"; +import { + Client, + type Signer, + type ClientOptions, + IdentifierKind, + createBackend, +} from "@xmtp/node-sdk"; import { createWalletClient, http, toBytes } from "viem"; import { mainnet } from "viem/chains"; import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; -import { createClient, type Client as ConnectClient } from "@connectrpc/connect"; +import { + createClient, + type Client as ConnectClient, +} from "@connectrpc/connect"; import { Notifications, SubscriptionSchema, @@ -37,12 +46,13 @@ export async function randomClient() { }; const encKey = getRandomValues(new Uint8Array(32)); - return await Client.create(signer, { - env: "dev", + const opts: ClientOptions = { apiUrl: config.nodeUrl, + env: "local", dbEncryptionKey: encKey, dbPath: `/tmp/test-${wallet.account.address}.db3`, - }); + }; + return await Client.create(signer, opts); } export function createNotificationClient() { @@ -60,13 +70,11 @@ export async function subscribeToTopics( xmtpClient: Client, // A notifications server client, like the one generated above. notificationClient: ConnectClient, - // We want to handle iOS subscriptions slightly differently because we can't filter regular notifications on the client - isIos: boolean ) { // Only subscribe to notifications which have a consent state of allowed // to protect users from SPAM notifications const consentedConversations = (await xmtpClient.conversations.list()).filter( - (c) => c.consentState() === 1 + (c) => c.consentState() === 1, ); // Get the HMAC Keys for all conversations where the keys exist @@ -75,7 +83,7 @@ export async function subscribeToTopics( // Convert the conversations to subscriptions const conversationSubscriptions = consentedConversations.map((c) => create(SubscriptionSchema, { - topic: c.id, + topic: c.topic, // V1 conversations don't have isSender support. // Use data only notifications here for iOS isSilent: false, @@ -83,13 +91,11 @@ export async function subscribeToTopics( create(Subscription_HmacKeySchema, { thirtyDayPeriodsSinceEpoch: Number(v.epoch), key: Uint8Array.from(v.key), - }) + }), ), - }) + }), ); - const inviteAndIntroSubscriptions: typeof conversationSubscriptions = []; - await notificationClient.subscribeWithMetadata({ installationId, subscriptions: conversationSubscriptions, diff --git a/mocks/Delivery.go b/mocks/Delivery.go index 08f37388..d4c4cdf7 100644 --- a/mocks/Delivery.go +++ b/mocks/Delivery.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/mocks/Installations.go b/mocks/Installations.go index 05546aaa..cd90bcde 100644 --- a/mocks/Installations.go +++ b/mocks/Installations.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/mocks/Subscriptions.go b/mocks/Subscriptions.go index 44174a6d..f0150159 100644 --- a/mocks/Subscriptions.go +++ b/mocks/Subscriptions.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks @@ -7,6 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" interfaces "github.com/xmtp/example-notification-server-go/pkg/interfaces" + + topic "github.com/xmtp/xmtpd/pkg/topic" ) // Subscriptions is an autogenerated mock type for the Subscriptions type @@ -14,9 +16,9 @@ type Subscriptions struct { mock.Mock } -// GetSubscriptions provides a mock function with given fields: ctx, topic, thirtyDayPeriod -func (_m *Subscriptions) GetSubscriptions(ctx context.Context, topic string, thirtyDayPeriod int) ([]interfaces.Subscription, error) { - ret := _m.Called(ctx, topic, thirtyDayPeriod) +// GetSubscriptions provides a mock function with given fields: ctx, t, thirtyDayPeriod +func (_m *Subscriptions) GetSubscriptions(ctx context.Context, t *topic.Topic, thirtyDayPeriod int) ([]interfaces.Subscription, error) { + ret := _m.Called(ctx, t, thirtyDayPeriod) if len(ret) == 0 { panic("no return value specified for GetSubscriptions") @@ -24,19 +26,19 @@ func (_m *Subscriptions) GetSubscriptions(ctx context.Context, topic string, thi var r0 []interfaces.Subscription var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, int) ([]interfaces.Subscription, error)); ok { - return rf(ctx, topic, thirtyDayPeriod) + if rf, ok := ret.Get(0).(func(context.Context, *topic.Topic, int) ([]interfaces.Subscription, error)); ok { + return rf(ctx, t, thirtyDayPeriod) } - if rf, ok := ret.Get(0).(func(context.Context, string, int) []interfaces.Subscription); ok { - r0 = rf(ctx, topic, thirtyDayPeriod) + if rf, ok := ret.Get(0).(func(context.Context, *topic.Topic, int) []interfaces.Subscription); ok { + r0 = rf(ctx, t, thirtyDayPeriod) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]interfaces.Subscription) } } - if rf, ok := ret.Get(1).(func(context.Context, string, int) error); ok { - r1 = rf(ctx, topic, thirtyDayPeriod) + if rf, ok := ret.Get(1).(func(context.Context, *topic.Topic, int) error); ok { + r1 = rf(ctx, t, thirtyDayPeriod) } else { r1 = ret.Error(1) } @@ -45,7 +47,7 @@ func (_m *Subscriptions) GetSubscriptions(ctx context.Context, topic string, thi } // Subscribe provides a mock function with given fields: ctx, installationId, topics -func (_m *Subscriptions) Subscribe(ctx context.Context, installationId string, topics []string) error { +func (_m *Subscriptions) Subscribe(ctx context.Context, installationId string, topics []*topic.Topic) error { ret := _m.Called(ctx, installationId, topics) if len(ret) == 0 { @@ -53,7 +55,7 @@ func (_m *Subscriptions) Subscribe(ctx context.Context, installationId string, t } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, []*topic.Topic) error); ok { r0 = rf(ctx, installationId, topics) } else { r0 = ret.Error(0) @@ -81,7 +83,7 @@ func (_m *Subscriptions) SubscribeWithMetadata(ctx context.Context, installation } // Unsubscribe provides a mock function with given fields: ctx, installationId, topics -func (_m *Subscriptions) Unsubscribe(ctx context.Context, installationId string, topics []string) error { +func (_m *Subscriptions) Unsubscribe(ctx context.Context, installationId string, topics []*topic.Topic) error { ret := _m.Called(ctx, installationId, topics) if len(ret) == 0 { @@ -89,7 +91,7 @@ func (_m *Subscriptions) Unsubscribe(ctx context.Context, installationId string, } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, []*topic.Topic) error); ok { r0 = rf(ctx, installationId, topics) } else { r0 = ret.Error(0) diff --git a/pkg/api/api.go b/pkg/api/api.go index 77679382..86e38738 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -13,6 +13,8 @@ import ( "github.com/xmtp/example-notification-server-go/pkg/options" proto "github.com/xmtp/example-notification-server-go/pkg/proto/notifications/v1" "github.com/xmtp/example-notification-server-go/pkg/proto/notifications/v1/notificationsv1connect" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" + "github.com/xmtp/xmtpd/pkg/topic" "go.uber.org/zap" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" @@ -142,7 +144,12 @@ func (s *ApiServer) Subscribe( ) (*connect.Response[emptypb.Empty], error) { s.logger.Info("Subscribe", zap.Any("req", req)) - err := s.subscriptions.Subscribe(ctx, req.Msg.InstallationId, req.Msg.Topics) + topics, err := normalizeTopics(req.Msg.Topics, req.Msg.TopicsBytes) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + + err = s.subscriptions.Subscribe(ctx, req.Msg.InstallationId, topics) if err != nil { s.logger.Error("error subscribing", zap.Error(err)) return nil, connect.NewError(connect.CodeInternal, err) @@ -157,7 +164,12 @@ func (s *ApiServer) Unsubscribe( ) (*connect.Response[emptypb.Empty], error) { s.logger.Info("Unsubscribe", zap.Any("req", req)) - err := s.subscriptions.Unsubscribe(ctx, req.Msg.InstallationId, req.Msg.Topics) + topics, err := normalizeTopics(req.Msg.Topics, req.Msg.TopicsBytes) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + + err = s.subscriptions.Unsubscribe(ctx, req.Msg.InstallationId, topics) if err != nil { s.logger.Error("error unsubscribing", zap.Error(err)) return nil, connect.NewError(connect.CodeInternal, err) @@ -169,8 +181,11 @@ func (s *ApiServer) Unsubscribe( func (s *ApiServer) SubscribeWithMetadata(ctx context.Context, req *connect.Request[proto.SubscribeWithMetadataRequest]) (*connect.Response[emptypb.Empty], error) { log := s.logger.With(zap.String("method", "subscribeWithMetadata")) log.Info("Subscribing") - inputs := buildSubscriptionInputs(req.Msg.Subscriptions) - err := s.subscriptions.SubscribeWithMetadata(ctx, req.Msg.InstallationId, inputs) + inputs, err := normalizeSubscriptionInputs(req.Msg.Subscriptions) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + err = s.subscriptions.SubscribeWithMetadata(ctx, req.Msg.InstallationId, inputs) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } @@ -178,16 +193,62 @@ func (s *ApiServer) SubscribeWithMetadata(ctx context.Context, req *connect.Requ return connect.NewResponse(&emptypb.Empty{}), nil } -func buildSubscriptionInputs(subs []*proto.Subscription) []interfaces.SubscriptionInput { +func normalizeTopics(stringTopics []string, bytesTopics [][]byte) ([]*topic.Topic, error) { + seen := make(map[string]struct{}) + var result []*topic.Topic + + for _, s := range stringTopics { + t, err := topicutil.ParseV3Topic(s) + if err != nil { + return nil, fmt.Errorf("invalid topic %q: %w", s, err) + } + key := string(t.Bytes()) + if _, ok := seen[key]; !ok { + seen[key] = struct{}{} + result = append(result, t) + } + } + + for _, b := range bytesTopics { + t, err := topic.ParseTopic(b) + if err != nil { + return nil, fmt.Errorf("invalid binary topic: %w", err) + } + key := string(t.Bytes()) + if _, ok := seen[key]; !ok { + seen[key] = struct{}{} + result = append(result, t) + } + } + + return result, nil +} + +func normalizeSubscriptionInputs(subs []*proto.Subscription) ([]interfaces.SubscriptionInput, error) { out := make([]interfaces.SubscriptionInput, len(subs)) for idx, sub := range subs { + var t *topic.Topic + var err error + if len(sub.TopicBytes) > 0 { + t, err = topic.ParseTopic(sub.TopicBytes) + if err != nil { + return nil, fmt.Errorf("invalid binary topic at index %d: %w", idx, err) + } + } else if sub.Topic != "" { + t, err = topicutil.ParseV3Topic(sub.Topic) + if err != nil { + return nil, fmt.Errorf("invalid topic %q at index %d: %w", sub.Topic, idx, err) + } + } else { + return nil, fmt.Errorf("subscription at index %d has no topic", idx) + } out[idx] = interfaces.SubscriptionInput{ - Topic: sub.Topic, + Topic: t, IsSilent: sub.IsSilent, HmacKeys: buildHmacKeys(sub.HmacKeys), } } - return out + return out, nil } func buildHmacKeys(protoKeys []*proto.Subscription_HmacKey) []interfaces.HmacKey { diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index 854a10c5..e284cf54 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -18,6 +18,8 @@ import ( "github.com/xmtp/example-notification-server-go/pkg/testutils" proto "github.com/xmtp/example-notification-server-go/pkg/proto/notifications/v1" protoconnect "github.com/xmtp/example-notification-server-go/pkg/proto/notifications/v1/notificationsv1connect" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" ) const INSTALLATION_ID = "install1" @@ -161,7 +163,7 @@ func Test_DeleteInstallation(t *testing.T) { func Test_Subscribe(t *testing.T) { ctx := setupTest(t) - topics := []string{"topic1"} + topics := []string{"/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto"} ctx.subscriptionsMock.On( "Subscribe", @@ -184,7 +186,7 @@ func Test_Subscribe(t *testing.T) { "Subscribe", mock.Anything, INSTALLATION_ID, - topics, + mock.Anything, ) } @@ -202,7 +204,7 @@ func Test_SubscribeError(t *testing.T) { ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ InstallationId: INSTALLATION_ID, - Topics: []string{"topic1"}, + Topics: []string{"/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto"}, }), ) @@ -212,7 +214,7 @@ func Test_SubscribeError(t *testing.T) { func Test_Unsubscribe(t *testing.T) { ctx := setupTest(t) - topics := []string{"topic1"} + topics := []string{"/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto"} ctx.subscriptionsMock.On( "Unsubscribe", @@ -235,6 +237,150 @@ func Test_Unsubscribe(t *testing.T) { "Unsubscribe", mock.Anything, INSTALLATION_ID, - topics, + mock.Anything, ) } + +func Test_Subscribe_StringTopics(t *testing.T) { + ctx := setupTest(t) + + topicStr := "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto" + ctx.subscriptionsMock.On("Subscribe", mock.Anything, INSTALLATION_ID, mock.Anything).Return(nil) + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + Topics: []string{topicStr}, + })) + require.NoError(t, err) + ctx.subscriptionsMock.AssertCalled(t, "Subscribe", mock.Anything, INSTALLATION_ID, mock.Anything) +} + +func Test_Subscribe_BytesTopics(t *testing.T) { + ctx := setupTest(t) + + parsed, _ := topicutil.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") + ctx.subscriptionsMock.On("Subscribe", mock.Anything, INSTALLATION_ID, mock.Anything).Return(nil) + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + TopicsBytes: [][]byte{parsed.Bytes()}, + })) + require.NoError(t, err) +} + +func Test_Subscribe_InvalidStringTopic(t *testing.T) { + ctx := setupTest(t) + + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + Topics: []string{"invalid-topic"}, + })) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid") +} + +func Test_Subscribe_InvalidBytesTopics(t *testing.T) { + ctx := setupTest(t) + + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + TopicsBytes: [][]byte{{0xFF}}, // Too short, invalid kind + })) + require.Error(t, err) +} + +func Test_Subscribe_MergedTopics(t *testing.T) { + ctx := setupTest(t) + + parsed, _ := topicutil.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") + ctx.subscriptionsMock.On("Subscribe", mock.Anything, INSTALLATION_ID, mock.MatchedBy(func(topics []*topicpkg.Topic) bool { + return len(topics) == 1 // deduplicated + })).Return(nil) + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + Topics: []string{"/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto"}, + TopicsBytes: [][]byte{parsed.Bytes()}, + })) + require.NoError(t, err) +} + +func Test_Subscribe_EmptyTopics(t *testing.T) { + ctx := setupTest(t) + + ctx.subscriptionsMock.On("Subscribe", mock.Anything, INSTALLATION_ID, mock.Anything).Return(nil) + _, err := ctx.client.Subscribe(ctx.ctx, connect.NewRequest(&proto.SubscribeRequest{ + InstallationId: INSTALLATION_ID, + })) + require.NoError(t, err) +} + +func Test_Unsubscribe_BytesTopics(t *testing.T) { + ctx := setupTest(t) + + parsed, _ := topicutil.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") + ctx.subscriptionsMock.On("Unsubscribe", mock.Anything, INSTALLATION_ID, mock.MatchedBy(func(topics []*topicpkg.Topic) bool { + return len(topics) == 1 && + topics[0].Kind() == topicpkg.TopicKindGroupMessagesV1 && + string(topics[0].Bytes()) == string(parsed.Bytes()) + })).Return(nil) + _, err := ctx.client.Unsubscribe(ctx.ctx, connect.NewRequest(&proto.UnsubscribeRequest{ + InstallationId: INSTALLATION_ID, + TopicsBytes: [][]byte{parsed.Bytes()}, + })) + require.NoError(t, err) + ctx.subscriptionsMock.AssertCalled(t, "Unsubscribe", mock.Anything, INSTALLATION_ID, mock.Anything) +} + +func Test_SubscribeWithMetadata_StringTopic(t *testing.T) { + ctx := setupTest(t) + + ctx.subscriptionsMock.On("SubscribeWithMetadata", mock.Anything, INSTALLATION_ID, mock.Anything).Return(nil) + _, err := ctx.client.SubscribeWithMetadata(ctx.ctx, connect.NewRequest(&proto.SubscribeWithMetadataRequest{ + InstallationId: INSTALLATION_ID, + Subscriptions: []*proto.Subscription{{ + Topic: "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", + IsSilent: true, + }}, + })) + require.NoError(t, err) + ctx.subscriptionsMock.AssertCalled(t, "SubscribeWithMetadata", mock.Anything, INSTALLATION_ID, mock.Anything) +} + +func Test_SubscribeWithMetadata_BytesTakesPrecedence(t *testing.T) { + ctx := setupTest(t) + + parsed, _ := topicutil.ParseV3Topic("/xmtp/mls/1/w-abcdef0123456789/proto") + ctx.subscriptionsMock.On("SubscribeWithMetadata", mock.Anything, INSTALLATION_ID, mock.MatchedBy(func(inputs []interfaces.SubscriptionInput) bool { + return len(inputs) == 1 && inputs[0].Topic.Kind() == topicpkg.TopicKindWelcomeMessagesV1 + })).Return(nil) + _, err := ctx.client.SubscribeWithMetadata(ctx.ctx, connect.NewRequest(&proto.SubscribeWithMetadataRequest{ + InstallationId: INSTALLATION_ID, + Subscriptions: []*proto.Subscription{{ + Topic: "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", + TopicBytes: parsed.Bytes(), + }}, + })) + require.NoError(t, err) +} + +func Test_SubscribeWithMetadata_InvalidTopic(t *testing.T) { + ctx := setupTest(t) + + _, err := ctx.client.SubscribeWithMetadata(ctx.ctx, connect.NewRequest(&proto.SubscribeWithMetadataRequest{ + InstallationId: INSTALLATION_ID, + Subscriptions: []*proto.Subscription{{ + Topic: "not-a-valid-topic", + }}, + })) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid") +} + +func Test_SubscribeWithMetadata_EmptyTopic(t *testing.T) { + ctx := setupTest(t) + + _, err := ctx.client.SubscribeWithMetadata(ctx.ctx, connect.NewRequest(&proto.SubscribeWithMetadataRequest{ + InstallationId: INSTALLATION_ID, + Subscriptions: []*proto.Subscription{{}}, + })) + require.Error(t, err) + require.Contains(t, err.Error(), "no topic") +} diff --git a/pkg/db/db.go b/pkg/db/db.go index 97d8ee09..ff04b29f 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -33,6 +33,10 @@ func Migrate(ctx context.Context, db *sql.DB) error { return migrations.Migrate(ctx, db) } +func MigrateUpTo(ctx context.Context, db *sql.DB, version uint) error { + return migrations.MigrateUpTo(ctx, db, version) +} + func CreateMigrationFiles(name string) ([]migrations.File, error) { return migrations.CreateFiles("pkg/db/migrations", name) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index 7f3ad2b0..2d362ac3 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -80,6 +80,50 @@ func TestMigrateExistingLegacySchema(t *testing.T) { require.Equal(t, latest, version) } +func TestMigration_BinaryTopics_DataConversion(t *testing.T) { + db := testdb.CreateEmptyTestDb(t) + + // Run migrations up through 00003 (db_perf_improvements, pre-binary schema) + require.NoError(t, database.MigrateUpTo(t.Context(), db, 3)) + + // Seed rows: conforming group, conforming welcome, non-conforming, duplicate (case diff) + _, err := db.ExecContext(t.Context(), ` + INSERT INTO installations (id) VALUES ('inst1'); + INSERT INTO subscriptions (installation_id, topic, is_active) VALUES + ('inst1', '/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto', TRUE), + ('inst1', '/xmtp/mls/1/w-f3ac64eba2272334/proto', TRUE), + ('inst1', '/xmtp/mls/1/w-test_installation/proto', TRUE), + ('inst1', '/xmtp/mls/1/g-24CE39D660600B3A98ADFF3075B6D1F4/proto', TRUE); + `) + require.NoError(t, err) + + // Run migration 00004 (binary conversion + drop legacy column) + require.NoError(t, database.MigrateUpTo(t.Context(), db, 4)) + + // Verify: group topic converted correctly (first byte 0x00 = TopicKindGroupMessagesV1) + var topicBytes []byte + err = db.QueryRowContext(t.Context(), + "SELECT topic FROM subscriptions WHERE installation_id = 'inst1' AND get_byte(topic, 0) = 0 LIMIT 1", + ).Scan(&topicBytes) + require.NoError(t, err) + require.Equal(t, byte(0x00), topicBytes[0]) // TopicKindGroupMessagesV1 + + // Verify: non-conforming row deleted, duplicate collapsed — 2 rows remain + var count int + err = db.QueryRowContext(t.Context(), + "SELECT COUNT(*) FROM subscriptions WHERE installation_id = 'inst1'", + ).Scan(&count) + require.NoError(t, err) + require.Equal(t, 2, count) + + // Verify topic_legacy column does not exist + err = db.QueryRowContext(t.Context(), + "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'topic_legacy'", + ).Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count) +} + func createRawDB(t *testing.T) *sql.DB { t.Helper() diff --git a/pkg/db/migrations/00004_binary_topics.down.sql b/pkg/db/migrations/00004_binary_topics.down.sql new file mode 100644 index 00000000..39901fdf --- /dev/null +++ b/pkg/db/migrations/00004_binary_topics.down.sql @@ -0,0 +1,11 @@ +DROP INDEX IF EXISTS subscriptions_topic_is_active_idx; +DROP INDEX IF EXISTS subscriptions_installation_id_topic_idx; + +ALTER TABLE subscriptions ADD COLUMN topic_legacy TEXT; +ALTER TABLE subscriptions DROP COLUMN topic; +ALTER TABLE subscriptions RENAME COLUMN topic_legacy TO topic; + +CREATE INDEX subscriptions_topic_is_active_idx + ON subscriptions (topic, is_active); +CREATE UNIQUE INDEX subscriptions_installation_id_topic_idx + ON subscriptions (installation_id, topic); diff --git a/pkg/db/migrations/00004_binary_topics.up.sql b/pkg/db/migrations/00004_binary_topics.up.sql new file mode 100644 index 00000000..e4935c9d --- /dev/null +++ b/pkg/db/migrations/00004_binary_topics.up.sql @@ -0,0 +1,45 @@ +-- Step 1: Drop indexes that reference the topic column +DROP INDEX IF EXISTS subscriptions_topic_is_active_idx; +DROP INDEX IF EXISTS subscriptions_installation_id_topic_idx; + +-- Step 2: Rename existing column +ALTER TABLE subscriptions RENAME COLUMN topic TO topic_legacy; + +-- Step 3: Add new binary column +ALTER TABLE subscriptions ADD COLUMN topic BYTEA; + +-- Step 4: Single-pass conversion using lower() inline (topic_legacy is not modified) +UPDATE subscriptions +SET topic = CASE + WHEN lower(topic_legacy) ~ '^/xmtp/mls/1/g-([0-9a-f]{2})+/proto$' + THEN decode('00', 'hex') || decode( + substring(lower(topic_legacy) FROM '/xmtp/mls/1/g-(.+)/proto$'), 'hex') + WHEN lower(topic_legacy) ~ '^/xmtp/mls/1/w-([0-9a-f]{2})+/proto$' + THEN decode('01', 'hex') || decode( + substring(lower(topic_legacy) FROM '/xmtp/mls/1/w-(.+)/proto$'), 'hex') + ELSE NULL +END; + +-- Step 5: Delete rows that couldn't be converted +DELETE FROM subscriptions WHERE topic IS NULL; + +-- Step 6: Deduplicate rows that collapsed to the same (installation_id, topic) after +-- hex normalization (e.g., rows differing only by hex casing). Keep the newest row. +DELETE FROM subscriptions older +USING subscriptions newer +WHERE older.installation_id = newer.installation_id + AND older.topic = newer.topic + AND older.id < newer.id; + +-- Step 7: Add NOT NULL constraint +ALTER TABLE subscriptions ALTER COLUMN topic SET NOT NULL; + +-- Step 8: Recreate indexes on new column +CREATE INDEX subscriptions_topic_is_active_idx + ON subscriptions (topic, is_active); + +CREATE UNIQUE INDEX subscriptions_installation_id_topic_idx + ON subscriptions (installation_id, topic); + +-- Step 9: Drop legacy column +ALTER TABLE subscriptions DROP COLUMN topic_legacy; diff --git a/pkg/db/migrations/migrations.go b/pkg/db/migrations/migrations.go index a1740cdb..65dfb0ce 100644 --- a/pkg/db/migrations/migrations.go +++ b/pkg/db/migrations/migrations.go @@ -56,6 +56,19 @@ func LatestVersion() (int, error) { // deployments keep their existing application tables while still allowing future // golang-migrate-only migrations to run normally after upgrade. func Migrate(ctx context.Context, db *sql.DB) error { + return withMigrator(ctx, db, func(m *migrate.Migrate) error { + return m.Up() + }) +} + +// MigrateUpTo runs migrations up to (and including) the given version. +func MigrateUpTo(ctx context.Context, db *sql.DB, version uint) error { + return withMigrator(ctx, db, func(m *migrate.Migrate) error { + return m.Migrate(version) + }) +} + +func withMigrator(ctx context.Context, db *sql.DB, fn func(*migrate.Migrate) error) error { if err := reconcileExistingBunSchema(ctx, db); err != nil { return err } @@ -92,7 +105,7 @@ func Migrate(ctx context.Context, db *sql.DB) error { _, _ = migrator.Close() }() - if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + if err := fn(migrator); err != nil && !errors.Is(err, migrate.ErrNoChange) { return err } diff --git a/pkg/db/queries/models.go b/pkg/db/queries/models.go index 376dcd10..1094bdec 100644 --- a/pkg/db/queries/models.go +++ b/pkg/db/queries/models.go @@ -28,9 +28,9 @@ type Subscription struct { ID int64 InstallationID string CreatedAt time.Time - Topic string IsActive bool IsSilent bool + Topic []byte } type SubscriptionHmacKey struct { diff --git a/pkg/db/queries/subscriptions.sql.go b/pkg/db/queries/subscriptions.sql.go index 961e8195..edbe17b6 100644 --- a/pkg/db/queries/subscriptions.sql.go +++ b/pkg/db/queries/subscriptions.sql.go @@ -16,13 +16,13 @@ import ( const batchInsertSubscriptions = `-- name: BatchInsertSubscriptions :exec INSERT INTO subscriptions (installation_id, topic, is_active, is_silent) SELECT $1::text, t.topic, TRUE, FALSE -FROM unnest($2::text[]) AS t(topic) +FROM unnest($2::bytea[]) AS t(topic) ON CONFLICT (installation_id, topic) DO NOTHING ` type BatchInsertSubscriptionsParams struct { InstallationID string - Topics []string + Topics [][]byte } func (q *Queries) BatchInsertSubscriptions(ctx context.Context, arg BatchInsertSubscriptionsParams) error { @@ -57,7 +57,7 @@ const batchUpsertSubscriptions = `-- name: BatchUpsertSubscriptions :many INSERT INTO subscriptions (installation_id, topic, is_active, is_silent) SELECT $1::text, t.topic, TRUE, t.is_silent FROM ROWS FROM ( - unnest($2::text[]), + unnest($2::bytea[]), unnest($3::boolean[]) ) AS t(topic, is_silent) ON CONFLICT (installation_id, topic) DO UPDATE @@ -67,13 +67,13 @@ RETURNING id, topic type BatchUpsertSubscriptionsParams struct { InstallationID string - Topics []string + Topics [][]byte IsSilents []bool } type BatchUpsertSubscriptionsRow struct { ID int64 - Topic string + Topic []byte } func (q *Queries) BatchUpsertSubscriptions(ctx context.Context, arg BatchUpsertSubscriptionsParams) ([]BatchUpsertSubscriptionsRow, error) { @@ -115,12 +115,12 @@ const deactivateSubscriptions = `-- name: DeactivateSubscriptions :exec UPDATE subscriptions SET is_active = FALSE WHERE installation_id = $1 - AND topic = ANY($2::text[]) + AND topic = ANY($2::bytea[]) ` type DeactivateSubscriptionsParams struct { InstallationID string - Topics []string + Topics [][]byte } func (q *Queries) DeactivateSubscriptions(ctx context.Context, arg DeactivateSubscriptionsParams) error { @@ -150,14 +150,14 @@ ORDER BY s.id type ListActiveSubscriptionsByTopicAndPeriodParams struct { ThirtyDayPeriod int32 - Topic string + Topic []byte } type ListActiveSubscriptionsByTopicAndPeriodRow struct { ID int64 CreatedAt time.Time InstallationID string - Topic string + Topic []byte IsActive bool IsSilent bool HasHmacKey interface{} @@ -202,20 +202,20 @@ const reactivateSubscriptions = `-- name: ReactivateSubscriptions :many UPDATE subscriptions SET is_active = TRUE WHERE installation_id = $1 - AND topic = ANY($2::text[]) + AND topic = ANY($2::bytea[]) RETURNING id, created_at, installation_id, topic, is_active, is_silent ` type ReactivateSubscriptionsParams struct { InstallationID string - Topics []string + Topics [][]byte } type ReactivateSubscriptionsRow struct { ID int64 CreatedAt time.Time InstallationID string - Topic string + Topic []byte IsActive bool IsSilent bool } diff --git a/pkg/db/sqlc/subscriptions.sql b/pkg/db/sqlc/subscriptions.sql index 046d129c..14aca7fc 100644 --- a/pkg/db/sqlc/subscriptions.sql +++ b/pkg/db/sqlc/subscriptions.sql @@ -2,14 +2,14 @@ UPDATE subscriptions SET is_active = TRUE WHERE installation_id = sqlc.arg(installation_id) - AND topic = ANY(sqlc.arg(topics)::text[]) + AND topic = ANY(sqlc.arg(topics)::bytea[]) RETURNING id, created_at, installation_id, topic, is_active, is_silent; -- name: DeactivateSubscriptions :exec UPDATE subscriptions SET is_active = FALSE WHERE installation_id = sqlc.arg(installation_id) - AND topic = ANY(sqlc.arg(topics)::text[]); + AND topic = ANY(sqlc.arg(topics)::bytea[]); -- name: ListActiveSubscriptionsByTopicAndPeriod :many SELECT @@ -34,7 +34,7 @@ ORDER BY s.id; INSERT INTO subscriptions (installation_id, topic, is_active, is_silent) SELECT sqlc.arg(installation_id)::text, t.topic, TRUE, t.is_silent FROM ROWS FROM ( - unnest(sqlc.arg(topics)::text[]), + unnest(sqlc.arg(topics)::bytea[]), unnest(sqlc.arg(is_silents)::boolean[]) ) AS t(topic, is_silent) ON CONFLICT (installation_id, topic) DO UPDATE @@ -55,7 +55,7 @@ SET key = EXCLUDED.key, updated_at = NOW(); -- name: BatchInsertSubscriptions :exec INSERT INTO subscriptions (installation_id, topic, is_active, is_silent) SELECT sqlc.arg(installation_id)::text, t.topic, TRUE, FALSE -FROM unnest(sqlc.arg(topics)::text[]) AS t(topic) +FROM unnest(sqlc.arg(topics)::bytea[]) AS t(topic) ON CONFLICT (installation_id, topic) DO NOTHING; -- name: DeactivateInstallationSubscriptions :exec diff --git a/pkg/delivery/apns.go b/pkg/delivery/apns.go index 52380130..f8003799 100644 --- a/pkg/delivery/apns.go +++ b/pkg/delivery/apns.go @@ -3,8 +3,8 @@ package delivery import ( "context" "errors" - "strings" "os" + "strings" "github.com/sideshow/apns2" "github.com/sideshow/apns2/payload" @@ -64,12 +64,7 @@ func (a ApnsDelivery) Send(ctx context.Context, req interfaces.SendRequest) erro return errors.New("missing message") } - notification := a.buildNotification(req.Subscription.IsSilent, - req.Installation.DeliveryMechanism.Token, - req.Message.ContentTopic, - string(req.MessageContext.MessageType), - req.Message.Message, - ) + notification := a.buildNotification(req) res, err := a.apnsClient.PushWithContext(ctx, notification) if res != nil { @@ -84,13 +79,13 @@ func (a ApnsDelivery) Send(ctx context.Context, req interfaces.SendRequest) erro return err } -func (a ApnsDelivery) buildNotification(isSilent bool, token string, contentTopic string, messageKind string, messageBytes []byte) *apns2.Notification { +func (a ApnsDelivery) buildNotification(req interfaces.SendRequest) *apns2.Notification { notificationPayload := payload.NewPayload(). - Custom("topic", contentTopic). - Custom("encryptedMessage", messageBytes). - Custom("messageKind", messageKind) + Custom("topic", req.Subscription.Topic). + Custom("encryptedMessage", req.Message.Message). + Custom("messageKind", string(req.MessageContext.MessageType)) - if isSilent { + if req.Subscription.IsSilent { notificationPayload = notificationPayload.ContentAvailable() } else { notificationPayload = notificationPayload. @@ -99,7 +94,7 @@ func (a ApnsDelivery) buildNotification(isSilent bool, token string, contentTopi } return &apns2.Notification{ - DeviceToken: token, + DeviceToken: req.Installation.DeliveryMechanism.Token, Topic: a.opts.Topic, Payload: notificationPayload, } diff --git a/pkg/delivery/apns_test.go b/pkg/delivery/apns_test.go new file mode 100644 index 00000000..112be501 --- /dev/null +++ b/pkg/delivery/apns_test.go @@ -0,0 +1,42 @@ +package delivery + +import ( + "encoding/json" + "testing" + + "github.com/sideshow/apns2/payload" + "github.com/stretchr/testify/require" + "github.com/xmtp/example-notification-server-go/pkg/interfaces" + "github.com/xmtp/example-notification-server-go/pkg/options" + v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" + "github.com/xmtp/example-notification-server-go/pkg/topics" +) + +func Test_ApnsDelivery_BuildNotification_TopicField(t *testing.T) { + parsed, err := topics.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") + require.NoError(t, err) + + a := ApnsDelivery{opts: options.ApnsOptions{Topic: "com.example.app"}} + req := interfaces.SendRequest{ + Message: &v1.Envelope{Message: []byte("test")}, + Subscription: interfaces.Subscription{ + TopicV4: parsed, + Topic: topics.TopicToString(parsed), + }, + Installation: interfaces.Installation{ + DeliveryMechanism: interfaces.DeliveryMechanism{Token: "device-token"}, + }, + MessageContext: interfaces.MessageContext{MessageType: topics.V3Conversation}, + } + + notification := a.buildNotification(req) + payloadBytes, err := notification.Payload.(*payload.Payload).MarshalJSON() + require.NoError(t, err) + + var p map[string]interface{} + require.NoError(t, json.Unmarshal(payloadBytes, &p)) + require.Equal(t, "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", p["topic"]) + require.NotContains(t, p, "topicBytesB64") + require.Equal(t, "device-token", notification.DeviceToken) + require.Equal(t, "com.example.app", notification.Topic) +} diff --git a/pkg/delivery/fcm.go b/pkg/delivery/fcm.go index 8cd396dc..fa3d89a9 100644 --- a/pkg/delivery/fcm.go +++ b/pkg/delivery/fcm.go @@ -3,7 +3,6 @@ package delivery import ( "context" "encoding/base64" - firebase "firebase.google.com/go/v4" "firebase.google.com/go/v4/messaging" "github.com/pkg/errors" @@ -54,13 +53,7 @@ func (f FcmDelivery) Send(ctx context.Context, req interfaces.SendRequest) error return errors.New("missing message") } - message := base64.StdEncoding.EncodeToString(req.Message.Message) - topic := req.Message.ContentTopic - data := map[string]string{ - "topic": topic, - "encryptedMessage": message, - "messageType": string(req.MessageContext.MessageType), - } + data := buildFcmData(req) apnsHeaders := map[string]string{} androidPriority := "high" @@ -87,11 +80,10 @@ func (f FcmDelivery) Send(ctx context.Context, req interfaces.SendRequest) error APNS: &messaging.APNSConfig{ Headers: apnsHeaders, Payload: &messaging.APNSPayload{ - CustomData: map[string]interface { - }{ - "topic": topic, - "encryptedMessage": message, - "messageType": string(req.MessageContext.MessageType), + CustomData: map[string]interface{}{ + "topic": req.Subscription.Topic, + "encryptedMessage": data["encryptedMessage"], + "messageType": data["messageType"], }, Aps: &messaging.Aps{ ContentAvailable: req.Subscription.IsSilent, @@ -103,3 +95,11 @@ func (f FcmDelivery) Send(ctx context.Context, req interfaces.SendRequest) error return err } + +func buildFcmData(req interfaces.SendRequest) map[string]string { + return map[string]string{ + "topic": req.Subscription.Topic, + "encryptedMessage": base64.StdEncoding.EncodeToString(req.Message.Message), + "messageType": string(req.MessageContext.MessageType), + } +} diff --git a/pkg/delivery/fcm_test.go b/pkg/delivery/fcm_test.go new file mode 100644 index 00000000..8713c978 --- /dev/null +++ b/pkg/delivery/fcm_test.go @@ -0,0 +1,31 @@ +package delivery + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xmtp/example-notification-server-go/pkg/interfaces" + v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" + "github.com/xmtp/example-notification-server-go/pkg/topics" +) + +func Test_BuildFcmData_TopicField(t *testing.T) { + parsed, err := topics.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") + require.NoError(t, err) + + req := interfaces.SendRequest{ + Message: &v1.Envelope{Message: []byte("test")}, + Subscription: interfaces.Subscription{ + TopicV4: parsed, + Topic: topics.TopicToString(parsed), + }, + MessageContext: interfaces.MessageContext{MessageType: topics.V3Conversation}, + } + + data := buildFcmData(req) + require.Equal(t, "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", data["topic"]) + require.NotContains(t, data, "topicBytesB64") + require.Equal(t, base64.StdEncoding.EncodeToString([]byte("test")), data["encryptedMessage"]) + require.Equal(t, "v3-conversation", data["messageType"]) +} diff --git a/pkg/installations/installations_test.go b/pkg/installations/installations_test.go index 8af47fa1..6aa0316a 100644 --- a/pkg/installations/installations_test.go +++ b/pkg/installations/installations_test.go @@ -10,6 +10,7 @@ import ( "github.com/xmtp/example-notification-server-go/pkg/interfaces" "github.com/xmtp/example-notification-server-go/pkg/subscriptions" "github.com/xmtp/example-notification-server-go/pkg/testutils" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" ) const INSTALLATION_ID = "foo" @@ -230,14 +231,16 @@ func Test_DeleteDeactivatesSubscriptions(t *testing.T) { _, err := installSvc.Register(ctx, buildInstallation(INSTALLATION_ID, interfaces.APNS, TOKEN)) require.NoError(t, err) - err = subSvc.Subscribe(ctx, INSTALLATION_ID, []string{"topic1", "topic2"}) + topic1 := topicpkg.NewTopic(topicpkg.TopicKindGroupMessagesV1, []byte{0x01}) + topic2 := topicpkg.NewTopic(topicpkg.TopicKindGroupMessagesV1, []byte{0x02}) + err = subSvc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{topic1, topic2}) require.NoError(t, err) err = installSvc.Delete(ctx, INSTALLATION_ID) require.NoError(t, err) // Subscriptions should be deactivated - subs, err := subSvc.GetSubscriptions(ctx, "topic1", 1) + subs, err := subSvc.GetSubscriptions(ctx, topic1, 1) require.NoError(t, err) require.Len(t, subs, 0) } diff --git a/pkg/interfaces/interfaces.go b/pkg/interfaces/interfaces.go index 9bc7188f..eb073ab9 100644 --- a/pkg/interfaces/interfaces.go +++ b/pkg/interfaces/interfaces.go @@ -8,6 +8,7 @@ import ( v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" "github.com/xmtp/example-notification-server-go/pkg/topics" + "github.com/xmtp/xmtpd/pkg/topic" ) type DeliveryMechanismKind string @@ -39,13 +40,14 @@ type Installation struct { } type Subscription struct { - Id int64 `json:"-"` - CreatedAt time.Time `json:"created_at"` - InstallationId string `json:"-"` - Topic string `json:"topic"` - IsActive bool `json:"-"` - IsSilent bool `json:"is_silent"` - HmacKey *HmacKey `json:"-"` + Id int64 `json:"-"` + CreatedAt time.Time `json:"created_at"` + InstallationId string `json:"-"` + Topic string `json:"topic"` + TopicV4 *topic.Topic `json:"-"` + IsActive bool `json:"-"` + IsSilent bool `json:"is_silent"` + HmacKey *HmacKey `json:"-"` } type SendRequest struct { @@ -79,13 +81,11 @@ type HmacKey struct { } type SubscriptionInput struct { - Topic string + Topic *topic.Topic IsSilent bool HmacKeys []HmacKey } -type HmacUpdates map[string][]HmacKey - // Pluggable Installation Service interface // //go:generate mockery --dir ../interfaces --name Installations --output ../../mocks --outpkg mocks @@ -99,9 +99,9 @@ type Installations interface { // //go:generate mockery --dir ../interfaces --name Subscriptions --output ../../mocks --outpkg mocks type Subscriptions interface { - Subscribe(ctx context.Context, installationId string, topics []string) error - Unsubscribe(ctx context.Context, installationId string, topics []string) error - GetSubscriptions(ctx context.Context, topic string, thirtyDayPeriod int) ([]Subscription, error) + Subscribe(ctx context.Context, installationId string, topics []*topic.Topic) error + Unsubscribe(ctx context.Context, installationId string, topics []*topic.Topic) error + GetSubscriptions(ctx context.Context, t *topic.Topic, thirtyDayPeriod int) ([]Subscription, error) SubscribeWithMetadata(ctx context.Context, installationId string, subscriptions []SubscriptionInput) error } diff --git a/pkg/interfaces/interfaces_test.go b/pkg/interfaces/interfaces_test.go new file mode 100644 index 00000000..e3211c21 --- /dev/null +++ b/pkg/interfaces/interfaces_test.go @@ -0,0 +1,40 @@ +package interfaces + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xmtp/xmtpd/pkg/topic" +) + +func Test_Subscription_MarshalJSON_TopicOnly(t *testing.T) { + tp := topic.NewTopic(topic.TopicKindGroupMessagesV1, []byte{0x24, 0xce}) + sub := Subscription{ + TopicV4: tp, + Topic: "/xmtp/mls/1/g-24ce/proto", + IsSilent: true, + } + data, err := json.Marshal(sub) + require.NoError(t, err) + + var result map[string]interface{} + require.NoError(t, json.Unmarshal(data, &result)) + require.Equal(t, "/xmtp/mls/1/g-24ce/proto", result["topic"]) + require.NotContains(t, result, "topicBytesB64") + require.Equal(t, true, result["is_silent"]) +} + +func Test_Subscription_MarshalJSON_TopicV4NotSerialized(t *testing.T) { + tp := topic.NewTopic(topic.TopicKindGroupMessagesV1, []byte{0x24, 0xce}) + sub := Subscription{TopicV4: tp, Topic: "", IsSilent: false} + data, err := json.Marshal(sub) + require.NoError(t, err) + + var result map[string]interface{} + require.NoError(t, json.Unmarshal(data, &result)) + require.Equal(t, "", result["topic"]) + // TopicV4 is json:"-", should not appear in output + _, hasTopicV4 := result["topicV4"] + require.False(t, hasTopicV4) +} diff --git a/pkg/proto/notifications/v1/service.pb.go b/pkg/proto/notifications/v1/service.pb.go index d939711e..1e9ee4bb 100644 --- a/pkg/proto/notifications/v1/service.pb.go +++ b/pkg/proto/notifications/v1/service.pb.go @@ -274,10 +274,12 @@ func (x *DeleteInstallationRequest) GetInstallationId() string { // A subscription with associated metadata type Subscription struct { - state protoimpl.MessageState `protogen:"open.v1"` - Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` - HmacKeys []*Subscription_HmacKey `protobuf:"bytes,2,rep,name=hmac_keys,json=hmacKeys,proto3" json:"hmac_keys,omitempty"` - IsSilent bool `protobuf:"varint,3,opt,name=is_silent,json=isSilent,proto3" json:"is_silent,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + HmacKeys []*Subscription_HmacKey `protobuf:"bytes,2,rep,name=hmac_keys,json=hmacKeys,proto3" json:"hmac_keys,omitempty"` + IsSilent bool `protobuf:"varint,3,opt,name=is_silent,json=isSilent,proto3" json:"is_silent,omitempty"` + // Binary topic (V4 format). If both topic and topic_bytes are set, topic_bytes takes precedence. + TopicBytes []byte `protobuf:"bytes,4,opt,name=topic_bytes,json=topicBytes,proto3" json:"topic_bytes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -333,6 +335,13 @@ func (x *Subscription) GetIsSilent() bool { return false } +func (x *Subscription) GetTopicBytes() []byte { + if x != nil { + return x.TopicBytes + } + return nil +} + // A request to subscribe to a list of topics and update the associated metadata type SubscribeWithMetadataRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -391,8 +400,10 @@ type SubscribeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` InstallationId string `protobuf:"bytes,1,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` Topics []string `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Binary topics (V4 format). Merged with string topics if both are set. + TopicsBytes [][]byte `protobuf:"bytes,3,rep,name=topics_bytes,json=topicsBytes,proto3" json:"topics_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SubscribeRequest) Reset() { @@ -439,13 +450,22 @@ func (x *SubscribeRequest) GetTopics() []string { return nil } +func (x *SubscribeRequest) GetTopicsBytes() [][]byte { + if x != nil { + return x.TopicsBytes + } + return nil +} + // Unsubscribe from a list of topics type UnsubscribeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` InstallationId string `protobuf:"bytes,1,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` Topics []string `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Binary topics (V4 format). Merged with string topics if both are set. + TopicsBytes [][]byte `protobuf:"bytes,3,rep,name=topics_bytes,json=topicsBytes,proto3" json:"topics_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UnsubscribeRequest) Reset() { @@ -492,6 +512,13 @@ func (x *UnsubscribeRequest) GetTopics() []string { return nil } +func (x *UnsubscribeRequest) GetTopicsBytes() [][]byte { + if x != nil { + return x.TopicsBytes + } + return nil +} + type Subscription_HmacKey struct { state protoimpl.MessageState `protogen:"open.v1"` ThirtyDayPeriodsSinceEpoch uint32 `protobuf:"varint,1,opt,name=thirty_day_periods_since_epoch,json=thirtyDayPeriodsSinceEpoch,proto3" json:"thirty_day_periods_since_epoch,omitempty"` @@ -562,23 +589,27 @@ const file_notifications_v1_service_proto_rawDesc = "" + "\vvalid_until\x18\x02 \x01(\x04R\n" + "validUntil\"D\n" + "\x19DeleteInstallationRequest\x12'\n" + - "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\"\xe7\x01\n" + + "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\"\x88\x02\n" + "\fSubscription\x12\x14\n" + "\x05topic\x18\x01 \x01(\tR\x05topic\x12C\n" + "\thmac_keys\x18\x02 \x03(\v2&.notifications.v1.Subscription.HmacKeyR\bhmacKeys\x12\x1b\n" + - "\tis_silent\x18\x03 \x01(\bR\bisSilent\x1a_\n" + + "\tis_silent\x18\x03 \x01(\bR\bisSilent\x12\x1f\n" + + "\vtopic_bytes\x18\x04 \x01(\fR\n" + + "topicBytes\x1a_\n" + "\aHmacKey\x12B\n" + "\x1ethirty_day_periods_since_epoch\x18\x01 \x01(\rR\x1athirtyDayPeriodsSinceEpoch\x12\x10\n" + "\x03key\x18\x02 \x01(\fR\x03key\"\x8d\x01\n" + "\x1cSubscribeWithMetadataRequest\x12'\n" + "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12D\n" + - "\rsubscriptions\x18\x02 \x03(\v2\x1e.notifications.v1.SubscriptionR\rsubscriptions\"S\n" + + "\rsubscriptions\x18\x02 \x03(\v2\x1e.notifications.v1.SubscriptionR\rsubscriptions\"v\n" + "\x10SubscribeRequest\x12'\n" + "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12\x16\n" + - "\x06topics\x18\x02 \x03(\tR\x06topics\"U\n" + + "\x06topics\x18\x02 \x03(\tR\x06topics\x12!\n" + + "\ftopics_bytes\x18\x03 \x03(\fR\vtopicsBytes\"x\n" + "\x12UnsubscribeRequest\x12'\n" + "\x0finstallation_id\x18\x01 \x01(\tR\x0einstallationId\x12\x16\n" + - "\x06topics\x18\x02 \x03(\tR\x06topics2\xd8\x03\n" + + "\x06topics\x18\x02 \x03(\tR\x06topics\x12!\n" + + "\ftopics_bytes\x18\x03 \x03(\fR\vtopicsBytes2\xd8\x03\n" + "\rNotifications\x12u\n" + "\x14RegisterInstallation\x12-.notifications.v1.RegisterInstallationRequest\x1a..notifications.v1.RegisterInstallationResponse\x12Y\n" + "\x12DeleteInstallation\x12+.notifications.v1.DeleteInstallationRequest\x1a\x16.google.protobuf.Empty\x12G\n" + diff --git a/pkg/subscriptions/subscriptions.go b/pkg/subscriptions/subscriptions.go index 0792dd85..dc98a8b7 100644 --- a/pkg/subscriptions/subscriptions.go +++ b/pkg/subscriptions/subscriptions.go @@ -3,10 +3,13 @@ package subscriptions import ( "context" "database/sql" + "errors" database "github.com/xmtp/example-notification-server-go/pkg/db" "github.com/xmtp/example-notification-server-go/pkg/db/queries" "github.com/xmtp/example-notification-server-go/pkg/interfaces" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" + "github.com/xmtp/xmtpd/pkg/topic" "go.uber.org/zap" ) @@ -24,27 +27,31 @@ func NewSubscriptionsService(logger *zap.Logger, db *sql.DB) *SubscriptionsServi } } -func (s SubscriptionsService) Subscribe(ctx context.Context, installationID string, topics []string) error { +func (s SubscriptionsService) Subscribe(ctx context.Context, installationID string, topics []*topic.Topic) error { return database.RunInTx(ctx, s.db, func(qtx *queries.Queries) error { + topicBytes := make([][]byte, len(topics)) + for i, t := range topics { + topicBytes[i] = t.Bytes() + } + updated, err := qtx.ReactivateSubscriptions(ctx, queries.ReactivateSubscriptionsParams{ InstallationID: installationID, - Topics: topics, + Topics: topicBytes, }) if err != nil { return err } - topicMap := make(map[string]bool, len(topics)) - for _, topic := range topics { - topicMap[topic] = true - } + reactivated := make(map[string]bool, len(updated)) for _, result := range updated { - delete(topicMap, result.Topic) + reactivated[string(result.Topic)] = true } - remaining := make([]string, 0, len(topicMap)) - for topic := range topicMap { - remaining = append(remaining, topic) + var remaining [][]byte + for _, t := range topics { + if !reactivated[string(t.Bytes())] { + remaining = append(remaining, t.Bytes()) + } } if len(remaining) > 0 { @@ -68,16 +75,19 @@ func (s SubscriptionsService) SubscribeWithMetadata( return nil } - topics := make([]string, len(subscriptions)) + topicBytes := make([][]byte, len(subscriptions)) isSilents := make([]bool, len(subscriptions)) for i, sub := range subscriptions { - topics[i] = sub.Topic + if sub.Topic == nil { + return errors.New("subscription topic must not be nil") + } + topicBytes[i] = sub.Topic.Bytes() isSilents[i] = sub.IsSilent } rows, err := qtx.BatchUpsertSubscriptions(ctx, queries.BatchUpsertSubscriptionsParams{ InstallationID: installationID, - Topics: topics, + Topics: topicBytes, IsSilents: isSilents, }) if err != nil { @@ -86,7 +96,7 @@ func (s SubscriptionsService) SubscribeWithMetadata( topicToID := make(map[string]int64, len(rows)) for _, row := range rows { - topicToID[row.Topic] = row.ID + topicToID[string(row.Topic)] = row.ID } var ( @@ -95,7 +105,7 @@ func (s SubscriptionsService) SubscribeWithMetadata( keys [][]byte ) for _, sub := range subscriptions { - id := topicToID[sub.Topic] + id := topicToID[string(sub.Topic.Bytes())] for _, keyUpdate := range sub.HmacKeys { subscriptionIDs = append(subscriptionIDs, id) periods = append(periods, int32(keyUpdate.ThirtyDayPeriodsSinceEpoch)) @@ -115,23 +125,30 @@ func (s SubscriptionsService) SubscribeWithMetadata( }) } -func (s SubscriptionsService) Unsubscribe(ctx context.Context, installationID string, topics []string) error { +func (s SubscriptionsService) Unsubscribe(ctx context.Context, installationID string, topics []*topic.Topic) error { + topicBytes := make([][]byte, len(topics)) + for i, t := range topics { + topicBytes[i] = t.Bytes() + } return s.queries.DeactivateSubscriptions(ctx, queries.DeactivateSubscriptionsParams{ InstallationID: installationID, - Topics: topics, + Topics: topicBytes, }) } func (s SubscriptionsService) GetSubscriptions( ctx context.Context, - topic string, + t *topic.Topic, thirtyDayPeriod int, ) ([]interfaces.Subscription, error) { + if t == nil { + return nil, errors.New("topic must not be nil") + } results, err := s.queries.ListActiveSubscriptionsByTopicAndPeriod( ctx, queries.ListActiveSubscriptionsByTopicAndPeriodParams{ ThirtyDayPeriod: int32(thirtyDayPeriod), - Topic: topic, + Topic: t.Bytes(), }, ) if err != nil { @@ -140,11 +157,17 @@ func (s SubscriptionsService) GetSubscriptions( out := make([]interfaces.Subscription, 0, len(results)) for _, result := range results { + parsedTopic, err := topic.ParseTopic(result.Topic) + if err != nil { + s.logger.Warn("failed to parse topic from DB", zap.Error(err)) + continue + } subscription := interfaces.Subscription{ Id: result.ID, CreatedAt: result.CreatedAt, InstallationId: result.InstallationID, - Topic: result.Topic, + Topic: topicutil.TopicToString(parsedTopic), + TopicV4: parsedTopic, IsActive: result.IsActive, IsSilent: result.IsSilent, } diff --git a/pkg/subscriptions/subscriptions_test.go b/pkg/subscriptions/subscriptions_test.go index 49ecf4e7..70d474d7 100644 --- a/pkg/subscriptions/subscriptions_test.go +++ b/pkg/subscriptions/subscriptions_test.go @@ -9,16 +9,20 @@ import ( "github.com/stretchr/testify/require" "github.com/xmtp/example-notification-server-go/pkg/interfaces" "github.com/xmtp/example-notification-server-go/pkg/testutils" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" ) const INSTALLATION_ID = "installation_1" -const TOPIC = "topic1" + +// Valid V3 topic and its parsed form for testing +var TEST_TOPIC, _ = topicutil.ParseV3Topic("/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto") type storedSubscription struct { ID int64 CreatedAt time.Time InstallationID string - Topic string + Topic []byte IsActive bool IsSilent bool } @@ -71,14 +75,14 @@ func Test_Subscribe(t *testing.T) { svc := createService(t, db) - err := svc.Subscribe(ctx, INSTALLATION_ID, []string{TOPIC}) + err := svc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC}) require.NoError(t, err) stored := fetchSubscriptions(t, ctx, db, INSTALLATION_ID) require.Len(t, stored, 1) require.Equal(t, INSTALLATION_ID, stored[0].InstallationID) require.True(t, stored[0].IsActive) - require.Equal(t, TOPIC, stored[0].Topic) + require.Equal(t, TEST_TOPIC.Bytes(), stored[0].Topic) } func Test_SubscribeMultiple(t *testing.T) { @@ -86,16 +90,25 @@ func Test_SubscribeMultiple(t *testing.T) { db := testutils.CreateTestDb(t) svc := createService(t, db) - topics := []string{"topic_1", "topic_2", "topic_3"} + topic1 := topicpkg.NewTopic(topicpkg.TopicKindGroupMessagesV1, []byte{0x01}) + topic2 := topicpkg.NewTopic(topicpkg.TopicKindGroupMessagesV1, []byte{0x02}) + topic3 := topicpkg.NewTopic(topicpkg.TopicKindGroupMessagesV1, []byte{0x03}) + topics := []*topicpkg.Topic{topic1, topic2, topic3} + err := svc.Subscribe(ctx, INSTALLATION_ID, topics) require.NoError(t, err) stored := fetchSubscriptions(t, ctx, db, INSTALLATION_ID) require.Len(t, stored, 3) - for _, result := range stored { + + storedTopicBytes := make([][]byte, len(stored)) + for i, result := range stored { require.Equal(t, INSTALLATION_ID, result.InstallationID) require.NotZero(t, result.CreatedAt) - require.Contains(t, topics, result.Topic) + storedTopicBytes[i] = result.Topic + } + for _, tp := range topics { + require.Contains(t, storedTopicBytes, tp.Bytes()) } } @@ -104,10 +117,10 @@ func Test_Unsubscribe(t *testing.T) { db := testutils.CreateTestDb(t) svc := createService(t, db) - err := svc.Subscribe(ctx, INSTALLATION_ID, []string{TOPIC}) + err := svc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC}) require.NoError(t, err) - err = svc.Unsubscribe(ctx, INSTALLATION_ID, []string{TOPIC}) + err = svc.Unsubscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC}) require.NoError(t, err) stored := fetchSubscriptions(t, ctx, db, INSTALLATION_ID) @@ -120,9 +133,9 @@ func Test_UnsubscribeResubscribe(t *testing.T) { db := testutils.CreateTestDb(t) svc := createService(t, db) - require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []string{TOPIC})) - require.NoError(t, svc.Unsubscribe(ctx, INSTALLATION_ID, []string{TOPIC})) - require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []string{TOPIC})) + require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC})) + require.NoError(t, svc.Unsubscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC})) + require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC})) stored := fetchSubscriptions(t, ctx, db, INSTALLATION_ID) require.Len(t, stored, 1) @@ -136,7 +149,7 @@ func Test_SubscribeWithMetadata(t *testing.T) { key := []byte("key") err := svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ - Topic: TOPIC, + Topic: TEST_TOPIC, IsSilent: true, HmacKeys: []interfaces.HmacKey{{ ThirtyDayPeriodsSinceEpoch: 1, @@ -145,10 +158,11 @@ func Test_SubscribeWithMetadata(t *testing.T) { }}) require.NoError(t, err) - results, err := svc.GetSubscriptions(ctx, TOPIC, 1) + results, err := svc.GetSubscriptions(ctx, TEST_TOPIC, 1) require.NoError(t, err) require.Len(t, results, 1) - require.Equal(t, TOPIC, results[0].Topic) + require.Equal(t, TEST_TOPIC.Kind(), results[0].TopicV4.Kind()) + require.Equal(t, TEST_TOPIC.Bytes(), results[0].TopicV4.Bytes()) require.NotNil(t, results[0].HmacKey) require.True(t, results[0].IsSilent) require.Equal(t, key, results[0].HmacKey.Key) @@ -160,21 +174,21 @@ func Test_UpdateIsSilent(t *testing.T) { svc := createService(t, db) require.NoError(t, svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ - Topic: TOPIC, + Topic: TEST_TOPIC, IsSilent: false, }})) - results, err := svc.GetSubscriptions(ctx, TOPIC, 1) + results, err := svc.GetSubscriptions(ctx, TEST_TOPIC, 1) require.NoError(t, err) require.Len(t, results, 1) require.False(t, results[0].IsSilent) require.NoError(t, svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ - Topic: TOPIC, + Topic: TEST_TOPIC, IsSilent: true, }})) - results, err = svc.GetSubscriptions(ctx, TOPIC, 1) + results, err = svc.GetSubscriptions(ctx, TEST_TOPIC, 1) require.NoError(t, err) require.Len(t, results, 1) require.True(t, results[0].IsSilent) @@ -186,7 +200,7 @@ func Test_UpdateHmacKeys(t *testing.T) { svc := createService(t, db) require.NoError(t, svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ - Topic: TOPIC, + Topic: TEST_TOPIC, IsSilent: true, HmacKeys: []interfaces.HmacKey{{ ThirtyDayPeriodsSinceEpoch: 1, @@ -195,7 +209,7 @@ func Test_UpdateHmacKeys(t *testing.T) { }})) require.NoError(t, svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ - Topic: TOPIC, + Topic: TEST_TOPIC, IsSilent: true, HmacKeys: []interfaces.HmacKey{{ ThirtyDayPeriodsSinceEpoch: 1, @@ -203,7 +217,7 @@ func Test_UpdateHmacKeys(t *testing.T) { }}, }})) - results, err := svc.GetSubscriptions(ctx, TOPIC, 1) + results, err := svc.GetSubscriptions(ctx, TEST_TOPIC, 1) require.NoError(t, err) require.Len(t, results, 1) require.Equal(t, []byte("key2"), results[0].HmacKey.Key) @@ -214,9 +228,22 @@ func Test_GetSubscriptions(t *testing.T) { db := testutils.CreateTestDb(t) svc := createService(t, db) - require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []string{TOPIC})) + require.NoError(t, svc.Subscribe(ctx, INSTALLATION_ID, []*topicpkg.Topic{TEST_TOPIC})) - subs, err := svc.GetSubscriptions(ctx, TOPIC, 1) + subs, err := svc.GetSubscriptions(ctx, TEST_TOPIC, 1) require.NoError(t, err) require.Len(t, subs, 1) } + +func Test_SubscribeWithMetadata_NilTopic(t *testing.T) { + ctx := t.Context() + db := testutils.CreateTestDb(t) + svc := createService(t, db) + + err := svc.SubscribeWithMetadata(ctx, INSTALLATION_ID, []interfaces.SubscriptionInput{{ + Topic: nil, + IsSilent: false, + }}) + require.Error(t, err) + require.Contains(t, err.Error(), "nil") +} diff --git a/pkg/testutils/topic.go b/pkg/testutils/topic.go new file mode 100644 index 00000000..12fc594e --- /dev/null +++ b/pkg/testutils/topic.go @@ -0,0 +1,23 @@ +package testutils + +import ( + "testing" + + "github.com/stretchr/testify/require" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" +) + +// MustParseTopic parses a V3 topic string or fails the test. +func MustParseTopic(t *testing.T, topicStr string) *topicpkg.Topic { + t.Helper() + parsed, err := topicutil.ParseV3Topic(topicStr) + require.NoError(t, err) + return parsed +} + +// MustParseTopicBytes parses a V3 topic string and returns raw bytes, or fails. +func MustParseTopicBytes(t *testing.T, topicStr string) []byte { + t.Helper() + return MustParseTopic(t, topicStr).Bytes() +} diff --git a/pkg/topics/topics.go b/pkg/topics/topics.go index 33e39426..19a2bb0f 100644 --- a/pkg/topics/topics.go +++ b/pkg/topics/topics.go @@ -1,31 +1,71 @@ package topics import ( + "encoding/hex" + "fmt" "strings" - v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" + "github.com/xmtp/xmtpd/pkg/topic" ) const V3_PREFIX = "/xmtp/mls/1/" -var messageTypeByPrefix = map[string]MessageType{ - "test": Test, - "g": V3Conversation, - "w": V3Welcome, +func ParseV3Topic(topicStr string) (*topic.Topic, error) { + topicStr = strings.TrimPrefix(topicStr, V3_PREFIX) + if !strings.HasSuffix(topicStr, "/proto") { + return nil, fmt.Errorf("invalid V3 topic: missing /proto suffix") + } + topicStr = strings.TrimSuffix(topicStr, "/proto") + + prefix, identifier, hasPrefix := strings.Cut(topicStr, "-") + if !hasPrefix || identifier == "" { + return nil, fmt.Errorf("invalid V3 topic: missing prefix or identifier") + } + + var kind topic.TopicKind + switch prefix { + case "g": + kind = topic.TopicKindGroupMessagesV1 + case "w": + kind = topic.TopicKindWelcomeMessagesV1 + default: + return nil, fmt.Errorf("invalid V3 topic: unknown prefix %q", prefix) + } + + identifierBytes, err := hex.DecodeString(strings.ToLower(identifier)) + if err != nil { + return nil, fmt.Errorf("invalid V3 topic: bad hex identifier: %w", err) + } + + return topic.NewTopic(kind, identifierBytes), nil } -func GetMessageType(env *v1.Envelope) MessageType { - topic := env.ContentTopic - if strings.HasPrefix(topic, "test-") { - return Test +func TopicToString(t *topic.Topic) string { + if t == nil { + return "" } - topic = strings.TrimPrefix(topic, V3_PREFIX) - prefix, _, hasPrefix := strings.Cut(topic, "-") - if hasPrefix { - if category, found := messageTypeByPrefix[prefix]; found { - return category - } + var prefix string + switch t.Kind() { + case topic.TopicKindGroupMessagesV1: + prefix = "g" + case topic.TopicKindWelcomeMessagesV1: + prefix = "w" + default: + return "" } + return V3_PREFIX + prefix + "-" + hex.EncodeToString(t.Identifier()) + "/proto" +} - return Unknown +func GetMessageTypeFromTopic(t *topic.Topic) MessageType { + if t == nil { + return Unknown + } + switch t.Kind() { + case topic.TopicKindGroupMessagesV1: + return V3Conversation + case topic.TopicKindWelcomeMessagesV1: + return V3Welcome + default: + return Unknown + } } diff --git a/pkg/topics/topics_test.go b/pkg/topics/topics_test.go new file mode 100644 index 00000000..e6a5a846 --- /dev/null +++ b/pkg/topics/topics_test.go @@ -0,0 +1,142 @@ +package topics + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xmtp/xmtpd/pkg/topic" +) + +func TestParseV3Topic(t *testing.T) { + tests := []struct { + name string + input string + wantKind topic.TopicKind + wantIdentifier string // hex-encoded expected identifier (empty if wantErr) + wantErr bool + }{ + { + name: "group message", + input: "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", + wantKind: topic.TopicKindGroupMessagesV1, + wantIdentifier: "24ce39d660600b3a98adff3075b6d1f4", + }, + { + name: "welcome message", + input: "/xmtp/mls/1/w-f3ac64eba2272334124975d673374bdd64a5535bf5f7b48ac5608ff499444be0/proto", + wantKind: topic.TopicKindWelcomeMessagesV1, + }, + { + name: "mixed case hex", + input: "/xmtp/mls/1/g-24CE39D660600B3A98ADFF3075B6D1F4/proto", + wantKind: topic.TopicKindGroupMessagesV1, + }, + { + name: "invalid pattern", + input: "/xmtp/mls/1/x-abc123/proto", + wantErr: true, + }, + { + name: "not hex", + input: "/xmtp/mls/1/g-zzzz/proto", + wantErr: true, + }, + { + name: "missing suffix", + input: "/xmtp/mls/1/g-24ce39d660600b3a", + wantErr: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result, err := ParseV3Topic(tc.input) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.wantKind, result.Kind()) + if tc.wantIdentifier != "" { + expected, _ := hex.DecodeString(tc.wantIdentifier) + require.Equal(t, expected, result.Identifier()) + } + }) + } +} + +func TestTopicToString(t *testing.T) { + tests := []struct { + name string + kind topic.TopicKind + identifier string // hex-encoded + want string + }{ + { + name: "group message", + kind: topic.TopicKindGroupMessagesV1, + identifier: "24ce39d660600b3a98adff3075b6d1f4", + want: "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto", + }, + { + name: "welcome message", + kind: topic.TopicKindWelcomeMessagesV1, + identifier: "f3ac64eba2272334", + want: "/xmtp/mls/1/w-f3ac64eba2272334/proto", + }, + { + name: "unknown kind returns empty", + kind: topic.TopicKindIdentityUpdatesV1, + identifier: "abcd", + want: "", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + identifier, _ := hex.DecodeString(tc.identifier) + tp := topic.NewTopic(tc.kind, identifier) + require.Equal(t, tc.want, TopicToString(tp)) + }) + } +} + +func TestTopicToString_Roundtrip(t *testing.T) { + original := "/xmtp/mls/1/g-24ce39d660600b3a98adff3075b6d1f4/proto" + parsed, err := ParseV3Topic(original) + require.NoError(t, err) + require.Equal(t, original, TopicToString(parsed)) +} + +func TestGetMessageTypeFromTopic(t *testing.T) { + tests := []struct { + name string + topic *topic.Topic + want MessageType + }{ + { + name: "group", + topic: topic.NewTopic(topic.TopicKindGroupMessagesV1, []byte{0x01}), + want: V3Conversation, + }, + { + name: "welcome", + topic: topic.NewTopic(topic.TopicKindWelcomeMessagesV1, []byte{0x01}), + want: V3Welcome, + }, + { + name: "unknown kind", + topic: topic.NewTopic(topic.TopicKindIdentityUpdatesV1, []byte{0x01}), + want: Unknown, + }, + { + name: "nil topic", + topic: nil, + want: Unknown, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, GetMessageTypeFromTopic(tc.topic)) + }) + } +} diff --git a/pkg/xmtp/listener.go b/pkg/xmtp/listener.go index d39d4693..150672b0 100644 --- a/pkg/xmtp/listener.go +++ b/pkg/xmtp/listener.go @@ -11,6 +11,8 @@ import ( "github.com/xmtp/example-notification-server-go/pkg/interfaces" "github.com/xmtp/example-notification-server-go/pkg/options" v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" + "github.com/xmtp/example-notification-server-go/pkg/topics" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" "go.uber.org/zap" ) @@ -139,11 +141,18 @@ func (l *Listener) startMessageWorkers() { } func (l *Listener) processEnvelope(env *v1.Envelope) error { - if !isV3Topic(env.ContentTopic) { + // Fast-path: skip expensive parsing for topics that can't be V3 + if !strings.HasPrefix(env.ContentTopic, topics.V3_PREFIX) { l.logger.Debug("ignoring message", zap.String("topic", env.ContentTopic)) return nil } - subs, err := l.subscriptions.GetSubscriptions(l.ctx, env.ContentTopic, getThirtyDayPeriodsFromEpoch(env)) + t, err := topics.ParseV3Topic(env.ContentTopic) + if err != nil { + l.logger.Info("ignoring message with non-convertible topic", + zap.String("topic", env.ContentTopic), zap.Error(err)) + return nil + } + subs, err := l.subscriptions.GetSubscriptions(l.ctx, t, getThirtyDayPeriodsFromEpoch(env)) if err != nil { return err } @@ -167,7 +176,7 @@ func (l *Listener) processEnvelope(env *v1.Envelope) error { return nil } - sendRequests := buildSendRequests(env, installations, subs) + sendRequests := buildSendRequests(env, t, installations, subs) for _, request := range sendRequests { if !l.shouldDeliver(request.MessageContext, request.Subscription) { l.logger.Info("Skipping delivery of request", @@ -223,13 +232,6 @@ func (l *Listener) refreshClient() error { return nil } -func isV3Topic(topic string) bool { - if strings.HasPrefix(topic, "/xmtp/mls/1/g-") || strings.HasPrefix(topic, "/xmtp/mls/1/w-") { - return true - } - return false -} - func buildIdempotencyKey(env *v1.Envelope) string { h := sha1.New() h.Write([]byte(env.ContentTopic)) @@ -237,9 +239,9 @@ func buildIdempotencyKey(env *v1.Envelope) string { return hex.EncodeToString(h.Sum(nil)) } -func buildSendRequests(envelope *v1.Envelope, installations []interfaces.Installation, subscriptions []interfaces.Subscription) []interfaces.SendRequest { +func buildSendRequests(envelope *v1.Envelope, t *topicpkg.Topic, installations []interfaces.Installation, subscriptions []interfaces.Subscription) []interfaces.SendRequest { idempotencyKey := buildIdempotencyKey(envelope) - messageContext := getContext(envelope) + messageContext := getContext(envelope, t) out := []interfaces.SendRequest{} installationMap := make(map[string]interfaces.Installation) for _, installation := range installations { diff --git a/pkg/xmtp/listener_test.go b/pkg/xmtp/listener_test.go index a7d5c06f..8abf27ad 100644 --- a/pkg/xmtp/listener_test.go +++ b/pkg/xmtp/listener_test.go @@ -15,13 +15,14 @@ import ( v1 "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" "github.com/xmtp/example-notification-server-go/pkg/subscriptions" "github.com/xmtp/example-notification-server-go/pkg/testutils" + topicutil "github.com/xmtp/example-notification-server-go/pkg/topics" ) const ( XMTP_ADDRESS = "localhost:25556" INSTALLATION_ID = "test_installation" INSTALLATION_ID_2 = "test_installation_2" - TEST_TOPIC = "/xmtp/mls/1/w-test_installation/proto" + TEST_TOPIC = "/xmtp/mls/1/w-abcdef0123456789/proto" DELIVERY_TOKEN = "test_token" ) @@ -53,7 +54,7 @@ func injectMessage(listener *Listener, topic string, message []byte) { } } -func subscribeToTopic(t *testing.T, l *Listener, installationId, topic string, isSilent bool) { +func subscribeToTopic(t *testing.T, l *Listener, installationId, topicStr string, isSilent bool) { _, err := l.installations.Register(t.Context(), interfaces.Installation{ Id: installationId, DeliveryMechanism: interfaces.DeliveryMechanism{ @@ -63,7 +64,10 @@ func subscribeToTopic(t *testing.T, l *Listener, installationId, topic string, i }) require.NoError(t, err) - err = l.subscriptions.SubscribeWithMetadata(t.Context(), installationId, []interfaces.SubscriptionInput{{Topic: topic, IsSilent: isSilent}}) + parsed, err := topicutil.ParseV3Topic(topicStr) + require.NoError(t, err) + + err = l.subscriptions.SubscribeWithMetadata(t.Context(), installationId, []interfaces.SubscriptionInput{{Topic: parsed, IsSilent: isSilent}}) require.NoError(t, err) } diff --git a/pkg/xmtp/message.go b/pkg/xmtp/message.go index a254ce5e..d71b863e 100644 --- a/pkg/xmtp/message.go +++ b/pkg/xmtp/message.go @@ -7,6 +7,7 @@ import ( messageApi "github.com/xmtp/example-notification-server-go/pkg/proto/message_api/v1" mlsV1 "github.com/xmtp/example-notification-server-go/pkg/proto/mls/api/v1" "github.com/xmtp/example-notification-server-go/pkg/topics" + topicpkg "github.com/xmtp/xmtpd/pkg/topic" "google.golang.org/protobuf/proto" ) @@ -26,8 +27,8 @@ func parseGroupMessage(groupMessage []byte) (*mlsV1.GroupMessage_V1, error) { return v1Message, nil } -func getContext(env *messageApi.Envelope) interfaces.MessageContext { - messageType := topics.GetMessageType(env) +func getContext(env *messageApi.Envelope, t *topicpkg.Topic) interfaces.MessageContext { + messageType := topics.GetMessageTypeFromTopic(t) var shouldPush *bool var hmacInputs, senderHmac *[]byte diff --git a/pkg/xmtp/message_test.go b/pkg/xmtp/message_test.go index 8427acbb..6e3af0e9 100644 --- a/pkg/xmtp/message_test.go +++ b/pkg/xmtp/message_test.go @@ -57,13 +57,17 @@ func Test_IdentifyV3Conversation(t *testing.T) { rawFixture := getRawFixture(t, "v3-conversation") envelope := getEnvelope(t, rawFixture) hmacKey := getHmacKey(t, rawFixture) - context := getContext(envelope) + + tp, err := topics.ParseV3Topic(envelope.ContentTopic) + require.NoError(t, err) + + context := getContext(envelope, tp) require.False(t, context.IsSender(hmacKey)) require.True(t, *context.ShouldPush) require.Equal(t, context.MessageType, topics.V3Conversation) wrongKey := []byte("foo") - contextWithWrongKey := getContext(envelope) + contextWithWrongKey := getContext(envelope, tp) require.False(t, contextWithWrongKey.IsSender(wrongKey)) require.True(t, *contextWithWrongKey.ShouldPush) require.Equal(t, contextWithWrongKey.MessageType, topics.V3Conversation) diff --git a/proto/notifications/v1/service.proto b/proto/notifications/v1/service.proto index fc7e83f7..2255d0ea 100644 --- a/proto/notifications/v1/service.proto +++ b/proto/notifications/v1/service.proto @@ -41,6 +41,8 @@ message Subscription { string topic = 1; repeated HmacKey hmac_keys = 2; bool is_silent = 3; + // Binary topic (V4 format). If both topic and topic_bytes are set, topic_bytes takes precedence. + bytes topic_bytes = 4; } // A request to subscribe to a list of topics and update the associated metadata @@ -53,12 +55,16 @@ message SubscribeWithMetadataRequest { message SubscribeRequest { string installation_id = 1; repeated string topics = 2; + // Binary topics (V4 format). Merged with string topics if both are set. + repeated bytes topics_bytes = 3; } // Unsubscribe from a list of topics message UnsubscribeRequest { string installation_id = 1; repeated string topics = 2; + // Binary topics (V4 format). Merged with string topics if both are set. + repeated bytes topics_bytes = 3; } service Notifications {