From 2407bc782264326a3d4c71dd1ebeb8ab75221923 Mon Sep 17 00:00:00 2001 From: Alex Lockwood Date: Mon, 28 Sep 2015 12:19:13 -0400 Subject: [PATCH 01/11] Update broken link to Google's Java implementation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89886a3..729cba9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ gcm === -The Android SDK provides a nice convenience library ([com.google.android.gcm.server](http://developer.android.com/reference/com/google/android/gcm/server/package-summary.html)) that greatly simplifies the interaction between Java-based application servers and Google's GCM servers. However, Google has not provided much support for application servers implemented in languages other than Java, specifically those written in the Go programming language. The `gcm` package helps to fill in this gap, providing a simple interface for sending GCM messages and automatically retrying requests in case of service unavailability. +The Android SDK provides a nice convenience library ([com.google.android.gcm.server](https://github.com/google/gcm/tree/master/client-libraries/java/rest-client/src/com/google/android/gcm/server)) that greatly simplifies the interaction between Java-based application servers and Google's GCM servers. However, Google has not provided much support for application servers implemented in languages other than Java, specifically those written in the Go programming language. The `gcm` package helps to fill in this gap, providing a simple interface for sending GCM messages and automatically retrying requests in case of service unavailability. Documentation: http://godoc.org/github.com/alexjlockwood/gcm From 532ebb3946fe1524432b00b8d04d39867c3d4c53 Mon Sep 17 00:00:00 2001 From: Alex Lockwood Date: Wed, 25 Jan 2017 10:52:02 -0500 Subject: [PATCH 02/11] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 729cba9..74b2090 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ gcm === +## NOTE: I no longer maintain this library. Feel free to fork! :) + The Android SDK provides a nice convenience library ([com.google.android.gcm.server](https://github.com/google/gcm/tree/master/client-libraries/java/rest-client/src/com/google/android/gcm/server)) that greatly simplifies the interaction between Java-based application servers and Google's GCM servers. However, Google has not provided much support for application servers implemented in languages other than Java, specifically those written in the Go programming language. The `gcm` package helps to fill in this gap, providing a simple interface for sending GCM messages and automatically retrying requests in case of service unavailability. Documentation: http://godoc.org/github.com/alexjlockwood/gcm From d638bc8a2f4cb996a776a32f61594c25f2c924f5 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 16:51:58 -0400 Subject: [PATCH 03/11] add go modules --- go.mod | 50 ++++++++++++++ go.sum | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..80c29c5 --- /dev/null +++ b/go.mod @@ -0,0 +1,50 @@ +module github.com/timehop/gcm + +go 1.22.0 + +require ( + firebase.google.com/go/v4 v4.14.1 + github.com/appleboy/go-fcm v1.2.1 + golang.org/x/oauth2 v0.21.0 +) + +require ( + cloud.google.com/go v0.114.0 // indirect + cloud.google.com/go/auth v0.5.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/firestore v1.15.0 // indirect + cloud.google.com/go/iam v1.1.8 // indirect + cloud.google.com/go/longrunning v0.5.7 // indirect + cloud.google.com/go/storage v1.41.0 // indirect + github.com/MicahParks/keyfunc v1.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.4 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/api v0.183.0 // indirect + google.golang.org/appengine/v2 v2.0.6 // indirect + google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c7c56cd --- /dev/null +++ b/go.sum @@ -0,0 +1,209 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= +cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= +cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= +firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= +github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-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= +google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc= +google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 4019b4f7341b0f2e7af1a3759c65dc3db85402b7 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 16:52:28 -0400 Subject: [PATCH 04/11] remove response.go in prep for utilizing google messaging module --- response.go | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 response.go diff --git a/response.go b/response.go deleted file mode 100644 index 657e7d5..0000000 --- a/response.go +++ /dev/null @@ -1,20 +0,0 @@ -package gcm - -// Response represents the GCM server's response to the application -// server's sent message. See the documentation for GCM Architectural -// Overview for more information: -// http://developer.android.com/google/gcm/gcm.html#send-msg -type Response struct { - MulticastID int64 `json:"multicast_id"` - Success int `json:"success"` - Failure int `json:"failure"` - CanonicalIDs int `json:"canonical_ids"` - Results []Result `json:"results"` -} - -// Result represents the status of a processed message. -type Result struct { - MessageID string `json:"message_id"` - RegistrationID string `json:"registration_id"` - Error string `json:"error"` -} From 5c9f72bc2afd733d4fd9e27b518af5d4e8e2e750 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 17:16:46 -0400 Subject: [PATCH 05/11] replace Message with google messaging module --- message.go | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/message.go b/message.go index b63c018..9a4967d 100644 --- a/message.go +++ b/message.go @@ -1,21 +1,11 @@ package gcm -// Message is used by the application server to send a message to -// the GCM server. See the documentation for GCM Architectural -// Overview for more information: -// http://developer.android.com/google/gcm/gcm.html#send-msg -type Message struct { - RegistrationIDs []string `json:"registration_ids"` - CollapseKey string `json:"collapse_key,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - DelayWhileIdle bool `json:"delay_while_idle,omitempty"` - TimeToLive int `json:"time_to_live,omitempty"` - RestrictedPackageName string `json:"restricted_package_name,omitempty"` - DryRun bool `json:"dry_run,omitempty"` -} +import ( + "firebase.google.com/go/v4/messaging" +) // NewMessage returns a new Message with the specified payload -// and registration IDs. -func NewMessage(data map[string]interface{}, regIDs ...string) *Message { - return &Message{RegistrationIDs: regIDs, Data: data} +// and Token(s). +func NewMessage(data map[string]string, tokens ...string) *messaging.MulticastMessage { + return &messaging.MulticastMessage{Tokens: tokens, Data: data} } From bde2788e11018e0405edd1f4340e2d8411c0b676 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 17:17:38 -0400 Subject: [PATCH 06/11] Update sender to FCM HTTP V1 replace legacy http client calls with Firebase module calls --- sender.go | 237 +++++++++++++++++++++++++++--------------------------- 1 file changed, 117 insertions(+), 120 deletions(-) diff --git a/sender.go b/sender.go index 3c6292a..6997de2 100644 --- a/sender.go +++ b/sender.go @@ -1,29 +1,39 @@ -// Google Cloud Messaging for application servers implemented using the -// Go programming language. package gcm import ( - "bytes" - "encoding/json" + "context" "errors" - "fmt" - "io/ioutil" + "firebase.google.com/go/v4/messaging" + "github.com/appleboy/go-fcm" "math/rand" - "net/http" "time" ) const ( - // GcmSendEndpoint is the endpoint for sending messages to the GCM server. - GcmSendEndpoint = "https://android.googleapis.com/gcm/send" // Initial delay before first retry, without jitter. backoffInitialDelay = 1000 // Maximum delay before a retry. maxBackoffDelay = 1024000 ) -// Declared as a mutable variable for testing purposes. -var gcmSendEndpoint = GcmSendEndpoint +// Errors + +type JSONParseError struct{ error } +type UnauthorizedError struct{ error } +type UnknownError struct{ error } + +const ( + ResponseErrorMissingRegistration = "MissingRegistration" + ResponseErrorInvalidRegistration = "InvalidRegistration" + ResponseErrorMismatchSenderID = "MismatchSenderId" + ResponseErrorNotRegistered = "NotRegistered" + ResponseErrorMessageTooBig = "MessageTooBig" + ResponseErrorInvalidDataKey = "InvalidDataKey" + ResponseErrorInvalidTTL = "InvalidTtl" + ResponseErrorUnavailable = "Unavailable" + ResponseErrorInternalServerError = "InternalServerError" + ResponseErrorInvalidPackageName = "InvalidPackageName" +) // Sender abstracts the interaction between the application server and the // GCM server. The developer must obtain an API key from the Google APIs @@ -31,63 +41,44 @@ var gcmSendEndpoint = GcmSendEndpoint // requests on the application server's behalf. To send a message to one or // more devices use the Sender's Send or SendNoRetry methods. // -// If the Http field is nil, a zeroed http.Client will be allocated and used -// to send messages. If your application server runs on Google AppEngine, -// you must use the "appengine/urlfetch" package to create the *http.Client -// as follows: +// If Sender Client is nil, checkSender will automatically initialize a Client // // func handler(w http.ResponseWriter, r *http.Request) { // c := appengine.NewContext(r) -// client := urlfetch.Client(c) -// sender := &gcm.Sender{ApiKey: key, Http: client} +// sender := &gcm.Sender{CredentialsJson: key} // // /* ... */ // } type Sender struct { - ApiKey string - Http *http.Client + CredentialsJson string + Client *fcm.Client } -// SendNoRetry sends a message to the GCM server without retrying in case of +// SendNoRetry sends a message to the FCM server without retrying in case of // service unavailability. A non-nil error is returned if a non-recoverable -// error occurs (i.e. if the response status is not "200 OK"). -func (s *Sender) SendNoRetry(msg *Message) (*Response, error) { - if err := checkSender(s); err != nil { - return nil, err - } else if err := checkMessage(msg); err != nil { - return nil, err +// error occurs. +// If msg is a valid MulticastMessage, then the failed tokens will also be returned. +func (s *Sender) SendNoRetry(msg *messaging.MulticastMessage) (*messaging.BatchResponse, []string, error) { + // Note that failed tokens returns as nil, since we cannot guarantee that msg.Tokens exists + if err := checkMessage(msg); err != nil { + return nil, nil, err + } else if err = checkSender(s); err != nil { + return nil, msg.Tokens, err } - - data, err := json.Marshal(msg) + resp, err := s.Client.SendMulticast(context.Background(), msg) if err != nil { - return nil, err + return resp, msg.Tokens, err } - req, err := http.NewRequest("POST", gcmSendEndpoint, bytes.NewBuffer(data)) - if err != nil { - return nil, err - } - req.Header.Add("Authorization", fmt.Sprintf("key=%s", s.ApiKey)) - req.Header.Add("Content-Type", "application/json") - - resp, err := s.Http.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%d error: %s", resp.StatusCode, resp.Status) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err + // Collect failed tokens + var failedTokens []string + for idx, r := range resp.Responses { + if !r.Success { + failedTokens = append(failedTokens, msg.Tokens[idx]) + } } - response := new(Response) - err = json.Unmarshal(body, response) - return response, err + return resp, failedTokens, nil } // Send sends a message to the GCM server, retrying in case of service @@ -96,116 +87,122 @@ func (s *Sender) SendNoRetry(msg *Message) (*Response, error) { // // Note that messages are retried using exponential backoff, and as a // result, this method may block for several seconds. -func (s *Sender) Send(msg *Message, retries int) (*Response, error) { - if err := checkSender(s); err != nil { - return nil, err - } else if err := checkMessage(msg); err != nil { - return nil, err +func (s *Sender) Send(msg *messaging.MulticastMessage, retries int) (*messaging.BatchResponse, []string, error) { + // Note that failed tokens returns as nil, since we cannot guarantee that msg.Tokens exists + if err := checkMessage(msg); err != nil { + return nil, nil, err + } else if err = checkSender(s); err != nil { + return nil, msg.Tokens, err } else if retries < 0 { - return nil, errors.New("'retries' must not be negative.") + return nil, msg.Tokens, errors.New("retries must not be negative") } // Send the message for the first time. - resp, err := s.SendNoRetry(msg) + resp, failedTokens, err := s.SendNoRetry(msg) if err != nil { - return nil, err - } else if resp.Failure == 0 || retries == 0 { - return resp, nil + return nil, failedTokens, err + } else if resp.FailureCount == 0 || retries == 0 { + return resp, failedTokens, nil } // One or more messages failed to send. - regIDs := msg.RegistrationIDs - allResults := make(map[string]Result, len(regIDs)) + regIDs := msg.Tokens + allResults := make(map[string]*messaging.SendResponse, len(regIDs)) backoff := backoffInitialDelay for i := 0; updateStatus(msg, resp, allResults) > 0 && i < retries; i++ { sleepTime := backoff/2 + rand.Intn(backoff) time.Sleep(time.Duration(sleepTime) * time.Millisecond) backoff = min(2*backoff, maxBackoffDelay) - if resp, err = s.SendNoRetry(msg); err != nil { - msg.RegistrationIDs = regIDs - return nil, err + if resp, failedTokens, err = s.SendNoRetry(msg); err != nil { + msg.Tokens = regIDs + return nil, failedTokens, err } } - // Bring the message back to its original state. - msg.RegistrationIDs = regIDs - - // Create a Response containing the overall results. - finalResults := make([]Result, len(regIDs)) - var success, failure, canonicalIDs int - for i := 0; i < len(regIDs); i++ { - result, _ := allResults[regIDs[i]] - finalResults[i] = result - if result.MessageID != "" { - if result.RegistrationID != "" { - canonicalIDs++ - } - success++ + // Restore the original list of registration tokens. + msg.Tokens = regIDs + + // Create a final BatchResponse and list of failed tokens. + finalResponses := make([]*messaging.SendResponse, len(regIDs)) + for i, token := range regIDs { + if result, ok := allResults[token]; ok { + finalResponses[i] = result } else { - failure++ + finalResponses[i] = &messaging.SendResponse{ + Success: false, + Error: errors.New("unknown error"), + } } } - return &Response{ - // Return the most recent multicast id. - MulticastID: resp.MulticastID, - Success: success, - Failure: failure, - CanonicalIDs: canonicalIDs, - Results: finalResults, - }, nil + finalBatchResponse := &messaging.BatchResponse{ + SuccessCount: resp.SuccessCount, + FailureCount: resp.FailureCount, + Responses: finalResponses, + } + + return finalBatchResponse, failedTokens, nil } // updateStatus updates the status of the messages sent to devices and // returns the number of recoverable errors that could be retried. -func updateStatus(msg *Message, resp *Response, allResults map[string]Result) int { - unsentRegIDs := make([]string, 0, resp.Failure) - for i := 0; i < len(resp.Results); i++ { - regID := msg.RegistrationIDs[i] - allResults[regID] = resp.Results[i] - if resp.Results[i].Error == "Unavailable" { +func updateStatus(msg *messaging.MulticastMessage, resp *messaging.BatchResponse, allResults map[string]*messaging.SendResponse) int { + unsentRegIDs := make([]string, 0, resp.FailureCount) + for i := 0; i < len(resp.Responses); i++ { + regID := msg.Tokens[i] + allResults[regID] = resp.Responses[i] + if resp.Responses[i].Error != nil && isRecoverableError(resp.Responses[i].Error) { unsentRegIDs = append(unsentRegIDs, regID) } } - msg.RegistrationIDs = unsentRegIDs + msg.Tokens = unsentRegIDs return len(unsentRegIDs) } -// min returns the smaller of two integers. For exciting religious wars -// about why this wasn't included in the "math" package, see this thread: -// https://groups.google.com/d/topic/golang-nuts/dbyqx_LGUxM/discussion -func min(a, b int) int { - if a < b { - return a - } - return b +// isRecoverableError checks if the error is a recoverable error. +// This is under the assumption that Legacy and HTTP V1 + SDK return +// the same errors. +// For more info, check out: +// https://firebase.google.com/docs/cloud-messaging/send-message#rest +func isRecoverableError(err error) bool { + return err.Error() == "Unavailable" } // checkSender returns an error if the sender is not well-formed and -// initializes a zeroed http.Client if one has not been provided. +// initializes a zeroed fcm.Client if one has not been provided. func checkSender(sender *Sender) error { - if sender.ApiKey == "" { - return errors.New("the sender's API key must not be empty") + if sender.CredentialsJson == "" { + return errors.New("the sender's credentials data must not be empty") } - if sender.Http == nil { - sender.Http = new(http.Client) + if sender.Client == nil { + client, err := initFCMClient(sender) + if err != nil { + return err + } + sender.Client = client } return nil } +func initFCMClient(sender *Sender) (*fcm.Client, error) { + ctx := context.Background() + client, err := fcm.NewClient( + ctx, + fcm.WithCredentialsJSON([]byte(sender.CredentialsJson)), + ) + return client, err +} + // checkMessage returns an error if the message is not well-formed. -func checkMessage(msg *Message) error { +func checkMessage(msg *messaging.MulticastMessage) error { if msg == nil { return errors.New("the message must not be nil") - } else if msg.RegistrationIDs == nil { - return errors.New("the message's RegistrationIDs field must not be nil") - } else if len(msg.RegistrationIDs) == 0 { - return errors.New("the message must specify at least one registration ID") - } else if len(msg.RegistrationIDs) > 1000 { - return errors.New("the message may specify at most 1000 registration IDs") - } else if msg.TimeToLive < 0 || 2419200 < msg.TimeToLive { - return errors.New("the message's TimeToLive field must be an integer " + - "between 0 and 2419200 (4 weeks)") + } else if msg.Tokens == nil { + return errors.New("the message's Tokens field must not be nil") + } else if len(msg.Tokens) == 0 { + return errors.New("the message must specify at least one Token") + } else if len(msg.Tokens) > 500 { + return errors.New("the message may specify at most 500 registration IDs") } return nil } From b59de576d43ac6edac8e69323fafa46396b82e36 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 17:18:17 -0400 Subject: [PATCH 07/11] update tests from legacy to http v1 sender --- sender_test.go | 138 +++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/sender_test.go b/sender_test.go index 1b2c359..86c3da2 100644 --- a/sender_test.go +++ b/sender_test.go @@ -1,8 +1,13 @@ package gcm import ( + "context" "encoding/json" + "errors" + "firebase.google.com/go/v4/messaging" "fmt" + "github.com/appleboy/go-fcm" + "golang.org/x/oauth2" "net/http" "net/http/httptest" "testing" @@ -10,7 +15,27 @@ import ( type testResponse struct { StatusCode int - Response *Response + Response *messaging.BatchResponse +} + +// MockTokenSource is a TokenSource implementation that can be used for testing. +type MockTokenSource struct { + AccessToken string +} + +// Token returns the test token associated with the TokenSource. +func (ts *MockTokenSource) Token() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: ts.AccessToken}, nil +} + +func getMockClient(server *httptest.Server) (*fcm.Client, error) { + return fcm.NewClient( + context.Background(), + fcm.WithEndpoint(server.URL), + fcm.WithProjectID("test"), + fcm.WithTokenSource(&MockTokenSource{AccessToken: "test-token"}), + ) + } func startTestServer(t *testing.T, responses ...*testResponse) *httptest.Server { @@ -31,82 +56,71 @@ func startTestServer(t *testing.T, responses ...*testResponse) *httptest.Server i++ } server := httptest.NewServer(http.HandlerFunc(handler)) - gcmSendEndpoint = server.URL return server } func TestSendNoRetryInvalidApiKey(t *testing.T) { server := startTestServer(t) defer server.Close() - sender := &Sender{ApiKey: ""} - if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}}); err == nil { - t.Fatal("test should fail when sender's ApiKey is \"\"") + sender := &Sender{CredentialsJson: ""} + if _, _, err := sender.SendNoRetry(&messaging.MulticastMessage{Tokens: []string{"1"}}); err == nil { + t.Fatal("test should fail when sender's CredentialsJson is \"\"") } } func TestSendInvalidApiKey(t *testing.T) { server := startTestServer(t) defer server.Close() - sender := &Sender{ApiKey: ""} - if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}}, 0); err == nil { - t.Fatal("test should fail when sender's ApiKey is \"\"") + sender := &Sender{CredentialsJson: ""} + if _, _, err := sender.Send(&messaging.MulticastMessage{Tokens: []string{"1"}}, 0); err == nil { + t.Fatal("test should fail when sender's CredentialsJson is \"\"") } } func TestSendNoRetryInvalidMessage(t *testing.T) { server := startTestServer(t) defer server.Close() - sender := &Sender{ApiKey: "test"} - if _, err := sender.SendNoRetry(nil); err == nil { + sender := &Sender{CredentialsJson: "test"} + if _, _, err := sender.SendNoRetry(nil); err == nil { t.Fatal("test should fail when message is nil") } - if _, err := sender.SendNoRetry(&Message{}); err == nil { - t.Fatal("test should fail when message RegistrationIDs field is nil") - } - if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{}}); err == nil { - t.Fatal("test should fail when message RegistrationIDs field is an empty slice") - } - if _, err := sender.SendNoRetry(&Message{RegistrationIDs: make([]string, 1001)}); err == nil { - t.Fatal("test should fail when more than 1000 RegistrationIDs are specified") + if _, _, err := sender.SendNoRetry(&messaging.MulticastMessage{}); err == nil { + t.Fatal("test should fail when message Tokens field is nil") } - if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}, TimeToLive: -1}); err == nil { - t.Fatal("test should fail when message TimeToLive field is negative") + if _, _, err := sender.SendNoRetry(&messaging.MulticastMessage{Tokens: []string{}}); err == nil { + t.Fatal("test should fail when message Tokens field is an empty slice") } - if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}, TimeToLive: 2419201}); err == nil { - t.Fatal("test should fail when message TimeToLive field is greater than 2419200") + if _, _, err := sender.SendNoRetry(&messaging.MulticastMessage{Tokens: make([]string, 501)}); err == nil { + t.Fatal("test should fail when more than 500 Tokens are specified") } } func TestSendInvalidMessage(t *testing.T) { server := startTestServer(t) defer server.Close() - sender := &Sender{ApiKey: "test"} - if _, err := sender.Send(nil, 0); err == nil { + sender := &Sender{CredentialsJson: "test"} + if _, _, err := sender.Send(nil, 0); err == nil { t.Fatal("test should fail when message is nil") } - if _, err := sender.Send(&Message{}, 0); err == nil { - t.Fatal("test should fail when message RegistrationIDs field is nil") + if _, _, err := sender.Send(&messaging.MulticastMessage{}, 0); err == nil { + t.Fatal("test should fail when message Tokens field is nil") } - if _, err := sender.Send(&Message{RegistrationIDs: []string{}}, 0); err == nil { - t.Fatal("test should fail when message RegistrationIDs field is an empty slice") + if _, _, err := sender.Send(&messaging.MulticastMessage{Tokens: []string{}}, 0); err == nil { + t.Fatal("test should fail when message Tokens field is an empty slice") } - if _, err := sender.Send(&Message{RegistrationIDs: make([]string, 1001)}, 0); err == nil { - t.Fatal("test should fail when more than 1000 RegistrationIDs are specified") - } - if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}, TimeToLive: -1}, 0); err == nil { - t.Fatal("test should fail when message TimeToLive field is negative") - } - if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}, TimeToLive: 2419201}, 0); err == nil { - t.Fatal("test should fail when message TimeToLive field is greater than 2419200") + if _, _, err := sender.Send(&messaging.MulticastMessage{Tokens: make([]string, 1001)}, 0); err == nil { + t.Fatal("test should fail when more than 1000 Tokens are specified") } + } func TestSendNoRetrySuccess(t *testing.T) { - server := startTestServer(t, &testResponse{Response: &Response{}}) + server := startTestServer(t, &testResponse{Response: &messaging.BatchResponse{}}) defer server.Close() - sender := &Sender{ApiKey: "test"} - msg := NewMessage(map[string]interface{}{"key": "value"}, "1") - if _, err := sender.SendNoRetry(msg); err != nil { + client, _ := getMockClient(server) + sender := &Sender{CredentialsJson: "test", Client: client} + msg := NewMessage(map[string]string{"key": "value"}, "1") + if _, _, err := sender.SendNoRetry(msg); err != nil { t.Fatalf("test failed with error: %s", err) } } @@ -114,49 +128,37 @@ func TestSendNoRetrySuccess(t *testing.T) { func TestSendNoRetryNonrecoverableFailure(t *testing.T) { server := startTestServer(t, &testResponse{StatusCode: http.StatusBadRequest}) defer server.Close() - sender := &Sender{ApiKey: "test"} - msg := NewMessage(map[string]interface{}{"key": "value"}, "1") - if _, err := sender.SendNoRetry(msg); err == nil { + sender := &Sender{CredentialsJson: "test"} + msg := NewMessage(map[string]string{"key": "value"}, "1") + if _, _, err := sender.SendNoRetry(msg); err == nil { t.Fatal("test expected non-recoverable error") } } -func TestSendOneRetrySuccess(t *testing.T) { - server := startTestServer(t, - &testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}}, - &testResponse{Response: &Response{Success: 1, Results: []Result{{MessageID: "id"}}}}, - ) - defer server.Close() - sender := &Sender{ApiKey: "test"} - msg := NewMessage(map[string]interface{}{"key": "value"}, "1") - if _, err := sender.Send(msg, 1); err != nil { - t.Fatal("send should succeed after one retry") - } -} - -func TestSendOneRetryFailure(t *testing.T) { +func TestSendSuccess(t *testing.T) { server := startTestServer(t, - &testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}}, - &testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}}, + &testResponse{Response: &messaging.BatchResponse{FailureCount: 1, Responses: []*messaging.SendResponse{{Error: errors.New("Unavailable")}}}}, + &testResponse{Response: &messaging.BatchResponse{FailureCount: 1, Responses: []*messaging.SendResponse{{Error: errors.New("Unavailable")}}}}, ) defer server.Close() - sender := &Sender{ApiKey: "test"} - msg := NewMessage(map[string]interface{}{"key": "value"}, "1") - resp, err := sender.Send(msg, 1) - if err != nil || resp.Failure != 1 { - t.Fatal("send should return response with one failure") + client, _ := getMockClient(server) + sender := &Sender{CredentialsJson: "test", Client: client} + msg := NewMessage(map[string]string{"key": "value"}, "1") + resp, _, err := sender.Send(msg, 1) + if err != nil || resp.SuccessCount != 1 { + t.Fatal("send should return response with one success") } } func TestSendOneRetryNonrecoverableFailure(t *testing.T) { server := startTestServer(t, - &testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}}, + &testResponse{Response: &messaging.BatchResponse{FailureCount: 1, Responses: []*messaging.SendResponse{{Error: errors.New("Unavailable")}}}}, &testResponse{StatusCode: http.StatusBadRequest}, ) defer server.Close() - sender := &Sender{ApiKey: "test"} - msg := NewMessage(map[string]interface{}{"key": "value"}, "1") - if _, err := sender.Send(msg, 1); err == nil { + sender := &Sender{CredentialsJson: "test"} + msg := NewMessage(map[string]string{"key": "value"}, "1") + if _, _, err := sender.Send(msg, 1); err == nil { t.Fatal("send should fail after one retry") } } From 7a785bffafd06beeb7ee4b96b3dabfb2287bebb5 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Fri, 14 Jun 2024 17:18:54 -0400 Subject: [PATCH 08/11] add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0537c22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.idea From 7494b6c4fbbdd852ca88f0072d6f33a20b712219 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Tue, 18 Jun 2024 14:15:31 -0400 Subject: [PATCH 09/11] update Unavailable check with IsUnavailable func from FCM SDK --- sender.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sender.go b/sender.go index 6997de2..458f3ae 100644 --- a/sender.go +++ b/sender.go @@ -160,12 +160,8 @@ func updateStatus(msg *messaging.MulticastMessage, resp *messaging.BatchResponse } // isRecoverableError checks if the error is a recoverable error. -// This is under the assumption that Legacy and HTTP V1 + SDK return -// the same errors. -// For more info, check out: -// https://firebase.google.com/docs/cloud-messaging/send-message#rest func isRecoverableError(err error) bool { - return err.Error() == "Unavailable" + return messaging.IsUnavailable(err) } // checkSender returns an error if the sender is not well-formed and From 72d6434a9887f9f8764562dce03e1f935e6e3de3 Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Tue, 18 Jun 2024 15:45:16 -0400 Subject: [PATCH 10/11] update new message to include notification + android config --- message.go | 13 +++++++++++-- sender_test.go | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/message.go b/message.go index 9a4967d..7d9129f 100644 --- a/message.go +++ b/message.go @@ -6,6 +6,15 @@ import ( // NewMessage returns a new Message with the specified payload // and Token(s). -func NewMessage(data map[string]string, tokens ...string) *messaging.MulticastMessage { - return &messaging.MulticastMessage{Tokens: tokens, Data: data} +func NewMessage(data map[string]string, notification *messaging.Notification, tokens ...string) *messaging.MulticastMessage { + return &messaging.MulticastMessage{ + Tokens: tokens, + Data: data, + Notification: notification, + Android: &messaging.AndroidConfig{ + Notification: &messaging.AndroidNotification{ + Icon: "ic_notification", + }, + }, + } } diff --git a/sender_test.go b/sender_test.go index 86c3da2..ec359c4 100644 --- a/sender_test.go +++ b/sender_test.go @@ -119,7 +119,7 @@ func TestSendNoRetrySuccess(t *testing.T) { defer server.Close() client, _ := getMockClient(server) sender := &Sender{CredentialsJson: "test", Client: client} - msg := NewMessage(map[string]string{"key": "value"}, "1") + msg := NewMessage(map[string]string{"key": "value"}, nil, "1") if _, _, err := sender.SendNoRetry(msg); err != nil { t.Fatalf("test failed with error: %s", err) } @@ -129,7 +129,7 @@ func TestSendNoRetryNonrecoverableFailure(t *testing.T) { server := startTestServer(t, &testResponse{StatusCode: http.StatusBadRequest}) defer server.Close() sender := &Sender{CredentialsJson: "test"} - msg := NewMessage(map[string]string{"key": "value"}, "1") + msg := NewMessage(map[string]string{"key": "value"}, nil, "1") if _, _, err := sender.SendNoRetry(msg); err == nil { t.Fatal("test expected non-recoverable error") } @@ -143,7 +143,7 @@ func TestSendSuccess(t *testing.T) { defer server.Close() client, _ := getMockClient(server) sender := &Sender{CredentialsJson: "test", Client: client} - msg := NewMessage(map[string]string{"key": "value"}, "1") + msg := NewMessage(map[string]string{"key": "value"}, nil, "1") resp, _, err := sender.Send(msg, 1) if err != nil || resp.SuccessCount != 1 { t.Fatal("send should return response with one success") @@ -157,7 +157,7 @@ func TestSendOneRetryNonrecoverableFailure(t *testing.T) { ) defer server.Close() sender := &Sender{CredentialsJson: "test"} - msg := NewMessage(map[string]string{"key": "value"}, "1") + msg := NewMessage(map[string]string{"key": "value"}, nil, "1") if _, _, err := sender.Send(msg, 1); err == nil { t.Fatal("send should fail after one retry") } From f998dc6a2b3bfdecda8768d4b2baa6e606e83cee Mon Sep 17 00:00:00 2001 From: Doron Galambos Date: Tue, 18 Jun 2024 15:49:03 -0400 Subject: [PATCH 11/11] move icon to variable to avoid magic strings --- message.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/message.go b/message.go index 7d9129f..61cd9a0 100644 --- a/message.go +++ b/message.go @@ -4,6 +4,10 @@ import ( "firebase.google.com/go/v4/messaging" ) +const ( + AndroidNotificationIcon = "ic_notification" +) + // NewMessage returns a new Message with the specified payload // and Token(s). func NewMessage(data map[string]string, notification *messaging.Notification, tokens ...string) *messaging.MulticastMessage { @@ -13,7 +17,7 @@ func NewMessage(data map[string]string, notification *messaging.Notification, to Notification: notification, Android: &messaging.AndroidConfig{ Notification: &messaging.AndroidNotification{ - Icon: "ic_notification", + Icon: AndroidNotificationIcon, }, }, }