diff --git a/push/gorush-with-mipush/bin/gorush b/push/gorush-with-mipush/bin/gorush new file mode 100755 index 0000000..44de5fd Binary files /dev/null and b/push/gorush-with-mipush/bin/gorush differ diff --git a/push/gorush-with-mipush/src/.deepsource.toml b/push/gorush-with-mipush/src/.deepsource.toml new file mode 100644 index 0000000..624fe30 --- /dev/null +++ b/push/gorush-with-mipush/src/.deepsource.toml @@ -0,0 +1,31 @@ +version = 1 + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_paths = ["github.com/appleboy/gorush"] + +[[analyzers]] +name = "docker" +enabled = true + + [analyzers.meta] + dockerfile_paths = ["docker/**"] + +[[analyzers]] +name = "test-coverage" +enabled = true + +[[analyzers]] +name = "secrets" +enabled = true + +test_patterns = [ + "**/**_test.go", +] + +exclude_patterns = [ + "**/examples/**" +] diff --git a/push/gorush-with-mipush/src/.dockerignore b/push/gorush-with-mipush/src/.dockerignore new file mode 100644 index 0000000..c13ca3f --- /dev/null +++ b/push/gorush-with-mipush/src/.dockerignore @@ -0,0 +1,2 @@ +* +!release/ diff --git a/push/gorush-with-mipush/src/.drone.jsonnet b/push/gorush-with-mipush/src/.drone.jsonnet new file mode 100644 index 0000000..e719a10 --- /dev/null +++ b/push/gorush-with-mipush/src/.drone.jsonnet @@ -0,0 +1,16 @@ +local pipeline = import 'pipeline.libsonnet'; +local name = 'gorush'; + +[ + pipeline.test, + pipeline.build(name, 'linux', 'amd64'), + pipeline.build(name, 'linux', 'arm64'), + pipeline.build(name, 'linux', 'arm'), + pipeline.release, + pipeline.notifications(depends_on=[ + 'linux-amd64', + 'linux-arm64', + 'linux-arm', + 'release-binary', + ]), +] diff --git a/push/gorush-with-mipush/src/.drone.yml b/push/gorush-with-mipush/src/.drone.yml new file mode 100644 index 0000000..0b17d36 --- /dev/null +++ b/push/gorush-with-mipush/src/.drone.yml @@ -0,0 +1,340 @@ +--- +kind: pipeline +name: testing + +platform: + os: linux + arch: amd64 + +steps: +- name: lint + pull: always + image: golangci/golangci-lint:v1.41.1 + commands: + - golangci-lint run -v + volumes: + - name: gopath + path: /go + +- name: embedmd + pull: always + image: golang:1.17 + commands: + - make embedmd + volumes: + - name: gopath + path: /go + +- name: hadolint + pull: always + image: hadolint/hadolint:latest-debian + commands: + - hadolint --version + - hadolint docker/Dockerfile.linux.amd64 + - hadolint docker/Dockerfile.linux.arm64 + - hadolint docker/Dockerfile.linux.arm + volumes: + - name: gopath + path: /go + +- name: test + pull: always + image: golang:1.17 + commands: + - make test + environment: + ANDROID_API_KEY: + from_secret: android_api_key + ANDROID_TEST_TOKEN: + from_secret: android_test_token + volumes: + - name: gopath + path: /go + +- name: codecov + pull: always + image: robertstettner/drone-codecov + settings: + token: + from_secret: codecov_token + +services: +- name: redis + image: redis + +- name: nsq + image: nsqio/nsq + commands: + - /nsqd + +volumes: +- name: gopath + temp: {} + +--- +kind: pipeline +name: linux-amd64 + +platform: + os: linux + arch: amd64 + +steps: +- name: build-push + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/amd64/gorush + environment: + CGO_ENABLED: 0 + when: + event: + exclude: + - tag + +- name: build-tag + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/amd64/gorush + environment: + CGO_ENABLED: 0 + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.17 + commands: + - ./release/linux/amd64/gorush --help + +- name: publish + pull: always + image: plugins/docker:linux-amd64 + settings: + auto_tag: true + auto_tag_suffix: linux-amd64 + cache_from: appleboy/gorush + daemon_off: false + dockerfile: docker/Dockerfile.linux.amd64 + password: + from_secret: docker_password + repo: appleboy/gorush + username: + from_secret: docker_username + when: + event: + exclude: + - pull_request + +trigger: + ref: + - refs/heads/master + - refs/pull/** + - refs/tags/** + +depends_on: +- testing + +--- +kind: pipeline +name: linux-arm64 + +platform: + os: linux + arch: arm64 + +steps: +- name: build-push + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm64/gorush + environment: + CGO_ENABLED: 0 + when: + event: + exclude: + - tag + +- name: build-tag + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm64/gorush + environment: + CGO_ENABLED: 0 + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.17 + commands: + - ./release/linux/arm64/gorush --help + +- name: publish + pull: always + image: plugins/docker:linux-arm64 + settings: + auto_tag: true + auto_tag_suffix: linux-arm64 + cache_from: appleboy/gorush + daemon_off: false + dockerfile: docker/Dockerfile.linux.arm64 + password: + from_secret: docker_password + repo: appleboy/gorush + username: + from_secret: docker_username + when: + event: + exclude: + - pull_request + +trigger: + ref: + - refs/heads/master + - refs/pull/** + - refs/tags/** + +depends_on: +- testing + +--- +kind: pipeline +name: linux-arm + +platform: + os: linux + arch: arm + +steps: +- name: build-push + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm/gorush + environment: + CGO_ENABLED: 0 + when: + event: + exclude: + - tag + +- name: build-tag + pull: always + image: golang:1.17 + commands: + - go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm/gorush + environment: + CGO_ENABLED: 0 + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.17 + commands: + - ./release/linux/arm/gorush --help + +- name: publish + pull: always + image: plugins/docker:linux-arm + settings: + auto_tag: true + auto_tag_suffix: linux-arm + cache_from: appleboy/gorush + daemon_off: false + dockerfile: docker/Dockerfile.linux.arm + password: + from_secret: docker_password + repo: appleboy/gorush + username: + from_secret: docker_username + when: + event: + exclude: + - pull_request + +trigger: + ref: + - refs/heads/master + - refs/pull/** + - refs/tags/** + +depends_on: +- testing + +--- +kind: pipeline +name: release-binary + +platform: + os: linux + arch: amd64 + +steps: +- name: build-all-binary + pull: always + image: golang:1.17 + commands: + - make release + when: + event: + - tag + +- name: deploy-all-binary + pull: always + image: plugins/github-release + settings: + api_key: + from_secret: github_release_api_key + files: + - dist/release/* + when: + event: + - tag + +trigger: + ref: + - refs/tags/** + +depends_on: +- testing + +--- +kind: pipeline +name: notifications + +platform: + os: linux + arch: amd64 + +steps: +- name: manifest + pull: always + image: plugins/manifest + settings: + ignore_missing: true + password: + from_secret: docker_password + spec: docker/manifest.tmpl + username: + from_secret: docker_username + +trigger: + ref: + - refs/heads/master + - refs/tags/** + +depends_on: +- linux-amd64 +- linux-arm64 +- linux-arm +- release-binary + +... diff --git a/push/gorush-with-mipush/src/.editorconfig b/push/gorush-with-mipush/src/.editorconfig new file mode 100644 index 0000000..0230121 --- /dev/null +++ b/push/gorush-with-mipush/src/.editorconfig @@ -0,0 +1,42 @@ +# unifying the coding style for different editors and IDEs => editorconfig.org + +; indicate this is the root of the project +root = true + +########################################################### +; common +########################################################### + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +########################################################### +; make +########################################################### + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab + +########################################################### +; markdown +########################################################### + +[*.md] +trim_trailing_whitespace = false + +########################################################### +; golang +########################################################### + +[*.go] +indent_style = tab diff --git a/push/gorush-with-mipush/src/.github/FUNDING.yml b/push/gorush-with-mipush/src/.github/FUNDING.yml new file mode 100644 index 0000000..aeff489 --- /dev/null +++ b/push/gorush-with-mipush/src/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # appleboy +patreon: # Replace with a single Patreon username +open_collective: gorush +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/push/gorush-with-mipush/src/.gitignore b/push/gorush-with-mipush/src/.gitignore new file mode 100644 index 0000000..1fd9c9a --- /dev/null +++ b/push/gorush-with-mipush/src/.gitignore @@ -0,0 +1,39 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +gin-bin +key.pem +.DS_Store +gorush/log/*.log +gorush.db +.cover +*.db* +coverage.txt +dist +custom +release +coverage.txt +node_modules +config.yml diff --git a/push/gorush-with-mipush/src/.idea/.gitignore b/push/gorush-with-mipush/src/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/push/gorush-with-mipush/src/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/push/gorush-with-mipush/src/.idea/gorush.iml b/push/gorush-with-mipush/src/.idea/gorush.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/push/gorush-with-mipush/src/.idea/gorush.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/push/gorush-with-mipush/src/.idea/modules.xml b/push/gorush-with-mipush/src/.idea/modules.xml new file mode 100644 index 0000000..0dabdff --- /dev/null +++ b/push/gorush-with-mipush/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/push/gorush-with-mipush/src/.idea/vcs.xml b/push/gorush-with-mipush/src/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/push/gorush-with-mipush/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/push/gorush-with-mipush/src/.revive.toml b/push/gorush-with-mipush/src/.revive.toml new file mode 100644 index 0000000..74743d5 --- /dev/null +++ b/push/gorush-with-mipush/src/.revive.toml @@ -0,0 +1,25 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 1 +warningCode = 1 + +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] diff --git a/push/gorush-with-mipush/src/HomebrewFormula/gorush.rb b/push/gorush-with-mipush/src/HomebrewFormula/gorush.rb new file mode 100644 index 0000000..5c67996 --- /dev/null +++ b/push/gorush-with-mipush/src/HomebrewFormula/gorush.rb @@ -0,0 +1,17 @@ +class Gorush < Formula + desc "A push notification server written in Go (Golang)." + homepage "https://github.com/appleboy/gorush" + head "https://github.com/appleboy/gorush.git" + + depends_on "go" => :build + + def install + ENV["GOPATH"] = buildpath + gorushpath = buildpath/"src/github.com/appleboy/gorush" + gorushpath.install buildpath.children + cd gorushpath do + system "go", "build", "-o", bin/"gorush" + prefix.install_metafiles + end + end +end diff --git a/push/gorush-with-mipush/src/LICENSE b/push/gorush-with-mipush/src/LICENSE new file mode 100644 index 0000000..446bb8b --- /dev/null +++ b/push/gorush-with-mipush/src/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Bo-Yi Wu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/push/gorush-with-mipush/src/Makefile b/push/gorush-with-mipush/src/Makefile new file mode 100644 index 0000000..c83f927 --- /dev/null +++ b/push/gorush-with-mipush/src/Makefile @@ -0,0 +1,176 @@ +DIST := dist +EXECUTABLE := gorush + +GO ?= go +DEPLOY_ACCOUNT := appleboy +DEPLOY_IMAGE := $(EXECUTABLE) +GOFMT ?= gofumpt -l -s -extra + +TARGETS ?= linux darwin windows +ARCHS ?= amd64 +GOFILES := $(shell find . -name "*.go" -type f) +TAGS ?= sqlite +LDFLAGS ?= -X 'main.Version=$(VERSION)' + +ifneq ($(shell uname), Darwin) + EXTLDFLAGS = -extldflags "-static" $(null) +else + EXTLDFLAGS = +endif + +ifneq ($(DRONE_TAG),) + VERSION ?= $(DRONE_TAG) +else + VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD) +endif + +.PHONY: all +all: build + +init: +ifeq ($(ANDROID_API_KEY),) + @echo "Missing ANDROID_API_KEY Parameter" + @exit 1 +endif +ifeq ($(ANDROID_TEST_TOKEN),) + @echo "Missing ANDROID_TEST_TOKEN Parameter" + @exit 1 +endif + @echo "Already set ANDROID_API_KEY and ANDROID_TEST_TOKEN globale variable." + +.PHONY: fmt +fmt: + @hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install mvdan.cc/gofumpt@v0.1.1; \ + fi + $(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: + @hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install mvdan.cc/gofumpt@v0.1.1; \ + fi + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +vet: + $(GO) vet ./... + +embedmd: + @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/campoy/embedmd@master; \ + fi + embedmd -d *.md + +lint: + @hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/mgechev/revive@v1.0.5; \ + fi + revive -config .revive.toml ./... || exit 1 + +.PHONY: install +install: $(GOFILES) + $(GO) install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' + @echo "\n==>\033[32m Installed gorush to ${GOPATH}/bin/gorush\033[m" + +.PHONY: build +build: $(EXECUTABLE) + +.PHONY: $(EXECUTABLE) +$(EXECUTABLE): $(GOFILES) + $(GO) build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/$@ + +.PHONY: misspell-check +misspell-check: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/client9/misspell/cmd/misspell@v0.3.4; \ + fi + misspell -error $(GOFILES) + +.PHONY: misspell +misspell: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/client9/misspell/cmd/misspell@v0.3.4; \ + fi + misspell -w $(GOFILES) + +.PHONY: test +test: init fmt-check + @$(GO) test -v -cover -tags $(TAGS) -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1 + +release: release-dirs release-build release-copy release-compress release-check + +release-dirs: + mkdir -p $(DIST)/binaries $(DIST)/release + +release-build: + @hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/mitchellh/gox@v1.0.1; \ + fi + gox -os="$(TARGETS)" -arch="$(ARCHS)" -tags="$(TAGS)" -ldflags="$(EXTLDFLAGS)-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}" + +.PHONY: release-compress +release-compress: + @hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) install github.com/ulikunitz/xz/cmd/gxz@v0.5.10; \ + fi + cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done; + +release-copy: + $(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));) + +release-check: + cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) + +build_linux_amd64: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/amd64/$(DEPLOY_IMAGE) + +build_linux_i386: + CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/i386/$(DEPLOY_IMAGE) + +build_linux_arm64: + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm64/$(DEPLOY_IMAGE) + +build_linux_arm: + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm/$(DEPLOY_IMAGE) + +build_linux_lambda: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags 'lambda' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/lambda/$(DEPLOY_IMAGE) + +build_darwin_amd64: + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/darwin/amd64/$(DEPLOY_IMAGE) + +build_darwin_i386: + CGO_ENABLED=0 GOOS=darwin GOARCH=386 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/darwin/i386/$(DEPLOY_IMAGE) + +build_darwin_arm64: + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/darwin/arm64/$(DEPLOY_IMAGE) + +build_darwin_arm: + CGO_ENABLED=0 GOOS=darwin GOARCH=arm GOARM=7 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/darwin/arm/$(DEPLOY_IMAGE) + +build_darwin_lambda: + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -tags 'lambda' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/darwin/lambda/$(DEPLOY_IMAGE) + +clean: + $(GO) clean -modcache -x -i ./... + find . -name coverage.txt -delete + find . -name *.tar.gz -delete + find . -name *.db -delete + -rm -rf release dist .cover + +generate_proto_js: + npm install grpc-tools + protoc -I rpc/proto rpc/proto/gorush.proto --js_out=import_style=commonjs,binary:rpc/example/node/ --grpc_out=rpc/example/node/ --plugin=protoc-gen-grpc="node_modules/.bin/grpc_tools_node_protoc_plugin" + +generate_proto_go: + protoc -I rpc/proto rpc/proto/gorush.proto --go_out=rpc/proto --go-grpc_out=require_unimplemented_servers=false:rpc/proto + +generate_proto: generate_proto_go generate_proto_js + +version: + @echo $(VERSION) diff --git a/push/gorush-with-mipush/src/Procfile b/push/gorush-with-mipush/src/Procfile new file mode 100644 index 0000000..4db3bff --- /dev/null +++ b/push/gorush-with-mipush/src/Procfile @@ -0,0 +1 @@ +web: bin/gorush -p $PORT diff --git a/push/gorush-with-mipush/src/README.md b/push/gorush-with-mipush/src/README.md new file mode 100644 index 0000000..67ff161 --- /dev/null +++ b/push/gorush-with-mipush/src/README.md @@ -0,0 +1,1333 @@ +# gorush + +A push notification micro server using [Gin](https://github.com/gin-gonic/gin) framework written in Go (Golang) and see the [demo app](https://github.com/appleboy/flutter-gorush). + +[![GoDoc](https://godoc.org/github.com/appleboy/gorush?status.svg)](https://godoc.org/github.com/appleboy/gorush) +[![Build Status](https://cloud.drone.io/api/badges/appleboy/gorush/status.svg)](https://cloud.drone.io/appleboy/gorush) +[![codecov](https://codecov.io/gh/appleboy/gorush/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/gorush) +[![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/gorush)](https://goreportcard.com/report/github.com/appleboy/gorush) +[![codebeat badge](https://codebeat.co/badges/0a4eff2d-c9ac-46ed-8fd7-b59942983390)](https://codebeat.co/projects/github-com-appleboy-gorush) +[![Docker Pulls](https://img.shields.io/docker/pulls/appleboy/gorush.svg)](https://hub.docker.com/r/appleboy/gorush/) +[![Netlify Status](https://api.netlify.com/api/v1/badges/8ab14c9f-44fd-4d9a-8bba-f73f76d253b1/deploy-status)](https://app.netlify.com/sites/gorush/deploys) +[![Financial Contributors on Open Collective](https://opencollective.com/gorush/all/badge.svg?label=financial+contributors)](https://opencollective.com/gorush) + +## Contents + +- [gorush](#gorush) + - [Contents](#contents) + - [Support Platform](#support-platform) + - [Features](#features) + - [Memory Usage](#memory-usage) + - [Basic Usage](#basic-usage) + - [Download a binary](#download-a-binary) + - [Install from source](#install-from-source) + - [Prerequisite Tools](#prerequisite-tools) + - [Fetch from GitHub](#fetch-from-github) + - [Command Usage](#command-usage) + - [Send Android notification](#send-android-notification) + - [Send Huawei (HMS) notification](#send-huawei-hms-notification) + - [Send iOS notification](#send-ios-notification) + - [Send Android or iOS notifications using Firebase](#send-android-or-ios-notifications-using-firebase) + - [Run gorush web server](#run-gorush-web-server) + - [Web API](#web-api) + - [GET /api/stat/go](#get-apistatgo) + - [GET /api/stat/app](#get-apistatapp) + - [GET /sys/stats](#get-sysstats) + - [GET /metrics](#get-metrics) + - [POST /api/push](#post-apipush) + - [Request body](#request-body) + - [iOS alert payload](#ios-alert-payload) + - [iOS sound payload](#ios-sound-payload) + - [Android notification payload](#android-notification-payload) + - [Huawei notification](#huawei-notification) + - [iOS Example](#ios-example) + - [Android Example](#android-example) + - [Huawei Example](#huawei-example) + - [Response body](#response-body) + - [Run gRPC service](#run-grpc-service) + - [Run gorush in Docker](#run-gorush-in-docker) + - [Run gorush in Kubernetes](#run-gorush-in-kubernetes) + - [Quick Start](#quick-start) + - [Create the Service Controller for AWS ELB](#create-the-service-controller-for-aws-elb) + - [Ingress Controller for AWS ALB](#ingress-controller-for-aws-alb) + - [Clean up the gorush:](#clean-up-the-gorush) + - [Run gorush in AWS Lambda](#run-gorush-in-aws-lambda) + - [Build gorush binary](#build-gorush-binary) + - [Deploy gorush application](#deploy-gorush-application) + - [Without an AWS account](#without-an-aws-account) + - [Stargazers over time](#stargazers-over-time) + - [License](#license) + +Buy Me A Coffee + +## Support Platform + +- [APNS](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html) +- [FCM](https://firebase.google.com/) +- [HMS](https://developer.huawei.com/consumer/en/hms/) + +[A live demo on Netlify](https://gorush.netlify.com/). + +## Features + +- Support [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) using [go-fcm](https://github.com/appleboy/go-fcm) library for Android. +- Support [HTTP/2](https://http2.github.io/) Apple Push Notification Service using [apns2](https://github.com/sideshow/apns2) library. +- Support [HMS Push Service](https://developer.huawei.com/consumer/en/hms/huawei-pushkit) using [go-hms-push](https://github.com/msalihkarakasli/go-hms-push) library for Huawei Devices. +- Support [YAML](https://github.com/go-yaml/yaml) configuration. +- Support command line to send single Android or iOS notification. +- Support Web API to send push notification. +- Support [HTTP/2](https://http2.github.io/) or HTTP/1.1 protocol. +- Support notification queue and multiple workers. +- Support `/api/stat/app` show notification success and failure counts. +- Support `/api/config` show your [YAML](https://en.wikipedia.org/wiki/YAML) config. +- Support store app stat to memory, [Redis](http://redis.io/), [BoltDB](https://github.com/boltdb/bolt), [BuntDB](https://github.com/tidwall/buntdb), [LevelDB](https://github.com/syndtr/goleveldb) or [BadgerDB](https://github.com/dgraph-io/badger). +- Support `p8`, `p12` or `pem` format of iOS certificate file. +- Support `/sys/stats` show response time, status code count, etc. +- Support for HTTP, HTTPS or SOCKS5 proxy. +- Support retry send notification if server response is fail. +- Support expose [prometheus](https://prometheus.io/) metrics. +- Support install TLS certificates from [Let's Encrypt](https://letsencrypt.org/) automatically. +- Support send notification through [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) protocol, we use [gRPC](https://grpc.io/) as default framework. +- Support running in Docker, [Kubernetes](https://kubernetes.io/) or [AWS Lambda](https://aws.amazon.com/lambda) ([Native Support in Golang](https://aws.amazon.com/blogs/compute/announcing-go-support-for-aws-lambda/)) +- Support graceful shutdown that workers and queue have been sent to APNs/FCM before shutdown service. +- Support different Queue as backend like [NSQ](https://nsq.io/), [NATS](https://nats.io/) or [Redis Pub/Sub](https://redis.io/topics/pubsub), defaut engine is local [Channel](https://tour.golang.org/concurrency/2). + +See the default [YAML config example](config/testdata/config.yml): + +[embedmd]:# (config/testdata/config.yml yaml) +```yaml +core: + enabled: true # enable httpd server + address: "" # ip address to bind (default: any) + shutdown_timeout: 30 # default is 30 second + port: "8088" # ignore this port number if auto_tls is enabled (listen 443). + worker_num: 0 # default worker number is runtime.NumCPU() + queue_num: 0 # default queue number is 8192 + max_notification: 100 + sync: false # set true if you need get error message from fail push notification in API response. + feedback_hook_url: "" # set a hook url if you need get error message asynchronously from fail push notification in API response. + feedback_timeout: 10 # default is 10 second + mode: "release" + ssl: false + cert_path: "cert.pem" + key_path: "key.pem" + cert_base64: "" + key_base64: "" + http_proxy: "" + pid: + enabled: false + path: "gorush.pid" + override: true + auto_tls: + enabled: false # Automatically install TLS certificates from Let's Encrypt. + folder: ".cache" # folder for storing TLS certificates + host: "" # which domains the Let's Encrypt will attempt + +grpc: + enabled: false # enable gRPC server + port: 9000 + +api: + push_uri: "/api/push" + stat_go_uri: "/api/stat/go" + stat_app_uri: "/api/stat/app" + config_uri: "/api/config" + sys_stat_uri: "/sys/stats" + metric_uri: "/metrics" + health_uri: "/healthz" + +android: + enabled: true + apikey: "YOUR_API_KEY" + max_retry: 0 # resend fail notification, default value zero is disabled + +huawei: + enabled: false + appsecret: "YOUR_APP_SECRET" + appid: "YOUR_APP_ID" + max_retry: 0 # resend fail notification, default value zero is disabled + +queue: + engine: "local" # support "local", "nsq", "nats" and "redis" default value is "local" + nsq: + addr: 127.0.0.1:4150 + topic: gorush + channel: gorush + nats: + addr: 127.0.0.1:4222 + subj: gorush + queue: gorush + redis: + addr: 127.0.0.1:6379 + channel: gorush + size: 1024 + +ios: + enabled: false + key_path: "key.pem" + key_base64: "" # load iOS key from base64 input + key_type: "pem" # could be pem, p12 or p8 type + password: "" # certificate password, default as empty string. + production: false + max_concurrent_pushes: 100 # just for push ios notification + max_retry: 0 # resend fail notification, default value zero is disabled + key_id: "" # KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + team_id: "" # TeamID from developer account (View Account -> Membership) + +log: + format: "string" # string or json + access_log: "stdout" # stdout: output to console, or define log path like "log/access_log" + access_level: "debug" + error_log: "stderr" # stderr: output to console, or define log path like "log/error_log" + error_level: "error" + hide_token: true + +stat: + engine: "memory" # support memory, redis, boltdb, buntdb or leveldb + redis: + cluster: false + addr: "localhost:6379" # if cluster is true, you may set this to "localhost:6379,localhost:6380,localhost:6381" + password: "" + db: 0 + boltdb: + path: "bolt.db" + bucket: "gorush" + buntdb: + path: "bunt.db" + leveldb: + path: "level.db" + badgerdb: + path: "badger.db" +``` + +## Memory Usage + +Memory average usage: **28Mb** (the total bytes of memory obtained from the OS.) + +![memory usage](screenshot/memory.png) + +Test Command (We use [bat](https://github.com/astaxie/bat) as default cli tool.): + +```sh +for i in {1..9999999}; do bat -b.N=1000 -b.C=100 POST localhost:8088/api/push notifications:=@notification.json; sleep 1; done +``` + +## Basic Usage + +How to send push notification using `gorush` command? (Android or iOS) + +### Download a binary + +The pre-compiled binaries can be downloaded from [release page](https://github.com/appleboy/gorush/releases). + +With `Go` installed + +```sh +go get -u -v github.com/appleboy/gorush +``` + +On linux + +```sh +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-linux-amd64 -O gorush +``` + +On OS X + +```sh +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-darwin-amd64 -O gorush +``` + +On Windows + +```sh +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-windows-amd64.exe -O gorush.exe +``` + +On macOS, use Homebrew. + +```sh +brew install --HEAD https://github.com/appleboy/gorush/raw/master/HomebrewFormula/gorush.rb +``` + +### Install from source + +#### Prerequisite Tools + +- [Git](http://git-scm.com/) +- [Go (at least Go 1.11)](https://golang.org/dl/) + +#### Fetch from GitHub + +Gorush uses the Go Modules support built into Go 1.11 to build. The easiest way to get started is to clone Gorush in a directory outside of the GOPATH, as in the following example: + +```sh +mkdir $HOME/src +cd $HOME/src +git clone https://github.com/appleboy/gorush.git +cd gorush +go install +``` + +### Command Usage + +```sh + ________ .__ + / _____/ ____ _______ __ __ ______| |__ +/ \ ___ / _ \\_ __ \| | \/ ___/| | \ +\ \_\ \( <_> )| | \/| | /\___ \ | Y \ + \______ / \____/ |__| |____//____ >|___| / + \/ \/ \/ + +Usage: gorush [options] + +Server Options: + -A, --address
Address to bind (default: any) + -p, --port Use port for clients (default: 8088) + -c, --config Configuration file path + -m, --message Notification message + -t, --token Notification token + -e, --engine Storage engine (memory, redis ...) + --title Notification title + --proxy <proxy> Proxy URL (support http, https, or socks5) + --pid <pid path> Process identifier path + --redis-addr <redis addr> Redis addr (default: localhost:6379) +iOS Options: + -i, --key <file> certificate key file path + -P, --password <password> certificate key password + --ios enabled iOS (default: false) + --production iOS production mode (default: false) +Android Options: + -k, --apikey <api_key> Android API Key + --android enabled android (default: false) +Huawei Options: + -hk, --hmskey <hms_key> HMS App Secret + -hid, --hmsid <hms_id> HMS App ID + --huawei enabled huawei (default: false) +Common Options: + --topic <topic> iOS or Android topic message + -h, --help Show this message + -V, --version Show version +``` + +### Send Android notification + +Send single notification with the following command. + +```bash +gorush -android -m "your message" -k "API Key" -t "Device token" +``` + +Send messages to topics. + +```bash +gorush --android --topic "/topics/foo-bar" \ + -m "This is a Firebase Cloud Messaging Topic Message" \ + -k your_api_key +``` + +- `-m`: Notification message. +- `-k`: [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) api key +- `-t`: Device token. +- `--title`: Notification title. +- `--topic`: Send messages to topics. note: don't add device token. +- `--proxy`: Set `http`, `https` or `socks5` proxy url. + +### Send Huawei (HMS) notification + +Send single notification with the following command. + +```bash +gorush -huawei -title "Gorush with HMS" -m "your message" -hk "API Key" -hid "App ID" -t "Device token" +``` + +Send messages to topics. + +```bash +gorush --huawei --topic "foo-bar" \ + -title "Gorush with HMS" \ + -m "This is a Huawei Mobile Services Topic Message" \ + -hk "API Key" \ + -hid "App ID" +``` + +- `-m`: Notification message. +- `-hk`: [Huawei Mobile Services](https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/Preparations) api secret key +- `-t`: Device token. +- `--title`: Notification title. +- `--topic`: Send messages to topics. note: don't add device token. +- `--proxy`: Set `http`, `https` or `socks5` proxy url. + +### Send iOS notification + +Send single notification with the following command. + +```bash +$ gorush -ios -m "your message" -i "your certificate path" \ + -t "device token" --topic "apns topic" +``` + +- `-m`: Notification message. +- `-i`: Apple Push Notification Certificate path (`pem` or `p12` file). +- `-t`: Device token. +- `--title`: Notification title. +- `--topic`: The topic of the remote notification. +- `--password`: The certificate password. + +The default endpoint is APNs development. Please add `-production` flag for APNs production push endpoint. + +```bash +$ gorush -ios -m "your message" -i "your certificate path" \ + -t "device token" \ + -production +``` + +### Send Android or iOS notifications using Firebase + +Send single notification with the following command: + +```bash +gorush -android -m "your message" -k "API key" -t "Device token" +``` + +## Run gorush web server + +Please make sure your [config.yml](config/testdata/config.yml) exist. Default port is `8088`. + +```bash +# for default config +$ gorush +# for custom config file +$ gorush -c config.yml +``` + +Get go status of api server using [httpie](https://github.com/jkbrzt/httpie) tool: + +```bash +http -v --verify=no --json GET http://localhost:8088/api/stat/go +``` + +## Web API + +Gorush support the following API. + +- **GET** `/api/stat/go` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler). +- **GET** `/api/stat/app` show notification success and failure counts. +- **GET** `/api/config` show server yml config file. +- **POST** `/api/push` push ios, android or huawei notifications. + +### GET /api/stat/go + +Golang cpu, memory, gc, etc information. Response with `200` http status code. + +```json +{ + "time": 1460686815848046600, + "go_version": "go1.6.1", + "go_os": "darwin", + "go_arch": "amd64", + "cpu_num": 4, + "goroutine_num": 15, + "gomaxprocs": 4, + "cgo_call_num": 1, + "memory_alloc": 7455192, + "memory_total_alloc": 8935464, + "memory_sys": 12560632, + "memory_lookups": 17, + "memory_mallocs": 31426, + "memory_frees": 11772, + "memory_stack": 524288, + "heap_alloc": 7455192, + "heap_sys": 8912896, + "heap_idle": 909312, + "heap_inuse": 8003584, + "heap_released": 0, + "heap_objects": 19654, + "gc_next": 9754725, + "gc_last": 1460686815762559700, + "gc_num": 2, + "gc_per_second": 0, + "gc_pause_per_second": 0, + "gc_pause": [ + 0.326576, + 0.227096 + ] +} +``` + +### GET /api/stat/app + +Show success or failure counts information of notification. + +```json +{ + "version": "v1.6.2", + "queue_max": 8192, + "queue_usage": 0, + "total_count": 77, + "ios": { + "push_success": 19, + "push_error": 38 + }, + "android": { + "push_success": 10, + "push_error": 10 + }, + "huawei": { + "push_success": 3, + "push_error": 1 + } +} +``` + +### GET /sys/stats + +Show response time, status code count, etc. + +```json +{ + "pid": 80332, + "uptime": "1m42.428010614s", + "uptime_sec": 102.428010614, + "time": "2016-06-26 12:27:11.675973571 +0800 CST", + "unixtime": 1466915231, + "status_code_count": { }, + "total_status_code_count": { + "200": 5 + }, + "count": 0, + "total_count": 5, + "total_response_time": "10.422441ms", + "total_response_time_sec": 0.010422441000000001, + "average_response_time": "2.084488ms", + "average_response_time_sec": 0.0020844880000000002 +} +``` + +### GET /metrics + +Support expose [prometheus](https://prometheus.io/) metrics. + +![metrics screenshot](screenshot/metrics.png) + +### POST /api/push + +Simple send iOS notification example, the `platform` value is `1`: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!" + } + ] +} +``` + +Simple send Android notification example, the `platform` value is `2`: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!" + } + ] +} +``` + +Simple send Huawei notification example, the `platform` value is `3`: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 3, + "title": "Gorush with HMS", + "message": "Hello World Huawei!" + } + ] +} +``` + +Simple send notification on Android and iOS devices using Firebase, the `platform` value is `2`: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "This notification will go to iOS and Android platform via Firebase!" + } + ] +} +``` + +Send multiple notifications as below: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!" + }, + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!" + }, + { + "tokens": ["token_a", "token_b"], + "platform": 3, + "message": "Hello World Huawei!", + "title": "Gorush with HMS" + }, + ..... + ] +} +``` + +See more example about [iOS](#ios-example), [Android](#android-example) or [Huawei](#huawei-example) + +### Request body + +The Request body must have a notifications array. The following is a parameter table for each notification. + +| name | type | description | required | note | +|-------------------------|--------------|---------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------| +| notif_id | string | A unique string that identifies the notification for async feedback | - | | +| tokens | string array | device tokens | o | | +| platform | int | platform(iOS,Android) | o | 1=iOS, 2=Android (Firebase), 3=Huawei (HMS) | +| message | string | message for notification | - | | +| title | string | notification title | - | | +| priority | string | Sets the priority of the message. | - | `normal` or `high` | +| content_available | bool | data messages wake the app by default. | - | | +| sound | interface{} | sound type | - | | +| data | string array | extensible partition | - | only Android and IOS | +| huawei_data | string | JSON object as string to extensible partition partition | - | only Huawei. See the [detail](#huawei-notification) | +| retry | int | retry send notification if fail response from server. Value must be small than `max_retry` field. | - | | +| topic | string | send messages to topics | | | +| image | string | image url to show in notification | - | only Android and Huawei | +| api_key | string | api key for firebase cloud message | - | only Android | +| to | string | The value must be a registration token, notification key, or topic. | - | only Android | +| collapse_key | string | a key for collapsing notifications | - | only Android | +| huawei_collapse_key | int | a key integer for collapsing notifications | - | only Huawei See the [detail](#huawei-notification) | +| delay_while_idle | bool | a flag for device idling | - | only Android | +| time_to_live | uint | expiration of message kept on FCM storage | - | only Android | +| huawei_ttl | string | expiration of message kept on HMS storage | - | only Huawei See the [detail](#huawei-notification) | +| restricted_package_name | string | the package name of the application | - | only Android | +| dry_run | bool | allows developers to test a request without actually sending a message | - | only Android | +| notification | string array | payload of a FCM message | - | only Android. See the [detail](#android-notification-payload) | +| huawei_notification | string array | payload of a HMS message | - | only Huawei. See the [detail](#huawei-notification) | +| app_id | string | hms app id | - | only Huawei. See the [detail](#huawei-notification) | +| bi_tag | string | Tag of a message in a batch delivery task | - | only Huawei. See the [detail](#huawei-notification) | +| fast_app_target | int | State of a mini program when a quick app sends a data message. | - | only Huawei. See the [detail](#huawei-notification) | +| expiration | int | expiration for notification | - | only iOS | +| apns_id | string | A canonical UUID that identifies the notification | - | only iOS | +| collapse_id | string | An identifier you use to coalesce multiple notifications into a single notification for the user | - | only iOS | +| push_type | string | The type of the notification. The value of this header is alert or background. | - | only iOS | +| badge | int | badge count | - | only iOS | +| category | string | the UIMutableUserNotificationCategory object | - | only iOS | +| alert | string array | payload of a iOS message | - | only iOS. See the [detail](#ios-alert-payload) | +| mutable_content | bool | enable Notification Service app extension. | - | only iOS(10.0+). | +| name | string | sets the name value on the aps sound dictionary. | - | only iOS | +| volume | float32 | sets the volume value on the aps sound dictionary. | - | only iOS | + +### iOS alert payload + +| name | type | description | required | note | +|----------------|------------------|--------------------------------------------------------------------------------------------------|----------|------| +| title | string | Apple Watch & Safari display this string as part of the notification interface. | - | | +| body | string | The text of the alert message. | - | | +| subtitle | string | Apple Watch & Safari display this string as part of the notification interface. | - | | +| action | string | The label of the action button. This one is required for Safari Push Notifications. | - | | +| action-loc-key | string | If a string is specified, the system displays an alert that includes the Close and View buttons. | - | | +| launch-image | string | The filename of an image file in the app bundle, with or without the filename extension. | - | | +| loc-args | array of strings | Variable string values to appear in place of the format specifiers in loc-key. | - | | +| loc-key | string | A key to an alert-message string in a Localizable.strings file for the current localization. | - | | +| title-loc-args | array of strings | Variable string values to appear in place of the format specifiers in title-loc-key. | - | | +| title-loc-key | string | The key to a title string in the Localizable.strings file for the current localization. | - | | + +See more detail about [APNs Remote Notification Payload](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). + +### iOS sound payload + +| name | type | description | required | note | +|----------------|------------------|--------------------------------------------------------------------------------------------------|----------|------| +| name | string | sets the name value on the aps sound dictionary. | - | | +| volume | float32 | sets the volume value on the aps sound dictionary. | - | | +| critical | int | sets the critical value on the aps sound dictionary. | - | | + +request format: + +```json +{ + "sound": { + "critical": 1, + "name": "default", + "volume": 2.0 + } +} +``` + +### Android notification payload + +| name | type | description | required | note | +|----------------|--------|-----------------------------------------------------------------------------------------------------------|----------|------| +| icon | string | Indicates notification icon. | - | | +| tag | string | Indicates whether each notification message results in a new entry on the notification center on Android. | - | | +| color | string | Indicates color of the icon, expressed in #rrggbb format | - | | +| click_action | string | The action associated with a user click on the notification. | - | | +| body_loc_key | string | Indicates the key to the body string for localization. | - | | +| body_loc_args | string | Indicates the string value to replace format specifiers in body string for localization. | - | | +| title_loc_key | string | Indicates the key to the title string for localization. | - | | +| title_loc_args | string | Indicates the string value to replace format specifiers in title string for localization. | - | | + +See more detail about [Firebase Cloud Messaging HTTP Protocol reference](https://firebase.google.com/docs/cloud-messaging/http-server-ref#send-downstream). + +### Huawei notification + +* app_id: app id from huawei developer console +* huawei_data: mapped to data +* huawei_notification: mapped to notification +* huawei_ttl: mapped to ttl +* huawei_collapse_key: mapped to collapse_key +* bi_tag: +* fast_app_target: + +See more detail about [Huawei Mobulse Services Push API reference](https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi). + +### iOS Example + +Send normal notification. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!" + } + ] +} +``` + +The following payload asks the system to display an alert with a Close button and a single action button.The title and body keys provide the contents of the alert. The “PLAY” string is used to retrieve a localized string from the appropriate Localizable.strings file of the app. The resulting string is used by the alert as the title of an action button. This payload also asks the system to badge the app’s icon with the number 5. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "badge": 5, + "alert": { + "title" : "Game Request", + "body" : "Bob wants to play poker", + "action-loc-key" : "PLAY" + } + } + ] +} +``` + +The following payload specifies that the device should display an alert message, plays a sound, and badges the app’s icon. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "You got your emails.", + "badge": 9, + "sound": { + "critical": 1, + "name": "default", + "volume": 1.0 + } + } + ] +} +``` + +Add other fields which user defined via `data` field. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!", + "data": { + "key1": "welcome", + "key2": 2 + } + } + ] +} +``` + +Support send notification from different environment. See the detail of [issue](https://github.com/appleboy/gorush/issues/246). + +```diff +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, ++ "production": true, + "message": "Hello World iOS Production!" + }, + { + "tokens": ["token_a", "token_b"], + "platform": 1, ++ "development": true, + "message": "Hello World iOS Sandbox!" + } + ] +} +``` + +### Android Example + +Send normal notification. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!", + "title": "You got message" + } + ] +} +``` + +Add `notification` payload. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!", + "title": "You got message", + "notification" : { + "icon": "myicon", + "color": "#112244" + } + } + ] +} +``` + +Add other fields which user defined via `data` field. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!", + "title": "You got message", + "data": { + "Nick" : "Mario", + "body" : "great match!", + "Room" : "PortugalVSDenmark" + } + } + ] +} +``` + +Send messages to topics + +```json +{ + "notifications": [ + { + "to": "/topics/foo-bar", + "platform": 2, + "message": "This is a Firebase Cloud Messaging Topic Message" + } + ] +} +``` + +### Huawei Example + +Send normal notification. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 3, + "message": "Hello World Huawei!", + "title": "You got message" + } + ] +} +``` + +Add `notification` payload. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 3, + "message": "Hello World Huawei!", + "title": "You got message", + "huawei_notification" : { + "icon": "myicon", + "color": "#112244" + } + } + ] +} +``` + +Add other fields which user defined via `huawei_data` field. + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 3, + "huawei_data": "{'title' : 'Mario','message' : 'great match!', 'Room' : 'PortugalVSDenmark'}" + } + ] +} +``` + +Send messages to topics + +```json +{ + "notifications": [ + { + "topic": "foo-bar", + "platform": 3, + "message": "This is a Huawei Mobile Services Topic Message", + "title": "You got message" + } + ] +} +``` + +### Response body + +Error response message table: + +| status code | message | +|-------------|--------------------------------------------| +| 400 | Missing `notifications` field. | +| 400 | Notifications field is empty. | +| 400 | Number of notifications(50) over limit(10) | + +Success response: + +```json +{ + "counts": 60, + "logs": [], + "success": "ok" +} +``` + +If you need error logs from sending fail notifications, please set a `feedback_hook_url`. The server with send the failing logs asynchronously to your API as `POST` requests. + +```diff +core: + port: "8088" # ignore this port number if auto_tls is enabled (listen 443). + worker_num: 0 # default worker number is runtime.NumCPU() + queue_num: 0 # default queue number is 8192 + max_notification: 100 + sync: false +- feedback_hook_url: "" ++ feedback_hook_url: "https://exemple.com/api/hook" +``` + +You can also switch to **sync** mode by setting the `sync` value as `true` on yaml config. + +```diff +core: + port: "8088" # ignore this port number if auto_tls is enabled (listen 443). + worker_num: 0 # default worker number is runtime.NumCPU() + queue_num: 0 # default queue number is 8192 + max_notification: 100 +- sync: false ++ sync: true +``` + +See the following error format. + +```json +{ + "counts": 60, + "logs": [ + { + "type": "failed-push", + "platform": "android", + "token": "*******", + "message": "Hello World Android!", + "error": "InvalidRegistration" + }, + { + "type": "failed-push", + "platform": "ios", + "token": "*****", + "message": "Hello World iOS1111!", + "error": "Post https://api.push.apple.com/3/device/bbbbb: remote error: tls: revoked certificate" + }, + { + "type": "failed-push", + "platform": "ios", + "token": "*******", + "message": "Hello World iOS222!", + "error": "Post https://api.push.apple.com/3/device/token_b: remote error: tls: revoked certificate" + } + ], + "success": "ok" +} +``` + +## Run gRPC service + +Gorush support [gRPC](https://grpc.io/) service. You can enable the gRPC in `config.yml`, default as disabled. Enable the gRPC server: + +```sh +GORUSH_GRPC_ENABLED=true GORUSH_GRPC_PORT=3000 gorush +``` + +The following example code to send single notification in Go. + +[embedmd]:# (rpc/example/go/send/main.go go) +```go +package main + +import ( + "context" + "log" + + "github.com/appleboy/gorush/rpc/proto" + + structpb "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/grpc" +) + +const ( + address = "localhost:9000" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + c := proto.NewGorushClient(conn) + + r, err := c.Send(context.Background(), &proto.NotificationRequest{ + Platform: 2, + Tokens: []string{"1234567890"}, + Message: "test message", + Badge: 1, + Category: "test", + Sound: "test", + Priority: proto.NotificationRequest_HIGH, + Alert: &proto.Alert{ + Title: "Test Title", + Body: "Test Alert Body", + Subtitle: "Test Alert Sub Title", + LocKey: "Test loc key", + LocArgs: []string{"test", "test"}, + }, + Data: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "key1": { + Kind: &structpb.Value_StringValue{StringValue: "welcome"}, + }, + "key2": { + Kind: &structpb.Value_NumberValue{NumberValue: 2}, + }, + }, + }, + }) + if err != nil { + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) + } +} +``` + +See the Node.js example and see more detail frome [README](rpc/example/node/README.md): + +[embedmd]:# (rpc/example/node/client.js js) +```js +var messages = require('./gorush_pb'); +var services = require('./gorush_grpc_pb'); + +var grpc = require('grpc'); + +function main() { + var client = new services.GorushClient('localhost:9000', + grpc.credentials.createInsecure()); + var request = new messages.NotificationRequest(); + var alert = new messages.Alert(); + request.setPlatform(2); + request.setTokensList(["1234567890"]); + request.setMessage("Hello!!"); + request.setTitle("hello2"); + request.setBadge(2); + request.setCategory("mycategory"); + request.setSound("sound") + alert.setTitle("title"); + request.setAlert(alert); + request.setThreadid("threadID"); + request.setContentavailable(false); + request.setMutablecontent(false); + client.send(request, function (err, response) { + if(err) { + console.log(err); + } else { + console.log("Success:", response.getSuccess()); + console.log("Counts:", response.getCounts()); + } + }); +} + +main(); +``` + +GRPC Health Checking example: See [document](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + +[embedmd]:# (rpc/example/go/send/main.go go) +```go +package main + +import ( + "context" + "log" + + "github.com/appleboy/gorush/rpc/proto" + + structpb "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/grpc" +) + +const ( + address = "localhost:9000" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + c := proto.NewGorushClient(conn) + + r, err := c.Send(context.Background(), &proto.NotificationRequest{ + Platform: 2, + Tokens: []string{"1234567890"}, + Message: "test message", + Badge: 1, + Category: "test", + Sound: "test", + Priority: proto.NotificationRequest_HIGH, + Alert: &proto.Alert{ + Title: "Test Title", + Body: "Test Alert Body", + Subtitle: "Test Alert Sub Title", + LocKey: "Test loc key", + LocArgs: []string{"test", "test"}, + }, + Data: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "key1": { + Kind: &structpb.Value_StringValue{StringValue: "welcome"}, + }, + "key2": { + Kind: &structpb.Value_NumberValue{NumberValue: 2}, + }, + }, + }, + }) + if err != nil { + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) + } +} +``` + +## Run gorush in Docker + +Set up `gorush` in the cloud in under 5 minutes with zero knowledge of Golang or Linux shell using our [gorush Docker image](https://hub.docker.com/r/appleboy/gorush/). + +```bash +docker pull appleboy/gorush +docker run --name gorush -p 80:8088 appleboy/gorush +``` + +Run `gorush` with your own config file. + +```bash +docker pull appleboy/gorush +docker run --name gorush -v ${PWD}/config.yml:/config.yml -p 80:8088 appleboy/gorush +``` + +Testing your gorush server using [httpie](https://github.com/jkbrzt/httpie) command. + +```bash +http -v --verify=no --json GET http://your.docker.host/api/stat/go +``` + +![statue screenshot](screenshot/status.png) + +## Run gorush in Kubernetes + +### Quick Start + +Create namespace as `gorush` as `gorush` and then your configuration map: + +```sh +kubectl create -f k8s/gorush-namespace.yaml +kubectl create -f k8s/gorush-configmap.yaml +``` + +Create redis service: + +```sh +kubectl create -f k8s/gorush-redis-deployment.yaml +kubectl create -f k8s/gorush-redis-service.yaml +``` + +Create gorush deployment controller provides declarative updates for Pods and ReplicaSets: + +```sh +kubectl create -f k8s/gorush-deployment.yaml +``` + +### Create the Service Controller for AWS ELB + +```sh +kubectl create -f k8s/gorush-service.yaml +``` + +### Ingress Controller for AWS ALB + +Update the following in `k8s/gorush-service.yaml` + +```diff +- type: LoadBalancer +- # type: NodePort ++ # type: LoadBalancer ++ type: NodePort +``` + +Then start the AWS ALB by the follwong command. + +```sh +kubectl create -f k8s/gorush-service.yaml +kubectl create -f k8s/gorush-aws-alb-ingress.yaml +``` + +### Clean up the gorush: + +```sh +kubectl delete -f k8s +``` + +## Run gorush in AWS Lambda + +![lambda](./screenshot/lambda.png) + +AWS excited to [announce Go as a supported language for AWS Lambda](https://aws.amazon.com/blogs/compute/announcing-go-support-for-aws-lambda/). You’re going to create an application that uses an [API Gateway](https://aws.amazon.com/apigateway) event source to create a simple Hello World RESTful API. + +### Build gorush binary + +Download source code first. + +```sh +git clone https://github.com/appleboy/gorush.git +cd gorush && make build_linux_lambda +``` + +you can see the binary file in `release/linux/lambda/` folder + +### Deploy gorush application + +we need to build a binary that will run on Linux, and ZIP it up into a deployment package. + +```sh +zip deployment.zip release/linux/lambda/gorush +``` + +Upload the `deployment.zip` via web UI or you can try the [drone-lambda](https://github.com/appleboy/drone-lambda) as the following command. it will zip your binary file and upload to AWS Lambda automatically. + +```sh +$ AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID \ + AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY \ + drone-lambda --region ap-southeast-1 \ + --function-name gorush \ + --source release/linux/lambda/gorush +``` + +### Without an AWS account + +Or you can deploy gorush to alternative solution like [netlify functions](https://docs.netlify.com/functions/overview/). [Netlify](https://www.netlify.com/) lets you deploy serverless Lambda functions without an AWS account, and with function management handled directly within Netlify. Please see the netlify.toml file: + +```toml +[build] + command = "./build.sh" + functions = "release/linux/lambda" + +[build.environment] + GO_IMPORT_PATH = "github.com/appleboy/gorush" + GO111MODULE = "on" + +[[redirects]] + from = "/*" + to = "/.netlify/functions/gorush/:splat" + status = 200 +``` + +## Stargazers over time + +[![Stargazers over time](https://starcharts.herokuapp.com/appleboy/gorush.svg)](https://starcharts.herokuapp.com/appleboy/gorush) + +## License + +Copyright 2019 Bo-Yi Wu [@appleboy](https://twitter.com/appleboy). + +Licensed under the MIT License. diff --git a/push/gorush-with-mipush/src/certificate/authkey-invalid.p8 b/push/gorush-with-mipush/src/certificate/authkey-invalid.p8 new file mode 100644 index 0000000..74a2d70 --- /dev/null +++ b/push/gorush-with-mipush/src/certificate/authkey-invalid.p8 @@ -0,0 +1,3 @@ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE +ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI +tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfZZZ diff --git a/push/gorush-with-mipush/src/certificate/authkey-valid.p8 b/push/gorush-with-mipush/src/certificate/authkey-valid.p8 new file mode 100644 index 0000000..62c30f5 --- /dev/null +++ b/push/gorush-with-mipush/src/certificate/authkey-valid.p8 @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE +ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI +tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfeQl +-----END PRIVATE KEY----- diff --git a/push/gorush-with-mipush/src/certificate/certificate-valid.p12 b/push/gorush-with-mipush/src/certificate/certificate-valid.p12 new file mode 100644 index 0000000..761b614 Binary files /dev/null and b/push/gorush-with-mipush/src/certificate/certificate-valid.p12 differ diff --git a/push/gorush-with-mipush/src/certificate/certificate-valid.pem b/push/gorush-with-mipush/src/certificate/certificate-valid.pem new file mode 100644 index 0000000..2939d7c --- /dev/null +++ b/push/gorush-with-mipush/src/certificate/certificate-valid.pem @@ -0,0 +1,59 @@ +Bag Attributes + localKeyID: 8C 1A 9F 00 66 BD 24 42 B9 5D 1E EB FE 5E 8B CA 04 3D 73 83 + friendlyName: APNS/2 Private Key +subject=/C=NZ/ST=Wellington/L=Wellington/O=Internet Widgits Pty Ltd/OU=9ZEH62KRVV/CN=APNS/2 Development IOS Push Services: com.sideshow.Apns2 +issuer=/C=NZ/ST=Wellington/L=Wellington/O=APNS/2 Inc./OU=APNS/2 Worldwide Developer Relations/CN=APNS/2 Worldwide Developer Relations Certification Authority +-----BEGIN CERTIFICATE----- +MIID6zCCAtMCAQIwDQYJKoZIhvcNAQELBQAwgcMxCzAJBgNVBAYTAk5aMRMwEQYD +VQQIEwpXZWxsaW5ndG9uMRMwEQYDVQQHEwpXZWxsaW5ndG9uMRQwEgYDVQQKEwtB +UE5TLzIgSW5jLjEtMCsGA1UECxMkQVBOUy8yIFdvcmxkd2lkZSBEZXZlbG9wZXIg +UmVsYXRpb25zMUUwQwYDVQQDEzxBUE5TLzIgV29ybGR3aWRlIERldmVsb3BlciBS +ZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTYwMTA4MDgzNDMw +WhcNMjYwMTA1MDgzNDMwWjCBsjELMAkGA1UEBhMCTloxEzARBgNVBAgTCldlbGxp +bmd0b24xEzARBgNVBAcTCldlbGxpbmd0b24xITAfBgNVBAoTGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDETMBEGA1UECxMKOVpFSDYyS1JWVjFBMD8GA1UEAxM4QVBO +Uy8yIERldmVsb3BtZW50IElPUyBQdXNoIFNlcnZpY2VzOiBjb20uc2lkZXNob3cu +QXBuczIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY0c1TKB5oZPwQ +7t1CwMIrvqB6GIU3tPy6RhckZXTkOB8YeBWJ7UKfCz8HGHFVomBP0T5OUbeqQzqW +YJbQzZ8a6ZMszbL0lO4X9++3Oi5/TtAwOUOK8rOFN25m2KfsayHQZ/4vWStK2Fwm +5aJbGLlpH/b/7z1D4vhmMgoBuT1IuyhGiyFxlZ9EtTloFvsqM1E5fYZOSZACyXTa +K4vdgbQMgUVsI714FAgLTlK0UeiRkmKm3pdbtfVbrthzI+IHXKItUIy+Fn20PRMh +dSnaztSz7tgBWCIx22qvcYogHWiOgUYIM772zE2y8UVOr8DsiRlsOHSA7EI4MJcQ +G2FUq2Z/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGyfyO2HMgcdeBcz3bt5BILX +f7RA2/UmVIwcKR1qotTsF+PnBmcILeyOQgDe9tGU5cRc79kDt3JRmMYROFIMgFRf +Wf22uOKtho7GQQaKvG+bkgMVdYFRlBHnF+KeqKH81qb9p+CT4Iw0GehIL1DijFLR +VIAIBYpz4oBPCIE1ISVT+Fgaf3JAh59kbPbNw9AIDxaBtP8EuzSTNwfbxoGbCobS +Wi1U8IsCwQFt8tM1m4ZXD1CcZIrGdryeAhVkvKIJRiU5QYWI2nqZN+JqQucm9ad0 +mYO5mJkIobUa4+ZJhCPKEdmgpFbRGk0wVuaDM9Cv6P2srsYAjaO4y3VP0GvNKRI= +-----END CERTIFICATE----- +Bag Attributes + localKeyID: 8C 1A 9F 00 66 BD 24 42 B9 5D 1E EB FE 5E 8B CA 04 3D 73 83 + friendlyName: APNS/2 Private Key +Key Attributes: <No Attributes> +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2NHNUygeaGT8EO7dQsDCK76gehiFN7T8ukYXJGV05DgfGHgV +ie1Cnws/BxhxVaJgT9E+TlG3qkM6lmCW0M2fGumTLM2y9JTuF/fvtzouf07QMDlD +ivKzhTduZtin7Gsh0Gf+L1krSthcJuWiWxi5aR/2/+89Q+L4ZjIKAbk9SLsoRosh +cZWfRLU5aBb7KjNROX2GTkmQAsl02iuL3YG0DIFFbCO9eBQIC05StFHokZJipt6X +W7X1W67YcyPiB1yiLVCMvhZ9tD0TIXUp2s7Us+7YAVgiMdtqr3GKIB1ojoFGCDO+ +9sxNsvFFTq/A7IkZbDh0gOxCODCXEBthVKtmfwIDAQABAoIBAQCW8ZCI+OAae1tE +ipZ9F2bWP3LHLXTo8FYVdCA+VWeITk3PoiIUkJmV0aWCUhDstgto5doDej5sCTur +Xvj/ynaerMeqJFYWkewjwZcgLyAZvwuO1v7fp9E0x/9TGDfnjjnPNeaundxW0cNt +zOY3l0HVHsy9Jpe3QDcAJovy4Tv5+hFY4kDxUBGsyjvhScVgKg5tLkJclm3sOu/L +GyLqpwNI3OJAdMIuVD4N2BZ1aOEap6mp2y8Ie0/R4YWcaZ5A4Pw7xUPl6SXc9uua +/78QTERtPC6ejyCBiE05a8m3Q3iud3Xtnlyws2KwhgBAfE6M4zR/f3OQB7ZIXMhy +ZpmZZw5xAoGBAPYn84IrlIQetWQfvPdM7Kzgh6UDHCugnlCDghwYpRJGi8hMfuZV +xNIrYAJzLYDQ01lFJRJgWXTcbqz9NBz1nhg+cNOz1/KY+38eudee6DNYmztP7jDP +2jnaS+dtjC8hAXObnFqG+NilMDLLu6aRmrJaImbjSrfyLiE6mvJ7u81nAoGBAOF9 +g93wZ0mL1rk2s5WwHGTNU/HaOtmWS4z7kA7f4QaRub+MwppZmmDZPHpiZX7BPcZz +iOPQh+xn7IqRGoQWBLykBVt8zZFoLZJoCR3n63lex5A4p/0Pp1gFZrR+xX8PYVos +3yeeiWyPKsXXNc0s5QwHZcX6Wb8EHThTXGCBetcpAoGAMeQJC9IPaPPcae2w3CLA +OY3MkFpgBEuqqsDsxwsLsfeQb0lp0v+BQ+O8suJrT5eDrq1ABUh3+SKQYAl13YS+ +xUUqkw35b9cn6iztF9HCWF3WIKBjs4r9PQqMpdxjNE4pQChC+Wov16ErcrAuWWVb +iFiSbm4U/9FbHisFqq3/c3MCgYB+vzSuPgFw37+0oEDVtQZgyuGSop5NzCNvfb/9 +/G3aaXNFbnO8mv0hzzoleMWgODLnJ+4cUAz3H3tgcCu9bzr+Zhv0zvQl9a8YCo6F +VuWPdW0rbg1PO8tOuMqATnno79ZC/9H3zS9l7BuY1V2SlNeyqT3VyOFFc6SREpps +TJul8QKBgAxnQB8MA7zPULu1clyaJLdtEdRPkKWN7lKYptc0e/VHfSsKxseWkfqi +zgXZ51kQTrT6Zb6HYRfwC1mMXHWRKRyYjAnCxVim6YQd+KVT49iRDDAiIFoMGA4i +vvcIlneqOZZPDIoKJ60IjO/DZHWkw5mLjaIrT+qQ3XAGdJA13hcm +-----END RSA PRIVATE KEY----- diff --git a/push/gorush-with-mipush/src/certificate/localhost.cert b/push/gorush-with-mipush/src/certificate/localhost.cert new file mode 100644 index 0000000..2471fc2 --- /dev/null +++ b/push/gorush-with-mipush/src/certificate/localhost.cert @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJALbZEDvUQrFKMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAzMjgwMzMwNDFaFw0yNjAzMjYwMzMwNDFaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMj1+xg4jVLzVnB5j7n1ul30WEE4BCzcNFxg5AOB5H5q+wje0YYiVFg6PQyv +GCipqIRXVRdVQ1hHSeunYGKe8lq3Sb1X8PUJ12v9uRbpS9DK1Owqk8rsPDu6sVTL +qKKgH1Z8yazzaS0AbXuA5e9gO/RzijbnpEP+quM4dueiMPVEJyLq+EoIQY+MM8MP +8dZzL4XZl7wL4UsCN7rPcO6W3tlnT0iO3h9c/Ym2hFhz+KNJ9KRRCvtPGZESigtK +bHsXH099WDo8v/Wp5/evBw/+JD0opxmCfHIBALHt9v53RvvsDZ1t33Rpu5C8znEY +Y2Ay7NgxhqjqoWJqA48lJeA0clsCAwEAAaNQME4wHQYDVR0OBBYEFC0bTU1Xofeh +NKIelashIsqKidDYMB8GA1UdIwQYMBaAFC0bTU1XofehNKIelashIsqKidDYMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAiJL8IMTwNX9XqQWYDFgkG4 +AnrVwQhreAqC9rSxDCjqqnMHPHGzcCeDMLAMoh0kOy20nowUGNtCZ0uBvnX2q1bN +g1jt+GBcLJDR3LL4CpNOlm3YhOycuNfWMxTA7BXkmnSrZD/7KhArsBEY8aulxwKJ +HRgNlIwe1oFD1YdX1BS5pp4t25B6Vq4A3FMMUkVoWE688nE168hvQgwjrHkgHhwe +eN8lGE2DhFraXnWmDMdwaHD3HRFGhyppIFN+f7BqbWX9gM+T2YRTfObIXLWbqJLD +3Mk/NkxqVcg4eY54wJ1ufCUGAYAIaY6fQqiNUz8nhwK3t45NBVT9y/uJXqnTLyY= +-----END CERTIFICATE----- diff --git a/push/gorush-with-mipush/src/certificate/localhost.key b/push/gorush-with-mipush/src/certificate/localhost.key new file mode 100644 index 0000000..4d28833 --- /dev/null +++ b/push/gorush-with-mipush/src/certificate/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAyPX7GDiNUvNWcHmPufW6XfRYQTgELNw0XGDkA4Hkfmr7CN7R +hiJUWDo9DK8YKKmohFdVF1VDWEdJ66dgYp7yWrdJvVfw9QnXa/25FulL0MrU7CqT +yuw8O7qxVMuooqAfVnzJrPNpLQBte4Dl72A79HOKNuekQ/6q4zh256Iw9UQnIur4 +SghBj4wzww/x1nMvhdmXvAvhSwI3us9w7pbe2WdPSI7eH1z9ibaEWHP4o0n0pFEK ++08ZkRKKC0psexcfT31YOjy/9ann968HD/4kPSinGYJ8cgEAse32/ndG++wNnW3f +dGm7kLzOcRhjYDLs2DGGqOqhYmoDjyUl4DRyWwIDAQABAoIBAGTKqsN9KbSfA42q +CqI0UuLouJMNa1qsnz5uAi6YKWgWdA4A44mpEjCmFRSVhUJvxWuK+cyYIQzXxIWD +D16nZdqF72AeCWZ9JySsvvZ00GfKM3y35iRy08sJWgOzmcLnGJCiSeyKsQe3HTJC +dhDXbXqvsHTVPZg01LTeDxUiTffU8NMKqR2AecQ2sTDwXEhAnTyAtnzl/XaBgFzu +U6G7FzGM5y9bxkfQVkvy+DEJkHGNOjzwcVfByyVl610ixmG1vmxVj9PbWmIPsUV8 +ySmjhvDQbOfoxW0h9vTlTqGtQcBw962osnDDMWFCdM7lzO0T7RRnPVGIRpCJOKhq +keqHKwECgYEA8wwI/iZughoTXTNG9LnQQ/WAtsqO80EjMTUheo5I1kOzmUz09pyh +iAsUDoN0/26tZ5WNjlnyZu7dvTc/x3dTZpmNnoo8gcVbQNECDRzqfuQ9PPXm1SN5 +6peBqAvBv78hjV05aXzPG/VBbeig7l299EarEA+a/oH3KrgDoqVqE0ECgYEA06vA +YJmgg4fZRucAYoaYsLz9Z9rCFjTe1PBTmUJkbOR8vFIHHTTEWi/SuxXL0wDSeoE2 +7BQm86gCC7/KgRdrzoBqZ5qS9Mv2dsLgY635VSgjjfZkVLiH1VRRpSQObYnfoysg +gatcHSKMExd4SLQByAuImXP+L5ayDBcEJfbqSpsCgYB78Is1b0uzNLDjOh7Y9Vhr +D2qPzEORcIoNsdZctOoXuXaAmmngyIbm5R9ZN1gWWc47oFwLV3rxWqXgs6fmg8cX +7v309vFcC9Q4/Vxaa4B5LNK9n3gTAIBPTOtlUnl+2my1tfBtBqRm0W6IKbTHWS5g +vxjEm/CiEIyGUEgqTMgHAQKBgBKuXdQoutng63QufwIzDtbKVzMLQ4XiNKhmbXph +OavCnp+gPbB+L7Yl8ltAmTSOJgVZ0hcT0DxA361Zx+2Mu58GBl4OblnchmwE1vj1 +KcQyPrEQxdoUTyiswGfqvrs8J9imvb+z9/U6T1KAB8Wi3WViXzPr4MsiaaRXg642 +FIdxAoGAZ7/735dkhJcyOfs+LKsLr68JSstoorXOYvdMu1+JGa9iLuhnHEcMVWC8 +IuihzPfloZtMbGYkZJn8l3BeGd8hmfFtgTgZGPoVRetft2LDFLnPxp2sEH5OFLsQ +R+K/kAOul8eStWuMXOFA9pMzGkGEgIFJMJOyaJON3kedQI8deCM= +-----END RSA PRIVATE KEY----- diff --git a/push/gorush-with-mipush/src/config/config.go b/push/gorush-with-mipush/src/config/config.go new file mode 100644 index 0000000..49a6a75 --- /dev/null +++ b/push/gorush-with-mipush/src/config/config.go @@ -0,0 +1,449 @@ +package config + +import ( + "bytes" + "fmt" + "io/ioutil" + "runtime" + "strings" + + "github.com/spf13/viper" +) + +var defaultConf = []byte(` +core: + enabled: true # enable httpd server + address: "" # ip address to bind (default: any) + shutdown_timeout: 30 # default is 30 second + port: "8088" # ignore this port number if auto_tls is enabled (listen 443). + worker_num: 0 # default worker number is runtime.NumCPU() + queue_num: 0 # default queue number is 8192 + max_notification: 100 + sync: false # set true if you need get error message from fail push notification in API response. + feedback_hook_url: "" # set webhook url if you need get error message asynchronously from fail push notification in API response. + feedback_timeout: 10 # default is 10 second + mode: "release" + ssl: false + cert_path: "cert.pem" + key_path: "key.pem" + cert_base64: "" + key_base64: "" + http_proxy: "" + pid: + enabled: false + path: "gorush.pid" + override: true + auto_tls: + enabled: false # Automatically install TLS certificates from Let's Encrypt. + folder: ".cache" # folder for storing TLS certificates + host: "" # which domains the Let's Encrypt will attempt + +grpc: + enabled: false # enable gRPC server + port: 9000 + +api: + push_uri: "/api/push" + stat_go_uri: "/api/stat/go" + stat_app_uri: "/api/stat/app" + config_uri: "/api/config" + sys_stat_uri: "/sys/stats" + metric_uri: "/metrics" + health_uri: "/healthz" + +android: + enabled: true + apikey: "YOUR_API_KEY" + max_retry: 0 # resend fail notification, default value zero is disabled + +huawei: + enabled: false + appsecret: "YOUR_APP_SECRET" + appid: "YOUR_APP_ID" + max_retry: 0 # resend fail notification, default value zero is disabled + +queue: + engine: "local" # support "local", "nsq", "nats" and "redis" default value is "local" + nsq: + addr: 127.0.0.1:4150 + topic: gorush + channel: gorush + nats: + addr: 127.0.0.1:4222 + subj: gorush + queue: gorush + redis: + addr: 127.0.0.1:6379 + channel: gorush + size: 1024 + +ios: + enabled: false + key_path: "" + key_base64: "" # load iOS key from base64 input + key_type: "pem" # could be pem, p12 or p8 type + password: "" # certificate password, default as empty string. + production: false + max_concurrent_pushes: 100 # just for push ios notification + max_retry: 0 # resend fail notification, default value zero is disabled + key_id: "" # KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + team_id: "" # TeamID from developer account (View Account -> Membership) + +log: + format: "string" # string or json + access_log: "stdout" # stdout: output to console, or define log path like "log/access_log" + access_level: "debug" + error_log: "stderr" # stderr: output to console, or define log path like "log/error_log" + error_level: "error" + hide_token: true + +stat: + engine: "memory" # support memory, redis, boltdb, buntdb or leveldb + redis: + cluster: false + addr: "localhost:6379" # if cluster is true, you may set this to "localhost:6379,localhost:6380,localhost:6381" + password: "" + db: 0 + boltdb: + path: "bolt.db" + bucket: "gorush" + buntdb: + path: "bunt.db" + leveldb: + path: "level.db" + badgerdb: + path: "badger.db" +`) + +// ConfYaml is config structure. +type ConfYaml struct { + Core SectionCore `yaml:"core"` + API SectionAPI `yaml:"api"` + Android SectionAndroid `yaml:"android"` + Huawei SectionHuawei `yaml:"huawei"` + MI SectionMI `yaml:"mi"` + Ios SectionIos `yaml:"ios"` + Queue SectionQueue `yaml:"queue"` + Log SectionLog `yaml:"log"` + Stat SectionStat `yaml:"stat"` + GRPC SectionGRPC `yaml:"grpc"` +} + +// SectionCore is sub section of config. +type SectionCore struct { + Enabled bool `yaml:"enabled"` + Address string `yaml:"address"` + ShutdownTimeout int64 `yaml:"shutdown_timeout"` + Port string `yaml:"port"` + MaxNotification int64 `yaml:"max_notification"` + WorkerNum int64 `yaml:"worker_num"` + QueueNum int64 `yaml:"queue_num"` + Mode string `yaml:"mode"` + Sync bool `yaml:"sync"` + SSL bool `yaml:"ssl"` + CertPath string `yaml:"cert_path"` + KeyPath string `yaml:"key_path"` + CertBase64 string `yaml:"cert_base64"` + KeyBase64 string `yaml:"key_base64"` + HTTPProxy string `yaml:"http_proxy"` + FeedbackURL string `yaml:"feedback_hook_url"` + FeedbackTimeout int64 `yaml:"feedback_timeout"` + PID SectionPID `yaml:"pid"` + AutoTLS SectionAutoTLS `yaml:"auto_tls"` +} + +// SectionAutoTLS support Let's Encrypt setting. +type SectionAutoTLS struct { + Enabled bool `yaml:"enabled"` + Folder string `yaml:"folder"` + Host string `yaml:"host"` +} + +// SectionAPI is sub section of config. +type SectionAPI struct { + PushURI string `yaml:"push_uri"` + StatGoURI string `yaml:"stat_go_uri"` + StatAppURI string `yaml:"stat_app_uri"` + ConfigURI string `yaml:"config_uri"` + SysStatURI string `yaml:"sys_stat_uri"` + MetricURI string `yaml:"metric_uri"` + HealthURI string `yaml:"health_uri"` +} + +// SectionAndroid is sub section of config. +type SectionAndroid struct { + Enabled bool `yaml:"enabled"` + APIKey string `yaml:"apikey"` + MaxRetry int `yaml:"max_retry"` +} + +// SectionHuawei is sub section of config. +type SectionHuawei struct { + Enabled bool `yaml:"enabled"` + AppSecret string `yaml:"appsecret"` + AppID string `yaml:"appid"` + MaxRetry int `yaml:"max_retry"` +} + +// SectionMI is sub section of config. +type SectionMI struct { + Enabled bool `yaml:"enabled"` + AppSecret string `yaml:"app_secret"` + Package string `yaml:"package"` + MaxRetry int `yaml:"max_retry"` +} + +// SectionIos is sub section of config. +type SectionIos struct { + Enabled bool `yaml:"enabled"` + KeyPath string `yaml:"key_path"` + KeyBase64 string `yaml:"key_base64"` + KeyType string `yaml:"key_type"` + Password string `yaml:"password"` + Production bool `yaml:"production"` + MaxConcurrentPushes uint `yaml:"max_concurrent_pushes"` + MaxRetry int `yaml:"max_retry"` + KeyID string `yaml:"key_id"` + TeamID string `yaml:"team_id"` +} + +// SectionLog is sub section of config. +type SectionLog struct { + Format string `yaml:"format"` + AccessLog string `yaml:"access_log"` + AccessLevel string `yaml:"access_level"` + ErrorLog string `yaml:"error_log"` + ErrorLevel string `yaml:"error_level"` + HideToken bool `yaml:"hide_token"` +} + +// SectionStat is sub section of config. +type SectionStat struct { + Engine string `yaml:"engine"` + Redis SectionRedis `yaml:"redis"` + BoltDB SectionBoltDB `yaml:"boltdb"` + BuntDB SectionBuntDB `yaml:"buntdb"` + LevelDB SectionLevelDB `yaml:"leveldb"` + BadgerDB SectionBadgerDB `yaml:"badgerdb"` +} + +// SectionQueue is sub section of config. +type SectionQueue struct { + Engine string `yaml:"engine"` + NSQ SectionNSQ `yaml:"nsq"` + NATS SectionNATS `yaml:"nats"` + Redis SectionRedisQueue `yaml:"redis"` +} + +// SectionNSQ is sub section of config. +type SectionNSQ struct { + Addr string `yaml:"addr"` + Topic string `yaml:"topic"` + Channel string `yaml:"channel"` +} + +// SectionNATS is sub section of config. +type SectionNATS struct { + Addr string `yaml:"addr"` + Subj string `yaml:"subj"` + Queue string `yaml:"queue"` +} + +// SectionRedisQueue is sub section of config. +type SectionRedisQueue struct { + Addr string `yaml:"addr"` + Channel string `yaml:"channel"` + Size int `yaml:"size"` +} + +// SectionRedis is sub section of config. +type SectionRedis struct { + Cluster bool `yaml:"cluster"` + Addr string `yaml:"addr"` + Password string `yaml:"password"` + DB int `yaml:"db"` +} + +// SectionBoltDB is sub section of config. +type SectionBoltDB struct { + Path string `yaml:"path"` + Bucket string `yaml:"bucket"` +} + +// SectionBuntDB is sub section of config. +type SectionBuntDB struct { + Path string `yaml:"path"` +} + +// SectionLevelDB is sub section of config. +type SectionLevelDB struct { + Path string `yaml:"path"` +} + +// SectionBadgerDB is sub section of config. +type SectionBadgerDB struct { + Path string `yaml:"path"` +} + +// SectionPID is sub section of config. +type SectionPID struct { + Enabled bool `yaml:"enabled"` + Path string `yaml:"path"` + Override bool `yaml:"override"` +} + +// SectionGRPC is sub section of config. +type SectionGRPC struct { + Enabled bool `yaml:"enabled"` + Port string `yaml:"port"` +} + +func setDefault() { + viper.SetDefault("ios.max_concurrent_pushes", uint(100)) +} + +// LoadConf load config from file and read in environment variables that match +func LoadConf(confPath ...string) (*ConfYaml, error) { + conf := &ConfYaml{} + + // load default values + setDefault() + + viper.SetConfigType("yaml") + viper.AutomaticEnv() // read in environment variables that match + viper.SetEnvPrefix("gorush") // will be uppercased automatically + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + + if len(confPath) > 0 && confPath[0] != "" { + content, err := ioutil.ReadFile(confPath[0]) + if err != nil { + return conf, err + } + + if err := viper.ReadConfig(bytes.NewBuffer(content)); err != nil { + return conf, err + } + } else { + // Search config in home directory with name ".gorush" (without extension). + viper.AddConfigPath("/etc/gorush/") + viper.AddConfigPath("$HOME/.gorush") + viper.AddConfigPath(".") + viper.SetConfigName("config") + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } else if err := viper.ReadConfig(bytes.NewBuffer(defaultConf)); err != nil { + // load default config + return conf, err + } + } + + // Core + conf.Core.Address = viper.GetString("core.address") + conf.Core.Port = viper.GetString("core.port") + conf.Core.ShutdownTimeout = int64(viper.GetInt("core.shutdown_timeout")) + conf.Core.Enabled = viper.GetBool("core.enabled") + conf.Core.WorkerNum = int64(viper.GetInt("core.worker_num")) + conf.Core.QueueNum = int64(viper.GetInt("core.queue_num")) + conf.Core.Mode = viper.GetString("core.mode") + conf.Core.Sync = viper.GetBool("core.sync") + conf.Core.FeedbackURL = viper.GetString("core.feedback_hook_url") + conf.Core.FeedbackTimeout = int64(viper.GetInt("core.feedback_timeout")) + conf.Core.SSL = viper.GetBool("core.ssl") + conf.Core.CertPath = viper.GetString("core.cert_path") + conf.Core.KeyPath = viper.GetString("core.key_path") + conf.Core.CertBase64 = viper.GetString("core.cert_base64") + conf.Core.KeyBase64 = viper.GetString("core.key_base64") + conf.Core.MaxNotification = int64(viper.GetInt("core.max_notification")) + conf.Core.HTTPProxy = viper.GetString("core.http_proxy") + conf.Core.PID.Enabled = viper.GetBool("core.pid.enabled") + conf.Core.PID.Path = viper.GetString("core.pid.path") + conf.Core.PID.Override = viper.GetBool("core.pid.override") + conf.Core.AutoTLS.Enabled = viper.GetBool("core.auto_tls.enabled") + conf.Core.AutoTLS.Folder = viper.GetString("core.auto_tls.folder") + conf.Core.AutoTLS.Host = viper.GetString("core.auto_tls.host") + + // Api + conf.API.PushURI = viper.GetString("api.push_uri") + conf.API.StatGoURI = viper.GetString("api.stat_go_uri") + conf.API.StatAppURI = viper.GetString("api.stat_app_uri") + conf.API.ConfigURI = viper.GetString("api.config_uri") + conf.API.SysStatURI = viper.GetString("api.sys_stat_uri") + conf.API.MetricURI = viper.GetString("api.metric_uri") + conf.API.HealthURI = viper.GetString("api.health_uri") + + // Android + conf.Android.Enabled = viper.GetBool("android.enabled") + conf.Android.APIKey = viper.GetString("android.apikey") + conf.Android.MaxRetry = viper.GetInt("android.max_retry") + + // Huawei + conf.Huawei.Enabled = viper.GetBool("huawei.enabled") + conf.Huawei.AppSecret = viper.GetString("huawei.appsecret") + conf.Huawei.AppID = viper.GetString("huawei.appid") + conf.Huawei.MaxRetry = viper.GetInt("huawei.max_retry") + + conf.MI.Enabled = viper.GetBool("mi.enabled") + conf.MI.AppSecret = viper.GetString("mi.appsecret") + conf.MI.Package = viper.GetString("mi.package") + conf.MI.MaxRetry = viper.GetInt("mi.max_retry") + + // iOS + conf.Ios.Enabled = viper.GetBool("ios.enabled") + conf.Ios.KeyPath = viper.GetString("ios.key_path") + conf.Ios.KeyBase64 = viper.GetString("ios.key_base64") + conf.Ios.KeyType = viper.GetString("ios.key_type") + conf.Ios.Password = viper.GetString("ios.password") + conf.Ios.Production = viper.GetBool("ios.production") + conf.Ios.MaxConcurrentPushes = viper.GetUint("ios.max_concurrent_pushes") + conf.Ios.MaxRetry = viper.GetInt("ios.max_retry") + conf.Ios.KeyID = viper.GetString("ios.key_id") + conf.Ios.TeamID = viper.GetString("ios.team_id") + + // log + conf.Log.Format = viper.GetString("log.format") + conf.Log.AccessLog = viper.GetString("log.access_log") + conf.Log.AccessLevel = viper.GetString("log.access_level") + conf.Log.ErrorLog = viper.GetString("log.error_log") + conf.Log.ErrorLevel = viper.GetString("log.error_level") + conf.Log.HideToken = viper.GetBool("log.hide_token") + + // Queue Engine + conf.Queue.Engine = viper.GetString("queue.engine") + conf.Queue.NSQ.Addr = viper.GetString("queue.nsq.addr") + conf.Queue.NSQ.Topic = viper.GetString("queue.nsq.topic") + conf.Queue.NSQ.Channel = viper.GetString("queue.nsq.channel") + conf.Queue.NATS.Addr = viper.GetString("queue.nats.addr") + conf.Queue.NATS.Subj = viper.GetString("queue.nats.subj") + conf.Queue.NATS.Queue = viper.GetString("queue.nats.queue") + conf.Queue.Redis.Addr = viper.GetString("queue.redis.addr") + conf.Queue.Redis.Channel = viper.GetString("queue.redis.channel") + conf.Queue.Redis.Size = viper.GetInt("queue.redis.size") + + // Stat Engine + conf.Stat.Engine = viper.GetString("stat.engine") + conf.Stat.Redis.Cluster = viper.GetBool("stat.redis.cluster") + conf.Stat.Redis.Addr = viper.GetString("stat.redis.addr") + conf.Stat.Redis.Password = viper.GetString("stat.redis.password") + conf.Stat.Redis.DB = viper.GetInt("stat.redis.db") + conf.Stat.BoltDB.Path = viper.GetString("stat.boltdb.path") + conf.Stat.BoltDB.Bucket = viper.GetString("stat.boltdb.bucket") + conf.Stat.BuntDB.Path = viper.GetString("stat.buntdb.path") + conf.Stat.LevelDB.Path = viper.GetString("stat.leveldb.path") + conf.Stat.BadgerDB.Path = viper.GetString("stat.badgerdb.path") + + // gRPC Server + conf.GRPC.Enabled = viper.GetBool("grpc.enabled") + conf.GRPC.Port = viper.GetString("grpc.port") + + if conf.Core.WorkerNum == int64(0) { + conf.Core.WorkerNum = int64(runtime.NumCPU()) + } + + if conf.Core.QueueNum == int64(0) { + conf.Core.QueueNum = int64(8192) + } + + return conf, nil +} diff --git a/push/gorush-with-mipush/src/config/config_test.go b/push/gorush-with-mipush/src/config/config_test.go new file mode 100644 index 0000000..0cff270 --- /dev/null +++ b/push/gorush-with-mipush/src/config/config_test.go @@ -0,0 +1,245 @@ +package config + +import ( + "os" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// Test file is missing +func TestMissingFile(t *testing.T) { + filename := "test" + _, err := LoadConf(filename) + + assert.NotNil(t, err) +} + +func TestEmptyConfig(t *testing.T) { + conf, err := LoadConf("testdata/empty.yml") + if err != nil { + panic("failed to load config.yml from file") + } + + assert.Equal(t, uint(100), conf.Ios.MaxConcurrentPushes) +} + +type ConfigTestSuite struct { + suite.Suite + ConfGorushDefault *ConfYaml + ConfGorush *ConfYaml +} + +func (suite *ConfigTestSuite) SetupTest() { + var err error + suite.ConfGorushDefault, err = LoadConf() + if err != nil { + panic("failed to load default config.yml") + } + suite.ConfGorush, err = LoadConf("testdata/config.yml") + if err != nil { + panic("failed to load config.yml from file") + } +} + +func (suite *ConfigTestSuite) TestValidateConfDefault() { + // Core + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.Address) + assert.Equal(suite.T(), "8088", suite.ConfGorushDefault.Core.Port) + assert.Equal(suite.T(), int64(30), suite.ConfGorushDefault.Core.ShutdownTimeout) + assert.Equal(suite.T(), true, suite.ConfGorushDefault.Core.Enabled) + assert.Equal(suite.T(), int64(runtime.NumCPU()), suite.ConfGorushDefault.Core.WorkerNum) + assert.Equal(suite.T(), int64(8192), suite.ConfGorushDefault.Core.QueueNum) + assert.Equal(suite.T(), "release", suite.ConfGorushDefault.Core.Mode) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Core.Sync) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.FeedbackURL) + assert.Equal(suite.T(), int64(10), suite.ConfGorushDefault.Core.FeedbackTimeout) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Core.SSL) + assert.Equal(suite.T(), "cert.pem", suite.ConfGorushDefault.Core.CertPath) + assert.Equal(suite.T(), "key.pem", suite.ConfGorushDefault.Core.KeyPath) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.KeyBase64) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.CertBase64) + assert.Equal(suite.T(), int64(100), suite.ConfGorushDefault.Core.MaxNotification) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.HTTPProxy) + // Pid + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Core.PID.Enabled) + assert.Equal(suite.T(), "gorush.pid", suite.ConfGorushDefault.Core.PID.Path) + assert.Equal(suite.T(), true, suite.ConfGorushDefault.Core.PID.Override) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Core.AutoTLS.Enabled) + assert.Equal(suite.T(), ".cache", suite.ConfGorushDefault.Core.AutoTLS.Folder) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Core.AutoTLS.Host) + + // Api + assert.Equal(suite.T(), "/api/push", suite.ConfGorushDefault.API.PushURI) + assert.Equal(suite.T(), "/api/stat/go", suite.ConfGorushDefault.API.StatGoURI) + assert.Equal(suite.T(), "/api/stat/app", suite.ConfGorushDefault.API.StatAppURI) + assert.Equal(suite.T(), "/api/config", suite.ConfGorushDefault.API.ConfigURI) + assert.Equal(suite.T(), "/sys/stats", suite.ConfGorushDefault.API.SysStatURI) + assert.Equal(suite.T(), "/metrics", suite.ConfGorushDefault.API.MetricURI) + assert.Equal(suite.T(), "/healthz", suite.ConfGorushDefault.API.HealthURI) + + // Android + assert.Equal(suite.T(), true, suite.ConfGorushDefault.Android.Enabled) + assert.Equal(suite.T(), "YOUR_API_KEY", suite.ConfGorushDefault.Android.APIKey) + assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Android.MaxRetry) + + // iOS + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Ios.Enabled) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.KeyPath) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.KeyBase64) + assert.Equal(suite.T(), "pem", suite.ConfGorushDefault.Ios.KeyType) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.Password) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Ios.Production) + assert.Equal(suite.T(), uint(100), suite.ConfGorushDefault.Ios.MaxConcurrentPushes) + assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Ios.MaxRetry) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.KeyID) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.TeamID) + + // queue + assert.Equal(suite.T(), "local", suite.ConfGorushDefault.Queue.Engine) + assert.Equal(suite.T(), "127.0.0.1:4150", suite.ConfGorushDefault.Queue.NSQ.Addr) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NSQ.Topic) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NSQ.Channel) + + assert.Equal(suite.T(), "127.0.0.1:4222", suite.ConfGorushDefault.Queue.NATS.Addr) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NATS.Subj) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NATS.Queue) + + assert.Equal(suite.T(), "127.0.0.1:6379", suite.ConfGorushDefault.Queue.Redis.Addr) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.Redis.Channel) + assert.Equal(suite.T(), 1024, suite.ConfGorushDefault.Queue.Redis.Size) + + // log + assert.Equal(suite.T(), "string", suite.ConfGorushDefault.Log.Format) + assert.Equal(suite.T(), "stdout", suite.ConfGorushDefault.Log.AccessLog) + assert.Equal(suite.T(), "debug", suite.ConfGorushDefault.Log.AccessLevel) + assert.Equal(suite.T(), "stderr", suite.ConfGorushDefault.Log.ErrorLog) + assert.Equal(suite.T(), "error", suite.ConfGorushDefault.Log.ErrorLevel) + assert.Equal(suite.T(), true, suite.ConfGorushDefault.Log.HideToken) + + assert.Equal(suite.T(), "memory", suite.ConfGorushDefault.Stat.Engine) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Stat.Redis.Cluster) + assert.Equal(suite.T(), "localhost:6379", suite.ConfGorushDefault.Stat.Redis.Addr) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Stat.Redis.Password) + assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Stat.Redis.DB) + + assert.Equal(suite.T(), "bolt.db", suite.ConfGorushDefault.Stat.BoltDB.Path) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Stat.BoltDB.Bucket) + + assert.Equal(suite.T(), "bunt.db", suite.ConfGorushDefault.Stat.BuntDB.Path) + assert.Equal(suite.T(), "level.db", suite.ConfGorushDefault.Stat.LevelDB.Path) + assert.Equal(suite.T(), "badger.db", suite.ConfGorushDefault.Stat.BadgerDB.Path) + + // gRPC + assert.Equal(suite.T(), false, suite.ConfGorushDefault.GRPC.Enabled) + assert.Equal(suite.T(), "9000", suite.ConfGorushDefault.GRPC.Port) +} + +func (suite *ConfigTestSuite) TestValidateConf() { + // Core + assert.Equal(suite.T(), "8088", suite.ConfGorush.Core.Port) + assert.Equal(suite.T(), int64(30), suite.ConfGorush.Core.ShutdownTimeout) + assert.Equal(suite.T(), true, suite.ConfGorush.Core.Enabled) + assert.Equal(suite.T(), int64(runtime.NumCPU()), suite.ConfGorush.Core.WorkerNum) + assert.Equal(suite.T(), int64(8192), suite.ConfGorush.Core.QueueNum) + assert.Equal(suite.T(), "release", suite.ConfGorush.Core.Mode) + assert.Equal(suite.T(), false, suite.ConfGorush.Core.Sync) + assert.Equal(suite.T(), "", suite.ConfGorush.Core.FeedbackURL) + assert.Equal(suite.T(), int64(10), suite.ConfGorush.Core.FeedbackTimeout) + assert.Equal(suite.T(), false, suite.ConfGorush.Core.SSL) + assert.Equal(suite.T(), "cert.pem", suite.ConfGorush.Core.CertPath) + assert.Equal(suite.T(), "key.pem", suite.ConfGorush.Core.KeyPath) + assert.Equal(suite.T(), "", suite.ConfGorush.Core.CertBase64) + assert.Equal(suite.T(), "", suite.ConfGorush.Core.KeyBase64) + assert.Equal(suite.T(), int64(100), suite.ConfGorush.Core.MaxNotification) + assert.Equal(suite.T(), "", suite.ConfGorush.Core.HTTPProxy) + // Pid + assert.Equal(suite.T(), false, suite.ConfGorush.Core.PID.Enabled) + assert.Equal(suite.T(), "gorush.pid", suite.ConfGorush.Core.PID.Path) + assert.Equal(suite.T(), true, suite.ConfGorush.Core.PID.Override) + assert.Equal(suite.T(), false, suite.ConfGorush.Core.AutoTLS.Enabled) + assert.Equal(suite.T(), ".cache", suite.ConfGorush.Core.AutoTLS.Folder) + assert.Equal(suite.T(), "", suite.ConfGorush.Core.AutoTLS.Host) + + // Api + assert.Equal(suite.T(), "/api/push", suite.ConfGorush.API.PushURI) + assert.Equal(suite.T(), "/api/stat/go", suite.ConfGorush.API.StatGoURI) + assert.Equal(suite.T(), "/api/stat/app", suite.ConfGorush.API.StatAppURI) + assert.Equal(suite.T(), "/api/config", suite.ConfGorush.API.ConfigURI) + assert.Equal(suite.T(), "/sys/stats", suite.ConfGorush.API.SysStatURI) + assert.Equal(suite.T(), "/metrics", suite.ConfGorush.API.MetricURI) + assert.Equal(suite.T(), "/healthz", suite.ConfGorush.API.HealthURI) + + // Android + assert.Equal(suite.T(), true, suite.ConfGorush.Android.Enabled) + assert.Equal(suite.T(), "YOUR_API_KEY", suite.ConfGorush.Android.APIKey) + assert.Equal(suite.T(), 0, suite.ConfGorush.Android.MaxRetry) + + // iOS + assert.Equal(suite.T(), false, suite.ConfGorush.Ios.Enabled) + assert.Equal(suite.T(), "key.pem", suite.ConfGorush.Ios.KeyPath) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.KeyBase64) + assert.Equal(suite.T(), "pem", suite.ConfGorush.Ios.KeyType) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.Password) + assert.Equal(suite.T(), false, suite.ConfGorush.Ios.Production) + assert.Equal(suite.T(), uint(100), suite.ConfGorush.Ios.MaxConcurrentPushes) + assert.Equal(suite.T(), 0, suite.ConfGorush.Ios.MaxRetry) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.KeyID) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.TeamID) + + // log + assert.Equal(suite.T(), "string", suite.ConfGorush.Log.Format) + assert.Equal(suite.T(), "stdout", suite.ConfGorush.Log.AccessLog) + assert.Equal(suite.T(), "debug", suite.ConfGorush.Log.AccessLevel) + assert.Equal(suite.T(), "stderr", suite.ConfGorush.Log.ErrorLog) + assert.Equal(suite.T(), "error", suite.ConfGorush.Log.ErrorLevel) + assert.Equal(suite.T(), true, suite.ConfGorush.Log.HideToken) + + assert.Equal(suite.T(), "memory", suite.ConfGorush.Stat.Engine) + assert.Equal(suite.T(), false, suite.ConfGorush.Stat.Redis.Cluster) + assert.Equal(suite.T(), "localhost:6379", suite.ConfGorush.Stat.Redis.Addr) + assert.Equal(suite.T(), "", suite.ConfGorush.Stat.Redis.Password) + assert.Equal(suite.T(), 0, suite.ConfGorush.Stat.Redis.DB) + + assert.Equal(suite.T(), "bolt.db", suite.ConfGorush.Stat.BoltDB.Path) + assert.Equal(suite.T(), "gorush", suite.ConfGorush.Stat.BoltDB.Bucket) + + assert.Equal(suite.T(), "bunt.db", suite.ConfGorush.Stat.BuntDB.Path) + assert.Equal(suite.T(), "level.db", suite.ConfGorush.Stat.LevelDB.Path) + assert.Equal(suite.T(), "badger.db", suite.ConfGorush.Stat.BadgerDB.Path) + + // gRPC + assert.Equal(suite.T(), false, suite.ConfGorush.GRPC.Enabled) + assert.Equal(suite.T(), "9000", suite.ConfGorush.GRPC.Port) +} + +func TestConfigTestSuite(t *testing.T) { + suite.Run(t, new(ConfigTestSuite)) +} + +func TestLoadConfigFromEnv(t *testing.T) { + os.Setenv("GORUSH_CORE_PORT", "9001") + os.Setenv("GORUSH_GRPC_ENABLED", "true") + os.Setenv("GORUSH_CORE_MAX_NOTIFICATION", "200") + os.Setenv("GORUSH_IOS_KEY_ID", "ABC123DEFG") + os.Setenv("GORUSH_IOS_TEAM_ID", "DEF123GHIJ") + os.Setenv("GORUSH_API_HEALTH_URI", "/healthz") + ConfGorush, err := LoadConf("testdata/config.yml") + if err != nil { + panic("failed to load config.yml from file") + } + assert.Equal(t, "9001", ConfGorush.Core.Port) + assert.Equal(t, int64(200), ConfGorush.Core.MaxNotification) + assert.True(t, ConfGorush.GRPC.Enabled) + assert.Equal(t, "ABC123DEFG", ConfGorush.Ios.KeyID) + assert.Equal(t, "DEF123GHIJ", ConfGorush.Ios.TeamID) + assert.Equal(t, "/healthz", ConfGorush.API.HealthURI) +} + +func TestLoadWrongDefaultYAMLConfig(t *testing.T) { + defaultConf = []byte(`a`) + _, err := LoadConf() + assert.Error(t, err) +} diff --git a/push/gorush-with-mipush/src/config/testdata/empty.yml b/push/gorush-with-mipush/src/config/testdata/empty.yml new file mode 100644 index 0000000..e69de29 diff --git a/push/gorush-with-mipush/src/contrib/init/debian/README.md b/push/gorush-with-mipush/src/contrib/init/debian/README.md new file mode 100644 index 0000000..39704c0 --- /dev/null +++ b/push/gorush-with-mipush/src/contrib/init/debian/README.md @@ -0,0 +1,37 @@ +# Run gorush in Debian/Ubuntu + +## Installation + +Put `gorush` binary into `/usr/bin` folder. + +```sh +cp gorush /usr/bin/ +chmod +x /usr/bin/gorush +``` + +put `gorush` init script into `/etc/rc.d` + +```sh +cp contrib/init/debian/gorush /etc.rc.d/ +``` + +install and remove System-V style init script links + +```sh +update-rc.d gorush start 20 2 3 4 5 . stop 80 0 1 6 . +``` + +## Start service + +create gorush configuration file. + +```sh +mkdir -p /etc/gorush +cp config/testdata/config.yml /etc/gorush/ +``` + +start gorush service. + +```sh +/etc/init.d/gorush start +``` diff --git a/push/gorush-with-mipush/src/contrib/init/debian/gorush b/push/gorush-with-mipush/src/contrib/init/debian/gorush new file mode 100644 index 0000000..55917b7 --- /dev/null +++ b/push/gorush-with-mipush/src/contrib/init/debian/gorush @@ -0,0 +1,87 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: gorush +# Required-Start: $syslog $network +# Required-Stop: $syslog $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the gorush web server +# Description: starts gorush using start-stop-daemon +### END INIT INFO + +# Original Author: Bo-Yi Wu (appleboy) + +# Do NOT "set -e" +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="the gorush web server" +NAME=gorush +DAEMON=$(which gorush) + +DAEMONUSER=www-data +PIDFILE=/var/run/$NAME.pid +CONFIGFILE=/etc/gorush/config.yml +DAEMONOPTS="-c $CONFIGFILE" + +USERBIND="setcap cap_net_bind_service=+ep" +STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}" + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Set the ulimits +ulimit -n 8192 + +do_start() +{ + $USERBIND $DAEMON + sh -c "USER=$DAEMONUSER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\ + --background --chuid $DAEMONUSER --exec $DAEMON -- $DAEMONOPTS" +} + +do_stop() +{ + start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PIDFILE --name $NAME --oknodo + rm -f $PIDFILE +} + +do_status() +{ + if [ -f $PIDFILE ]; then + if kill -0 $(cat "$PIDFILE"); then + echo "$NAME is running, PID is $(cat $PIDFILE)" + else + echo "$NAME process is dead, but pidfile exists" + fi + else + echo "$NAME is not running" + fi +} + +case "$1" in + start) + echo "Starting $DESC" "$NAME" + do_start + ;; + stop) + echo "Stopping $DESC" "$NAME" + do_stop + ;; + status) + do_status + ;; + restart) + echo "Restarting $DESC" "$NAME" + do_stop + do_start + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 + exit 2 + ;; +esac + +exit 0 diff --git a/push/gorush-with-mipush/src/core/core.go b/push/gorush-with-mipush/src/core/core.go new file mode 100644 index 0000000..e75d172 --- /dev/null +++ b/push/gorush-with-mipush/src/core/core.go @@ -0,0 +1,19 @@ +package core + +const ( + // PlatFormIos constant is 1 for iOS + PlatFormIos = iota + 1 + // PlatFormAndroid constant is 2 for Android + PlatFormAndroid + // PlatFormHuawei constant is 3 for Huawei + PlatFormHuawei + //PlaFormMI constant is 4 for MI + PlaFormMI +) + +const ( + // SucceededPush is log block + SucceededPush = "succeeded-push" + // FailedPush is log block + FailedPush = "failed-push" +) diff --git a/push/gorush-with-mipush/src/core/queue.go b/push/gorush-with-mipush/src/core/queue.go new file mode 100644 index 0000000..d2a9630 --- /dev/null +++ b/push/gorush-with-mipush/src/core/queue.go @@ -0,0 +1,20 @@ +package core + +// Queue as backend +type Queue string + +var ( + // LocalQueue for channel in Go + LocalQueue Queue = "local" + // NSQ a realtime distributed messaging platform + NSQ Queue = "nsq" + // NATS Connective Technology for Adaptive Edge & Distributed Systems + NATS Queue = "nats" + // Redis Pub/Sub + Redis Queue = "redis" +) + +// IsLocalQueue check is Local Queue +func IsLocalQueue(q Queue) bool { + return q == LocalQueue +} diff --git a/push/gorush-with-mipush/src/doc.go b/push/gorush-with-mipush/src/doc.go new file mode 100644 index 0000000..284f3b8 --- /dev/null +++ b/push/gorush-with-mipush/src/doc.go @@ -0,0 +1,55 @@ +// A push notification server using Gin framework written in Go (Golang). +// +// Details about the gorush project are found in github page: +// +// https://github.com/appleboy/gorush +// +// The pre-compiled binaries can be downloaded from release page. +// +// Send Android notification +// +// $ gorush -android -m="your message" -k="API Key" -t="Device token" +// +// Send iOS notification +// +// $ gorush -ios -m="your message" -i="API Key" -t="Device token" +// +// The default endpoint is APNs development. Please add -production flag for APNs production push endpoint. +// +// $ gorush -ios -m="your message" -i="API Key" -t="Device token" -production +// +// Run gorush web server +// +// $ gorush -c config.yml +// +// Get go status of api server using httpie tool: +// +// $ http -v --verify=no --json GET https://localhost:8088/api/stat/go +// +// Simple send iOS notification example, the platform value is 1: +// +// { +// "notifications": [ +// { +// "tokens": ["token_a", "token_b"], +// "platform": 1, +// "message": "Hello World iOS!" +// } +// ] +// } +// +// Simple send Android notification example, the platform value is 2: +// +// { +// "notifications": [ +// { +// "tokens": ["token_a", "token_b"], +// "platform": 2, +// "message": "Hello World Android!" +// } +// ] +// } +// +// For more details, see the documentation and example. +// +package main diff --git a/push/gorush-with-mipush/src/docker-compose.yml b/push/gorush-with-mipush/src/docker-compose.yml new file mode 100644 index 0000000..c11303a --- /dev/null +++ b/push/gorush-with-mipush/src/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + gorush: + image: appleboy/gorush + restart: always + ports: + - "8088:8088" + - "9000:9000" + logging: + options: + max-size: "100k" + max-file: "3" + environment: + - GORUSH_CORE_QUEUE_NUM=512 diff --git a/push/gorush-with-mipush/src/docker/Dockerfile.linux.amd64 b/push/gorush-with-mipush/src/docker/Dockerfile.linux.amd64 new file mode 100644 index 0000000..784ea73 --- /dev/null +++ b/push/gorush-with-mipush/src/docker/Dockerfile.linux.amd64 @@ -0,0 +1,14 @@ +FROM plugins/base:linux-amd64 + +LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \ + org.label-schema.name="Gorush" \ + org.label-schema.vendor="Bo-Yi Wu" \ + org.label-schema.schema-version="1.0" + +COPY release/linux/amd64/gorush /bin/ + +EXPOSE 8088 9000 +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ + CMD ["/bin/gorush", "--ping"] + +ENTRYPOINT ["/bin/gorush"] diff --git a/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm b/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm new file mode 100644 index 0000000..7e06c24 --- /dev/null +++ b/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm @@ -0,0 +1,14 @@ +FROM plugins/base:linux-arm + +LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \ + org.label-schema.name="Gorush" \ + org.label-schema.vendor="Bo-Yi Wu" \ + org.label-schema.schema-version="1.0" + +COPY release/linux/arm/gorush /bin/ + +EXPOSE 8088 9000 +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ + CMD ["/bin/gorush", "--ping"] + +ENTRYPOINT ["/bin/gorush"] diff --git a/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm64 b/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm64 new file mode 100644 index 0000000..cc42062 --- /dev/null +++ b/push/gorush-with-mipush/src/docker/Dockerfile.linux.arm64 @@ -0,0 +1,14 @@ +FROM plugins/base:linux-arm64 + +LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \ + org.label-schema.name="Gorush" \ + org.label-schema.vendor="Bo-Yi Wu" \ + org.label-schema.schema-version="1.0" + +COPY release/linux/arm64/gorush /bin/ + +EXPOSE 8088 9000 +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ + CMD ["/bin/gorush", "--ping"] + +ENTRYPOINT ["/bin/gorush"] diff --git a/push/gorush-with-mipush/src/docker/Dockerfile.windows.amd64 b/push/gorush-with-mipush/src/docker/Dockerfile.windows.amd64 new file mode 100644 index 0000000..db2faa2 --- /dev/null +++ b/push/gorush-with-mipush/src/docker/Dockerfile.windows.amd64 @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/windows/nanoserver:1809-amd64 + +LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \ + org.label-schema.name="Gorush" \ + org.label-schema.vendor="Bo-Yi Wu" \ + org.label-schema.schema-version="1.0" + +COPY release/gorush.exe C:/bin/gorush.exe + +EXPOSE 8088 9000 +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ + CMD ["\\gorush.exe", "--ping"] + +ENTRYPOINT [ "C:\\bin\\gorush.exe" ] diff --git a/push/gorush-with-mipush/src/docker/manifest.tmpl b/push/gorush-with-mipush/src/docker/manifest.tmpl new file mode 100644 index 0000000..5dfd533 --- /dev/null +++ b/push/gorush-with-mipush/src/docker/manifest.tmpl @@ -0,0 +1,25 @@ +image: appleboy/gorush:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - + image: appleboy/gorush:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 + platform: + architecture: amd64 + os: linux + - + image: appleboy/gorush:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 + platform: + architecture: arm64 + os: linux + variant: v8 + - + image: appleboy/gorush:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm + platform: + architecture: arm + os: linux + variant: v7 diff --git a/push/gorush-with-mipush/src/go.mod b/push/gorush-with-mipush/src/go.mod new file mode 100644 index 0000000..1e95ddd --- /dev/null +++ b/push/gorush-with-mipush/src/go.mod @@ -0,0 +1,46 @@ +module github.com/appleboy/gorush + +go 1.15 + +require ( + github.com/apex/gateway v1.1.2 + github.com/appleboy/gin-status-api v1.1.0 + github.com/appleboy/go-fcm v0.1.5 + github.com/appleboy/gofight/v2 v2.1.2 + github.com/appleboy/graceful v0.0.3 + github.com/asdine/storm/v3 v3.2.1 + github.com/buger/jsonparser v1.1.1 + github.com/dgraph-io/badger/v3 v3.2103.1 + github.com/geek-go/xmpush v0.0.0-20200624150426-da36515dfc49 // indirect + github.com/gin-contrib/logger v0.2.0 + github.com/gin-gonic/gin v1.7.4 + github.com/go-redis/redis/v8 v8.11.3 + github.com/golang-queue/nats v0.0.4 + github.com/golang-queue/nsq v0.0.6 + github.com/golang-queue/queue v0.0.10 + github.com/golang-queue/redisdb v0.0.5 + github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect + github.com/golang/protobuf v1.5.2 + github.com/google/flatbuffers v2.0.0+incompatible // indirect + github.com/json-iterator/go v1.1.10 + github.com/mattn/go-isatty v0.0.12 + github.com/mitchellh/mapstructure v1.4.1 + github.com/msalihkarakasli/go-hms-push v0.0.0-20210731212030-00e7b986815b + github.com/prometheus/client_golang v1.10.0 + github.com/rs/zerolog v1.23.0 + github.com/sideshow/apns2 v0.20.0 + github.com/sirupsen/logrus v1.8.1 + github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.7.0 + github.com/syndtr/goleveldb v1.0.0 + github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678 + github.com/tidwall/buntdb v1.2.0 + github.com/tidwall/gjson v1.12.1 // indirect + golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + google.golang.org/grpc v1.36.1 + google.golang.org/protobuf v1.27.1 +) + +replace github.com/msalihkarakasli/go-hms-push => github.com/spawn2kill/go-hms-push v0.0.0-20211125124117-e20af53b1304 diff --git a/push/gorush-with-mipush/src/go.sum b/push/gorush-with-mipush/src/go.sum new file mode 100644 index 0000000..fadd6e8 --- /dev/null +++ b/push/gorush-with-mipush/src/go.sum @@ -0,0 +1,807 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= +github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/gateway v1.1.2 h1:OWyLov8eaau8YhkYKkRuOAYqiUhpBJalBR1o+3FzX+8= +github.com/apex/gateway v1.1.2/go.mod h1:AMTkVbz5u5Hvd6QOGhhg0JUrNgCcLVu3XNJOGntdoB4= +github.com/appleboy/gin-status-api v1.1.0 h1:zoXePlNxk/Aa3Jmh8TI2xX0KTF8iET/QwOM065pcrok= +github.com/appleboy/gin-status-api v1.1.0/go.mod h1:qUmpFERWhlzRX4Hx+fEznIio8gXAXEDpEnb0Ald1d+g= +github.com/appleboy/go-fcm v0.1.5 h1:fKbcZf/7vwGsvDkcop8a+kCHnK+tt4wXX0X7uEzwI6E= +github.com/appleboy/go-fcm v0.1.5/go.mod h1:MSxZ4LqGRsnywOjnlXJXMqbjZrG4vf+0oHitfC9HRH0= +github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= +github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= +github.com/appleboy/graceful v0.0.3 h1:qEnm61ofF76fTo2UISfjRZVhMhFnkLKTBJoWcK9lBXQ= +github.com/appleboy/graceful v0.0.3/go.mod h1:Q2mVx0t+N0lCDZc5MJudbcpTm6cgGM/J2gZCZIqD9dc= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac= +github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-lambda-go v1.17.0 h1:Ogihmi8BnpmCNktKAGpNwSiILNNING1MiosnKUfU8m0= +github.com/aws/aws-lambda-go v1.17.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +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/dgraph-io/badger/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk= +github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fukata/golang-stats-api-handler v1.0.0 h1:N6M25vhs1yAvwGBpFY6oBmMOZeJdcWnvA+wej8pKeko= +github.com/fukata/golang-stats-api-handler v1.0.0/go.mod h1:1sIi4/rHq6s/ednWMZqTmRq3765qTUSs/c3xF6lj8J8= +github.com/geek-go/xmpush v0.0.0-20200624150426-da36515dfc49 h1:naokiojfByv5V87OEWLYGeqDk0HNSn9jJ5fK0M/g6kw= +github.com/geek-go/xmpush v0.0.0-20200624150426-da36515dfc49/go.mod h1:tS5MqiTbVThcXwqKqn2KQIphJZ7xlWEkWy55SG3Ey0I= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/logger v0.2.0 h1:YkdOGKdm/Nnrrd3bjBjcjd3ow1kR2KUfxxP4/rlL23E= +github.com/gin-contrib/logger v0.2.0/go.mod h1:dYxbt3GB+rvPyJSvox5lLsnKYwh8PjWrC9TQtR+hpUw= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= +github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= +github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-queue/nats v0.0.4 h1:in1fM5Aa5HYeuyXCC5A00zvWxvVvBs9gdCiIr429u7I= +github.com/golang-queue/nats v0.0.4/go.mod h1:P82IIiPlNT+hGUvfddwJCn+yXd8tPeKtS9UK4AU5+I4= +github.com/golang-queue/nsq v0.0.6 h1:GXk9Dx9ex3/rQDSaK78RK2B0CBNc0ym45hclEjNDNEk= +github.com/golang-queue/nsq v0.0.6/go.mod h1:oKhZjEiAZ4scaQTePCSSnsmvyHb6ID0AsqE5rtKrAOE= +github.com/golang-queue/queue v0.0.10 h1:cGqMgHMf2eamwdd3hmOzGcSQogGu9tMhhVYPQMrMC1g= +github.com/golang-queue/queue v0.0.10/go.mod h1:ku8iyjYffqYY6Duts+xl+QYfN3/KDK4MEvXMZUkHyio= +github.com/golang-queue/redisdb v0.0.5 h1:kW+zXopFVtBmd0/19aD3fZCWc1OoRbOV+MpXo2OBp+s= +github.com/golang-queue/redisdb v0.0.5/go.mod h1:3LzXV4ldTCNKT0LsZXiKEhbrOM5gGISLQjYuPip+geM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats-server/v2 v2.3.4 h1:WcNa6HDFX8gjZPHb8CJ9wxRHEjJSlhWUb/MKb6/mlUY= +github.com/nats-io/nats-server/v2 v2.3.4/go.mod h1:3mtbaN5GkCo/Z5T3nNj0I0/W1fPkKzLiDC6jjWJKp98= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 h1:9GqilBhZaR3xYis0JgMlJjNw933WIobdjKhilXm+Vls= +github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nsqio/go-nsq v1.0.8 h1:3L2F8tNLlwXXlp2slDUrUWSBn2O3nMh8R1/KEDFTHPk= +github.com/nsqio/go-nsq v1.0.8/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= +github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sideshow/apns2 v0.20.0 h1:5Lzk4DUq+waVc6/BkKzpDTpQjtk/BZOP0YsayBpY1NE= +github.com/sideshow/apns2 v0.20.0/go.mod h1:f7dArLPLbiZ3qPdzzrZXdCSlMp8FD0p6z7tHssDOLvk= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spawn2kill/go-hms-push v0.0.0-20211125124117-e20af53b1304 h1:NFx3I+/cQkqXlnrDzyOAXUDKWFjPQBcO0LSqowMn1Jo= +github.com/spawn2kill/go-hms-push v0.0.0-20211125124117-e20af53b1304/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678 h1:kFej3rMKjbzysHYvLmv5iOlbRymDMkNJxbovYb/iP0c= +github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678/go.mod h1:GkZsNBOco11YY68OnXUARbSl26IOXXAeYf6ZKmSZR2M= +github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU= +github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU= +github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58= +github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA= +github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= +github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/push/gorush-with-mipush/src/helm/gorush/.helmignore b/push/gorush-with-mipush/src/helm/gorush/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/push/gorush-with-mipush/src/helm/gorush/Chart.yaml b/push/gorush-with-mipush/src/helm/gorush/Chart.yaml new file mode 100644 index 0000000..ea3df48 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: gorush +description: A push notification micro server using Gin framework written in Go (Golang) +type: application +version: 0.1.0 +appVersion: "1.14.0" +dependencies: + - name: redis + version: ~14.1 + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/NOTES.txt b/push/gorush-with-mipush/src/helm/gorush/templates/NOTES.txt new file mode 100644 index 0000000..2d942df --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "gorush.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "gorush.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "gorush.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "gorush.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/_helpers.tpl b/push/gorush-with-mipush/src/helm/gorush/templates/_helpers.tpl new file mode 100644 index 0000000..f46bea8 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gorush.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "gorush.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "gorush.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "gorush.labels" -}} +helm.sh/chart: {{ include "gorush.chart" . }} +{{ include "gorush.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gorush.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gorush.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "gorush.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "gorush.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/configmap.yml b/push/gorush-with-mipush/src/helm/gorush/templates/configmap.yml new file mode 100644 index 0000000..ba96176 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/configmap.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} +data: + # stat + stats: + engine: {{ .Values.stat.engine }} + {{- if .Values.redis.enabled }} + redis: + host: {{ .Values.redis.host }}:{{ .Values.redis.port }} + {{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/deployment.yaml b/push/gorush-with-mipush/src/helm/gorush/templates/deployment.yaml new file mode 100644 index 0000000..3d98490 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "gorush.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gorush.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gorush.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + env: + - name: GORUSH_STAT_ENGINE + valueFrom: + configMapKeyRef: + name: {{ .Chart.Name }}-config + key: stat.engine + {{- if .Values.redis.enabled }} + - name: GORUSH_STAT_REDIS_ADDR + valueFrom: + configMapKeyRef: + name: {{ .Chart.Name }}-config + key: stat.redis.host + {{- end }} + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/hpa.yaml b/push/gorush-with-mipush/src/helm/gorush/templates/hpa.yaml new file mode 100644 index 0000000..b8d1df7 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Chart.Name }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/ingress.yaml b/push/gorush-with-mipush/src/helm/gorush/templates/ingress.yaml new file mode 100644 index 0000000..6235863 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.ingress.enabled -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: Prefix + backend: + service: + name: gorush + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/service.yaml b/push/gorush-with-mipush/src/helm/gorush/templates/service.yaml new file mode 100644 index 0000000..d3846ce --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gorush.selectorLabels" . | nindent 4 }} diff --git a/push/gorush-with-mipush/src/helm/gorush/templates/serviceaccount.yaml b/push/gorush-with-mipush/src/helm/gorush/templates/serviceaccount.yaml new file mode 100644 index 0000000..67a5df5 --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gorush.serviceAccountName" . }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/push/gorush-with-mipush/src/helm/gorush/values.yaml b/push/gorush-with-mipush/src/helm/gorush/values.yaml new file mode 100644 index 0000000..6fc42db --- /dev/null +++ b/push/gorush-with-mipush/src/helm/gorush/values.yaml @@ -0,0 +1,73 @@ +# Default values for gorush. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: appleboy/gorush + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: false + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8088 + +stats: + engine: memory + +redis: + enabled: false + host: redis + port: 6379 + +ingress: + enabled: false + annotations: {} + hosts: + - host: gorush.example.com + paths: + - path: / + tls: [] + +resources: {} + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/push/gorush-with-mipush/src/k8s/gorush-aws-alb-ingress.yaml b/push/gorush-with-mipush/src/k8s/gorush-aws-alb-ingress.yaml new file mode 100644 index 0000000..9603c0a --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-aws-alb-ingress.yaml @@ -0,0 +1,20 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: gorush + namespace: gorush + annotations: + # Kubernetes Ingress Controller for AWS ALB + # https://github.com/coreos/alb-ingress-controller + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/subnets: subnet-aa3dfbe3,subnet-4aff342d + alb.ingress.kubernetes.io/security-groups: sg-71069b17 +spec: + rules: + - host: gorush.example.com + http: + paths: + - path: / + backend: + serviceName: gorush + servicePort: 8088 diff --git a/push/gorush-with-mipush/src/k8s/gorush-configmap.yaml b/push/gorush-with-mipush/src/k8s/gorush-configmap.yaml new file mode 100644 index 0000000..f5dc63a --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: gorush-config + namespace: gorush +data: + # stat + stat.engine: redis + stat.redis.host: redis:6379 diff --git a/push/gorush-with-mipush/src/k8s/gorush-deployment.yaml b/push/gorush-with-mipush/src/k8s/gorush-deployment.yaml new file mode 100644 index 0000000..963ed43 --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gorush + namespace: gorush +spec: + replicas: 3 + selector: + matchLabels: + app: gorush + tier: frontend + template: + metadata: + labels: + app: gorush + tier: frontend + spec: + containers: + - image: appleboy/gorush + name: gorush + imagePullPolicy: Always + ports: + - containerPort: 8088 + livenessProbe: + httpGet: + path: /healthz + port: 8088 + initialDelaySeconds: 3 + periodSeconds: 3 + env: + - name: GORUSH_STAT_ENGINE + valueFrom: + configMapKeyRef: + name: gorush-config + key: stat.engine + - name: GORUSH_STAT_REDIS_ADDR + valueFrom: + configMapKeyRef: + name: gorush-config + key: stat.redis.host diff --git a/push/gorush-with-mipush/src/k8s/gorush-namespace.yaml b/push/gorush-with-mipush/src/k8s/gorush-namespace.yaml new file mode 100644 index 0000000..9bac3da --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: gorush diff --git a/push/gorush-with-mipush/src/k8s/gorush-redis-deployment.yaml b/push/gorush-with-mipush/src/k8s/gorush-redis-deployment.yaml new file mode 100644 index 0000000..4ffa842 --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-redis-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: gorush +spec: + selector: + matchLabels: + app: redis + role: master + tier: backend + replicas: 1 + template: + metadata: + labels: + app: redis + role: master + tier: backend + spec: + containers: + - name: master + image: redis + ports: + - containerPort: 6379 diff --git a/push/gorush-with-mipush/src/k8s/gorush-redis-service.yaml b/push/gorush-with-mipush/src/k8s/gorush-redis-service.yaml new file mode 100644 index 0000000..5dd361a --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-redis-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: gorush + labels: + app: redis + role: master + tier: backend +spec: + ports: + - port: 6379 + targetPort: 6379 + selector: + app: redis + role: master + tier: backend diff --git a/push/gorush-with-mipush/src/k8s/gorush-service.yaml b/push/gorush-with-mipush/src/k8s/gorush-service.yaml new file mode 100644 index 0000000..8a68b16 --- /dev/null +++ b/push/gorush-with-mipush/src/k8s/gorush-service.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: gorush + namespace: gorush + labels: + app: gorush + tier: frontend +spec: + selector: + app: gorush + tier: frontend + # if your cluster supports it, uncomment the following to automatically create + # an external load-balanced IP for the frontend service. + # type: LoadBalancer + # + # if you want to expose the service to the outside (without a load balancer in front) + # type: NodePort + # + # if you want gorush to be accessible only within the cluster + # type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 8088 diff --git a/push/gorush-with-mipush/src/logx/log.go b/push/gorush-with-mipush/src/logx/log.go new file mode 100644 index 0000000..b7b78b6 --- /dev/null +++ b/push/gorush-with-mipush/src/logx/log.go @@ -0,0 +1,251 @@ +package logx + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/appleboy/gorush/core" + + "github.com/mattn/go-isatty" + "github.com/sirupsen/logrus" +) + +var ( + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + reset = string([]byte{27, 91, 48, 109}) +) + +// LogPushEntry is push response log +type LogPushEntry struct { + ID string `json:"notif_id,omitempty"` + Type string `json:"type"` + Platform string `json:"platform"` + Token string `json:"token"` + Message string `json:"message"` + Error string `json:"error"` +} + +var isTerm bool + +func init() { + isTerm = isatty.IsTerminal(os.Stdout.Fd()) +} + +var ( + // LogAccess is log server request log + LogAccess = logrus.New() + // LogError is log server error log + LogError = logrus.New() +) + +// InitLog use for initial log module +func InitLog(accessLevel, accessLog, errorLevel, errorLog string) error { + var err error + + if !isTerm { + LogAccess.SetFormatter(&logrus.JSONFormatter{}) + LogError.SetFormatter(&logrus.JSONFormatter{}) + } else { + LogAccess.Formatter = &logrus.TextFormatter{ + TimestampFormat: "2006/01/02 - 15:04:05", + FullTimestamp: true, + } + + LogError.Formatter = &logrus.TextFormatter{ + TimestampFormat: "2006/01/02 - 15:04:05", + FullTimestamp: true, + } + } + + // set logger + if err = SetLogLevel(LogAccess, accessLevel); err != nil { + return errors.New("Set access log level error: " + err.Error()) + } + + if err = SetLogLevel(LogError, errorLevel); err != nil { + return errors.New("Set error log level error: " + err.Error()) + } + + if err = SetLogOut(LogAccess, accessLog); err != nil { + return errors.New("Set access log path error: " + err.Error()) + } + + if err = SetLogOut(LogError, errorLog); err != nil { + return errors.New("Set error log path error: " + err.Error()) + } + + return nil +} + +// SetLogOut provide log stdout and stderr output +func SetLogOut(log *logrus.Logger, outString string) error { + switch outString { + case "stdout": + log.Out = os.Stdout + case "stderr": + log.Out = os.Stderr + default: + f, err := os.OpenFile(outString, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600) + if err != nil { + return err + } + + log.Out = f + } + + return nil +} + +// SetLogLevel is define log level what you want +// log level: panic, fatal, error, warn, info and debug +func SetLogLevel(log *logrus.Logger, levelString string) error { + level, err := logrus.ParseLevel(levelString) + if err != nil { + return err + } + + log.Level = level + + return nil +} + +func colorForPlatForm(platform int) string { + switch platform { + case core.PlatFormIos: + return blue + case core.PlatFormAndroid: + return yellow + case core.PlatFormHuawei: + return green + default: + return reset + } +} + +func typeForPlatForm(platform int) string { + switch platform { + case core.PlatFormIos: + return "ios" + case core.PlatFormAndroid: + return "android" + case core.PlatFormHuawei: + return "huawei" + default: + return "" + } +} + +func hideToken(token string, markLen int) string { + if token == "" { + return "" + } + + if len(token) < markLen*2 { + return strings.Repeat("*", len(token)) + } + + start := token[len(token)-markLen:] + end := token[0:markLen] + + result := strings.Replace(token, start, strings.Repeat("*", markLen), -1) + result = strings.Replace(result, end, strings.Repeat("*", markLen), -1) + + return result +} + +// GetLogPushEntry get push data into log structure +func GetLogPushEntry(input *InputLog) LogPushEntry { + var errMsg string + + plat := typeForPlatForm(input.Platform) + + if input.Error != nil { + errMsg = input.Error.Error() + } + + token := input.Token + if input.HideToken { + token = hideToken(input.Token, 10) + } + + return LogPushEntry{ + ID: input.ID, + Type: input.Status, + Platform: plat, + Token: token, + Message: input.Message, + Error: errMsg, + } +} + +// InputLog log request +type InputLog struct { + ID string + Status string + Token string + Message string + Platform int + Error error + HideToken bool + Format string +} + +// LogPush record user push request and server response. +func LogPush(input *InputLog) LogPushEntry { + var platColor, resetColor, output string + + if isTerm { + platColor = colorForPlatForm(input.Platform) + resetColor = reset + } + + log := GetLogPushEntry(input) + + if input.Format == "json" { + logJSON, _ := json.Marshal(log) + + output = string(logJSON) + } else { + var typeColor string + switch input.Status { + case core.SucceededPush: + if isTerm { + typeColor = green + } + + output = fmt.Sprintf("|%s %s %s| %s%s%s [%s] %s", + typeColor, log.Type, resetColor, + platColor, log.Platform, resetColor, + log.Token, + log.Message, + ) + case core.FailedPush: + if isTerm { + typeColor = red + } + + output = fmt.Sprintf("|%s %s %s| %s%s%s [%s] | %s | Error Message: %s", + typeColor, log.Type, resetColor, + platColor, log.Platform, resetColor, + log.Token, + log.Message, + log.Error, + ) + } + } + + switch input.Status { + case core.SucceededPush: + LogAccess.Info(output) + case core.FailedPush: + LogError.Error(output) + } + + return log +} diff --git a/push/gorush-with-mipush/src/logx/log/.gitkeep b/push/gorush-with-mipush/src/logx/log/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/push/gorush-with-mipush/src/logx/log/access.log b/push/gorush-with-mipush/src/logx/log/access.log new file mode 100644 index 0000000..e69de29 diff --git a/push/gorush-with-mipush/src/logx/log_interface.go b/push/gorush-with-mipush/src/logx/log_interface.go new file mode 100644 index 0000000..51a564c --- /dev/null +++ b/push/gorush-with-mipush/src/logx/log_interface.go @@ -0,0 +1,45 @@ +package logx + +import ( + "fmt" + + "github.com/sirupsen/logrus" +) + +// QueueLogger for simple logger. +func QueueLogger() DefaultQueueLogger { + return DefaultQueueLogger{ + accessLogger: LogAccess, + errorLogger: LogError, + } +} + +// DefaultQueueLogger for queue custom logger +type DefaultQueueLogger struct { + accessLogger *logrus.Logger + errorLogger *logrus.Logger +} + +func (l DefaultQueueLogger) Infof(format string, args ...interface{}) { + l.accessLogger.Printf(format, args...) +} + +func (l DefaultQueueLogger) Errorf(format string, args ...interface{}) { + l.errorLogger.Printf(format, args...) +} + +func (l DefaultQueueLogger) Fatalf(format string, args ...interface{}) { + l.errorLogger.Fatalf(format, args...) +} + +func (l DefaultQueueLogger) Info(args ...interface{}) { + l.accessLogger.Println(fmt.Sprint(args...)) +} + +func (l DefaultQueueLogger) Error(args ...interface{}) { + l.errorLogger.Println(fmt.Sprint(args...)) +} + +func (l DefaultQueueLogger) Fatal(args ...interface{}) { + l.errorLogger.Println(fmt.Sprint(args...)) +} diff --git a/push/gorush-with-mipush/src/logx/log_test.go b/push/gorush-with-mipush/src/logx/log_test.go new file mode 100644 index 0000000..de6d275 --- /dev/null +++ b/push/gorush-with-mipush/src/logx/log_test.go @@ -0,0 +1,172 @@ +package logx + +import ( + "errors" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestSetLogLevel(t *testing.T) { + log := logrus.New() + + err := SetLogLevel(log, "debug") + assert.Nil(t, err) + + err = SetLogLevel(log, "invalid") + assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) +} + +func TestSetLogOut(t *testing.T) { + log := logrus.New() + + err := SetLogOut(log, "stdout") + assert.Nil(t, err) + + err = SetLogOut(log, "stderr") + assert.Nil(t, err) + + err = SetLogOut(log, "log/access.log") + assert.Nil(t, err) + + // missing create logs folder. + err = SetLogOut(log, "logs/access.log") + assert.NotNil(t, err) +} + +func TestInitDefaultLog(t *testing.T) { + cfg, _ := config.LoadConf() + + // no errors on default config + assert.Nil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) + + cfg.Log.AccessLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) + + isTerm = true + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestAccessLevel(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.AccessLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestErrorLevel(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.ErrorLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestAccessLogPath(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.AccessLog = "logs/access.log" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestErrorLogPath(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.ErrorLog = "logs/error.log" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestPlatFormType(t *testing.T) { + assert.Equal(t, "ios", typeForPlatForm(core.PlatFormIos)) + assert.Equal(t, "android", typeForPlatForm(core.PlatFormAndroid)) + assert.Equal(t, "huawei", typeForPlatForm(core.PlatFormHuawei)) + assert.Equal(t, "", typeForPlatForm(10000)) +} + +func TestPlatFormColor(t *testing.T) { + assert.Equal(t, blue, colorForPlatForm(core.PlatFormIos)) + assert.Equal(t, yellow, colorForPlatForm(core.PlatFormAndroid)) + assert.Equal(t, green, colorForPlatForm(core.PlatFormHuawei)) + assert.Equal(t, reset, colorForPlatForm(1000000)) +} + +func TestHideToken(t *testing.T) { + assert.Equal(t, "", hideToken("", 2)) + assert.Equal(t, "**345678**", hideToken("1234567890", 2)) + assert.Equal(t, "*****", hideToken("12345", 10)) +} + +func TestLogPushEntry(t *testing.T) { + in := InputLog{} + + in.Platform = 1 + assert.Equal(t, "ios", GetLogPushEntry(&in).Platform) + + in.Error = errors.New("error") + assert.Equal(t, "error", GetLogPushEntry(&in).Error) + + in.Token = "1234567890" + in.HideToken = true + assert.Equal(t, "**********", GetLogPushEntry(&in).Token) +} + +func TestLogPush(t *testing.T) { + in := InputLog{} + isTerm = true + + in.Format = "json" + in.Status = "succeeded-push" + assert.Equal(t, "succeeded-push", LogPush(&in).Type) + + in.Format = "" + in.Message = "success" + assert.Equal(t, "success", LogPush(&in).Message) + + in.Status = "failed-push" + in.Message = "failed" + assert.Equal(t, "failed", LogPush(&in).Message) +} diff --git a/push/gorush-with-mipush/src/main.go b/push/gorush-with-mipush/src/main.go new file mode 100644 index 0000000..84f380f --- /dev/null +++ b/push/gorush-with-mipush/src/main.go @@ -0,0 +1,502 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/router" + "github.com/appleboy/gorush/rpc" + "github.com/appleboy/gorush/status" + + "github.com/appleboy/graceful" + "github.com/golang-queue/nats" + "github.com/golang-queue/nsq" + "github.com/golang-queue/queue" + "github.com/golang-queue/redisdb" +) + +func main() { + opts := config.ConfYaml{} + + var ( + ping bool + showVersion bool + configFile string + topic string + message string + token string + title string + ) + + flag.BoolVar(&showVersion, "version", false, "Print version information.") + flag.BoolVar(&showVersion, "V", false, "Print version information.") + flag.StringVar(&configFile, "c", "", "Configuration file path.") + flag.StringVar(&configFile, "config", "", "Configuration file path.") + flag.StringVar(&opts.Core.PID.Path, "pid", "", "PID file path.") + flag.StringVar(&opts.Ios.KeyPath, "i", "", "iOS certificate key file path") + flag.StringVar(&opts.Ios.KeyPath, "key", "", "iOS certificate key file path") + flag.StringVar(&opts.Ios.KeyID, "key-id", "", "iOS Key ID for P8 token") + flag.StringVar(&opts.Ios.TeamID, "team-id", "", "iOS Team ID for P8 token") + flag.StringVar(&opts.Ios.Password, "P", "", "iOS certificate password for gorush") + flag.StringVar(&opts.Ios.Password, "password", "", "iOS certificate password for gorush") + flag.StringVar(&opts.Android.APIKey, "k", "", "Android api key configuration for gorush") + flag.StringVar(&opts.Android.APIKey, "apikey", "", "Android api key configuration for gorush") + + flag.StringVar(&opts.Huawei.AppSecret, "hk", "", "Huawei api key configuration for gorush") + flag.StringVar(&opts.Huawei.AppSecret, "hmskey", "", "Huawei api key configuration for gorush") + flag.StringVar(&opts.Huawei.AppID, "hid", "", "HMS app id configuration for gorush") + flag.StringVar(&opts.Huawei.AppID, "hmsid", "", "HMS app id configuration for gorush") + + flag.StringVar(&opts.MI.AppSecret, "mk", "", "MIPUSH api key configuration for gorush") + flag.StringVar(&opts.MI.AppSecret, "mikey", "", "MIPUSH api key configuration for gorush") + flag.StringVar(&opts.MI.Package, "mp", "", "MIPUSH package id configuration for gorush") + flag.StringVar(&opts.MI.Package, "mipkg", "", "MIPUSH package id configuration for gorush") + + flag.StringVar(&opts.Core.Address, "A", "", "address to bind") + flag.StringVar(&opts.Core.Address, "address", "", "address to bind") + flag.StringVar(&opts.Core.Port, "p", "", "port number for gorush") + flag.StringVar(&opts.Core.Port, "port", "", "port number for gorush") + flag.StringVar(&token, "t", "", "token string") + flag.StringVar(&token, "token", "", "token string") + flag.StringVar(&opts.Stat.Engine, "e", "", "store engine") + flag.StringVar(&opts.Stat.Engine, "engine", "", "store engine") + flag.StringVar(&opts.Stat.Redis.Addr, "redis-addr", "", "redis addr") + flag.StringVar(&message, "m", "", "notification message") + flag.StringVar(&message, "message", "", "notification message") + flag.StringVar(&title, "title", "", "notification title") + flag.BoolVar(&opts.Android.Enabled, "android", false, "send android notification") + flag.BoolVar(&opts.Huawei.Enabled, "huawei", false, "send huawei notification") + flag.BoolVar(&opts.Ios.Enabled, "ios", false, "send ios notification") + flag.BoolVar(&opts.MI.Enabled, "mi", false, "send mi notification") + flag.BoolVar(&opts.Ios.Production, "production", false, "production mode in iOS") + flag.StringVar(&topic, "topic", "", "apns topic in iOS") + flag.StringVar(&opts.Core.HTTPProxy, "proxy", "", "http proxy url") + flag.BoolVar(&ping, "ping", false, "ping server") + + flag.Usage = usage + flag.Parse() + + router.SetVersion(Version) + + // Show version and exit + if showVersion { + router.PrintGoRushVersion() + os.Exit(0) + } + + // set default parameters. + cfg, err := config.LoadConf(configFile) + if err != nil { + log.Printf("Load yaml config file error: '%v'", err) + + return + } + + // Initialize push slots for concurrent iOS pushes + notify.MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) + + if opts.Ios.KeyPath != "" { + cfg.Ios.KeyPath = opts.Ios.KeyPath + } + + if opts.Ios.KeyID != "" { + cfg.Ios.KeyID = opts.Ios.KeyID + } + + if opts.Ios.TeamID != "" { + cfg.Ios.TeamID = opts.Ios.TeamID + } + + if opts.Ios.Password != "" { + cfg.Ios.Password = opts.Ios.Password + } + + if opts.Android.APIKey != "" { + cfg.Android.APIKey = opts.Android.APIKey + } + + if opts.Huawei.AppSecret != "" { + cfg.Huawei.AppSecret = opts.Huawei.AppSecret + } + + if opts.Huawei.AppID != "" { + cfg.Huawei.AppID = opts.Huawei.AppID + } + + if opts.Stat.Engine != "" { + cfg.Stat.Engine = opts.Stat.Engine + } + + if opts.Stat.Redis.Addr != "" { + cfg.Stat.Redis.Addr = opts.Stat.Redis.Addr + } + + // overwrite server port and address + if opts.Core.Port != "" { + cfg.Core.Port = opts.Core.Port + } + if opts.Core.Address != "" { + cfg.Core.Address = opts.Core.Address + } + + if err = logx.InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + ); err != nil { + log.Fatalf("can't load log module, error: %v", err) + } + + if opts.Core.HTTPProxy != "" { + cfg.Core.HTTPProxy = opts.Core.HTTPProxy + } + + if cfg.Core.HTTPProxy != "" { + err = notify.SetProxy(cfg.Core.HTTPProxy) + + if err != nil { + logx.LogError.Fatalf("Set Proxy error: %v", err) + } + } + + if ping { + if err := pinger(cfg); err != nil { + logx.LogError.Warnf("ping server error: %v", err) + } + return + } + + // send android notification + if opts.Android.Enabled { + cfg.Android.Enabled = opts.Android.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormAndroid, + Message: message, + Title: title, + } + + // send message to single device + if token != "" { + req.Tokens = []string{token} + } + + // send topic message + if topic != "" { + req.To = topic + } + + err := notify.CheckMessage(req) + if err != nil { + logx.LogError.Fatal(err) + } + + if err := status.InitAppStatus(cfg); err != nil { + return + } + + if _, err := notify.PushToAndroid(req, cfg); err != nil { + return + } + + return + } + + // send huawei notification + if opts.Huawei.Enabled { + cfg.Huawei.Enabled = opts.Huawei.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormHuawei, + Message: message, + Title: title, + } + + // send message to single device + if token != "" { + req.Tokens = []string{token} + } + + // send topic message + if topic != "" { + req.To = topic + } + + err := notify.CheckMessage(req) + if err != nil { + logx.LogError.Fatal(err) + } + + if err := status.InitAppStatus(cfg); err != nil { + return + } + + if _, err := notify.PushToHuawei(req, cfg); err != nil { + return + } + + return + } + + // send ios notification + if opts.Ios.Enabled { + if opts.Ios.Production { + cfg.Ios.Production = opts.Ios.Production + } + + cfg.Ios.Enabled = opts.Ios.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormIos, + Message: message, + Title: title, + } + + // send message to single device + if token != "" { + req.Tokens = []string{token} + } + + // send topic message + if topic != "" { + req.Topic = topic + } + + err := notify.CheckMessage(req) + if err != nil { + logx.LogError.Fatal(err) + } + + if err := status.InitAppStatus(cfg); err != nil { + return + } + + if err := notify.InitAPNSClient(cfg); err != nil { + return + } + + if _, err := notify.PushToIOS(req, cfg); err != nil { + return + } + + return + } + + if err = notify.CheckPushConf(cfg); err != nil { + logx.LogError.Fatal(err) + } + + if opts.Core.PID.Path != "" { + cfg.Core.PID.Path = opts.Core.PID.Path + cfg.Core.PID.Enabled = true + cfg.Core.PID.Override = true + } + + if err = createPIDFile(cfg); err != nil { + logx.LogError.Fatal(err) + } + + if err = status.InitAppStatus(cfg); err != nil { + logx.LogError.Fatal(err) + } + + var w queue.Worker + switch core.Queue(cfg.Queue.Engine) { + case core.LocalQueue: + w = queue.NewConsumer( + queue.WithQueueSize(int(cfg.Core.QueueNum)), + queue.WithFn(notify.Run(cfg)), + queue.WithLogger(logx.QueueLogger()), + ) + case core.NSQ: + w = nsq.NewWorker( + nsq.WithAddr(cfg.Queue.NSQ.Addr), + nsq.WithTopic(cfg.Queue.NSQ.Topic), + nsq.WithChannel(cfg.Queue.NSQ.Channel), + nsq.WithMaxInFlight(int(cfg.Core.WorkerNum)), + nsq.WithRunFunc(notify.Run(cfg)), + nsq.WithLogger(logx.QueueLogger()), + ) + case core.NATS: + w = nats.NewWorker( + nats.WithAddr(cfg.Queue.NATS.Addr), + nats.WithSubj(cfg.Queue.NATS.Subj), + nats.WithQueue(cfg.Queue.NATS.Queue), + nats.WithRunFunc(notify.Run(cfg)), + nats.WithLogger(logx.QueueLogger()), + ) + case core.Redis: + w = redisdb.NewWorker( + redisdb.WithAddr(cfg.Queue.Redis.Addr), + redisdb.WithChannel(cfg.Queue.Redis.Channel), + redisdb.WithChannelSize(cfg.Queue.Redis.Size), + redisdb.WithRunFunc(notify.Run(cfg)), + redisdb.WithLogger(logx.QueueLogger()), + ) + default: + logx.LogError.Fatalf("we don't support queue engine: %s", cfg.Queue.Engine) + } + + q := queue.NewPool( + int(cfg.Core.WorkerNum), + queue.WithWorker(w), + queue.WithLogger(logx.QueueLogger()), + ) + + g := graceful.NewManager( + graceful.WithLogger(logx.QueueLogger()), + ) + + g.AddShutdownJob(func() error { + logx.LogAccess.Info("close the queue system, current queue usage: ", q.Usage()) + // stop queue system and wait job completed + q.Release() + // close the connection with storage + logx.LogAccess.Info("close the storage connection: ", cfg.Stat.Engine) + if err := status.StatStorage.Close(); err != nil { + logx.LogError.Fatal("can't close the storage connection: ", err.Error()) + } + return nil + }) + + if cfg.Ios.Enabled { + if err = notify.InitAPNSClient(cfg); err != nil { + logx.LogError.Fatal(err) + } + } + + if cfg.Android.Enabled { + if _, err = notify.InitFCMClient(cfg, cfg.Android.APIKey); err != nil { + logx.LogError.Fatal(err) + } + } + + if cfg.Huawei.Enabled { + if _, err = notify.InitHMSClient(cfg, cfg.Huawei.AppSecret, cfg.Huawei.AppID); err != nil { + logx.LogError.Fatal(err) + } + } + + g.AddRunningJob(func(ctx context.Context) error { + return router.RunHTTPServer(ctx, cfg, q) + }) + + g.AddRunningJob(func(ctx context.Context) error { + return rpc.RunGRPCServer(ctx, cfg) + }) + + <-g.Done() +} + +// Version control for notify. +var Version = "No Version Provided" + +var usageStr = ` + ________ .__ + / _____/ ____ _______ __ __ ______| |__ +/ \ ___ / _ \\_ __ \| | \/ ___/| | \ +\ \_\ \( <_> )| | \/| | /\___ \ | Y \ + \______ / \____/ |__| |____//____ >|___| / + \/ \/ \/ + +Usage: gorush [options] + +Server Options: + -A, --address <address> Address to bind (default: any) + -p, --port <port> Use port for clients (default: 8088) + -c, --config <file> Configuration file path + -m, --message <message> Notification message + -t, --token <token> Notification token + -e, --engine <engine> Storage engine (memory, redis ...) + --title <title> Notification title + --proxy <proxy> Proxy URL + --pid <pid path> Process identifier path + --redis-addr <redis addr> Redis addr (default: localhost:6379) + --ping healthy check command for container +iOS Options: + -i, --key <file> certificate key file path + -P, --password <password> certificate key password + --ios enabled iOS (default: false) + --production iOS production mode (default: false) +Android Options: + -k, --apikey <api_key> Android API Key + --android enabled android (default: false) +Huawei Options: + -hk, --hmskey <hms_key> HMS App Secret + -hid, --hmsid <hms_id> HMS App ID + --huawei enabled huawei (default: false) +MIPUSH Options: + -mk, --mikey <mipush_key> MIPUSH App Secret + -mp, --mipkg <mipush_package> MIPUSH Package + --mi enabled mipush (default: false) +Common Options: + --topic <topic> iOS, Android or Huawei topic message + -h, --help Show this message + -V, --version Show version +` + +// usage will print out the flag options for the server. +func usage() { + fmt.Printf("%s\n", usageStr) +} + +// handles pinging the endpoint and returns an error if the +// agent is in an unhealthy state. +func pinger(cfg *config.ConfYaml) error { + transport := &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 5 * time.Second, + }).Dial, + TLSHandshakeTimeout: 5 * time.Second, + } + client := &http.Client{ + Timeout: time.Second * 10, + Transport: transport, + } + resp, err := client.Get("http://localhost:" + cfg.Core.Port + cfg.API.HealthURI) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status code") + } + return nil +} + +func createPIDFile(cfg *config.ConfYaml) error { + if !cfg.Core.PID.Enabled { + return nil + } + + pidPath := cfg.Core.PID.Path + _, err := os.Stat(pidPath) + if os.IsNotExist(err) || cfg.Core.PID.Override { + currentPid := os.Getpid() + if err := os.MkdirAll(filepath.Dir(pidPath), os.ModePerm); err != nil { + return fmt.Errorf("can't create PID folder on %v", err) + } + + file, err := os.Create(pidPath) + if err != nil { + return fmt.Errorf("can't create PID file: %v", err) + } + defer file.Close() + if _, err := file.WriteString(strconv.FormatInt(int64(currentPid), 10)); err != nil { + return fmt.Errorf("can't write PID information on %s: %v", pidPath, err) + } + } else { + return fmt.Errorf("%s already exists", pidPath) + } + return nil +} diff --git a/push/gorush-with-mipush/src/metric/metrics.go b/push/gorush-with-mipush/src/metric/metrics.go new file mode 100644 index 0000000..e717ef0 --- /dev/null +++ b/push/gorush-with-mipush/src/metric/metrics.go @@ -0,0 +1,134 @@ +package metric + +import ( + "github.com/appleboy/gorush/status" + + "github.com/prometheus/client_golang/prometheus" +) + +const namespace = "gorush_" + +// Metrics implements the prometheus.Metrics interface and +// exposes gorush metrics for prometheus +type Metrics struct { + TotalPushCount *prometheus.Desc + IosSuccess *prometheus.Desc + IosError *prometheus.Desc + AndroidSuccess *prometheus.Desc + AndroidError *prometheus.Desc + HuaweiSuccess *prometheus.Desc + HuaweiError *prometheus.Desc + QueueUsage *prometheus.Desc + GetQueueUsage func() int +} + +var getGetQueueUsage = func() int { return 0 } + +// NewMetrics returns a new Metrics with all prometheus.Desc initialized +func NewMetrics(c ...func() int) Metrics { + m := Metrics{ + TotalPushCount: prometheus.NewDesc( + namespace+"total_push_count", + "Number of push count", + nil, nil, + ), + IosSuccess: prometheus.NewDesc( + namespace+"ios_success", + "Number of iOS success count", + nil, nil, + ), + IosError: prometheus.NewDesc( + namespace+"ios_error", + "Number of iOS fail count", + nil, nil, + ), + AndroidSuccess: prometheus.NewDesc( + namespace+"android_success", + "Number of android success count", + nil, nil, + ), + AndroidError: prometheus.NewDesc( + namespace+"android_fail", + "Number of android fail count", + nil, nil, + ), + HuaweiSuccess: prometheus.NewDesc( + namespace+"huawei_success", + "Number of huawei success count", + nil, nil, + ), + HuaweiError: prometheus.NewDesc( + namespace+"huawei_fail", + "Number of huawei fail count", + nil, nil, + ), + QueueUsage: prometheus.NewDesc( + namespace+"queue_usage", + "Length of internal queue", + nil, nil, + ), + GetQueueUsage: getGetQueueUsage, + } + + if len(c) > 0 { + m.GetQueueUsage = c[0] + } + + return m +} + +// Describe returns all possible prometheus.Desc +func (c Metrics) Describe(ch chan<- *prometheus.Desc) { + ch <- c.TotalPushCount + ch <- c.IosSuccess + ch <- c.IosError + ch <- c.AndroidSuccess + ch <- c.AndroidError + ch <- c.HuaweiSuccess + ch <- c.HuaweiError + ch <- c.QueueUsage +} + +// Collect returns the metrics with values +func (c Metrics) Collect(ch chan<- prometheus.Metric) { + ch <- prometheus.MustNewConstMetric( + c.TotalPushCount, + prometheus.CounterValue, + float64(status.StatStorage.GetTotalCount()), + ) + ch <- prometheus.MustNewConstMetric( + c.IosSuccess, + prometheus.CounterValue, + float64(status.StatStorage.GetIosSuccess()), + ) + ch <- prometheus.MustNewConstMetric( + c.IosError, + prometheus.CounterValue, + float64(status.StatStorage.GetIosError()), + ) + ch <- prometheus.MustNewConstMetric( + c.AndroidSuccess, + prometheus.CounterValue, + float64(status.StatStorage.GetAndroidSuccess()), + ) + ch <- prometheus.MustNewConstMetric( + c.AndroidError, + prometheus.CounterValue, + float64(status.StatStorage.GetAndroidError()), + ) + ch <- prometheus.MustNewConstMetric( + c.HuaweiSuccess, + prometheus.CounterValue, + float64(status.StatStorage.GetHuaweiSuccess()), + ) + ch <- prometheus.MustNewConstMetric( + c.HuaweiError, + prometheus.CounterValue, + float64(status.StatStorage.GetHuaweiError()), + ) + ch <- prometheus.MustNewConstMetric( + c.QueueUsage, + prometheus.GaugeValue, + float64(c.GetQueueUsage()), + ) +} diff --git a/push/gorush-with-mipush/src/metric/metrics_test.go b/push/gorush-with-mipush/src/metric/metrics_test.go new file mode 100644 index 0000000..2e6c1b2 --- /dev/null +++ b/push/gorush-with-mipush/src/metric/metrics_test.go @@ -0,0 +1,15 @@ +package metric + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMetrics(t *testing.T) { + m := NewMetrics() + assert.Equal(t, 0, m.GetQueueUsage()) + + m = NewMetrics(func() int { return 1 }) + assert.Equal(t, 1, m.GetQueueUsage()) +} diff --git a/push/gorush-with-mipush/src/netlify.toml b/push/gorush-with-mipush/src/netlify.toml new file mode 100644 index 0000000..7eaa48c --- /dev/null +++ b/push/gorush-with-mipush/src/netlify.toml @@ -0,0 +1,13 @@ +[build] + command = "make build_linux_lambda" + functions = "release/linux/lambda" + +[build.environment] + GO_VERSION = "1.16" + GO_IMPORT_PATH = "github.com/appleboy/gorush" + GO111MODULE = "on" + +[[redirects]] + from = "/*" + to = "/.netlify/functions/gorush/:splat" + status = 200 diff --git a/push/gorush-with-mipush/src/notify/feedback.go b/push/gorush-with-mipush/src/notify/feedback.go new file mode 100644 index 0000000..c4f873e --- /dev/null +++ b/push/gorush-with-mipush/src/notify/feedback.go @@ -0,0 +1,53 @@ +package notify + +import ( + "bytes" + "errors" + "net" + "net/http" + "time" + + "github.com/appleboy/gorush/logx" +) + +// DispatchFeedback sends a feedback to the configured gateway. +func DispatchFeedback(log logx.LogPushEntry, url string, timeout int64) error { + if url == "" { + return errors.New("url can't be empty") + } + + payload, err := json.Marshal(log) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + transport := &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 5 * time.Second, + }).Dial, + TLSHandshakeTimeout: 5 * time.Second, + } + client := &http.Client{ + Timeout: time.Duration(timeout) * time.Second, + Transport: transport, + } + + resp, err := client.Do(req) + + if resp != nil { + defer resp.Body.Close() + } + + if err != nil { + return err + } + + return nil +} diff --git a/push/gorush-with-mipush/src/notify/feedback_test.go b/push/gorush-with-mipush/src/notify/feedback_test.go new file mode 100644 index 0000000..ec20b50 --- /dev/null +++ b/push/gorush-with-mipush/src/notify/feedback_test.go @@ -0,0 +1,75 @@ +package notify + +import ( + "log" + "net/http" + "net/http/httptest" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + + "github.com/stretchr/testify/assert" +) + +func TestEmptyFeedbackURL(t *testing.T) { + cfg, _ := config.LoadConf() + logEntry := logx.LogPushEntry{ + ID: "", + Type: "", + Platform: "", + Token: "", + Message: "", + Error: "", + } + + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) + assert.NotNil(t, err) +} + +func TestHTTPErrorInFeedbackCall(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Core.FeedbackURL = "http://test.example.com/api/" + logEntry := logx.LogPushEntry{ + ID: "", + Type: "", + Platform: "", + Token: "", + Message: "", + Error: "", + } + + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) + assert.NotNil(t, err) +} + +func TestSuccessfulFeedbackCall(t *testing.T) { + // Mock http server + httpMock := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/dispatch" { + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(`{}`)) + if err != nil { + log.Println(err) + panic(err) + } + } + }), + ) + defer httpMock.Close() + + cfg, _ := config.LoadConf() + cfg.Core.FeedbackURL = httpMock.URL + logEntry := logx.LogPushEntry{ + ID: "", + Type: "", + Platform: "", + Token: "", + Message: "", + Error: "", + } + + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) + assert.Nil(t, err) +} diff --git a/push/gorush-with-mipush/src/notify/global.go b/push/gorush-with-mipush/src/notify/global.go new file mode 100644 index 0000000..d9d105d --- /dev/null +++ b/push/gorush-with-mipush/src/notify/global.go @@ -0,0 +1,20 @@ +package notify + +import ( + "github.com/appleboy/go-fcm" + "github.com/msalihkarakasli/go-hms-push/push/core" + "github.com/sideshow/apns2" +) + +var ( + // ApnsClient is apns client + ApnsClient *apns2.Client + // FCMClient is apns client + FCMClient *fcm.Client + // HMSClient is Huawei push client + HMSClient *core.HMSClient + // MIPUSHClient is mi push client + MIPUSHClient *XMPush + // MaxConcurrentIOSPushes pool to limit the number of concurrent iOS pushes + MaxConcurrentIOSPushes chan struct{} +) diff --git a/push/gorush-with-mipush/src/notify/main_test.go b/push/gorush-with-mipush/src/notify/main_test.go new file mode 100644 index 0000000..899ef0b --- /dev/null +++ b/push/gorush-with-mipush/src/notify/main_test.go @@ -0,0 +1,19 @@ +package notify + +import ( + "log" + "os" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/status" +) + +func TestMain(m *testing.M) { + cfg, _ := config.LoadConf() + if err := status.InitAppStatus(cfg); err != nil { + log.Fatal(err) + } + + os.Exit(m.Run()) +} diff --git a/push/gorush-with-mipush/src/notify/notification.go b/push/gorush-with-mipush/src/notify/notification.go new file mode 100644 index 0000000..8e19c2d --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification.go @@ -0,0 +1,273 @@ +package notify + +import ( + "context" + "errors" + "net/http" + "net/url" + "os" + "strings" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + + "github.com/appleboy/go-fcm" + "github.com/golang-queue/queue" + jsoniter "github.com/json-iterator/go" + "github.com/msalihkarakasli/go-hms-push/push/model" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// D provide string array +type D map[string]interface{} + +const ( + // ApnsPriorityLow will tell APNs to send the push message at a time that takes + // into account power considerations for the device. Notifications with this + // priority might be grouped and delivered in bursts. They are throttled, and + // in some cases are not delivered. + ApnsPriorityLow = 5 + + // ApnsPriorityHigh will tell APNs to send the push message immediately. + // Notifications with this priority must trigger an alert, sound, or badge on + // the target device. It is an error to use this priority for a push + // notification that contains only the content-available key. + ApnsPriorityHigh = 10 +) + +// Alert is APNs payload +type Alert struct { + Action string `json:"action,omitempty"` + ActionLocKey string `json:"action-loc-key,omitempty"` + Body string `json:"body,omitempty"` + LaunchImage string `json:"launch-image,omitempty"` + LocArgs []string `json:"loc-args,omitempty"` + LocKey string `json:"loc-key,omitempty"` + Title string `json:"title,omitempty"` + Subtitle string `json:"subtitle,omitempty"` + TitleLocArgs []string `json:"title-loc-args,omitempty"` + TitleLocKey string `json:"title-loc-key,omitempty"` + SummaryArg string `json:"summary-arg,omitempty"` + SummaryArgCount int `json:"summary-arg-count,omitempty"` +} + +// RequestPush support multiple notification request. +type RequestPush struct { + Notifications []PushNotification `json:"notifications" binding:"required"` +} + +// ResponsePush response of notification request. +type ResponsePush struct { + Logs []logx.LogPushEntry `json:"logs"` +} + +// PushNotification is single notification request +type PushNotification struct { + // Common + ID string `json:"notif_id,omitempty"` + Tokens []string `json:"tokens" binding:"required"` + Platform int `json:"platform" binding:"required"` + Message string `json:"message,omitempty"` + Title string `json:"title,omitempty"` + Image string `json:"image,omitempty"` + Priority string `json:"priority,omitempty"` + ContentAvailable bool `json:"content_available,omitempty"` + MutableContent bool `json:"mutable_content,omitempty"` + Sound interface{} `json:"sound,omitempty"` + Data D `json:"data,omitempty"` + Retry int `json:"retry,omitempty"` + + // Android + APIKey string `json:"api_key,omitempty"` + To string `json:"to,omitempty"` + CollapseKey string `json:"collapse_key,omitempty"` + DelayWhileIdle bool `json:"delay_while_idle,omitempty"` + TimeToLive *uint `json:"time_to_live,omitempty"` + RestrictedPackageName string `json:"restricted_package_name,omitempty"` + DryRun bool `json:"dry_run,omitempty"` + Condition string `json:"condition,omitempty"` + Notification *fcm.Notification `json:"notification,omitempty"` + + // Huawei + AppID string `json:"app_id,omitempty"` + AppSecret string `json:"app_secret,omitempty"` + HuaweiNotification *model.AndroidNotification `json:"huawei_notification,omitempty"` + HuaweiData string `json:"huawei_data,omitempty"` + HuaweiCollapseKey int `json:"huawei_collapse_key,omitempty"` + HuaweiTTL string `json:"huawei_ttl,omitempty"` + BiTag string `json:"bi_tag,omitempty"` + FastAppTarget int `json:"fast_app_target,omitempty"` + + // iOS + Expiration *int64 `json:"expiration,omitempty"` + ApnsID string `json:"apns_id,omitempty"` + CollapseID string `json:"collapse_id,omitempty"` + Topic string `json:"topic,omitempty"` + PushType string `json:"push_type,omitempty"` + Badge *int `json:"badge,omitempty"` + Category string `json:"category,omitempty"` + ThreadID string `json:"thread-id,omitempty"` + URLArgs []string `json:"url-args,omitempty"` + Alert Alert `json:"alert,omitempty"` + Production bool `json:"production,omitempty"` + Development bool `json:"development,omitempty"` + SoundName string `json:"name,omitempty"` + SoundVolume float32 `json:"volume,omitempty"` + Apns D `json:"apns,omitempty"` +} + +// Bytes for queue message +func (p *PushNotification) Bytes() []byte { + b, err := json.Marshal(p) + if err != nil { + panic(err) + } + return b +} + +// IsTopic check if message format is topic for FCM +// ref: https://firebase.google.com/docs/cloud-messaging/send-message#topic-http-post-request +func (p *PushNotification) IsTopic() bool { + if p.Platform == core.PlatFormAndroid { + return p.To != "" && strings.HasPrefix(p.To, "/topics/") || p.Condition != "" + } + + if p.Platform == core.PlatFormHuawei { + return p.Topic != "" || p.Condition != "" + } + + return false +} + +// CheckMessage for check request message +func CheckMessage(req *PushNotification) error { + var msg string + + // ignore send topic mesaage from FCM + if !req.IsTopic() && len(req.Tokens) == 0 && req.To == "" { + msg = "the message must specify at least one registration ID" + logx.LogAccess.Debug(msg) + return errors.New(msg) + } + + if len(req.Tokens) == core.PlatFormIos && req.Tokens[0] == "" { + msg = "the token must not be empty" + logx.LogAccess.Debug(msg) + return errors.New(msg) + } + + if req.Platform == core.PlatFormAndroid && len(req.Tokens) > 1000 { + msg = "the message may specify at most 1000 registration IDs" + logx.LogAccess.Debug(msg) + return errors.New(msg) + } + + if req.Platform == core.PlatFormHuawei && len(req.Tokens) > 500 { + msg = "the message may specify at most 500 registration IDs for Huawei" + logx.LogAccess.Debug(msg) + return errors.New(msg) + } + + // ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref + if req.Platform == core.PlatFormAndroid && req.TimeToLive != nil && *req.TimeToLive > uint(2419200) { + msg = "the message's TimeToLive field must be an integer " + + "between 0 and 2419200 (4 weeks)" + logx.LogAccess.Debug(msg) + return errors.New(msg) + } + + return nil +} + +// SetProxy only working for FCM server. +func SetProxy(proxy string) error { + proxyURL, err := url.ParseRequestURI(proxy) + if err != nil { + return err + } + + http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} + logx.LogAccess.Debug("Set http proxy as " + proxy) + + return nil +} + +// CheckPushConf provide check your yml config. +func CheckPushConf(cfg *config.ConfYaml) error { + if !cfg.Ios.Enabled && !cfg.Android.Enabled && !cfg.Huawei.Enabled { + return errors.New("please enable iOS, Android or Huawei config in yml config") + } + + if cfg.Ios.Enabled { + if cfg.Ios.KeyPath == "" && cfg.Ios.KeyBase64 == "" { + return errors.New("missing iOS certificate key") + } + + // check certificate file exist + if cfg.Ios.KeyPath != "" { + if _, err := os.Stat(cfg.Ios.KeyPath); os.IsNotExist(err) { + return errors.New("certificate file does not exist") + } + } + } + + if cfg.Android.Enabled { + if cfg.Android.APIKey == "" { + return errors.New("Missing Android API Key") + } + } + + if cfg.Huawei.Enabled { + if cfg.Huawei.AppSecret == "" { + return errors.New("Missing Huawei App Secret") + } + + if cfg.Huawei.AppID == "" { + return errors.New("Missing Huawei App ID") + } + } + + return nil +} + +// SendNotification send notification +func SendNotification(req queue.QueuedMessage, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + v, ok := req.(*PushNotification) + if !ok { + if err = json.Unmarshal(req.Bytes(), &v); err != nil { + return + } + } + + switch v.Platform { + case core.PlatFormIos: + resp, err = PushToIOS(v, cfg) + case core.PlatFormAndroid: + resp, err = PushToAndroid(v, cfg) + case core.PlatFormHuawei: + resp, err = PushToHuawei(v, cfg) + case core.PlaFormMI: + resp, err = PushToMI(v, cfg) + } + + if cfg.Core.FeedbackURL != "" { + for _, l := range resp.Logs { + err := DispatchFeedback(l, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) + if err != nil { + logx.LogError.Error(err) + } + } + } + + return +} + +// Run send notification +var Run = func(cfg *config.ConfYaml) func(ctx context.Context, msg queue.QueuedMessage) error { + return func(ctx context.Context, msg queue.QueuedMessage) error { + _, err := SendNotification(msg, cfg) + return err + } +} diff --git a/push/gorush-with-mipush/src/notify/notification_apns.go b/push/gorush-with-mipush/src/notify/notification_apns.go new file mode 100644 index 0000000..d1b2a9d --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_apns.go @@ -0,0 +1,463 @@ +package notify + +import ( + "crypto/ecdsa" + "crypto/tls" + "encoding/base64" + "errors" + "net" + "net/http" + "path/filepath" + "sync" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + + "github.com/mitchellh/mapstructure" + "github.com/sideshow/apns2" + "github.com/sideshow/apns2/certificate" + "github.com/sideshow/apns2/payload" + "github.com/sideshow/apns2/token" + "golang.org/x/net/http2" +) + +var ( + idleConnTimeout = 90 * time.Second + tlsDialTimeout = 20 * time.Second + tcpKeepAlive = 60 * time.Second +) + +var doOnce sync.Once + +// DialTLS is the default dial function for creating TLS connections for +// non-proxied HTTPS requests. +var DialTLS = func(cfg *tls.Config) func(network, addr string) (net.Conn, error) { + return func(network, addr string) (net.Conn, error) { + dialer := &net.Dialer{ + Timeout: tlsDialTimeout, + KeepAlive: tcpKeepAlive, + } + return tls.DialWithDialer(dialer, network, addr, cfg) + } +} + +// Sound sets the aps sound on the payload. +type Sound struct { + Critical int `json:"critical,omitempty"` + Name string `json:"name,omitempty"` + Volume float32 `json:"volume,omitempty"` +} + +// InitAPNSClient use for initialize APNs Client. +func InitAPNSClient(cfg *config.ConfYaml) error { + if cfg.Ios.Enabled { + var err error + var authKey *ecdsa.PrivateKey + var certificateKey tls.Certificate + var ext string + + if cfg.Ios.KeyPath != "" { + ext = filepath.Ext(cfg.Ios.KeyPath) + + switch ext { + case ".p12": + certificateKey, err = certificate.FromP12File(cfg.Ios.KeyPath, cfg.Ios.Password) + case ".pem": + certificateKey, err = certificate.FromPemFile(cfg.Ios.KeyPath, cfg.Ios.Password) + case ".p8": + authKey, err = token.AuthKeyFromFile(cfg.Ios.KeyPath) + default: + err = errors.New("wrong certificate key extension") + } + + if err != nil { + logx.LogError.Error("Cert Error:", err.Error()) + + return err + } + } else if cfg.Ios.KeyBase64 != "" { + ext = "." + cfg.Ios.KeyType + key, err := base64.StdEncoding.DecodeString(cfg.Ios.KeyBase64) + if err != nil { + logx.LogError.Error("base64 decode error:", err.Error()) + + return err + } + switch ext { + case ".p12": + certificateKey, err = certificate.FromP12Bytes(key, cfg.Ios.Password) + case ".pem": + certificateKey, err = certificate.FromPemBytes(key, cfg.Ios.Password) + case ".p8": + authKey, err = token.AuthKeyFromBytes(key) + default: + err = errors.New("wrong certificate key type") + } + + if err != nil { + logx.LogError.Error("Cert Error:", err.Error()) + + return err + } + } + + if ext == ".p8" { + if cfg.Ios.KeyID == "" || cfg.Ios.TeamID == "" { + msg := "You should provide ios.KeyID and ios.TeamID for P8 token" + logx.LogError.Error(msg) + return errors.New(msg) + } + token := &token.Token{ + AuthKey: authKey, + // KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + KeyID: cfg.Ios.KeyID, + // TeamID from developer account (View Account -> Membership) + TeamID: cfg.Ios.TeamID, + } + + ApnsClient, err = newApnsTokenClient(cfg, token) + } else { + ApnsClient, err = newApnsClient(cfg, certificateKey) + } + + if h2Transport, ok := ApnsClient.HTTPClient.Transport.(*http2.Transport); ok { + configureHTTP2ConnHealthCheck(h2Transport) + } + + if err != nil { + logx.LogError.Error("Transport Error:", err.Error()) + + return err + } + + doOnce.Do(func() { + MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) + }) + } + + return nil +} + +func newApnsClient(cfg *config.ConfYaml, certificate tls.Certificate) (*apns2.Client, error) { + var client *apns2.Client + + if cfg.Ios.Production { + client = apns2.NewClient(certificate).Production() + } else { + client = apns2.NewClient(certificate).Development() + } + + if cfg.Core.HTTPProxy == "" { + return client, nil + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + + if len(certificate.Certificate) > 0 { + tlsConfig.BuildNameToCertificate() + } + + transport := &http.Transport{ + TLSClientConfig: tlsConfig, + DialTLS: DialTLS(tlsConfig), + Proxy: http.DefaultTransport.(*http.Transport).Proxy, + IdleConnTimeout: idleConnTimeout, + } + + h2Transport, err := http2.ConfigureTransports(transport) + if err != nil { + return nil, err + } + + configureHTTP2ConnHealthCheck(h2Transport) + + client.HTTPClient.Transport = transport + + return client, nil +} + +func newApnsTokenClient(cfg *config.ConfYaml, token *token.Token) (*apns2.Client, error) { + var client *apns2.Client + + if cfg.Ios.Production { + client = apns2.NewTokenClient(token).Production() + } else { + client = apns2.NewTokenClient(token).Development() + } + + if cfg.Core.HTTPProxy == "" { + return client, nil + } + + transport := &http.Transport{ + DialTLS: DialTLS(nil), + Proxy: http.DefaultTransport.(*http.Transport).Proxy, + IdleConnTimeout: idleConnTimeout, + } + + h2Transport, err := http2.ConfigureTransports(transport) + if err != nil { + return nil, err + } + + configureHTTP2ConnHealthCheck(h2Transport) + + client.HTTPClient.Transport = transport + + return client, nil +} + +func configureHTTP2ConnHealthCheck(h2Transport *http2.Transport) { + h2Transport.ReadIdleTimeout = 1 * time.Second + h2Transport.PingTimeout = 1 * time.Second +} + +func iosAlertDictionary(payload *payload.Payload, req *PushNotification) *payload.Payload { + // Alert dictionary + + if len(req.Title) > 0 { + payload.AlertTitle(req.Title) + } + + if len(req.Message) > 0 && len(req.Title) > 0 { + payload.AlertBody(req.Message) + } + + if len(req.Alert.Title) > 0 { + payload.AlertTitle(req.Alert.Title) + } + + // Apple Watch & Safari display this string as part of the notification interface. + if len(req.Alert.Subtitle) > 0 { + payload.AlertSubtitle(req.Alert.Subtitle) + } + + if len(req.Alert.TitleLocKey) > 0 { + payload.AlertTitleLocKey(req.Alert.TitleLocKey) + } + + if len(req.Alert.LocArgs) > 0 { + payload.AlertLocArgs(req.Alert.LocArgs) + } + + if len(req.Alert.TitleLocArgs) > 0 { + payload.AlertTitleLocArgs(req.Alert.TitleLocArgs) + } + + if len(req.Alert.Body) > 0 { + payload.AlertBody(req.Alert.Body) + } + + if len(req.Alert.LaunchImage) > 0 { + payload.AlertLaunchImage(req.Alert.LaunchImage) + } + + if len(req.Alert.LocKey) > 0 { + payload.AlertLocKey(req.Alert.LocKey) + } + + if len(req.Alert.Action) > 0 { + payload.AlertAction(req.Alert.Action) + } + + if len(req.Alert.ActionLocKey) > 0 { + payload.AlertActionLocKey(req.Alert.ActionLocKey) + } + + // General + if len(req.Category) > 0 { + payload.Category(req.Category) + } + + if len(req.Alert.SummaryArg) > 0 { + payload.AlertSummaryArg(req.Alert.SummaryArg) + } + + if req.Alert.SummaryArgCount > 0 { + payload.AlertSummaryArgCount(req.Alert.SummaryArgCount) + } + + return payload +} + +// GetIOSNotification use for define iOS notification. +// The iOS Notification Payload (Payload Key Reference) +// Ref: https://apple.co/2VtH6Iu +func GetIOSNotification(req *PushNotification) *apns2.Notification { + notification := &apns2.Notification{ + ApnsID: req.ApnsID, + Topic: req.Topic, + CollapseID: req.CollapseID, + } + + if req.Expiration != nil { + notification.Expiration = time.Unix(*req.Expiration, 0) + } + + if len(req.Priority) > 0 { + if req.Priority == "normal" { + notification.Priority = apns2.PriorityLow + } else if req.Priority == "high" { + notification.Priority = apns2.PriorityHigh + } + } + + if len(req.PushType) > 0 { + notification.PushType = apns2.EPushType(req.PushType) + } + + payload := payload.NewPayload() + + // add alert object if message length > 0 and title is empty + if len(req.Message) > 0 && req.Title == "" { + payload.Alert(req.Message) + } + + // zero value for clear the badge on the app icon. + if req.Badge != nil && *req.Badge >= 0 { + payload.Badge(*req.Badge) + } + + if req.MutableContent { + payload.MutableContent() + } + + switch req.Sound.(type) { + // from http request binding + case map[string]interface{}: + result := &Sound{} + _ = mapstructure.Decode(req.Sound, &result) + payload.Sound(result) + // from http request binding for non critical alerts + case string: + payload.Sound(&req.Sound) + case Sound: + payload.Sound(&req.Sound) + } + + if len(req.SoundName) > 0 { + payload.SoundName(req.SoundName) + } + + if req.SoundVolume > 0 { + payload.SoundVolume(req.SoundVolume) + } + + if req.ContentAvailable { + payload.ContentAvailable() + } + + if len(req.URLArgs) > 0 { + payload.URLArgs(req.URLArgs) + } + + if len(req.ThreadID) > 0 { + payload.ThreadID(req.ThreadID) + } + + for k, v := range req.Data { + payload.Custom(k, v) + } + + payload = iosAlertDictionary(payload, req) + + notification.Payload = payload + + return notification +} + +func getApnsClient(cfg *config.ConfYaml, req *PushNotification) (client *apns2.Client) { + switch { + case req.Production: + client = ApnsClient.Production() + case req.Development: + client = ApnsClient.Development() + default: + if cfg.Ios.Production { + client = ApnsClient.Production() + } else { + client = ApnsClient.Development() + } + } + + return +} + +// PushToIOS provide send notification to APNs server. +func PushToIOS(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for iOS") + + var ( + retryCount = 0 + maxRetry = cfg.Ios.MaxRetry + ) + + if req.Retry > 0 && req.Retry < maxRetry { + maxRetry = req.Retry + } + + resp = &ResponsePush{} + +Retry: + var newTokens []string + + notification := GetIOSNotification(req) + client := getApnsClient(cfg, req) + + var wg sync.WaitGroup + for _, token := range req.Tokens { + // occupy push slot + MaxConcurrentIOSPushes <- struct{}{} + wg.Add(1) + go func(notification apns2.Notification, token string) { + notification.DeviceToken = token + + // send ios notification + res, err := client.Push(¬ification) + if err != nil || (res != nil && res.StatusCode != http.StatusOK) { + if err == nil { + // error message: + // ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65 + err = errors.New(res.Reason) + } + + // apns server error + errLog := logPush(cfg, core.FailedPush, token, req, err) + resp.Logs = append(resp.Logs, errLog) + + status.StatStorage.AddIosError(1) + // We should retry only "retryable" statuses. More info about response: + // See https://apple.co/3AdNane (Handling Notification Responses from APNs) + if res != nil && res.StatusCode >= http.StatusInternalServerError { + newTokens = append(newTokens, token) + } + } + + if res != nil && res.Sent() { + logPush(cfg, core.SucceededPush, token, req, nil) + status.StatStorage.AddIosSuccess(1) + } + + // free push slot + <-MaxConcurrentIOSPushes + wg.Done() + }(*notification, token) + } + + wg.Wait() + + if len(newTokens) > 0 && retryCount < maxRetry { + retryCount++ + + // resend fail token + req.Tokens = newTokens + goto Retry + } + + return resp, nil +} diff --git a/push/gorush-with-mipush/src/notify/notification_apns_test.go b/push/gorush-with-mipush/src/notify/notification_apns_test.go new file mode 100644 index 0000000..6a6c97c --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_apns_test.go @@ -0,0 +1,774 @@ +package notify + +import ( + "log" + "net/http" + "net/url" + "testing" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/status" + "github.com/buger/jsonparser" + "github.com/sideshow/apns2" + "github.com/stretchr/testify/assert" +) + +const ( + // nolint + certificateValidP12 = `MIIKlgIBAzCCClwGCSqGSIb3DQEHAaCCCk0EggpJMIIKRTCCBMcGCSqGSIb3DQEHBqCCBLgwggS0AgEAMIIErQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQID/GJtcRhjvwCAggAgIIEgE5ralQoQBDgHgdp5+EwBaMjcZEJUXmYRdVCttIwfN2OxlIs54tob3/wpUyWGqJ+UXy9X+4EsWpDPUfTN/w88GMgj0kftpTqG0+3Hu/9pkZO4pdLCiyMGOJnXCOdhHFirtTXAR3QvnKKIpXIKrmZ4rcr/24Uvd/u669Tz8VDgcGOQazKeyvtdW7TJBxMFRv+IsQi/qCj5PkQ0jBbZ1LAc4C8mCMwOcH+gi/e471mzPWihQmynH2yJlZ4jb+taxQ/b8Dhlni2vcIMn+HknRk3Cyo8jfFvvO0BjvVvEAPxPJt7X96VFFS2KlyXjY3zt0siGrzQpczgPB/1vTqhQUvoOBw6kcXWgOjwt+gR8Mmo2DELnQqGhbYuWu52doLgVvD+zGr5vLYXHz6gAXnI6FVyHb+oABeBet3cer3EzGR7r+VoLmWSBm8SyRHwi0mxE63S7oD1j22jaTo7jnQBFZaY+cPaATcFjqW67x4j8kXh9NRPoINSgodLJrgmet2D1iOKuLTkCWf0UTi2HUkn9Zf0y+IIViZaVE4mWaGb9xTBClfa4KwM5gSz3jybksFKbtnzzPFuzClu+2mdthJs/58Ao40eyaykNmzSPhDv1F8Mai8bfaAqSdcBl5ZB2PF33xhuNSS4j2uIh1ICGv9DueyN507iEMQO2yCcaQTMKejV7/52h9LReS5/QPXDJhWMVpTb5FGCP7EmO0lZTeBNO5MlDzDQfz5xcFqHqfoby2sfAMU8HNB8wzdcwHtacgKGLBjLkapxyTsqYE5Kry6UxclvF4soR8TZoQ69E7WsKZLmTaw2+msmnDJubpY0NqkRqkVk7umtVC0D+w6AIKDrY58HMlm80/ImgGXwybA1kuZMxqMzaH/xFiAHOSIGuVPtGgGFYNEdGbfOryuhFo9l1nSECWm8MN9hYwB1Rn9p6rkd+zrvbU1zv13drtrZ/vL0NlT02tlkS8NdWLGJkZhWgc2c89GyRb7mjuHRHu/BWGED3y7vjHo/lnkPsLJXw0ovIlqhtW0BtN/xSpGg0phDbn0Et5jb7Xmc+fWimgbtIUHcnJOV5QSYFzlR+kbzx0oKRARU4B3CWkdPeaXkrmw0IriS6vOdZcM8YBJ6BtXEDLsrSH7tHxeknYHLEl0uy9Oc1+Huyrz8j7Zxo8SQj9H+RX0HeMl8YB3HUBLHYcqCEBjm7mHI4rP8ULVkC5oCA5w3tJfMyvS/jZRiwMUyr0tiWhrh/AM3wPPX54cqozefojWKrqGtK9I+n0cfwW9rU3FsUcpMTo9uQ27O7NejKP2X/LLMZkQvWUEabZNjNrWsbp6d51/frfIR7kRlZAmmt2yS23h6w6RvKTAVUrNatEyzokfNAIDml6lYLweNJATZU08BznhPpuvh3bKOSos5uaJBYpsOYexoMGnAig428qypw0cmv6sCjO/xdIL86COVNQp/UtjcXJ9/E0bnVmzfpgA3WCy+29YXPx7DZ1U+bQ9jOO/P9pwqLwTH+gpcZiVm3ru1Tmiq6iZ8cG7tMLfTBNXljvtlDzCCBXYGCSqGSIb3DQEHAaCCBWcEggVjMIIFXzCCBVsGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAgCvAo2HCM89AICCAAEggTIOcfaF6qWYXlo+BNBjYIllg0VwQSJXZmcqj2vXlDPIPrTuQ+QDmGnhYR6hVbcMrk3o7eQhH3ThyHM+KEzkYx1IAYCOdEQXYcFguoDG1CxHrgE1Y0H8yndc/yPw2tqkx6X9ZemdYp3welXZjYgUi9MKvGbN6lZ0cFTU+2+0+H/IyKQ3OUjDNymhOxypOPBaK2eQsJ7XumgJ6nLvNZDRx/f277J+LD/z0pOhzUOljhvA3dkBMpEvomX4erZihErunqP1jbH9O3eIYq9J7czGS2xuckolW19KqWOyWh8KRI/LnAqiEh2e0hZ7lpltj79PenO66VGPbn2f85A6b6PD4kipgoMB2IRibkoodyn/oo3WizO386fqtEfUlbFmxI4y4utobWe7nZ2VuBLgA/mgyyxqAJK1erM98NDWB/Njo1CPsaMl9ubXKPOyIZG0fOLUa23DfkJUEiCb839yKc2oEJkI0wtrvbeh1TAPv4vL4TxiXdiJ/6YrSa0/FQh6nqk1jiK+p22MzvEIkDOyPqk/GsAlc/k2kQ/M86tF50wtc08wnXv8+G8k6qTZ7VCluffzAUt64La47qj8XIfh7tKleznzQSbyjlNX8DsFVzGbCg9G4PKxrLAVnKEgIK1kOopSF1UUMqSKE0D3s5AURQhX8/Cf9h+WtNsWK+y7EMOntsBc2op0M7fQ9Jm73NF7CCYeqb0W7sziJSzqJsJgNp0+ArAcZQExeltxAb6kye3Z5JtP/oaB+jmcHKy9l/nhzKA3MzJwCZ5Q3oviPlNqJvFVBmGEEvC6iULLuv6VSxNdB2uH3Tsfa1TMOOHOadBTcyWatjscYS9ynkXuw1+8+FvEu3EV0UwopZmlSaYfMKQ2jshT4Cgg1zy15uKjomojtAaaF+D/U6KZVQk/7rzdaDmvkJvNtc5n9BW96tmrOhI6L+/WihS570qaitQUsHBBTOetlHXYEPiOkH8BhjzNHXLH9YpC8OEQOhO+1jEninDKNdbU7SCqV0+YE6kfR5Bfkw2MxoIQLtUnHjK6GR/q3fxo1TirbTe8c8dp907wgcXkT/rONX/iG1JTjxV2ixR1oM68LYI3eJzY801/xBSnmOjdzOPUHXCNHDTf9kPjkOtZWkGbZugf4ckRH/L8dK2Vo4QpFUN8AZjomanzLxjQZ+DVFNoPDT2K+0pezsMiwSJlyBGoIQHN0/2zVNVLo/KfARIOac1iC8+duj5S/1c52+PvP7FkMe72QUV0KUQ7AJHXUvQtFZx4Ny579/B/3c4D72CFSydhw3/+nL9+Nz956UafZ6G7HZ96frMTgajMcXQe1uXwgN2iTnnNtLdcC/ARHS1RkjgXHohO+VGuQxOo23PPABVaxex2SGGXX7Fc4MI2Xr4uaimZIzcUkuHUnhZQGkcFlVekZ/wJXookq0Fv8DuPuv7mGCx6BKERU9I+NMU6xLNe6VsfkS8t5uVq1EIINnddGl9VGpqOPN8EgU47gh6CcDkP8sxXsT8pZ1vQyJrUlWGYp68/okoQ+7lqnd06wzVDIwAE/+pq9PUxLdNvYE0sNe4JrEcKO0xp/zxCqLjHLT+rB896v2OsU0BA5tPQA7xkKp4PuQr6qO8fTVyfhImVmoFX6b9VgtLHIlJMVowIwYJKoZIhvcNAQkVMRYEFIwanwBmvSRCuV0e6/5ei8oEPXODMDMGCSqGSIb3DQEJFDEmHiQAQQBQAE4AUwAvADIAIABQAHIAaQB2AGEAdABlACAASwBlAHkwMTAhMAkGBSsOAwIaBQAEFK7XWCbKGSKmxNqE2E8dmCfwhaQxBAjPcbkv12ro6gICCAA=` + // nolint + certificateValidPEM = `QmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKc3ViamVjdD0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUludGVybmV0IFdpZGdpdHMgUHR5IEx0ZC9PVT05WkVINjJLUlZWL0NOPUFQTlMvMiBEZXZlbG9wbWVudCBJT1MgUHVzaCBTZXJ2aWNlczogY29tLnNpZGVzaG93LkFwbnMyCmlzc3Vlcj0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUFQTlMvMiBJbmMuL09VPUFQTlMvMiBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucy9DTj1BUE5TLzIgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ2ekNDQXRNQ0FRSXdEUVlKS29aSWh2Y05BUUVMQlFBd2djTXhDekFKQmdOVkJBWVRBazVhTVJNd0VRWUQKVlFRSUV3cFhaV3hzYVc1bmRHOXVNUk13RVFZRFZRUUhFd3BYWld4c2FXNW5kRzl1TVJRd0VnWURWUVFLRXd0QgpVRTVUTHpJZ1NXNWpMakV0TUNzR0ExVUVDeE1rUVZCT1V5OHlJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnClVtVnNZWFJwYjI1ek1VVXdRd1lEVlFRREV6eEJVRTVUTHpJZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlMKWld4aGRHbHZibk1nUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3dIaGNOTVRZd01UQTRNRGd6TkRNdwpXaGNOTWpZd01UQTFNRGd6TkRNd1dqQ0JzakVMTUFrR0ExVUVCaE1DVGxveEV6QVJCZ05WQkFnVENsZGxiR3hwCmJtZDBiMjR4RXpBUkJnTlZCQWNUQ2xkbGJHeHBibWQwYjI0eElUQWZCZ05WQkFvVEdFbHVkR1Z5Ym1WMElGZHAKWkdkcGRITWdVSFI1SUV4MFpERVRNQkVHQTFVRUN4TUtPVnBGU0RZeVMxSldWakZCTUQ4R0ExVUVBeE00UVZCTwpVeTh5SUVSbGRtVnNiM0J0Wlc1MElFbFBVeUJRZFhOb0lGTmxjblpwWTJWek9pQmpiMjB1YzJsa1pYTm9iM2N1ClFYQnVjekl3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFkwYzFUS0I1b1pQd1EKN3QxQ3dNSXJ2cUI2R0lVM3RQeTZSaGNrWlhUa09COFllQldKN1VLZkN6OEhHSEZWb21CUDBUNU9VYmVxUXpxVwpZSmJRelo4YTZaTXN6YkwwbE80WDkrKzNPaTUvVHRBd09VT0s4ck9GTjI1bTJLZnNheUhRWi80dldTdEsyRndtCjVhSmJHTGxwSC9iLzd6MUQ0dmhtTWdvQnVUMUl1eWhHaXlGeGxaOUV0VGxvRnZzcU0xRTVmWVpPU1pBQ3lYVGEKSzR2ZGdiUU1nVVZzSTcxNEZBZ0xUbEswVWVpUmttS20zcGRidGZWYnJ0aHpJK0lIWEtJdFVJeStGbjIwUFJNaApkU25henRTejd0Z0JXQ0l4MjJxdmNZb2dIV2lPZ1VZSU03NzJ6RTJ5OFVWT3I4RHNpUmxzT0hTQTdFSTRNSmNRCkcyRlVxMlovQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBR3lmeU8ySE1nY2RlQmN6M2J0NUJJTFgKZjdSQTIvVW1WSXdjS1IxcW90VHNGK1BuQm1jSUxleU9RZ0RlOXRHVTVjUmM3OWtEdDNKUm1NWVJPRklNZ0ZSZgpXZjIydU9LdGhvN0dRUWFLdkcrYmtnTVZkWUZSbEJIbkYrS2VxS0g4MXFiOXArQ1Q0SXcwR2VoSUwxRGlqRkxSClZJQUlCWXB6NG9CUENJRTFJU1ZUK0ZnYWYzSkFoNTlrYlBiTnc5QUlEeGFCdFA4RXV6U1ROd2ZieG9HYkNvYlMKV2kxVThJc0N3UUZ0OHRNMW00WlhEMUNjWklyR2RyeWVBaFZrdktJSlJpVTVRWVdJMm5xWk4rSnFRdWNtOWFkMAptWU81bUprSW9iVWE0K1pKaENQS0VkbWdwRmJSR2swd1Z1YURNOUN2NlAyc3JzWUFqYU80eTNWUDBHdk5LUkk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KQmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKS2V5IEF0dHJpYnV0ZXM6IDxObyBBdHRyaWJ1dGVzPgotLS0tLUJFR0lOIFJTQSBQUklWQVRFIEtFWS0tLS0tCk1JSUVvd0lCQUFLQ0FRRUEyTkhOVXlnZWFHVDhFTzdkUXNEQ0s3NmdlaGlGTjdUOHVrWVhKR1YwNURnZkdIZ1YKaWUxQ253cy9CeGh4VmFKZ1Q5RStUbEczcWtNNmxtQ1cwTTJmR3VtVExNMnk5SlR1Ri9mdnR6b3VmMDdRTURsRAppdkt6aFRkdVp0aW43R3NoMEdmK0wxa3JTdGhjSnVXaVd4aTVhUi8yLys4OVErTDRaaklLQWJrOVNMc29Sb3NoCmNaV2ZSTFU1YUJiN0tqTlJPWDJHVGttUUFzbDAyaXVMM1lHMERJRkZiQ085ZUJRSUMwNVN0Rkhva1pKaXB0NlgKVzdYMVc2N1ljeVBpQjF5aUxWQ012aFo5dEQwVElYVXAyczdVcys3WUFWZ2lNZHRxcjNHS0lCMW9qb0ZHQ0RPKwo5c3hOc3ZGRlRxL0E3SWtaYkRoMGdPeENPRENYRUJ0aFZLdG1md0lEQVFBQkFvSUJBUUNXOFpDSStPQWFlMXRFCmlwWjlGMmJXUDNMSExYVG84RllWZENBK1ZXZUlUazNQb2lJVWtKbVYwYVdDVWhEc3RndG81ZG9EZWo1c0NUdXIKWHZqL3luYWVyTWVxSkZZV2tld2p3WmNnTHlBWnZ3dU8xdjdmcDlFMHgvOVRHRGZuampuUE5lYXVuZHhXMGNOdAp6T1kzbDBIVkhzeTlKcGUzUURjQUpvdnk0VHY1K2hGWTRrRHhVQkdzeWp2aFNjVmdLZzV0TGtKY2xtM3NPdS9MCkd5THFwd05JM09KQWRNSXVWRDROMkJaMWFPRWFwNm1wMnk4SWUwL1I0WVdjYVo1QTRQdzd4VVBsNlNYYzl1dWEKLzc4UVRFUnRQQzZlanlDQmlFMDVhOG0zUTNpdWQzWHRubHl3czJLd2hnQkFmRTZNNHpSL2YzT1FCN1pJWE1oeQpacG1aWnc1eEFvR0JBUFluODRJcmxJUWV0V1FmdlBkTTdLemdoNlVESEN1Z25sQ0RnaHdZcFJKR2k4aE1mdVpWCnhOSXJZQUp6TFlEUTAxbEZKUkpnV1hUY2JxejlOQnoxbmhnK2NOT3oxL0tZKzM4ZXVkZWU2RE5ZbXp0UDdqRFAKMmpuYVMrZHRqQzhoQVhPYm5GcUcrTmlsTURMTHU2YVJtckphSW1ialNyZnlMaUU2bXZKN3U4MW5Bb0dCQU9GOQpnOTN3WjBtTDFyazJzNVd3SEdUTlUvSGFPdG1XUzR6N2tBN2Y0UWFSdWIrTXdwcFptbURaUEhwaVpYN0JQY1p6CmlPUFFoK3huN0lxUkdvUVdCTHlrQlZ0OHpaRm9MWkpvQ1IzbjYzbGV4NUE0cC8wUHAxZ0ZaclIreFg4UFlWb3MKM3llZWlXeVBLc1hYTmMwczVRd0haY1g2V2I4RUhUaFRYR0NCZXRjcEFvR0FNZVFKQzlJUGFQUGNhZTJ3M0NMQQpPWTNNa0ZwZ0JFdXFxc0RzeHdzTHNmZVFiMGxwMHYrQlErTzhzdUpyVDVlRHJxMUFCVWgzK1NLUVlBbDEzWVMrCnhVVXFrdzM1YjljbjZpenRGOUhDV0YzV0lLQmpzNHI5UFFxTXBkeGpORTRwUUNoQytXb3YxNkVyY3JBdVdXVmIKaUZpU2JtNFUvOUZiSGlzRnFxMy9jM01DZ1lCK3Z6U3VQZ0Z3MzcrMG9FRFZ0UVpneXVHU29wNU56Q052ZmIvOQovRzNhYVhORmJuTzhtdjBoenpvbGVNV2dPRExuSis0Y1VBejNIM3RnY0N1OWJ6citaaHYwenZRbDlhOFlDbzZGClZ1V1BkVzByYmcxUE84dE91TXFBVG5ubzc5WkMvOUgzelM5bDdCdVkxVjJTbE5leXFUM1Z5T0ZGYzZTUkVwcHMKVEp1bDhRS0JnQXhuUUI4TUE3elBVTHUxY2x5YUpMZHRFZFJQa0tXTjdsS1lwdGMwZS9WSGZTc0t4c2VXa2ZxaQp6Z1haNTFrUVRyVDZaYjZIWVJmd0MxbU1YSFdSS1J5WWpBbkN4VmltNllRZCtLVlQ0OWlSRERBaUlGb01HQTRpCnZ2Y0lsbmVxT1paUERJb0tKNjBJak8vRFpIV2t3NW1MamFJclQrcVEzWEFHZEpBMTNoY20KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K` + // nolint + authkeyInvalidP8 = `TUlHSEFnRUFNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQkcwd2F3SUJBUVFnRWJWemZQblpQeGZBeXhxRQpaVjA1bGFBb0pBbCsvNlh0Mk80bU9CNjExc09oUkFOQ0FBU2dGVEtqd0pBQVU5NWcrKy92ektXSGt6QVZtTk1JCnRCNXZUalpPT0l3bkViNzBNc1daRkl5VUZEMVA5R3dzdHo0K2FrSFg3dkk4Qkg2aEhtQm1mWlpaCg==` + // nolint + authkeyValidP8 = `LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ0ViVnpmUG5aUHhmQXl4cUUKWlYwNWxhQW9KQWwrLzZYdDJPNG1PQjYxMXNPaFJBTkNBQVNnRlRLandKQUFVOTVnKysvdnpLV0hrekFWbU5NSQp0QjV2VGpaT09Jd25FYjcwTXNXWkZJeVVGRDFQOUd3c3R6NCtha0hYN3ZJOEJINmhIbUJtZmVRbAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==` +) + +func TestDisabledAndroidIosConf(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Android.Enabled = false + cfg.Huawei.Enabled = false + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "please enable iOS, Android or Huawei config in yml config", err.Error()) +} + +func TestMissingIOSCertificate(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = "" + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "missing iOS certificate key", err.Error()) + + cfg.Ios.KeyPath = "test.pem" + err = CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "certificate file does not exist", err.Error()) +} + +func TestIOSNotificationStructure(t *testing.T) { + var dat map[string]interface{} + unix := time.Now().Unix() + + test := "test" + expectBadge := 0 + message := "Welcome notification Server" + expiration := time.Now().Unix() + req := &PushNotification{ + ApnsID: test, + Topic: test, + Expiration: &expiration, + Priority: "normal", + Message: message, + Badge: &expectBadge, + Sound: Sound{ + Critical: 1, + Name: test, + Volume: 1.0, + }, + ContentAvailable: true, + Data: D{ + "key1": "test", + "key2": 2, + }, + Category: test, + URLArgs: []string{"a", "b"}, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + alert, _ := jsonparser.GetString(data, "aps", "alert") + badge, _ := jsonparser.GetInt(data, "aps", "badge") + soundName, _ := jsonparser.GetString(data, "aps", "sound", "name") + soundCritical, _ := jsonparser.GetInt(data, "aps", "sound", "critical") + soundVolume, _ := jsonparser.GetFloat(data, "aps", "sound", "volume") + contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available") + category, _ := jsonparser.GetString(data, "aps", "category") + key1 := dat["key1"] + key2 := dat["key2"] + aps := dat["aps"].(map[string]interface{}) + urlArgs := aps["url-args"].([]interface{}) + + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, test, notification.Topic) + assert.Equal(t, unix, notification.Expiration.Unix()) + assert.Equal(t, ApnsPriorityLow, notification.Priority) + assert.Equal(t, message, alert) + assert.Equal(t, expectBadge, int(badge)) + assert.Equal(t, expectBadge, *req.Badge) + assert.Equal(t, test, soundName) + assert.Equal(t, 1.0, soundVolume) + assert.Equal(t, int64(1), soundCritical) + assert.Equal(t, 1, int(contentAvailable)) + assert.Equal(t, "test", key1) + assert.Equal(t, 2, int(key2.(float64))) + assert.Equal(t, test, category) + assert.Contains(t, urlArgs, "a") + assert.Contains(t, urlArgs, "b") +} + +func TestIOSSoundAndVolume(t *testing.T) { + var dat map[string]interface{} + + test := "test" + message := "Welcome notification Server" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Sound: Sound{ + Critical: 3, + Name: test, + Volume: 4.5, + }, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + alert, _ := jsonparser.GetString(data, "aps", "alert") + soundName, _ := jsonparser.GetString(data, "aps", "sound", "name") + soundCritical, _ := jsonparser.GetInt(data, "aps", "sound", "critical") + soundVolume, _ := jsonparser.GetFloat(data, "aps", "sound", "volume") + + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, test, notification.Topic) + assert.Equal(t, ApnsPriorityLow, notification.Priority) + assert.Equal(t, message, alert) + assert.Equal(t, test, soundName) + assert.Equal(t, 4.5, soundVolume) + assert.Equal(t, int64(3), soundCritical) + + req.SoundName = "foobar" + req.SoundVolume = 5.5 + notification = GetIOSNotification(req) + dump, _ = json.Marshal(notification.Payload) + data = []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + soundName, _ = jsonparser.GetString(data, "aps", "sound", "name") + soundVolume, _ = jsonparser.GetFloat(data, "aps", "sound", "volume") + soundCritical, _ = jsonparser.GetInt(data, "aps", "sound", "critical") + assert.Equal(t, 5.5, soundVolume) + assert.Equal(t, int64(1), soundCritical) + assert.Equal(t, "foobar", soundName) + + req = &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Sound: map[string]interface{}{ + "critical": 3, + "name": "test", + "volume": 4.5, + }, + } + + notification = GetIOSNotification(req) + dump, _ = json.Marshal(notification.Payload) + data = []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + soundName, _ = jsonparser.GetString(data, "aps", "sound", "name") + soundVolume, _ = jsonparser.GetFloat(data, "aps", "sound", "volume") + soundCritical, _ = jsonparser.GetInt(data, "aps", "sound", "critical") + assert.Equal(t, 4.5, soundVolume) + assert.Equal(t, int64(3), soundCritical) + assert.Equal(t, "test", soundName) + + req = &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Sound: "default", + } + + notification = GetIOSNotification(req) + dump, _ = json.Marshal(notification.Payload) + data = []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + soundName, _ = jsonparser.GetString(data, "aps", "sound") + assert.Equal(t, "default", soundName) +} + +func TestIOSSummaryArg(t *testing.T) { + var dat map[string]interface{} + + test := "test" + message := "Welcome notification Server" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Alert: Alert{ + SummaryArg: "test", + SummaryArgCount: 3, + }, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + panic(err) + } + + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, test, notification.Topic) + assert.Equal(t, ApnsPriorityLow, notification.Priority) + assert.Equal(t, "test", dat["aps"].(map[string]interface{})["alert"].(map[string]interface{})["summary-arg"]) + assert.Equal(t, float64(3), dat["aps"].(map[string]interface{})["alert"].(map[string]interface{})["summary-arg-count"]) +} + +// Silent Notification which payload’s aps dictionary must not contain the alert, sound, or badge keys. +// ref: https://goo.gl/m9xyqG +func TestSendZeroValueForBadgeKey(t *testing.T) { + var dat map[string]interface{} + + test := "test" + message := "Welcome notification Server" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Sound: test, + ContentAvailable: true, + MutableContent: true, + ThreadID: test, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + alert, _ := jsonparser.GetString(data, "aps", "alert") + badge, _ := jsonparser.GetInt(data, "aps", "badge") + sound, _ := jsonparser.GetString(data, "aps", "sound") + threadID, _ := jsonparser.GetString(data, "aps", "thread-id") + contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available") + mutableContent, _ := jsonparser.GetInt(data, "aps", "mutable-content") + + if req.Badge != nil { + t.Errorf("req.Badge must be nil") + } + + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, test, notification.Topic) + assert.Equal(t, ApnsPriorityLow, notification.Priority) + assert.Equal(t, message, alert) + assert.Equal(t, 0, int(badge)) + assert.Equal(t, test, sound) + assert.Equal(t, test, threadID) + assert.Equal(t, 1, int(contentAvailable)) + assert.Equal(t, 1, int(mutableContent)) + + // Add Bage + expectBadge := 10 + req.Badge = &expectBadge + + notification = GetIOSNotification(req) + + dump, _ = json.Marshal(notification.Payload) + data = []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + if req.Badge == nil { + t.Errorf("req.Badge must be equal %d", *req.Badge) + } + + badge, _ = jsonparser.GetInt(data, "aps", "badge") + assert.Equal(t, expectBadge, *req.Badge) + assert.Equal(t, expectBadge, int(badge)) +} + +// Silent Notification: +// The payload’s aps dictionary must include the content-available key with a value of 1. +// The payload’s aps dictionary must not contain the alert, sound, or badge keys. +// ref: https://goo.gl/m9xyqG +func TestCheckSilentNotification(t *testing.T) { + var dat map[string]interface{} + + test := "test" + req := &PushNotification{ + ApnsID: test, + Topic: test, + CollapseID: test, + Priority: "normal", + ContentAvailable: true, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + assert.Equal(t, test, notification.CollapseID) + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, test, notification.Topic) + assert.Nil(t, dat["aps"].(map[string]interface{})["alert"]) + assert.Nil(t, dat["aps"].(map[string]interface{})["sound"]) + assert.Nil(t, dat["aps"].(map[string]interface{})["badge"]) +} + +// URL: https://goo.gl/5xFo3C +// Example 2 +// { +// "aps" : { +// "alert" : { +// "title" : "Game Request", +// "body" : "Bob wants to play poker", +// "action-loc-key" : "PLAY" +// }, +// "badge" : 5 +// }, +// "acme1" : "bar", +// "acme2" : [ "bang", "whiz" ] +// } +func TestAlertStringExample2ForIos(t *testing.T) { + var dat map[string]interface{} + + test := "test" + title := "Game Request" + body := "Bob wants to play poker" + actionLocKey := "PLAY" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Alert: Alert{ + Title: title, + Body: body, + ActionLocKey: actionLocKey, + }, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + assert.Equal(t, title, dat["aps"].(map[string]interface{})["alert"].(map[string]interface{})["title"]) + assert.Equal(t, body, dat["aps"].(map[string]interface{})["alert"].(map[string]interface{})["body"]) + assert.Equal(t, actionLocKey, dat["aps"].(map[string]interface{})["alert"].(map[string]interface{})["action-loc-key"]) +} + +// URL: https://goo.gl/5xFo3C +// Example 3 +// { +// "aps" : { +// "alert" : "You got your emails.", +// "badge" : 9, +// "sound" : "bingbong.aiff" +// }, +// "acme1" : "bar", +// "acme2" : 42 +// } +func TestAlertStringExample3ForIos(t *testing.T) { + var dat map[string]interface{} + + test := "test" + badge := 9 + sound := "bingbong.aiff" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + ContentAvailable: true, + Message: test, + Badge: &badge, + Sound: sound, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + assert.Equal(t, sound, dat["aps"].(map[string]interface{})["sound"]) + assert.Equal(t, float64(badge), dat["aps"].(map[string]interface{})["badge"].(float64)) + assert.Equal(t, test, dat["aps"].(map[string]interface{})["alert"]) +} + +func TestMessageAndTitle(t *testing.T) { + var dat map[string]interface{} + + test := "test" + message := "Welcome notification Server" + title := "Welcome notification Server title" + req := &PushNotification{ + ApnsID: test, + Topic: test, + Priority: "normal", + Message: message, + Title: title, + ContentAvailable: true, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + alert, _ := jsonparser.GetString(data, "aps", "alert") + alertBody, _ := jsonparser.GetString(data, "aps", "alert", "body") + alertTitle, _ := jsonparser.GetString(data, "aps", "alert", "title") + + assert.Equal(t, test, notification.ApnsID) + assert.Equal(t, ApnsPriorityLow, notification.Priority) + assert.Equal(t, message, alertBody) + assert.Equal(t, title, alertTitle) + assert.NotEqual(t, message, alert) + + // Add alert body + messageOverride := "Welcome notification Server overridden" + req.Alert.Body = messageOverride + + notification = GetIOSNotification(req) + + dump, _ = json.Marshal(notification.Payload) + data = []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + alertBodyOverridden, _ := jsonparser.GetString(data, "aps", "alert", "body") + alertTitle, _ = jsonparser.GetString(data, "aps", "alert", "title") + assert.Equal(t, messageOverride, alertBodyOverridden) + assert.NotEqual(t, message, alertBodyOverridden) + assert.Equal(t, title, alertTitle) +} + +func TestIOSAlertNotificationStructure(t *testing.T) { + var dat map[string]interface{} + + test := "test" + req := &PushNotification{ + Message: "Welcome", + Title: test, + Alert: Alert{ + Action: test, + ActionLocKey: test, + Body: test, + LaunchImage: test, + LocArgs: []string{"a", "b"}, + LocKey: test, + Subtitle: test, + TitleLocArgs: []string{"a", "b"}, + TitleLocKey: test, + }, + } + + notification := GetIOSNotification(req) + + dump, _ := json.Marshal(notification.Payload) + data := []byte(string(dump)) + + if err := json.Unmarshal(data, &dat); err != nil { + log.Println(err) + panic(err) + } + + action, _ := jsonparser.GetString(data, "aps", "alert", "action") + actionLocKey, _ := jsonparser.GetString(data, "aps", "alert", "action-loc-key") + body, _ := jsonparser.GetString(data, "aps", "alert", "body") + launchImage, _ := jsonparser.GetString(data, "aps", "alert", "launch-image") + locKey, _ := jsonparser.GetString(data, "aps", "alert", "loc-key") + title, _ := jsonparser.GetString(data, "aps", "alert", "title") + subtitle, _ := jsonparser.GetString(data, "aps", "alert", "subtitle") + titleLocKey, _ := jsonparser.GetString(data, "aps", "alert", "title-loc-key") + aps := dat["aps"].(map[string]interface{}) + alert := aps["alert"].(map[string]interface{}) + titleLocArgs := alert["title-loc-args"].([]interface{}) + locArgs := alert["loc-args"].([]interface{}) + + assert.Equal(t, test, action) + assert.Equal(t, test, actionLocKey) + assert.Equal(t, test, body) + assert.Equal(t, test, launchImage) + assert.Equal(t, test, locKey) + assert.Equal(t, test, title) + assert.Equal(t, test, subtitle) + assert.Equal(t, test, titleLocKey) + assert.Contains(t, titleLocArgs, "a") + assert.Contains(t, titleLocArgs, "b") + assert.Contains(t, locArgs, "a") + assert.Contains(t, locArgs, "b") +} + +func TestWrongIosCertificateExt(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "test" + err := InitAPNSClient(cfg) + + assert.Error(t, err) + assert.Equal(t, "wrong certificate key extension", err.Error()) + + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = "abcd" + cfg.Ios.KeyType = "abcd" + err = InitAPNSClient(cfg) + + assert.Error(t, err) + assert.Equal(t, "wrong certificate key type", err.Error()) +} + +func TestAPNSClientDevHost(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.p12" + err := InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = certificateValidP12 + cfg.Ios.KeyType = "p12" + err = InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) +} + +func TestAPNSClientProdHost(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.Production = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostProduction, ApnsClient.Host) + + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = certificateValidPEM + cfg.Ios.KeyType = "pem" + err = InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostProduction, ApnsClient.Host) +} + +func TestAPNSClientInvaildToken(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-invalid.p8" + err := InitAPNSClient(cfg) + assert.Error(t, err) + + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = authkeyInvalidP8 + cfg.Ios.KeyType = "p8" + err = InitAPNSClient(cfg) + assert.Error(t, err) + + // empty key-id or team-id + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + err = InitAPNSClient(cfg) + assert.Error(t, err) + + cfg.Ios.KeyID = "key-id" + cfg.Ios.TeamID = "" + err = InitAPNSClient(cfg) + assert.Error(t, err) + + cfg.Ios.KeyID = "" + cfg.Ios.TeamID = "team-id" + err = InitAPNSClient(cfg) + assert.Error(t, err) +} + +func TestAPNSClientVaildToken(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + cfg.Ios.KeyID = "key-id" + cfg.Ios.TeamID = "team-id" + err := InitAPNSClient(cfg) + assert.NoError(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + cfg.Ios.Production = true + err = InitAPNSClient(cfg) + assert.NoError(t, err) + assert.Equal(t, apns2.HostProduction, ApnsClient.Host) + + // test base64 + cfg.Ios.Production = false + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = authkeyValidP8 + cfg.Ios.KeyType = "p8" + err = InitAPNSClient(cfg) + assert.NoError(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + cfg.Ios.Production = true + err = InitAPNSClient(cfg) + assert.NoError(t, err) + assert.Equal(t, apns2.HostProduction, ApnsClient.Host) +} + +func TestAPNSClientUseProxy(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.p12" + cfg.Core.HTTPProxy = "http://127.0.0.1:8080" + _ = SetProxy(cfg.Core.HTTPProxy) + err := InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + req, _ := http.NewRequest("GET", apns2.HostDevelopment, nil) + actualProxyURL, err := ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) + assert.Nil(t, err) + + expectedProxyURL, _ := url.ParseRequestURI(cfg.Core.HTTPProxy) + assert.Equal(t, expectedProxyURL, actualProxyURL) + + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + cfg.Ios.TeamID = "example.team" + cfg.Ios.KeyID = "example.key" + err = InitAPNSClient(cfg) + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + assert.NotNil(t, ApnsClient.Token) + + req, _ = http.NewRequest("GET", apns2.HostDevelopment, nil) + actualProxyURL, err = ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) + assert.Nil(t, err) + + expectedProxyURL, _ = url.ParseRequestURI(cfg.Core.HTTPProxy) + assert.Equal(t, expectedProxyURL, actualProxyURL) + + http.DefaultTransport.(*http.Transport).Proxy = nil +} + +func TestPushToIOS(t *testing.T) { + cfg, _ := config.LoadConf() + MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) + assert.Nil(t, err) + err = status.InitAppStatus(cfg) + assert.Nil(t, err) + + req := &PushNotification{ + // nolint + Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7", "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef1"}, + Platform: 1, + Message: "Welcome", + } + + // send fail + resp, err := PushToIOS(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 2) +} + +func TestApnsHostFromRequest(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) + assert.Nil(t, err) + err = status.InitAppStatus(cfg) + assert.Nil(t, err) + + req := &PushNotification{ + Production: true, + } + client := getApnsClient(cfg, req) + assert.Equal(t, apns2.HostProduction, client.Host) + + req = &PushNotification{ + Development: true, + } + client = getApnsClient(cfg, req) + assert.Equal(t, apns2.HostDevelopment, client.Host) + + req = &PushNotification{} + cfg.Ios.Production = true + client = getApnsClient(cfg, req) + assert.Equal(t, apns2.HostProduction, client.Host) + + cfg.Ios.Production = false + client = getApnsClient(cfg, req) + assert.Equal(t, apns2.HostDevelopment, client.Host) +} diff --git a/push/gorush-with-mipush/src/notify/notification_fcm.go b/push/gorush-with-mipush/src/notify/notification_fcm.go new file mode 100644 index 0000000..772393b --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_fcm.go @@ -0,0 +1,246 @@ +package notify + +import ( + "errors" + "fmt" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + + "github.com/appleboy/go-fcm" +) + +// InitFCMClient use for initialize FCM Client. +func InitFCMClient(cfg *config.ConfYaml, key string) (*fcm.Client, error) { + var err error + + if key == "" && cfg.Android.APIKey == "" { + return nil, errors.New("Missing Android API Key") + } + + if key != "" && key != cfg.Android.APIKey { + return fcm.NewClient(key) + } + + if FCMClient == nil { + FCMClient, err = fcm.NewClient(cfg.Android.APIKey) + return FCMClient, err + } + + return FCMClient, nil +} + +// GetAndroidNotification use for define Android notification. +// HTTP Connection Server Reference for Android +// https://firebase.google.com/docs/cloud-messaging/http-server-ref +func GetAndroidNotification(req *PushNotification) *fcm.Message { + notification := &fcm.Message{ + To: req.To, + Condition: req.Condition, + CollapseKey: req.CollapseKey, + ContentAvailable: req.ContentAvailable, + MutableContent: req.MutableContent, + DelayWhileIdle: req.DelayWhileIdle, + TimeToLive: req.TimeToLive, + RestrictedPackageName: req.RestrictedPackageName, + DryRun: req.DryRun, + } + + if len(req.Tokens) > 0 { + notification.RegistrationIDs = req.Tokens + } + + if req.Priority == "high" || req.Priority == "normal" { + notification.Priority = req.Priority + } + + // Add another field + if len(req.Data) > 0 { + notification.Data = make(map[string]interface{}) + for k, v := range req.Data { + notification.Data[k] = v + } + } + + n := &fcm.Notification{} + isNotificationSet := false + if req.Notification != nil { + isNotificationSet = true + n = req.Notification + } + + if len(req.Message) > 0 { + isNotificationSet = true + n.Body = req.Message + } + + if len(req.Title) > 0 { + isNotificationSet = true + n.Title = req.Title + } + + if len(req.Image) > 0 { + isNotificationSet = true + n.Image = req.Image + } + + if v, ok := req.Sound.(string); ok && len(v) > 0 { + isNotificationSet = true + n.Sound = v + } + + if isNotificationSet { + notification.Notification = n + } + + // handle iOS apns in fcm + + if len(req.Apns) > 0 { + notification.Apns = req.Apns + } + + return notification +} + +// PushToAndroid provide send notification to Android server. +func PushToAndroid(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for Android") + + var ( + client *fcm.Client + retryCount = 0 + maxRetry = cfg.Android.MaxRetry + ) + + if req.Retry > 0 && req.Retry < maxRetry { + maxRetry = req.Retry + } + + // check message + err = CheckMessage(req) + if err != nil { + logx.LogError.Error("request error: " + err.Error()) + return + } + + resp = &ResponsePush{} + +Retry: + notification := GetAndroidNotification(req) + + if req.APIKey != "" { + client, err = InitFCMClient(cfg, req.APIKey) + } else { + client, err = InitFCMClient(cfg, cfg.Android.APIKey) + } + + if err != nil { + // FCM server error + logx.LogError.Error("FCM server error: " + err.Error()) + return + } + + res, err := client.Send(notification) + if err != nil { + // Send Message error + logx.LogError.Error("FCM server send message error: " + err.Error()) + + if req.IsTopic() { + errLog := logPush(cfg, core.FailedPush, req.To, req, err) + resp.Logs = append(resp.Logs, errLog) + status.StatStorage.AddAndroidError(1) + } else { + for _, token := range req.Tokens { + errLog := logPush(cfg, core.FailedPush, token, req, err) + resp.Logs = append(resp.Logs, errLog) + } + status.StatStorage.AddAndroidError(int64(len(req.Tokens))) + } + return + } + + if !req.IsTopic() { + logx.LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) + } + + status.StatStorage.AddAndroidSuccess(int64(res.Success)) + status.StatStorage.AddAndroidError(int64(res.Failure)) + + var newTokens []string + // result from Send messages to specific devices + for k, result := range res.Results { + to := "" + if k < len(req.Tokens) { + to = req.Tokens[k] + } else { + to = req.To + } + + if result.Error != nil { + // We should retry only "retryable" statuses. More info about response: + // https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-plain-text + if !result.Unregistered() { + newTokens = append(newTokens, to) + } + + errLog := logPush(cfg, core.FailedPush, to, req, result.Error) + resp.Logs = append(resp.Logs, errLog) + continue + } + + logPush(cfg, core.SucceededPush, to, req, nil) + } + + // result from Send messages to topics + if req.IsTopic() { + to := "" + if req.To != "" { + to = req.To + } else { + to = req.Condition + } + logx.LogAccess.Debug("Send Topic Message: ", to) + // Success + if res.MessageID != 0 { + logPush(cfg, core.SucceededPush, to, req, nil) + } else { + // failure + errLog := logPush(cfg, core.FailedPush, to, req, res.Error) + resp.Logs = append(resp.Logs, errLog) + } + } + + // Device Group HTTP Response + if len(res.FailedRegistrationIDs) > 0 { + newTokens = append(newTokens, res.FailedRegistrationIDs...) + + // nolint + errLog := logPush(cfg, core.FailedPush, notification.To, req, errors.New("device group: partial success or all fails")) + resp.Logs = append(resp.Logs, errLog) + } + + if len(newTokens) > 0 && retryCount < maxRetry { + retryCount++ + + // resend fail token + req.Tokens = newTokens + goto Retry + } + + return resp, nil +} + +func logPush(cfg *config.ConfYaml, status, token string, req *PushNotification, err error) logx.LogPushEntry { + return logx.LogPush(&logx.InputLog{ + ID: req.ID, + Status: status, + Token: token, + Message: req.Message, + Platform: req.Platform, + Error: err, + HideToken: cfg.Log.HideToken, + Format: cfg.Log.Format, + }) +} diff --git a/push/gorush-with-mipush/src/notify/notification_fcm_test.go b/push/gorush-with-mipush/src/notify/notification_fcm_test.go new file mode 100644 index 0000000..f07c337 --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_fcm_test.go @@ -0,0 +1,272 @@ +package notify + +import ( + "os" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + + "github.com/appleboy/go-fcm" + "github.com/stretchr/testify/assert" +) + +func TestMissingAndroidAPIKey(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = "" + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "Missing Android API Key", err.Error()) +} + +func TestMissingKeyForInitFCMClient(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Android.APIKey = "" + client, err := InitFCMClient(cfg, "") + + assert.Nil(t, client) + assert.Error(t, err) + assert.Equal(t, "Missing Android API Key", err.Error()) +} + +func TestPushToAndroidWrongToken(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + req := &PushNotification{ + Tokens: []string{"aaaaaa", "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome", + } + + // Android Success count: 0, Failure count: 2 + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 2) +} + +func TestPushToAndroidRightTokenForJSONLog(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + // log for json + cfg.Log.Format = "json" + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := &PushNotification{ + Tokens: []string{androidToken}, + Platform: core.PlatFormAndroid, + Message: "Welcome", + } + + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 0) +} + +func TestPushToAndroidRightTokenForStringLog(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := &PushNotification{ + Tokens: []string{androidToken}, + Platform: core.PlatFormAndroid, + Message: "Welcome", + } + + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 0) +} + +func TestOverwriteAndroidAPIKey(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Core.Sync = true + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := &PushNotification{ + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome", + // overwrite android api key + APIKey: "1234", + } + + // FCM server error: 401 error: 401 Unauthorized (Wrong API Key) + resp, err := PushToAndroid(req, cfg) + + assert.Error(t, err) + assert.Len(t, resp.Logs, 2) +} + +func TestFCMMessage(t *testing.T) { + var err error + + // the message must specify at least one registration ID + req := &PushNotification{ + Message: "Test", + Tokens: []string{}, + } + + err = CheckMessage(req) + assert.Error(t, err) + + // the token must not be empty + req = &PushNotification{ + Message: "Test", + Tokens: []string{""}, + } + + err = CheckMessage(req) + assert.Error(t, err) + + // ignore check token length if send topic message + req = &PushNotification{ + Message: "Test", + Platform: core.PlatFormAndroid, + To: "/topics/foo-bar", + } + + err = CheckMessage(req) + assert.NoError(t, err) + + // "condition": "'dogs' in topics || 'cats' in topics", + req = &PushNotification{ + Message: "Test", + Platform: core.PlatFormAndroid, + Condition: "'dogs' in topics || 'cats' in topics", + } + + err = CheckMessage(req) + assert.NoError(t, err) + + // the message may specify at most 1000 registration IDs + req = &PushNotification{ + Message: "Test", + Platform: core.PlatFormAndroid, + Tokens: make([]string, 1001), + } + + err = CheckMessage(req) + assert.Error(t, err) + + // the message's TimeToLive field must be an integer + // between 0 and 2419200 (4 weeks) + timeToLive := uint(2419201) + req = &PushNotification{ + Message: "Test", + Platform: core.PlatFormAndroid, + Tokens: []string{"XXXXXXXXX"}, + TimeToLive: &timeToLive, + } + + err = CheckMessage(req) + assert.Error(t, err) + + // Pass + timeToLive = uint(86400) + req = &PushNotification{ + Message: "Test", + Platform: core.PlatFormAndroid, + Tokens: []string{"XXXXXXXXX"}, + TimeToLive: &timeToLive, + } + + err = CheckMessage(req) + assert.NoError(t, err) +} + +func TestCheckAndroidMessage(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + timeToLive := uint(2419201) + req := &PushNotification{ + Tokens: []string{"aaaaaa", "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome", + TimeToLive: &timeToLive, + } + + // the message's TimeToLive field must be an integer between 0 and 2419200 (4 weeks) + resp, err := PushToAndroid(req, cfg) + assert.NotNil(t, err) + assert.Nil(t, resp) +} + +func TestAndroidNotificationStructure(t *testing.T) { + test := "test" + timeToLive := uint(100) + req := &PushNotification{ + Tokens: []string{"a", "b"}, + Message: "Welcome", + To: test, + Priority: "high", + CollapseKey: "1", + ContentAvailable: true, + DelayWhileIdle: true, + TimeToLive: &timeToLive, + RestrictedPackageName: test, + DryRun: true, + Title: test, + Sound: test, + Data: D{ + "a": "1", + "b": 2, + }, + Notification: &fcm.Notification{ + Color: test, + Tag: test, + Body: "", + }, + } + + notification := GetAndroidNotification(req) + + assert.Equal(t, test, notification.To) + assert.Equal(t, "high", notification.Priority) + assert.Equal(t, "1", notification.CollapseKey) + assert.True(t, notification.ContentAvailable) + assert.True(t, notification.DelayWhileIdle) + assert.Equal(t, uint(100), *notification.TimeToLive) + assert.Equal(t, test, notification.RestrictedPackageName) + assert.True(t, notification.DryRun) + assert.Equal(t, test, notification.Notification.Title) + assert.Equal(t, test, notification.Notification.Sound) + assert.Equal(t, test, notification.Notification.Color) + assert.Equal(t, test, notification.Notification.Tag) + assert.Equal(t, "Welcome", notification.Notification.Body) + assert.Equal(t, "1", notification.Data["a"]) + assert.Equal(t, 2, notification.Data["b"]) + + // test empty body + req = &PushNotification{ + Tokens: []string{"a", "b"}, + To: test, + Notification: &fcm.Notification{ + Body: "", + }, + } + notification = GetAndroidNotification(req) + + assert.Equal(t, test, notification.To) + assert.Equal(t, "", notification.Notification.Body) +} diff --git a/push/gorush-with-mipush/src/notify/notification_hms.go b/push/gorush-with-mipush/src/notify/notification_hms.go new file mode 100644 index 0000000..1bdfc15 --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_hms.go @@ -0,0 +1,226 @@ +package notify + +import ( + "context" + "errors" + "sync" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + c "github.com/msalihkarakasli/go-hms-push/push/config" + client "github.com/msalihkarakasli/go-hms-push/push/core" + "github.com/msalihkarakasli/go-hms-push/push/model" +) + +var ( + pushError error + pushClient *client.HMSClient + once sync.Once +) + +// GetPushClient use for create HMS Push. +func GetPushClient(conf *c.Config) (*client.HMSClient, error) { + once.Do(func() { + client, err := client.NewHttpClient(conf) + if err != nil { + panic(err) + } + pushClient = client + pushError = err + }) + + return pushClient, pushError +} + +// InitHMSClient use for initialize HMS Client. +func InitHMSClient(cfg *config.ConfYaml, appSecret, appID string) (*client.HMSClient, error) { + if appSecret == "" { + return nil, errors.New("Missing Huawei App Secret") + } + + if appID == "" { + return nil, errors.New("Missing Huawei App ID") + } + + conf := &c.Config{ + AppId: appID, + AppSecret: appSecret, + AuthUrl: "https://oauth-login.cloud.huawei.com/oauth2/v3/token", + PushUrl: "https://push-api.cloud.huawei.com", + } + + if appSecret != cfg.Huawei.AppSecret || appID != cfg.Huawei.AppID { + return GetPushClient(conf) + } + + if HMSClient == nil { + return GetPushClient(conf) + } + + return HMSClient, nil +} + +// GetHuaweiNotification use for define HMS notification. +// HTTP Connection Server Reference for HMS +// https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi +func GetHuaweiNotification(req *PushNotification) (*model.MessageRequest, error) { + msgRequest := model.NewNotificationMsgRequest() + + msgRequest.Message.Android = model.GetDefaultAndroid() + + if len(req.Tokens) > 0 { + msgRequest.Message.Token = req.Tokens + } + + if len(req.Topic) > 0 { + msgRequest.Message.Topic = req.Topic + } + + if len(req.To) > 0 { + msgRequest.Message.Topic = req.To + } + + if len(req.Condition) > 0 { + msgRequest.Message.Condition = req.Condition + } + + if req.Priority == "high" { + msgRequest.Message.Android.Urgency = "HIGH" + } + + // if req.HuaweiCollapseKey != nil { + msgRequest.Message.Android.CollapseKey = req.HuaweiCollapseKey + //} + + if len(req.Category) > 0 { + msgRequest.Message.Android.Category = req.Category + } + + if len(req.HuaweiTTL) > 0 { + msgRequest.Message.Android.TTL = req.HuaweiTTL + } + + if len(req.BiTag) > 0 { + msgRequest.Message.Android.BiTag = req.BiTag + } + + msgRequest.Message.Android.FastAppTarget = req.FastAppTarget + + // Add data fields + if len(req.HuaweiData) > 0 { + msgRequest.Message.Data = req.HuaweiData + } + + // Notification Message + if req.HuaweiNotification != nil { + msgRequest.Message.Android.Notification = req.HuaweiNotification + + if msgRequest.Message.Android.Notification.ClickAction == nil { + msgRequest.Message.Android.Notification.ClickAction = model.GetDefaultClickAction() + } + } + + setDefaultAndroidNotification := func() { + if msgRequest.Message.Android == nil { + msgRequest.Message.Android.Notification = model.GetDefaultAndroidNotification() + } + } + + if len(req.Message) > 0 { + setDefaultAndroidNotification() + msgRequest.Message.Android.Notification.Body = req.Message + } + + if len(req.Title) > 0 { + setDefaultAndroidNotification() + msgRequest.Message.Android.Notification.Title = req.Title + } + + if len(req.Image) > 0 { + setDefaultAndroidNotification() + msgRequest.Message.Android.Notification.Image = req.Image + } + + if v, ok := req.Sound.(string); ok && len(v) > 0 { + setDefaultAndroidNotification() + msgRequest.Message.Android.Notification.Sound = v + } else if msgRequest.Message.Android.Notification != nil { + msgRequest.Message.Android.Notification.DefaultSound = true + } + + b, err := json.Marshal(msgRequest) + if err != nil { + logx.LogError.Error("Failed to marshal the default message! Error is " + err.Error()) + return nil, err + } + + logx.LogAccess.Debugf("Default message is %s", string(b)) + return msgRequest, nil +} + +// PushToHuawei provide send notification to Android server. +func PushToHuawei(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for Huawei") + + var ( + client *client.HMSClient + retryCount = 0 + maxRetry = cfg.Huawei.MaxRetry + ) + + if req.Retry > 0 && req.Retry < maxRetry { + maxRetry = req.Retry + } + + // check message + err = CheckMessage(req) + if err != nil { + logx.LogError.Error("request error: " + err.Error()) + return + } + + client, err = InitHMSClient(cfg, cfg.Huawei.AppSecret, cfg.Huawei.AppID) + + if err != nil { + // HMS server error + logx.LogError.Error("HMS server error: " + err.Error()) + return + } + + resp = &ResponsePush{} + +Retry: + isError := false + + notification, _ := GetHuaweiNotification(req) + + res, err := client.SendMessage(context.Background(), notification) + if err != nil { + // Send Message error + errLog := logPush(cfg, core.FailedPush, req.To, req, err) + resp.Logs = append(resp.Logs, errLog) + logx.LogError.Error("HMS server send message error: " + err.Error()) + return + } + + // Huawei Push Send API does not support exact results for each token + if res.Code == "80000000" { + status.StatStorage.AddHuaweiSuccess(int64(1)) + logx.LogAccess.Debug("Huwaei Send Notification is completed successfully!") + } else { + isError = true + status.StatStorage.AddHuaweiError(int64(1)) + logx.LogAccess.Debug("Huawei Send Notification is failed! Code: " + res.Code) + } + + if isError && retryCount < maxRetry { + retryCount++ + + // resend all tokens + goto Retry + } + + return resp, nil +} diff --git a/push/gorush-with-mipush/src/notify/notification_hms_test.go b/push/gorush-with-mipush/src/notify/notification_hms_test.go new file mode 100644 index 0000000..27e1d84 --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_hms_test.go @@ -0,0 +1,50 @@ +package notify + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestMissingHuaweiAppSecret(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Huawei.Enabled = true + cfg.Huawei.AppSecret = "" + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App Secret", err.Error()) +} + +func TestMissingHuaweiAppID(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Huawei.Enabled = true + cfg.Huawei.AppID = "" + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App ID", err.Error()) +} + +func TestMissingAppSecretForInitHMSClient(t *testing.T) { + cfg, _ := config.LoadConf() + client, err := InitHMSClient(cfg, "", "APP_SECRET") + + assert.Nil(t, client) + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App Secret", err.Error()) +} + +func TestMissingAppIDForInitHMSClient(t *testing.T) { + cfg, _ := config.LoadConf() + client, err := InitHMSClient(cfg, "APP_ID", "") + + assert.Nil(t, client) + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App ID", err.Error()) +} diff --git a/push/gorush-with-mipush/src/notify/notification_mipush.go b/push/gorush-with-mipush/src/notify/notification_mipush.go new file mode 100644 index 0000000..18a938c --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_mipush.go @@ -0,0 +1,311 @@ +package notify + +import ( + "context" + "errors" + "fmt" + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + "github.com/geek-go/xmpush" + "strconv" + "strings" + "sync" +) + +var ( + mipushError error + mipushClient *XMPush + mipushOnce sync.Once +) + +// GetMIPushClient use for create HMS Push. +func GetMIPushClient(conf *XmpushConfig) (*XMPush, error) { + once.Do(func() { + client := &XMPush{ + Config: &XmpushConfig{ + AppSecret: conf.AppSecret, + Package: conf.Package, + }, + } + mipushClient = client + pushError = nil + }) + + return mipushClient, pushError +} + +func InitMIPUSHClient(cfg *config.ConfYaml, appSecret, pkg string) (*XMPush, error) { + if appSecret == "" { + return nil, errors.New("Missing MI App Secret") + } + + if pkg == "" { + return nil, errors.New("Missing MI App Package") + } + conf := &XmpushConfig{ + AppSecret: appSecret, + Package: pkg, + } + if appSecret != cfg.MI.AppSecret || pkg != cfg.MI.Package { + return GetMIPushClient(conf) + } + if MIPUSHClient == nil { + return GetMIPushClient(conf) + } + return MIPUSHClient, nil + +} + +func GetMINotification(req *PushNotification, pkg string) (*xmpush.Message, error) { + payload := &Payload{ + PushTitle: req.Title, + PushBody: req.Message, + IsShowNotify: "1", + } + payloadStr, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + //是否透传 + passThrough := 1 + if payload.IsShowNotify == "1" { + passThrough = 0 //通知栏消息 + } + + message := &xmpush.Message{ + Payload: string(payloadStr), + Title: payload.PushTitle, + Description: payload.PushBody, + PassThrough: int32(passThrough), + NotifyType: 1, + RestrictedPackageName: pkg, + Extra: map[string]string{ + "notify_foreground": "1", + }, + } + if len(req.Tokens) > 0 { + for i, token := range req.Tokens { + if i == 0 { + message.RegistrationId = token + } else { + message.RegistrationId = message.RegistrationId + "," + token + } + } + } + return message, nil +} + +func PushToMI(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for MI") + + var ( + client *XMPush + retryCount = 0 + maxRetry = cfg.MI.MaxRetry + ) + + if req.Retry > 0 && req.Retry < maxRetry { + maxRetry = req.Retry + } + + client, err = InitMIPUSHClient(cfg, cfg.MI.AppSecret, cfg.MI.Package) + if err != nil { + // MI server error + logx.LogError.Error("MI server error: " + err.Error()) + return + } + + resp = &ResponsePush{} + +Retry: + isError := false + notification, err1 := GetMINotification(req, client.Config.Package) + if err1 != nil { + return nil, err1 + } + + res, err := client.SendMessage(context.Background(), notification) + if err != nil { + // Send Message error + errLog := logPush(cfg, core.FailedPush, req.To, req, err) + resp.Logs = append(resp.Logs, errLog) + logx.LogError.Error("HMS server send message error: " + err.Error()) + return + } + // Huawei Push Send API does not support exact results for each token + if res.Code == 0 { + status.StatStorage.AddMISuccess(int64(1)) + logx.LogAccess.Debug("MI Send Notification is completed successfully!") + } else { + isError = true + status.StatStorage.AddMIError(int64(1)) + logx.LogAccess.Debug("MI Send Notification is failed! Code: " + strconv.Itoa(int(res.Code))) + } + + if isError && retryCount < maxRetry { + retryCount++ + + // resend all tokens + goto Retry + } + + return nil, nil +} + +var Cfg = &XmpushConfig{ + AppSecret: "", + Package: "", +} + +// XmpushConfig ... +type XmpushConfig struct { + AppSecret string `toml:"app_secret"` + Package string `toml:"package"` +} + +//Payload 消息payload,根据业务自定义 +type Payload struct { + PushTitle string `json:"push_title"` + PushBody string `json:"push_body"` + IsShowNotify string `json:"is_show_notify"` + Ext string `json:"ext"` +} + +type XMPush struct { + Config *XmpushConfig +} + +// NewXmPush 获取实例 +func NewXmPush(config *XmpushConfig) (*XMPush, error) { + + if config.Package == "" || config.AppSecret == "" { + return nil, errors.New("please check config") + } + + xm := &XMPush{ + Config: config, + } + + return xm, nil +} + +func (m *XMPush) SendMessage(ctx context.Context, notification *xmpush.Message) (*xmpush.Result, error) { + // 通过 regID 推送 + if notification.RegistrationId != "" { + return xmpush.SendMessageByRegIds(m.Config.AppSecret, notification) + } + // 群推 + return xmpush.SendMessageAll(m.Config.AppSecret, notification) + +} + +//SendByCid 根据用户cid推送 +func (m *XMPush) SendByCid(cid string, payload *Payload) error { + return m.SendByCids([]string{cid}, payload) +} + +//SendByCids 根据用户cids批量推送 +func (m *XMPush) SendByCids(cids []string, payload *Payload) error { + + payloadStr, _ := json.Marshal(payload) + + //是否透传 + passThrough := 1 + if payload.IsShowNotify == "1" { + passThrough = 0 //通知栏消息 + } + + message := &xmpush.Message{ + Payload: string(payloadStr), + Title: payload.PushTitle, + Description: payload.PushBody, + PassThrough: int32(passThrough), + NotifyType: 1, + RestrictedPackageName: m.Config.Package, + Extra: map[string]string{ + "notify_foreground": "1", + }, + } + + message.RegistrationId = strings.Join(cids, ",") + + result, err := xmpush.SendMessageByRegIds(m.Config.AppSecret, message) + if err != nil { + return err + } + + fmt.Println(result) + + return nil +} + +// SendByAliases 根据别名推送批量推送 +func (m *XMPush) SendByAliases(aliases []string, payload *Payload) error { + + payloadStr, _ := json.Marshal(payload) + + //是否透传 + passThrough := 1 + if payload.IsShowNotify == "1" { + passThrough = 0 //通知栏消息 + } + + message := &xmpush.Message{ + Payload: string(payloadStr), + Title: payload.PushTitle, + Description: payload.PushBody, + PassThrough: int32(passThrough), + NotifyType: 1, + RestrictedPackageName: m.Config.Package, + Extra: map[string]string{ + "notify_foreground": "1", + }, + } + + message.Alias = strings.Join(aliases, ",") + + result, err := xmpush.SendMessageByRegAliasIds(m.Config.AppSecret, message) + if err != nil { + return err + } + + fmt.Println(result) + + return nil +} + +// SendAll 推送给所有人 +func (m *XMPush) SendAll(payload *Payload) error { + + payloadStr, _ := json.Marshal(payload) + + //是否透传 + passThrough := 1 + if payload.IsShowNotify == "1" { + passThrough = 0 //通知栏消息 + } + + message := &xmpush.Message{ + Payload: string(payloadStr), + Title: payload.PushTitle, + Description: payload.PushBody, + PassThrough: int32(passThrough), + NotifyType: 1, + RestrictedPackageName: m.Config.Package, + Extra: map[string]string{ + "notify_foreground": "1", + }, + } + + result, err := xmpush.SendMessageAll(m.Config.AppSecret, message) + if err != nil { + return err + } + + fmt.Println(result) + + return nil +} diff --git a/push/gorush-with-mipush/src/notify/notification_mipush_test.go b/push/gorush-with-mipush/src/notify/notification_mipush_test.go new file mode 100644 index 0000000..58b11a6 --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_mipush_test.go @@ -0,0 +1,59 @@ +package notify + +import ( + "os" + "testing" +) + +//测试单推 +func TestXmPush_SendByCid(t *testing.T) { + iXmPush, err := NewXmPush(Cfg) + if err != nil { + t.Error(err) + os.Exit(1) + } + + cid := "xxx" + payLoad := Payload{"这是测试title", "这是测试内容", "1", ""} + err = iXmPush.SendByCid(cid, &payLoad) + if err != nil { + t.Error(err) + } else { + t.Log("ok") + } +} + +//测试群推 +func TestXmPush_SendByCids(t *testing.T) { + iXmPush, err := NewXmPush(Cfg) + if err != nil { + t.Error(err) + os.Exit(1) + } + + cids := []string{"xxx"} + payLoad := Payload{"这是测试title", "这是测试内容", "1", ""} + err = iXmPush.SendByCids(cids, &payLoad) + if err != nil { + t.Error(err) + } else { + t.Log("ok") + } +} + +//测试全推 +func TestXmPush_SendAll(t *testing.T) { + iXmPush, err := NewXmPush(Cfg) + if err != nil { + t.Error(err) + os.Exit(1) + } + + payLoad := Payload{"这是测试title", "这是测试内容", "1", ""} + err = iXmPush.SendAll(&payLoad) + if err != nil { + t.Error(err) + } else { + t.Log("ok") + } +} diff --git a/push/gorush-with-mipush/src/notify/notification_test.go b/push/gorush-with-mipush/src/notify/notification_test.go new file mode 100644 index 0000000..559a5bb --- /dev/null +++ b/push/gorush-with-mipush/src/notify/notification_test.go @@ -0,0 +1,34 @@ +package notify + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestCorrectConf(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = "xxxxx" + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + + err := CheckPushConf(cfg) + + assert.NoError(t, err) +} + +func TestSetProxyURL(t *testing.T) { + err := SetProxy("87.236.233.92:8080") + assert.Error(t, err) + assert.Equal(t, "parse \"87.236.233.92:8080\": invalid URI for request", err.Error()) + + err = SetProxy("a.html") + assert.Error(t, err) + + err = SetProxy("http://87.236.233.92:8080") + assert.NoError(t, err) +} diff --git a/push/gorush-with-mipush/src/pipeline.libsonnet b/push/gorush-with-mipush/src/pipeline.libsonnet new file mode 100644 index 0000000..e595670 --- /dev/null +++ b/push/gorush-with-mipush/src/pipeline.libsonnet @@ -0,0 +1,273 @@ +{ + test:: { + kind: 'pipeline', + name: 'testing', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'lint', + image: 'golangci/golangci-lint:v1.41.1', + pull: 'always', + commands: [ + 'golangci-lint run -v', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'embedmd', + image: 'golang:1.17', + pull: 'always', + commands: [ + 'make embedmd', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'hadolint', + image: 'hadolint/hadolint:latest-debian', + pull: 'always', + commands: [ + 'hadolint --version', + 'hadolint docker/Dockerfile.linux.amd64', + 'hadolint docker/Dockerfile.linux.arm64', + 'hadolint docker/Dockerfile.linux.arm', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'test', + image: 'golang:1.17', + pull: 'always', + environment: { + ANDROID_API_KEY: { 'from_secret': 'android_api_key' }, + ANDROID_TEST_TOKEN: { 'from_secret': 'android_test_token' }, + }, + commands: [ + 'make test', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'codecov', + image: 'robertstettner/drone-codecov', + pull: 'always', + settings: { + token: { 'from_secret': 'codecov_token' }, + }, + }, + ], + volumes: [ + { + name: 'gopath', + temp: {}, + }, + ], + services: [ + { + name: 'redis', + image: 'redis', + }, + { + name: 'nsq', + image: 'nsqio/nsq', + commands: [ + "/nsqd", + ], + }, + ], + }, + + build(name, os='linux', arch='amd64'):: { + kind: 'pipeline', + name: os + '-' + arch, + platform: { + os: os, + arch: arch, + }, + steps: [ + { + name: 'build-push', + image: 'golang:1.17', + pull: 'always', + environment: { + CGO_ENABLED: '0', + }, + commands: [ + 'go build -v -ldflags \'-X main.build=${DRONE_BUILD_NUMBER}\' -a -o release/' + os + '/' + arch + '/' + name, + ], + when: { + event: { + exclude: [ 'tag' ], + }, + }, + }, + // { + // name: 'build-push-lambda', + // image: 'golang:1.17', + // pull: 'always', + // environment: { + // CGO_ENABLED: '0', + // }, + // commands: [ + // 'go build -v -tags \'lambda\' -ldflags \'-X main.build=${DRONE_BUILD_NUMBER}\' -a -o release/' + os + '/' + arch + '/lambda/' + name, + // ], + // when: { + // event: { + // exclude: [ 'tag' ], + // }, + // }, + // }, + { + name: 'build-tag', + image: 'golang:1.17', + pull: 'always', + environment: { + CGO_ENABLED: '0', + }, + commands: [ + 'go build -v -ldflags \'-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\' -a -o release/' + os + '/' + arch + '/' + name, + ], + when: { + event: [ 'tag' ], + }, + }, + { + name: 'executable', + image: 'golang:1.17', + pull: 'always', + commands: [ + './release/' + os + '/' + arch + '/' + name + ' --help', + ], + }, + { + name: 'publish', + image: 'plugins/docker:' + os + '-' + arch, + pull: 'always', + settings: { + daemon_off: 'false', + auto_tag: true, + auto_tag_suffix: os + '-' + arch, + dockerfile: 'docker/Dockerfile.' + os + '.' + arch, + repo: 'appleboy/' + name, + cache_from: 'appleboy/' + name, + username: { 'from_secret': 'docker_username' }, + password: { 'from_secret': 'docker_password' }, + }, + when: { + event: { + exclude: [ 'pull_request' ], + }, + }, + }, + ], + depends_on: [ + 'testing', + ], + trigger: { + ref: [ + 'refs/heads/master', + 'refs/pull/**', + 'refs/tags/**', + ], + }, + }, + + release:: { + kind: 'pipeline', + name: 'release-binary', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'build-all-binary', + image: 'golang:1.17', + pull: 'always', + commands: [ + 'make release' + ], + when: { + event: [ 'tag' ], + }, + }, + { + name: 'deploy-all-binary', + image: 'plugins/github-release', + pull: 'always', + settings: { + files: [ 'dist/release/*' ], + api_key: { 'from_secret': 'github_release_api_key' }, + }, + when: { + event: [ 'tag' ], + }, + }, + ], + depends_on: [ + 'testing', + ], + trigger: { + ref: [ + 'refs/tags/**', + ], + }, + }, + + notifications(os='linux', arch='amd64', depends_on=[]):: { + kind: 'pipeline', + name: 'notifications', + platform: { + os: os, + arch: arch, + }, + steps: [ + { + name: 'manifest', + image: 'plugins/manifest', + pull: 'always', + settings: { + username: { from_secret: 'docker_username' }, + password: { from_secret: 'docker_password' }, + spec: 'docker/manifest.tmpl', + ignore_missing: true, + }, + }, + ], + depends_on: depends_on, + trigger: { + ref: [ + 'refs/heads/master', + 'refs/tags/**', + ], + }, + }, + + signature(key):: { + kind: 'signature', + hmac: key, + }, +} diff --git a/push/gorush-with-mipush/src/router/server.go b/push/gorush-with-mipush/src/router/server.go new file mode 100644 index 0000000..7abada9 --- /dev/null +++ b/push/gorush-with-mipush/src/router/server.go @@ -0,0 +1,322 @@ +package router + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "os" + "sync" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/metric" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/status" + + api "github.com/appleboy/gin-status-api" + "github.com/gin-contrib/logger" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/golang-queue/queue" + "github.com/mattn/go-isatty" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/thoas/stats" + "golang.org/x/crypto/acme/autocert" +) + +var doOnce sync.Once + +func abortWithError(c *gin.Context, code int, message string) { + c.AbortWithStatusJSON(code, gin.H{ + "code": code, + "message": message, + }) +} + +func rootHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "text": "Welcome to notification server.", + }) +} + +func heartbeatHandler(c *gin.Context) { + c.AbortWithStatus(http.StatusOK) +} + +func versionHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "source": "https://github.com/appleboy/gorush", + "version": GetVersion(), + }) +} + +func pushHandler(cfg *config.ConfYaml, q *queue.Queue) gin.HandlerFunc { + return func(c *gin.Context) { + var form notify.RequestPush + var msg string + + if err := c.ShouldBindWith(&form, binding.JSON); err != nil { + msg = "Missing notifications field." + logx.LogAccess.Debug(err) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + if len(form.Notifications) == 0 { + msg = "Notifications field is empty." + logx.LogAccess.Debug(msg) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + if int64(len(form.Notifications)) > cfg.Core.MaxNotification { + msg = fmt.Sprintf("Number of notifications(%d) over limit(%d)", len(form.Notifications), cfg.Core.MaxNotification) + logx.LogAccess.Debug(msg) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + // Deprecated: the CloseNotifier interface predates Go's context package. + // New code should use Request.Context instead. + // Change to context package + <-c.Request.Context().Done() + // Don't send notification after client timeout or disconnected. + // See the following issue for detail information. + // https://github.com/appleboy/gorush/issues/422 + if cfg.Core.Sync { + cancel() + } + }() + + counts, logs := handleNotification(ctx, cfg, form, q) + + c.JSON(http.StatusOK, gin.H{ + "success": "ok", + "counts": counts, + "logs": logs, + }) + } +} + +func configHandler(cfg *config.ConfYaml) gin.HandlerFunc { + return func(c *gin.Context) { + c.YAML(http.StatusCreated, cfg) + } +} + +func metricsHandler(c *gin.Context) { + promhttp.Handler().ServeHTTP(c.Writer, c.Request) +} + +func appStatusHandler(q *queue.Queue) gin.HandlerFunc { + return func(c *gin.Context) { + result := status.App{} + + result.Version = GetVersion() + result.QueueMax = q.Capacity() + result.QueueUsage = q.Usage() + result.TotalCount = status.StatStorage.GetTotalCount() + result.Ios.PushSuccess = status.StatStorage.GetIosSuccess() + result.Ios.PushError = status.StatStorage.GetIosError() + result.Android.PushSuccess = status.StatStorage.GetAndroidSuccess() + result.Android.PushError = status.StatStorage.GetAndroidError() + result.Huawei.PushSuccess = status.StatStorage.GetHuaweiSuccess() + result.Huawei.PushError = status.StatStorage.GetHuaweiError() + + c.JSON(http.StatusOK, result) + } +} + +func sysStatsHandler() gin.HandlerFunc { + return func(c *gin.Context) { + c.JSON(http.StatusOK, status.Stats.Data()) + } +} + +// StatMiddleware response time, status code count, etc. +func StatMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + beginning, recorder := status.Stats.Begin(c.Writer) + c.Next() + status.Stats.End(beginning, stats.WithRecorder(recorder)) + } +} + +func autoTLSServer(cfg *config.ConfYaml, q *queue.Queue) *http.Server { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(cfg.Core.AutoTLS.Host), + Cache: autocert.DirCache(cfg.Core.AutoTLS.Folder), + } + + return &http.Server{ + Addr: ":https", + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + Handler: routerEngine(cfg, q), + } +} + +func routerEngine(cfg *config.ConfYaml, q *queue.Queue) *gin.Engine { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if cfg.Core.Mode == "debug" { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() + + isTerm := isatty.IsTerminal(os.Stdout.Fd()) + if isTerm { + log.Logger = log.Output( + zerolog.ConsoleWriter{ + Out: os.Stdout, + NoColor: false, + }, + ) + } + + // Support metrics + doOnce.Do(func() { + m := metric.NewMetrics(func() int { + return q.Usage() + }) + prometheus.MustRegister(m) + }) + + // set server mode + gin.SetMode(cfg.Core.Mode) + + r := gin.New() + + // Global middleware + r.Use(logger.SetLogger( + logger.WithUTC(true), + logger.WithSkipPath([]string{ + cfg.API.HealthURI, + cfg.API.MetricURI, + }), + )) + r.Use(gin.Recovery()) + r.Use(VersionMiddleware()) + r.Use(StatMiddleware()) + + r.GET(cfg.API.StatGoURI, api.GinHandler) + r.GET(cfg.API.StatAppURI, appStatusHandler(q)) + r.GET(cfg.API.ConfigURI, configHandler(cfg)) + r.GET(cfg.API.SysStatURI, sysStatsHandler()) + r.POST(cfg.API.PushURI, pushHandler(cfg, q)) + r.GET(cfg.API.MetricURI, metricsHandler) + r.GET(cfg.API.HealthURI, heartbeatHandler) + r.HEAD(cfg.API.HealthURI, heartbeatHandler) + r.GET("/version", versionHandler) + r.GET("/", rootHandler) + + return r +} + +// markFailedNotification adds failure logs for all tokens in push notification +func markFailedNotification(cfg *config.ConfYaml, notification *notify.PushNotification, reason string) []logx.LogPushEntry { + logx.LogError.Error(reason) + logs := make([]logx.LogPushEntry, 0) + for _, token := range notification.Tokens { + logs = append(logs, logx.GetLogPushEntry(&logx.InputLog{ + ID: notification.ID, + Status: core.FailedPush, + Token: token, + Message: notification.Message, + Platform: notification.Platform, + Error: errors.New(reason), + HideToken: cfg.Log.HideToken, + Format: cfg.Log.Format, + })) + } + + return logs +} + +// HandleNotification add notification to queue list. +func handleNotification(ctx context.Context, cfg *config.ConfYaml, req notify.RequestPush, q *queue.Queue) (int, []logx.LogPushEntry) { + var count int + wg := sync.WaitGroup{} + newNotification := []*notify.PushNotification{} + + if cfg.Core.Sync && !core.IsLocalQueue(core.Queue(cfg.Queue.Engine)) { + cfg.Core.Sync = false + } + + for i := range req.Notifications { + notification := &req.Notifications[i] + switch notification.Platform { + case core.PlatFormIos: + if !cfg.Ios.Enabled { + continue + } + case core.PlatFormAndroid: + if !cfg.Android.Enabled { + continue + } + case core.PlatFormHuawei: + if !cfg.Huawei.Enabled { + continue + } + case core.PlaFormMI: + if !cfg.MI.Enabled { + continue + } + + } + newNotification = append(newNotification, notification) + } + + logs := make([]logx.LogPushEntry, 0, count) + for _, notification := range newNotification { + if cfg.Core.Sync { + wg.Add(1) + } + + if core.IsLocalQueue(core.Queue(cfg.Queue.Engine)) && cfg.Core.Sync { + func(msg *notify.PushNotification, cfg *config.ConfYaml) { + if err := q.QueueTask(func(ctx context.Context) error { + defer wg.Done() + resp, err := notify.SendNotification(msg, cfg) + if err != nil { + return err + } + + // add log + logs = append(logs, resp.Logs...) + + return nil + }); err != nil { + logx.LogError.Error(err) + } + }(notification, cfg) + } else if err := q.Queue(notification); err != nil { + resp := markFailedNotification(cfg, notification, "max capacity reached") + // add log + logs = append(logs, resp...) + wg.Done() + } + + count += len(notification.Tokens) + // Count topic message + if notification.To != "" { + count++ + } + } + + if cfg.Core.Sync { + wg.Wait() + } + + status.StatStorage.AddTotalCount(int64(count)) + + return count, logs +} diff --git a/push/gorush-with-mipush/src/router/server_lambda.go b/push/gorush-with-mipush/src/router/server_lambda.go new file mode 100644 index 0000000..2251546 --- /dev/null +++ b/push/gorush-with-mipush/src/router/server_lambda.go @@ -0,0 +1,27 @@ +//go:build lambda +// +build lambda + +package router + +import ( + "context" + "net/http" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + + "github.com/apex/gateway" + "github.com/golang-queue/queue" +) + +// RunHTTPServer provide run http or https protocol. +func RunHTTPServer(ctx context.Context, cfg *config.ConfYaml, q *queue.Queue, s ...*http.Server) (err error) { + if !cfg.Core.Enabled { + logx.LogAccess.Debug("httpd server is disabled.") + return nil + } + + logx.LogAccess.Info("HTTPD server is running on " + cfg.Core.Port + " port.") + + return gateway.ListenAndServe(cfg.Core.Address+":"+cfg.Core.Port, routerEngine(cfg, q)) +} diff --git a/push/gorush-with-mipush/src/router/server_normal.go b/push/gorush-with-mipush/src/router/server_normal.go new file mode 100644 index 0000000..a7dbe9b --- /dev/null +++ b/push/gorush-with-mipush/src/router/server_normal.go @@ -0,0 +1,125 @@ +//go:build !lambda +// +build !lambda + +package router + +import ( + "context" + "crypto/tls" + "encoding/base64" + "errors" + "net/http" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + + "github.com/golang-queue/queue" + "golang.org/x/sync/errgroup" +) + +// RunHTTPServer provide run http or https protocol. +func RunHTTPServer(ctx context.Context, cfg *config.ConfYaml, q *queue.Queue, s ...*http.Server) (err error) { + var server *http.Server + + if !cfg.Core.Enabled { + logx.LogAccess.Info("httpd server is disabled.") + return nil + } + + if len(s) == 0 { + server = &http.Server{ + Addr: cfg.Core.Address + ":" + cfg.Core.Port, + Handler: routerEngine(cfg, q), + } + } else { + server = s[0] + } + + logx.LogAccess.Info("HTTPD server is running on " + cfg.Core.Port + " port.") + if cfg.Core.AutoTLS.Enabled { + return startServer(ctx, autoTLSServer(cfg, q), cfg) + } else if cfg.Core.SSL { + config := &tls.Config{ + MinVersion: tls.VersionTLS10, + } + + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + config.Certificates = make([]tls.Certificate, 1) + if cfg.Core.CertPath != "" && cfg.Core.KeyPath != "" { + config.Certificates[0], err = tls.LoadX509KeyPair(cfg.Core.CertPath, cfg.Core.KeyPath) + if err != nil { + logx.LogError.Error("Failed to load https cert file: ", err) + return err + } + } else if cfg.Core.CertBase64 != "" && cfg.Core.KeyBase64 != "" { + cert, err := base64.StdEncoding.DecodeString(cfg.Core.CertBase64) + if err != nil { + logx.LogError.Error("base64 decode error:", err.Error()) + return err + } + key, err := base64.StdEncoding.DecodeString(cfg.Core.KeyBase64) + if err != nil { + logx.LogError.Error("base64 decode error:", err.Error()) + return err + } + if config.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { + logx.LogError.Error("tls key pair error:", err.Error()) + return err + } + } else { + return errors.New("missing https cert config") + } + + server.TLSConfig = config + } + + return startServer(ctx, server, cfg) +} + +func listenAndServe(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + var g errgroup.Group + g.Go(func() error { + <-ctx.Done() + timeout := time.Duration(cfg.Core.ShutdownTimeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return s.Shutdown(ctx) + }) + g.Go(func() error { + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + return g.Wait() +} + +func listenAndServeTLS(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + var g errgroup.Group + g.Go(func() error { + <-ctx.Done() + timeout := time.Duration(cfg.Core.ShutdownTimeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return s.Shutdown(ctx) + }) + g.Go(func() error { + if err := s.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + return g.Wait() +} + +func startServer(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + if s.TLSConfig == nil { + return listenAndServe(ctx, s, cfg) + } + + return listenAndServeTLS(ctx, s, cfg) +} diff --git a/push/gorush-with-mipush/src/router/server_test.go b/push/gorush-with-mipush/src/router/server_test.go new file mode 100644 index 0000000..0d988e8 --- /dev/null +++ b/push/gorush-with-mipush/src/router/server_test.go @@ -0,0 +1,679 @@ +package router + +import ( + "context" + "crypto/tls" + "io/ioutil" + "log" + "net/http" + "os" + "runtime" + "testing" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/status" + + "github.com/appleboy/gofight/v2" + "github.com/buger/jsonparser" + "github.com/gin-gonic/gin" + "github.com/golang-queue/queue" + "github.com/stretchr/testify/assert" +) + +var ( + goVersion = runtime.Version() + q *queue.Queue +) + +func TestMain(m *testing.M) { + cfg := initTest() + if err := status.InitAppStatus(cfg); err != nil { + log.Fatal(err) + } + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + if _, err := notify.InitFCMClient(cfg, ""); err != nil { + log.Fatal(err) + } + + q = queue.NewPool( + int(cfg.Core.WorkerNum), + queue.WithFn(func(ctx context.Context, msg queue.QueuedMessage) error { + _, err := notify.SendNotification(msg, cfg) + return err + }), + queue.WithLogger(logx.QueueLogger()), + ) + + code := m.Run() + defer func() { + q.Release() + os.Exit(code) + }() +} + +func initTest() *config.ConfYaml { + cfg, _ := config.LoadConf() + cfg.Core.Mode = "test" + return cfg +} + +// testRequest is testing url string if server is running +func testRequest(t *testing.T, url string) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{ + Timeout: time.Second * 10, + Transport: tr, + } + + resp, err := client.Get(url) + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("close body err:", err) + } + }() + + assert.NoError(t, err) + + _, ioerr := ioutil.ReadAll(resp.Body) + assert.NoError(t, ioerr) + assert.Equal(t, "200 OK", resp.Status, "should get a 200") +} + +func TestPrintGoRushVersion(t *testing.T) { + SetVersion("3.0.0") + ver := GetVersion() + PrintGoRushVersion() + + assert.Equal(t, "3.0.0", ver) +} + +func TestRunNormalServer(t *testing.T) { + cfg := initTest() + + gin.SetMode(gin.TestMode) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "http://localhost:8088/api/stat/go") +} + +func TestRunTLSServer(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "../certificate/localhost.cert" + cfg.Core.KeyPath = "../certificate/localhost.key" + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "https://localhost:8087/api/stat/go") +} + +func TestRunTLSBase64Server(t *testing.T) { + // nolint + cert := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrekNDQWVPZ0F3SUJBZ0lKQUxiWkVEdlVRckZLTUEwR0NTcUdTSWIzRFFFQkJRVUFNQlF4RWpBUUJnTlYKQkFNTUNXeHZZMkZzYUc5emREQWVGdzB4TmpBek1qZ3dNek13TkRGYUZ3MHlOakF6TWpZd016TXdOREZhTUJReApFakFRQmdOVkJBTU1DV3h2WTJGc2FHOXpkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DCmdnRUJBTWoxK3hnNGpWTHpWbkI1ajduMXVsMzBXRUU0QkN6Y05GeGc1QU9CNUg1cSt3amUwWVlpVkZnNlBReXYKR0NpcHFJUlhWUmRWUTFoSFNldW5ZR0tlOGxxM1NiMVg4UFVKMTJ2OXVSYnBTOURLMU93cWs4cnNQRHU2c1ZUTApxS0tnSDFaOHlhenphUzBBYlh1QTVlOWdPL1J6aWpibnBFUCtxdU00ZHVlaU1QVkVKeUxxK0VvSVFZK01NOE1QCjhkWnpMNFhabDd3TDRVc0NON3JQY082VzN0bG5UMGlPM2g5Yy9ZbTJoRmh6K0tOSjlLUlJDdnRQR1pFU2lndEsKYkhzWEgwOTlXRG84di9XcDUvZXZCdy8rSkQwb3B4bUNmSElCQUxIdDl2NTNSdnZzRFoxdDMzUnB1NUM4em5FWQpZMkF5N05neGhxanFvV0pxQTQ4bEplQTBjbHNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkMwYlRVMVhvZmVoCk5LSWVsYXNoSXNxS2lkRFlNQjhHQTFVZEl3UVlNQmFBRkMwYlRVMVhvZmVoTktJZWxhc2hJc3FLaWREWU1Bd0cKQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBaUpMOElNVHdOWDlYcVFXWURGZ2tHNApBbnJWd1FocmVBcUM5clN4RENqcXFuTUhQSEd6Y0NlRE1MQU1vaDBrT3kyMG5vd1VHTnRDWjB1QnZuWDJxMWJOCmcxanQrR0JjTEpEUjNMTDRDcE5PbG0zWWhPeWN1TmZXTXhUQTdCWGttblNyWkQvN0toQXJzQkVZOGF1bHh3S0oKSFJnTmxJd2Uxb0ZEMVlkWDFCUzVwcDR0MjVCNlZxNEEzRk1NVWtWb1dFNjg4bkUxNjhodlFnd2pySGtnSGh3ZQplTjhsR0UyRGhGcmFYbldtRE1kd2FIRDNIUkZHaHlwcElGTitmN0JxYldYOWdNK1QyWVJUZk9iSVhMV2JxSkxECjNNay9Oa3hxVmNnNGVZNTR3SjF1ZkNVR0FZQUlhWTZmUXFpTlV6OG5od0szdDQ1TkJWVDl5L3VKWHFuVEx5WT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=` + // nolint + key := `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeVBYN0dEaU5Vdk5XY0htUHVmVzZYZlJZUVRnRUxOdzBYR0RrQTRIa2ZtcjdDTjdSCmhpSlVXRG85REs4WUtLbW9oRmRWRjFWRFdFZEo2NmRnWXA3eVdyZEp2VmZ3OVFuWGEvMjVGdWxMME1yVTdDcVQKeXV3OE83cXhWTXVvb3FBZlZuekpyUE5wTFFCdGU0RGw3MkE3OUhPS051ZWtRLzZxNHpoMjU2SXc5VVFuSXVyNApTZ2hCajR3end3L3gxbk12aGRtWHZBdmhTd0kzdXM5dzdwYmUyV2RQU0k3ZUgxejlpYmFFV0hQNG8wbjBwRkVLCiswOFprUktLQzBwc2V4Y2ZUMzFZT2p5Lzlhbm45NjhIRC80a1BTaW5HWUo4Y2dFQXNlMzIvbmRHKyt3Tm5XM2YKZEdtN2tMek9jUmhqWURMczJER0dxT3FoWW1vRGp5VWw0RFJ5V3dJREFRQUJBb0lCQUdUS3FzTjlLYlNmQTQycQpDcUkwVXVMb3VKTU5hMXFzbno1dUFpNllLV2dXZEE0QTQ0bXBFakNtRlJTVmhVSnZ4V3VLK2N5WUlRelh4SVdECkQxNm5aZHFGNzJBZUNXWjlKeVNzdnZaMDBHZktNM3kzNWlSeTA4c0pXZ096bWNMbkdKQ2lTZXlLc1FlM0hUSkMKZGhEWGJYcXZzSFRWUFpnMDFMVGVEeFVpVGZmVThOTUtxUjJBZWNRMnNURHdYRWhBblR5QXRuemwvWGFCZ0Z6dQpVNkc3RnpHTTV5OWJ4a2ZRVmt2eStERUprSEdOT2p6d2NWZkJ5eVZsNjEwaXhtRzF2bXhWajlQYldtSVBzVVY4CnlTbWpodkRRYk9mb3hXMGg5dlRsVHFHdFFjQnc5NjJvc25ERE1XRkNkTTdsek8wVDdSUm5QVkdJUnBDSk9LaHEKa2VxSEt3RUNnWUVBOHd3SS9pWnVnaG9UWFRORzlMblFRL1dBdHNxTzgwRWpNVFVoZW81STFrT3ptVXowOXB5aAppQXNVRG9OMC8yNnRaNVdOamxueVp1N2R2VGMveDNkVFpwbU5ub284Z2NWYlFORUNEUnpxZnVROVBQWG0xU041CjZwZUJxQXZCdjc4aGpWMDVhWHpQRy9WQmJlaWc3bDI5OUVhckVBK2Evb0gzS3JnRG9xVnFFMEVDZ1lFQTA2dkEKWUptZ2c0ZlpSdWNBWW9hWXNMejlaOXJDRmpUZTFQQlRtVUprYk9SOHZGSUhIVFRFV2kvU3V4WEwwd0RTZW9FMgo3QlFtODZnQ0M3L0tnUmRyem9CcVo1cVM5TXYyZHNMZ1k2MzVWU2dqamZaa1ZMaUgxVlJScFNRT2JZbmZveXNnCmdhdGNIU0tNRXhkNFNMUUJ5QXVJbVhQK0w1YXlEQmNFSmZicVNwc0NnWUI3OElzMWIwdXpOTERqT2g3WTlWaHIKRDJxUHpFT1JjSW9Oc2RaY3RPb1h1WGFBbW1uZ3lJYm01UjlaTjFnV1djNDdvRndMVjNyeFdxWGdzNmZtZzhjWAo3djMwOXZGY0M5UTQvVnhhYTRCNUxOSzluM2dUQUlCUFRPdGxVbmwrMm15MXRmQnRCcVJtMFc2SUtiVEhXUzVnCnZ4akVtL0NpRUl5R1VFZ3FUTWdIQVFLQmdCS3VYZFFvdXRuZzYzUXVmd0l6RHRiS1Z6TUxRNFhpTktobWJYcGgKT2F2Q25wK2dQYkIrTDdZbDhsdEFtVFNPSmdWWjBoY1QwRHhBMzYxWngrMk11NThHQmw0T2JsbmNobXdFMXZqMQpLY1F5UHJFUXhkb1VUeWlzd0dmcXZyczhKOWltdmIrejkvVTZUMUtBQjhXaTNXVmlYelByNE1zaWFhUlhnNjQyCkZJZHhBb0dBWjcvNzM1ZGtoSmN5T2ZzK0xLc0xyNjhKU3N0b29yWE9ZdmRNdTErSkdhOWlMdWhuSEVjTVZXQzgKSXVpaHpQZmxvWnRNYkdZa1pKbjhsM0JlR2Q4aG1mRnRnVGdaR1BvVlJldGZ0MkxERkxuUHhwMnNFSDVPRkxzUQpSK0sva0FPdWw4ZVN0V3VNWE9GQTlwTXpHa0dFZ0lGSk1KT3lhSk9OM2tlZFFJOGRlQ009Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==` + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8089" + cfg.Core.CertPath = "" + cfg.Core.KeyPath = "" + cfg.Core.CertBase64 = cert + cfg.Core.KeyBase64 = key + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "https://localhost:8089/api/stat/go") +} + +func TestRunAutoTLSServer(t *testing.T) { + cfg := initTest() + cfg.Core.AutoTLS.Enabled = true + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) +} + +func TestLoadTLSCertError(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "../config/config.yml" + cfg.Core.KeyPath = "../config/config.yml" + + assert.Error(t, RunHTTPServer(context.Background(), cfg, q)) +} + +func TestMissingTLSCertcfgg(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "" + cfg.Core.KeyPath = "" + cfg.Core.CertBase64 = "" + cfg.Core.KeyBase64 = "" + + err := RunHTTPServer(context.Background(), cfg, q) + assert.Error(t, RunHTTPServer(context.Background(), cfg, q)) + assert.Equal(t, "missing https cert config", err.Error()) +} + +func TestRootHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // log for json + cfg.Log.Format = "json" + + r.GET("/"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "text") + + assert.Equal(t, "Welcome to notification server.", value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIStatusGoHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/api/stat/go"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "go_version") + + assert.Equal(t, goVersion, value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIStatusAppHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + appVersion := "v1.0.0" + SetVersion(appVersion) + + r.GET("/api/stat/app"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "version") + + assert.Equal(t, appVersion, value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIConfigHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/api/config"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusCreated, r.Code) + }) +} + +func TestMissingNotificationsParameter(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // missing notifications parameter. + r.POST("/api/push"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestEmptyNotifications(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []notify.PushNotification{}, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestMutableContent(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome From API", + "mutable_content": 1, + "topic": "test", + "badge": 1, + "alert": gofight.D{ + "title": "title", + "body": "body", + }, + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + // json: cannot unmarshal number into Go struct field notify.PushNotification.mutable_content of type bool + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestOutOfRangeMaxNotifications(t *testing.T) { + cfg := initTest() + + cfg.Core.MaxNotification = int64(1) + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome API From Android", + }, + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome API From Android", + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestSuccessPushHandler(t *testing.T) { + t.Skip() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + r := gofight.New() + + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{androidToken, "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome Android", + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestSysStatsHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/sys/stats"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestMetricsHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/metrics"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestGETHeartbeatHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/healthz"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestHEADHeartbeatHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.HEAD("/healthz"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestVersionHandler(t *testing.T) { + SetVersion("3.0.0") + cfg := initTest() + + r := gofight.New() + + r.GET("/version"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "version") + + assert.Equal(t, "3.0.0", value) + }) +} + +func TestDisabledHTTPServer(t *testing.T) { + cfg := initTest() + cfg.Core.Enabled = false + err := RunHTTPServer(context.Background(), cfg, q) + cfg.Core.Enabled = true + + assert.Nil(t, err) +} + +func TestSenMultipleNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 3, count) + assert.Equal(t, 0, len(logs)) +} + +func TestDisabledAndroidNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = false + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c5209d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 1, count) + assert.Equal(t, 0, len(logs)) +} + +func TestSyncModeForNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + // enable sync mode + cfg.Core.Sync = true + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{ + "11aa01229f15f0f0c12029d8c111d1ae1f2365f14cebc4af26cd6d76b7919ef7", + }, + Platform: core.PlatFormIos, + Message: "Welcome iOS Sync", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android Sync", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 3, count) + assert.Equal(t, 2, len(logs)) +} + +func TestSyncModeForTopicNotification(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Log.HideToken = false + + // enable sync mode + cfg.Core.Sync = true + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // android + { + // error:InvalidParameters + // Check that the provided parameters have the right name and type. + To: "/topics/foo-bar@@@##", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + To: "/topics/foo-bar", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + Condition: "'dogs' in topics || 'cats' in topics", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 2, count) + assert.Equal(t, 1, len(logs)) +} + +func TestSyncModeForDeviceGroupNotification(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Log.HideToken = false + + // enable sync mode + cfg.Core.Sync = true + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // android + { + To: "aUniqueKey", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Device Group Message!", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 1, count) + assert.Equal(t, 1, len(logs)) +} + +func TestDisabledIosNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = false + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c52021d8cf3cd0ae1f2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS platform", + }, + // android + { + Tokens: []string{androidToken, androidToken + "_"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android platform", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 2, count) + assert.Equal(t, 0, len(logs)) +} diff --git a/push/gorush-with-mipush/src/router/version.go b/push/gorush-with-mipush/src/router/version.go new file mode 100644 index 0000000..6300ec3 --- /dev/null +++ b/push/gorush-with-mipush/src/router/version.go @@ -0,0 +1,38 @@ +package router + +import ( + "fmt" + "runtime" + + "github.com/gin-gonic/gin" +) + +var version string + +// SetVersion for setup version string. +func SetVersion(ver string) { + version = ver +} + +// GetVersion for get current version. +func GetVersion() string { + return version +} + +// PrintGoRushVersion provide print server engine +func PrintGoRushVersion() { + fmt.Printf(`GoRush %s, Compiler: %s %s, Copyright (C) 2019 Bo-Yi Wu, Inc.`, + version, + runtime.Compiler, + runtime.Version()) + fmt.Println() +} + +// VersionMiddleware : add version on header. +func VersionMiddleware() gin.HandlerFunc { + // Set out header value for each response + return func(c *gin.Context) { + c.Header("X-GORUSH-VERSION", version) + c.Next() + } +} diff --git a/push/gorush-with-mipush/src/rpc/client_grpc_health.go b/push/gorush-with-mipush/src/rpc/client_grpc_health.go new file mode 100644 index 0000000..39fce02 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/client_grpc_health.go @@ -0,0 +1,58 @@ +package rpc + +import ( + "context" + + "github.com/appleboy/gorush/rpc/proto" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// generate protobuffs +// protoc --go_out=plugins=grpc,import_path=proto:. *.proto + +type healthClient struct { + client proto.HealthClient + conn *grpc.ClientConn +} + +// NewGrpcHealthClient returns a new grpc Client. +func NewGrpcHealthClient(conn *grpc.ClientConn) Health { + client := new(healthClient) + client.client = proto.NewHealthClient(conn) + client.conn = conn + return client +} + +func (c *healthClient) Close() error { + return c.conn.Close() +} + +func (c *healthClient) Check(ctx context.Context) (bool, error) { + var res *proto.HealthCheckResponse + var err error + req := new(proto.HealthCheckRequest) + + res, err = c.client.Check(ctx, req) + if err == nil { + if res.GetStatus() == proto.HealthCheckResponse_SERVING { + return true, nil + } + return false, nil + } + switch status.Code(err) { + case + codes.Aborted, + codes.DataLoss, + codes.DeadlineExceeded, + codes.Internal, + codes.Unavailable: + // non-fatal errors + default: + return false, err + } + + return false, err +} diff --git a/push/gorush-with-mipush/src/rpc/client_test.go b/push/gorush-with-mipush/src/rpc/client_test.go new file mode 100644 index 0000000..9ab1e3e --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/client_test.go @@ -0,0 +1 @@ +package rpc diff --git a/push/gorush-with-mipush/src/rpc/example/go/health/main.go b/push/gorush-with-mipush/src/rpc/example/go/health/main.go new file mode 100644 index 0000000..91d66f2 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/go/health/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "log" + "time" + + "github.com/appleboy/gorush/rpc" + + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +const ( + address = "localhost:9000" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + + client := rpc.NewGrpcHealthClient(conn) + + for { + ok, err := client.Check(context.Background()) + if !ok || err != nil { + log.Printf("can't connect grpc server: %v, code: %v\n", err, status.Code(err)) + } else { + log.Println("connect the grpc server successfully") + } + + <-time.After(time.Second) + } +} diff --git a/push/gorush-with-mipush/src/rpc/example/go/send/main.go b/push/gorush-with-mipush/src/rpc/example/go/send/main.go new file mode 100644 index 0000000..f9b0581 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/go/send/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "log" + + "github.com/appleboy/gorush/rpc/proto" + + structpb "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/grpc" +) + +const ( + address = "localhost:9000" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + c := proto.NewGorushClient(conn) + + r, err := c.Send(context.Background(), &proto.NotificationRequest{ + Platform: 2, + Tokens: []string{"1234567890"}, + Message: "test message", + Badge: 1, + Category: "test", + Sound: "test", + Priority: proto.NotificationRequest_HIGH, + Alert: &proto.Alert{ + Title: "Test Title", + Body: "Test Alert Body", + Subtitle: "Test Alert Sub Title", + LocKey: "Test loc key", + LocArgs: []string{"test", "test"}, + }, + Data: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "key1": { + Kind: &structpb.Value_StringValue{StringValue: "welcome"}, + }, + "key2": { + Kind: &structpb.Value_NumberValue{NumberValue: 2}, + }, + }, + }, + }) + if err != nil { + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) + } +} diff --git a/push/gorush-with-mipush/src/rpc/example/node/.gitignore b/push/gorush-with-mipush/src/rpc/example/node/.gitignore new file mode 100644 index 0000000..65078ce --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/.gitignore @@ -0,0 +1,4 @@ +*~ +node_modules +npm-debug.log +.yarn-cache diff --git a/push/gorush-with-mipush/src/rpc/example/node/README.md b/push/gorush-with-mipush/src/rpc/example/node/README.md new file mode 100644 index 0000000..7d36cf9 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/README.md @@ -0,0 +1,19 @@ +# gRPC in 3 minutes (Node.js) + +## PREREQUISITES + +`node`: This requires Node 12.x or greater. + +## INSTALL + +```sh +npm install +npm install -g grpc-tools +``` + +## Node gRPC protoc + +```sh +cd $GOPATH/src/github.com/appleboy/gorush +protoc -I rpc/proto rpc/proto/gorush.proto --js_out=import_style=commonjs,binary:rpc/example/node/ --grpc_out=rpc/example/node/ --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` +``` diff --git a/push/gorush-with-mipush/src/rpc/example/node/client.js b/push/gorush-with-mipush/src/rpc/example/node/client.js new file mode 100644 index 0000000..e487b2d --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/client.js @@ -0,0 +1,33 @@ +var messages = require('./gorush_pb'); +var services = require('./gorush_grpc_pb'); + +var grpc = require('grpc'); + +function main() { + var client = new services.GorushClient('localhost:9000', + grpc.credentials.createInsecure()); + var request = new messages.NotificationRequest(); + var alert = new messages.Alert(); + request.setPlatform(2); + request.setTokensList(["1234567890"]); + request.setMessage("Hello!!"); + request.setTitle("hello2"); + request.setBadge(2); + request.setCategory("mycategory"); + request.setSound("sound") + alert.setTitle("title"); + request.setAlert(alert); + request.setThreadid("threadID"); + request.setContentavailable(false); + request.setMutablecontent(false); + client.send(request, function (err, response) { + if(err) { + console.log(err); + } else { + console.log("Success:", response.getSuccess()); + console.log("Counts:", response.getCounts()); + } + }); +} + +main(); diff --git a/push/gorush-with-mipush/src/rpc/example/node/gorush_grpc_pb.js b/push/gorush-with-mipush/src/rpc/example/node/gorush_grpc_pb.js new file mode 100644 index 0000000..00f8738 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/gorush_grpc_pb.js @@ -0,0 +1,82 @@ +// GENERATED CODE -- DO NOT EDIT! + +'use strict'; +var grpc = require('grpc'); +var gorush_pb = require('./gorush_pb.js'); +var google_protobuf_struct_pb = require('google-protobuf/google/protobuf/struct_pb.js'); + +function serialize_proto_HealthCheckRequest(arg) { + if (!(arg instanceof gorush_pb.HealthCheckRequest)) { + throw new Error('Expected argument of type proto.HealthCheckRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_proto_HealthCheckRequest(buffer_arg) { + return gorush_pb.HealthCheckRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_proto_HealthCheckResponse(arg) { + if (!(arg instanceof gorush_pb.HealthCheckResponse)) { + throw new Error('Expected argument of type proto.HealthCheckResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_proto_HealthCheckResponse(buffer_arg) { + return gorush_pb.HealthCheckResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_proto_NotificationReply(arg) { + if (!(arg instanceof gorush_pb.NotificationReply)) { + throw new Error('Expected argument of type proto.NotificationReply'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_proto_NotificationReply(buffer_arg) { + return gorush_pb.NotificationReply.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_proto_NotificationRequest(arg) { + if (!(arg instanceof gorush_pb.NotificationRequest)) { + throw new Error('Expected argument of type proto.NotificationRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_proto_NotificationRequest(buffer_arg) { + return gorush_pb.NotificationRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +var GorushService = exports.GorushService = { + send: { + path: '/proto.Gorush/Send', + requestStream: false, + responseStream: false, + requestType: gorush_pb.NotificationRequest, + responseType: gorush_pb.NotificationReply, + requestSerialize: serialize_proto_NotificationRequest, + requestDeserialize: deserialize_proto_NotificationRequest, + responseSerialize: serialize_proto_NotificationReply, + responseDeserialize: deserialize_proto_NotificationReply, + }, +}; + +exports.GorushClient = grpc.makeGenericClientConstructor(GorushService); +var HealthService = exports.HealthService = { + check: { + path: '/proto.Health/Check', + requestStream: false, + responseStream: false, + requestType: gorush_pb.HealthCheckRequest, + responseType: gorush_pb.HealthCheckResponse, + requestSerialize: serialize_proto_HealthCheckRequest, + requestDeserialize: deserialize_proto_HealthCheckRequest, + responseSerialize: serialize_proto_HealthCheckResponse, + responseDeserialize: deserialize_proto_HealthCheckResponse, + }, +}; + +exports.HealthClient = grpc.makeGenericClientConstructor(HealthService); diff --git a/push/gorush-with-mipush/src/rpc/example/node/gorush_pb.js b/push/gorush-with-mipush/src/rpc/example/node/gorush_pb.js new file mode 100644 index 0000000..bf5d084 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/gorush_pb.js @@ -0,0 +1,1661 @@ +// source: gorush.proto +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! +/* eslint-disable */ +// @ts-nocheck + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +var google_protobuf_struct_pb = require('google-protobuf/google/protobuf/struct_pb.js'); +goog.object.extend(proto, google_protobuf_struct_pb); +goog.exportSymbol('proto.proto.Alert', null, global); +goog.exportSymbol('proto.proto.HealthCheckRequest', null, global); +goog.exportSymbol('proto.proto.HealthCheckResponse', null, global); +goog.exportSymbol('proto.proto.HealthCheckResponse.ServingStatus', null, global); +goog.exportSymbol('proto.proto.NotificationReply', null, global); +goog.exportSymbol('proto.proto.NotificationRequest', null, global); +goog.exportSymbol('proto.proto.NotificationRequest.Priority', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.proto.Alert = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.proto.Alert.repeatedFields_, null); +}; +goog.inherits(proto.proto.Alert, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.proto.Alert.displayName = 'proto.proto.Alert'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.proto.NotificationRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.proto.NotificationRequest.repeatedFields_, null); +}; +goog.inherits(proto.proto.NotificationRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.proto.NotificationRequest.displayName = 'proto.proto.NotificationRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.proto.NotificationReply = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.proto.NotificationReply, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.proto.NotificationReply.displayName = 'proto.proto.NotificationReply'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.proto.HealthCheckRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.proto.HealthCheckRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.proto.HealthCheckRequest.displayName = 'proto.proto.HealthCheckRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.proto.HealthCheckResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.proto.HealthCheckResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.proto.HealthCheckResponse.displayName = 'proto.proto.HealthCheckResponse'; +} + +/** + * List of repeated fields within this message type. + * @private {!Array<number>} + * @const + */ +proto.proto.Alert.repeatedFields_ = [9,10]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.proto.Alert.prototype.toObject = function(opt_includeInstance) { + return proto.proto.Alert.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.proto.Alert} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.Alert.toObject = function(includeInstance, msg) { + var f, obj = { + title: jspb.Message.getFieldWithDefault(msg, 1, ""), + body: jspb.Message.getFieldWithDefault(msg, 2, ""), + subtitle: jspb.Message.getFieldWithDefault(msg, 3, ""), + action: jspb.Message.getFieldWithDefault(msg, 4, ""), + actionlockey: jspb.Message.getFieldWithDefault(msg, 5, ""), + launchimage: jspb.Message.getFieldWithDefault(msg, 6, ""), + lockey: jspb.Message.getFieldWithDefault(msg, 7, ""), + titlelockey: jspb.Message.getFieldWithDefault(msg, 8, ""), + locargsList: (f = jspb.Message.getRepeatedField(msg, 9)) == null ? undefined : f, + titlelocargsList: (f = jspb.Message.getRepeatedField(msg, 10)) == null ? undefined : f + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.proto.Alert} + */ +proto.proto.Alert.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.proto.Alert; + return proto.proto.Alert.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.proto.Alert} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.proto.Alert} + */ +proto.proto.Alert.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setTitle(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setBody(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSubtitle(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setAction(value); + break; + case 5: + var value = /** @type {string} */ (reader.readString()); + msg.setActionlockey(value); + break; + case 6: + var value = /** @type {string} */ (reader.readString()); + msg.setLaunchimage(value); + break; + case 7: + var value = /** @type {string} */ (reader.readString()); + msg.setLockey(value); + break; + case 8: + var value = /** @type {string} */ (reader.readString()); + msg.setTitlelockey(value); + break; + case 9: + var value = /** @type {string} */ (reader.readString()); + msg.addLocargs(value); + break; + case 10: + var value = /** @type {string} */ (reader.readString()); + msg.addTitlelocargs(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.proto.Alert.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.proto.Alert.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.proto.Alert} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.Alert.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getTitle(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getBody(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSubtitle(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getAction(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getActionlockey(); + if (f.length > 0) { + writer.writeString( + 5, + f + ); + } + f = message.getLaunchimage(); + if (f.length > 0) { + writer.writeString( + 6, + f + ); + } + f = message.getLockey(); + if (f.length > 0) { + writer.writeString( + 7, + f + ); + } + f = message.getTitlelockey(); + if (f.length > 0) { + writer.writeString( + 8, + f + ); + } + f = message.getLocargsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 9, + f + ); + } + f = message.getTitlelocargsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 10, + f + ); + } +}; + + +/** + * optional string title = 1; + * @return {string} + */ +proto.proto.Alert.prototype.getTitle = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setTitle = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string body = 2; + * @return {string} + */ +proto.proto.Alert.prototype.getBody = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setBody = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string subtitle = 3; + * @return {string} + */ +proto.proto.Alert.prototype.getSubtitle = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setSubtitle = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string action = 4; + * @return {string} + */ +proto.proto.Alert.prototype.getAction = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setAction = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional string actionLocKey = 5; + * @return {string} + */ +proto.proto.Alert.prototype.getActionlockey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setActionlockey = function(value) { + return jspb.Message.setProto3StringField(this, 5, value); +}; + + +/** + * optional string launchImage = 6; + * @return {string} + */ +proto.proto.Alert.prototype.getLaunchimage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 6, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setLaunchimage = function(value) { + return jspb.Message.setProto3StringField(this, 6, value); +}; + + +/** + * optional string locKey = 7; + * @return {string} + */ +proto.proto.Alert.prototype.getLockey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setLockey = function(value) { + return jspb.Message.setProto3StringField(this, 7, value); +}; + + +/** + * optional string titleLocKey = 8; + * @return {string} + */ +proto.proto.Alert.prototype.getTitlelockey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setTitlelockey = function(value) { + return jspb.Message.setProto3StringField(this, 8, value); +}; + + +/** + * repeated string locArgs = 9; + * @return {!Array<string>} + */ +proto.proto.Alert.prototype.getLocargsList = function() { + return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 9)); +}; + + +/** + * @param {!Array<string>} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setLocargsList = function(value) { + return jspb.Message.setField(this, 9, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.addLocargs = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 9, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.clearLocargsList = function() { + return this.setLocargsList([]); +}; + + +/** + * repeated string titleLocArgs = 10; + * @return {!Array<string>} + */ +proto.proto.Alert.prototype.getTitlelocargsList = function() { + return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 10)); +}; + + +/** + * @param {!Array<string>} value + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.setTitlelocargsList = function(value) { + return jspb.Message.setField(this, 10, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.addTitlelocargs = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 10, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.proto.Alert} returns this + */ +proto.proto.Alert.prototype.clearTitlelocargsList = function() { + return this.setTitlelocargsList([]); +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array<number>} + * @const + */ +proto.proto.NotificationRequest.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.proto.NotificationRequest.prototype.toObject = function(opt_includeInstance) { + return proto.proto.NotificationRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.proto.NotificationRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.NotificationRequest.toObject = function(includeInstance, msg) { + var f, obj = { + tokensList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, + platform: jspb.Message.getFieldWithDefault(msg, 2, 0), + message: jspb.Message.getFieldWithDefault(msg, 3, ""), + title: jspb.Message.getFieldWithDefault(msg, 4, ""), + topic: jspb.Message.getFieldWithDefault(msg, 5, ""), + key: jspb.Message.getFieldWithDefault(msg, 6, ""), + badge: jspb.Message.getFieldWithDefault(msg, 7, 0), + category: jspb.Message.getFieldWithDefault(msg, 8, ""), + alert: (f = msg.getAlert()) && proto.proto.Alert.toObject(includeInstance, f), + sound: jspb.Message.getFieldWithDefault(msg, 10, ""), + contentavailable: jspb.Message.getBooleanFieldWithDefault(msg, 11, false), + threadid: jspb.Message.getFieldWithDefault(msg, 12, ""), + mutablecontent: jspb.Message.getBooleanFieldWithDefault(msg, 13, false), + data: (f = msg.getData()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f), + image: jspb.Message.getFieldWithDefault(msg, 15, ""), + priority: jspb.Message.getFieldWithDefault(msg, 16, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.proto.NotificationRequest} + */ +proto.proto.NotificationRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.proto.NotificationRequest; + return proto.proto.NotificationRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.proto.NotificationRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.proto.NotificationRequest} + */ +proto.proto.NotificationRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.addTokens(value); + break; + case 2: + var value = /** @type {number} */ (reader.readInt32()); + msg.setPlatform(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setTitle(value); + break; + case 5: + var value = /** @type {string} */ (reader.readString()); + msg.setTopic(value); + break; + case 6: + var value = /** @type {string} */ (reader.readString()); + msg.setKey(value); + break; + case 7: + var value = /** @type {number} */ (reader.readInt32()); + msg.setBadge(value); + break; + case 8: + var value = /** @type {string} */ (reader.readString()); + msg.setCategory(value); + break; + case 9: + var value = new proto.proto.Alert; + reader.readMessage(value,proto.proto.Alert.deserializeBinaryFromReader); + msg.setAlert(value); + break; + case 10: + var value = /** @type {string} */ (reader.readString()); + msg.setSound(value); + break; + case 11: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setContentavailable(value); + break; + case 12: + var value = /** @type {string} */ (reader.readString()); + msg.setThreadid(value); + break; + case 13: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setMutablecontent(value); + break; + case 14: + var value = new google_protobuf_struct_pb.Struct; + reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader); + msg.setData(value); + break; + case 15: + var value = /** @type {string} */ (reader.readString()); + msg.setImage(value); + break; + case 16: + var value = /** @type {!proto.proto.NotificationRequest.Priority} */ (reader.readEnum()); + msg.setPriority(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.proto.NotificationRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.proto.NotificationRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.proto.NotificationRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.NotificationRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getTokensList(); + if (f.length > 0) { + writer.writeRepeatedString( + 1, + f + ); + } + f = message.getPlatform(); + if (f !== 0) { + writer.writeInt32( + 2, + f + ); + } + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getTitle(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getTopic(); + if (f.length > 0) { + writer.writeString( + 5, + f + ); + } + f = message.getKey(); + if (f.length > 0) { + writer.writeString( + 6, + f + ); + } + f = message.getBadge(); + if (f !== 0) { + writer.writeInt32( + 7, + f + ); + } + f = message.getCategory(); + if (f.length > 0) { + writer.writeString( + 8, + f + ); + } + f = message.getAlert(); + if (f != null) { + writer.writeMessage( + 9, + f, + proto.proto.Alert.serializeBinaryToWriter + ); + } + f = message.getSound(); + if (f.length > 0) { + writer.writeString( + 10, + f + ); + } + f = message.getContentavailable(); + if (f) { + writer.writeBool( + 11, + f + ); + } + f = message.getThreadid(); + if (f.length > 0) { + writer.writeString( + 12, + f + ); + } + f = message.getMutablecontent(); + if (f) { + writer.writeBool( + 13, + f + ); + } + f = message.getData(); + if (f != null) { + writer.writeMessage( + 14, + f, + google_protobuf_struct_pb.Struct.serializeBinaryToWriter + ); + } + f = message.getImage(); + if (f.length > 0) { + writer.writeString( + 15, + f + ); + } + f = message.getPriority(); + if (f !== 0.0) { + writer.writeEnum( + 16, + f + ); + } +}; + + +/** + * @enum {number} + */ +proto.proto.NotificationRequest.Priority = { + NORMAL: 0, + HIGH: 1 +}; + +/** + * repeated string tokens = 1; + * @return {!Array<string>} + */ +proto.proto.NotificationRequest.prototype.getTokensList = function() { + return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** + * @param {!Array<string>} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setTokensList = function(value) { + return jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.addTokens = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.clearTokensList = function() { + return this.setTokensList([]); +}; + + +/** + * optional int32 platform = 2; + * @return {number} + */ +proto.proto.NotificationRequest.prototype.getPlatform = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setPlatform = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + +/** + * optional string message = 3; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setMessage = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string title = 4; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getTitle = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setTitle = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional string topic = 5; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getTopic = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setTopic = function(value) { + return jspb.Message.setProto3StringField(this, 5, value); +}; + + +/** + * optional string key = 6; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 6, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setKey = function(value) { + return jspb.Message.setProto3StringField(this, 6, value); +}; + + +/** + * optional int32 badge = 7; + * @return {number} + */ +proto.proto.NotificationRequest.prototype.getBadge = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 7, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setBadge = function(value) { + return jspb.Message.setProto3IntField(this, 7, value); +}; + + +/** + * optional string category = 8; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getCategory = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setCategory = function(value) { + return jspb.Message.setProto3StringField(this, 8, value); +}; + + +/** + * optional Alert alert = 9; + * @return {?proto.proto.Alert} + */ +proto.proto.NotificationRequest.prototype.getAlert = function() { + return /** @type{?proto.proto.Alert} */ ( + jspb.Message.getWrapperField(this, proto.proto.Alert, 9)); +}; + + +/** + * @param {?proto.proto.Alert|undefined} value + * @return {!proto.proto.NotificationRequest} returns this +*/ +proto.proto.NotificationRequest.prototype.setAlert = function(value) { + return jspb.Message.setWrapperField(this, 9, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.clearAlert = function() { + return this.setAlert(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.proto.NotificationRequest.prototype.hasAlert = function() { + return jspb.Message.getField(this, 9) != null; +}; + + +/** + * optional string sound = 10; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getSound = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 10, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setSound = function(value) { + return jspb.Message.setProto3StringField(this, 10, value); +}; + + +/** + * optional bool contentAvailable = 11; + * @return {boolean} + */ +proto.proto.NotificationRequest.prototype.getContentavailable = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 11, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setContentavailable = function(value) { + return jspb.Message.setProto3BooleanField(this, 11, value); +}; + + +/** + * optional string threadID = 12; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getThreadid = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 12, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setThreadid = function(value) { + return jspb.Message.setProto3StringField(this, 12, value); +}; + + +/** + * optional bool mutableContent = 13; + * @return {boolean} + */ +proto.proto.NotificationRequest.prototype.getMutablecontent = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 13, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setMutablecontent = function(value) { + return jspb.Message.setProto3BooleanField(this, 13, value); +}; + + +/** + * optional google.protobuf.Struct data = 14; + * @return {?proto.google.protobuf.Struct} + */ +proto.proto.NotificationRequest.prototype.getData = function() { + return /** @type{?proto.google.protobuf.Struct} */ ( + jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 14)); +}; + + +/** + * @param {?proto.google.protobuf.Struct|undefined} value + * @return {!proto.proto.NotificationRequest} returns this +*/ +proto.proto.NotificationRequest.prototype.setData = function(value) { + return jspb.Message.setWrapperField(this, 14, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.clearData = function() { + return this.setData(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.proto.NotificationRequest.prototype.hasData = function() { + return jspb.Message.getField(this, 14) != null; +}; + + +/** + * optional string image = 15; + * @return {string} + */ +proto.proto.NotificationRequest.prototype.getImage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 15, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setImage = function(value) { + return jspb.Message.setProto3StringField(this, 15, value); +}; + + +/** + * optional Priority priority = 16; + * @return {!proto.proto.NotificationRequest.Priority} + */ +proto.proto.NotificationRequest.prototype.getPriority = function() { + return /** @type {!proto.proto.NotificationRequest.Priority} */ (jspb.Message.getFieldWithDefault(this, 16, 0)); +}; + + +/** + * @param {!proto.proto.NotificationRequest.Priority} value + * @return {!proto.proto.NotificationRequest} returns this + */ +proto.proto.NotificationRequest.prototype.setPriority = function(value) { + return jspb.Message.setProto3EnumField(this, 16, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.proto.NotificationReply.prototype.toObject = function(opt_includeInstance) { + return proto.proto.NotificationReply.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.proto.NotificationReply} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.NotificationReply.toObject = function(includeInstance, msg) { + var f, obj = { + success: jspb.Message.getBooleanFieldWithDefault(msg, 1, false), + counts: jspb.Message.getFieldWithDefault(msg, 2, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.proto.NotificationReply} + */ +proto.proto.NotificationReply.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.proto.NotificationReply; + return proto.proto.NotificationReply.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.proto.NotificationReply} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.proto.NotificationReply} + */ +proto.proto.NotificationReply.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setSuccess(value); + break; + case 2: + var value = /** @type {number} */ (reader.readInt32()); + msg.setCounts(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.proto.NotificationReply.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.proto.NotificationReply.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.proto.NotificationReply} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.NotificationReply.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSuccess(); + if (f) { + writer.writeBool( + 1, + f + ); + } + f = message.getCounts(); + if (f !== 0) { + writer.writeInt32( + 2, + f + ); + } +}; + + +/** + * optional bool success = 1; + * @return {boolean} + */ +proto.proto.NotificationReply.prototype.getSuccess = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.proto.NotificationReply} returns this + */ +proto.proto.NotificationReply.prototype.setSuccess = function(value) { + return jspb.Message.setProto3BooleanField(this, 1, value); +}; + + +/** + * optional int32 counts = 2; + * @return {number} + */ +proto.proto.NotificationReply.prototype.getCounts = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.proto.NotificationReply} returns this + */ +proto.proto.NotificationReply.prototype.setCounts = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.proto.HealthCheckRequest.prototype.toObject = function(opt_includeInstance) { + return proto.proto.HealthCheckRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.proto.HealthCheckRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.HealthCheckRequest.toObject = function(includeInstance, msg) { + var f, obj = { + service: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.proto.HealthCheckRequest} + */ +proto.proto.HealthCheckRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.proto.HealthCheckRequest; + return proto.proto.HealthCheckRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.proto.HealthCheckRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.proto.HealthCheckRequest} + */ +proto.proto.HealthCheckRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setService(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.proto.HealthCheckRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.proto.HealthCheckRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.proto.HealthCheckRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.HealthCheckRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getService(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string service = 1; + * @return {string} + */ +proto.proto.HealthCheckRequest.prototype.getService = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.proto.HealthCheckRequest} returns this + */ +proto.proto.HealthCheckRequest.prototype.setService = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.proto.HealthCheckResponse.prototype.toObject = function(opt_includeInstance) { + return proto.proto.HealthCheckResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.proto.HealthCheckResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.HealthCheckResponse.toObject = function(includeInstance, msg) { + var f, obj = { + status: jspb.Message.getFieldWithDefault(msg, 1, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.proto.HealthCheckResponse} + */ +proto.proto.HealthCheckResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.proto.HealthCheckResponse; + return proto.proto.HealthCheckResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.proto.HealthCheckResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.proto.HealthCheckResponse} + */ +proto.proto.HealthCheckResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {!proto.proto.HealthCheckResponse.ServingStatus} */ (reader.readEnum()); + msg.setStatus(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.proto.HealthCheckResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.proto.HealthCheckResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.proto.HealthCheckResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.proto.HealthCheckResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getStatus(); + if (f !== 0.0) { + writer.writeEnum( + 1, + f + ); + } +}; + + +/** + * @enum {number} + */ +proto.proto.HealthCheckResponse.ServingStatus = { + UNKNOWN: 0, + SERVING: 1, + NOT_SERVING: 2 +}; + +/** + * optional ServingStatus status = 1; + * @return {!proto.proto.HealthCheckResponse.ServingStatus} + */ +proto.proto.HealthCheckResponse.prototype.getStatus = function() { + return /** @type {!proto.proto.HealthCheckResponse.ServingStatus} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {!proto.proto.HealthCheckResponse.ServingStatus} value + * @return {!proto.proto.HealthCheckResponse} returns this + */ +proto.proto.HealthCheckResponse.prototype.setStatus = function(value) { + return jspb.Message.setProto3EnumField(this, 1, value); +}; + + +goog.object.extend(exports, proto.proto); diff --git a/push/gorush-with-mipush/src/rpc/example/node/package-lock.json b/push/gorush-with-mipush/src/rpc/example/node/package-lock.json new file mode 100644 index 0000000..e7a86fe --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/package-lock.json @@ -0,0 +1,623 @@ +{ + "name": "gorush-examples", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + } + }, + "@types/bytebuffer": { + "version": "5.0.42", + "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.42.tgz", + "integrity": "sha512-lEgKojWUAc/MG2t649oZS5AfYFP2xRNPoDuwDBlBMjHXd8MaGPgFgtCXUK7inZdBOygmVf10qxc1Us8GXC96aw==", + "requires": { + "@types/long": "*", + "@types/node": "*" + } + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/node": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", + "integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "ascli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", + "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "requires": { + "colour": "~0.7.1", + "optjs": "~3.2.2" + } + }, + "async": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "2.19.0", + "process": "0.11.10" + } + }, + "google-protobuf": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.10.0.tgz", + "integrity": "sha512-d0cMO8TJ6xtB/WrVHCv5U81L2ulX+aCD58IljyAN6mHwdHHJ2jbcauX5glvivi3s3hx7EYEo7eUA9WftzamMnw==" + }, + "grpc": { + "version": "1.24.9", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.9.tgz", + "integrity": "sha512-BOq1AJocZJcG/6qyX3LX2KvKy91RIix10GFLhqWg+1L6b73uWIN2w0cq+lSi0q9mXfkjeFaBz83+oau7oJqG3Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.4", + "@types/bytebuffer": "^5.0.40", + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "protobufjs": "^5.0.3" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "0.1.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + } + } +} diff --git a/push/gorush-with-mipush/src/rpc/example/node/package.json b/push/gorush-with-mipush/src/rpc/example/node/package.json new file mode 100644 index 0000000..44ff530 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/example/node/package.json @@ -0,0 +1,12 @@ +{ + "name": "gorush-examples", + "version": "0.1.0", + "dependencies": { + "async": "^3.1.0", + "global": "^4.4.0", + "google-protobuf": "^3.10.0", + "grpc": "^1.24.9", + "lodash": "^4.17.21", + "minimist": ">=1.2.2" + } +} diff --git a/push/gorush-with-mipush/src/rpc/health.go b/push/gorush-with-mipush/src/rpc/health.go new file mode 100644 index 0000000..c074d75 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/health.go @@ -0,0 +1,11 @@ +package rpc + +import ( + "context" +) + +// Health defines a health-check connection. +type Health interface { + // Check returns if server is healthy or not + Check(c context.Context) (bool, error) +} diff --git a/push/gorush-with-mipush/src/rpc/proto/gorush.pb.go b/push/gorush-with-mipush/src/rpc/proto/gorush.pb.go new file mode 100644 index 0000000..bf1cfac --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/proto/gorush.pb.go @@ -0,0 +1,782 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: gorush.proto + +package proto + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type NotificationRequest_Priority int32 + +const ( + NotificationRequest_NORMAL NotificationRequest_Priority = 0 + NotificationRequest_HIGH NotificationRequest_Priority = 1 +) + +// Enum value maps for NotificationRequest_Priority. +var ( + NotificationRequest_Priority_name = map[int32]string{ + 0: "NORMAL", + 1: "HIGH", + } + NotificationRequest_Priority_value = map[string]int32{ + "NORMAL": 0, + "HIGH": 1, + } +) + +func (x NotificationRequest_Priority) Enum() *NotificationRequest_Priority { + p := new(NotificationRequest_Priority) + *p = x + return p +} + +func (x NotificationRequest_Priority) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (NotificationRequest_Priority) Descriptor() protoreflect.EnumDescriptor { + return file_gorush_proto_enumTypes[0].Descriptor() +} + +func (NotificationRequest_Priority) Type() protoreflect.EnumType { + return &file_gorush_proto_enumTypes[0] +} + +func (x NotificationRequest_Priority) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use NotificationRequest_Priority.Descriptor instead. +func (NotificationRequest_Priority) EnumDescriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{1, 0} +} + +type HealthCheckResponse_ServingStatus int32 + +const ( + HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 + HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 + HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 +) + +// Enum value maps for HealthCheckResponse_ServingStatus. +var ( + HealthCheckResponse_ServingStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SERVING", + 2: "NOT_SERVING", + } + HealthCheckResponse_ServingStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SERVING": 1, + "NOT_SERVING": 2, + } +) + +func (x HealthCheckResponse_ServingStatus) Enum() *HealthCheckResponse_ServingStatus { + p := new(HealthCheckResponse_ServingStatus) + *p = x + return p +} + +func (x HealthCheckResponse_ServingStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor { + return file_gorush_proto_enumTypes[1].Descriptor() +} + +func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType { + return &file_gorush_proto_enumTypes[1] +} + +func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HealthCheckResponse_ServingStatus.Descriptor instead. +func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{4, 0} +} + +type Alert struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + Body string `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` + Subtitle string `protobuf:"bytes,3,opt,name=subtitle,proto3" json:"subtitle,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + ActionLocKey string `protobuf:"bytes,5,opt,name=actionLocKey,proto3" json:"actionLocKey,omitempty"` + LaunchImage string `protobuf:"bytes,6,opt,name=launchImage,proto3" json:"launchImage,omitempty"` + LocKey string `protobuf:"bytes,7,opt,name=locKey,proto3" json:"locKey,omitempty"` + TitleLocKey string `protobuf:"bytes,8,opt,name=titleLocKey,proto3" json:"titleLocKey,omitempty"` + LocArgs []string `protobuf:"bytes,9,rep,name=locArgs,proto3" json:"locArgs,omitempty"` + TitleLocArgs []string `protobuf:"bytes,10,rep,name=titleLocArgs,proto3" json:"titleLocArgs,omitempty"` +} + +func (x *Alert) Reset() { + *x = Alert{} + if protoimpl.UnsafeEnabled { + mi := &file_gorush_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Alert) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Alert) ProtoMessage() {} + +func (x *Alert) ProtoReflect() protoreflect.Message { + mi := &file_gorush_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Alert.ProtoReflect.Descriptor instead. +func (*Alert) Descriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{0} +} + +func (x *Alert) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Alert) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *Alert) GetSubtitle() string { + if x != nil { + return x.Subtitle + } + return "" +} + +func (x *Alert) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *Alert) GetActionLocKey() string { + if x != nil { + return x.ActionLocKey + } + return "" +} + +func (x *Alert) GetLaunchImage() string { + if x != nil { + return x.LaunchImage + } + return "" +} + +func (x *Alert) GetLocKey() string { + if x != nil { + return x.LocKey + } + return "" +} + +func (x *Alert) GetTitleLocKey() string { + if x != nil { + return x.TitleLocKey + } + return "" +} + +func (x *Alert) GetLocArgs() []string { + if x != nil { + return x.LocArgs + } + return nil +} + +func (x *Alert) GetTitleLocArgs() []string { + if x != nil { + return x.TitleLocArgs + } + return nil +} + +type NotificationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tokens []string `protobuf:"bytes,1,rep,name=tokens,proto3" json:"tokens,omitempty"` + Platform int32 `protobuf:"varint,2,opt,name=platform,proto3" json:"platform,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Topic string `protobuf:"bytes,5,opt,name=topic,proto3" json:"topic,omitempty"` + Key string `protobuf:"bytes,6,opt,name=key,proto3" json:"key,omitempty"` + Badge int32 `protobuf:"varint,7,opt,name=badge,proto3" json:"badge,omitempty"` + Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` + Alert *Alert `protobuf:"bytes,9,opt,name=alert,proto3" json:"alert,omitempty"` + Sound string `protobuf:"bytes,10,opt,name=sound,proto3" json:"sound,omitempty"` + ContentAvailable bool `protobuf:"varint,11,opt,name=contentAvailable,proto3" json:"contentAvailable,omitempty"` + ThreadID string `protobuf:"bytes,12,opt,name=threadID,proto3" json:"threadID,omitempty"` + MutableContent bool `protobuf:"varint,13,opt,name=mutableContent,proto3" json:"mutableContent,omitempty"` + Data *structpb.Struct `protobuf:"bytes,14,opt,name=data,proto3" json:"data,omitempty"` + Image string `protobuf:"bytes,15,opt,name=image,proto3" json:"image,omitempty"` + Priority NotificationRequest_Priority `protobuf:"varint,16,opt,name=priority,proto3,enum=proto.NotificationRequest_Priority" json:"priority,omitempty"` + ID string `protobuf:"bytes,17,opt,name=ID,proto3" json:"ID,omitempty"` +} + +func (x *NotificationRequest) Reset() { + *x = NotificationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gorush_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NotificationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotificationRequest) ProtoMessage() {} + +func (x *NotificationRequest) ProtoReflect() protoreflect.Message { + mi := &file_gorush_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotificationRequest.ProtoReflect.Descriptor instead. +func (*NotificationRequest) Descriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{1} +} + +func (x *NotificationRequest) GetTokens() []string { + if x != nil { + return x.Tokens + } + return nil +} + +func (x *NotificationRequest) GetPlatform() int32 { + if x != nil { + return x.Platform + } + return 0 +} + +func (x *NotificationRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *NotificationRequest) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *NotificationRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *NotificationRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *NotificationRequest) GetBadge() int32 { + if x != nil { + return x.Badge + } + return 0 +} + +func (x *NotificationRequest) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *NotificationRequest) GetAlert() *Alert { + if x != nil { + return x.Alert + } + return nil +} + +func (x *NotificationRequest) GetSound() string { + if x != nil { + return x.Sound + } + return "" +} + +func (x *NotificationRequest) GetContentAvailable() bool { + if x != nil { + return x.ContentAvailable + } + return false +} + +func (x *NotificationRequest) GetThreadID() string { + if x != nil { + return x.ThreadID + } + return "" +} + +func (x *NotificationRequest) GetMutableContent() bool { + if x != nil { + return x.MutableContent + } + return false +} + +func (x *NotificationRequest) GetData() *structpb.Struct { + if x != nil { + return x.Data + } + return nil +} + +func (x *NotificationRequest) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +func (x *NotificationRequest) GetPriority() NotificationRequest_Priority { + if x != nil { + return x.Priority + } + return NotificationRequest_NORMAL +} + +func (x *NotificationRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type NotificationReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Counts int32 `protobuf:"varint,2,opt,name=counts,proto3" json:"counts,omitempty"` +} + +func (x *NotificationReply) Reset() { + *x = NotificationReply{} + if protoimpl.UnsafeEnabled { + mi := &file_gorush_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NotificationReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotificationReply) ProtoMessage() {} + +func (x *NotificationReply) ProtoReflect() protoreflect.Message { + mi := &file_gorush_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotificationReply.ProtoReflect.Descriptor instead. +func (*NotificationReply) Descriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{2} +} + +func (x *NotificationReply) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *NotificationReply) GetCounts() int32 { + if x != nil { + return x.Counts + } + return 0 +} + +type HealthCheckRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` +} + +func (x *HealthCheckRequest) Reset() { + *x = HealthCheckRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gorush_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckRequest) ProtoMessage() {} + +func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_gorush_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead. +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{3} +} + +func (x *HealthCheckRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +type HealthCheckResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=proto.HealthCheckResponse_ServingStatus" json:"status,omitempty"` +} + +func (x *HealthCheckResponse) Reset() { + *x = HealthCheckResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gorush_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckResponse) ProtoMessage() {} + +func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_gorush_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead. +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{4} +} + +func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { + if x != nil { + return x.Status + } + return HealthCheckResponse_UNKNOWN +} + +var File_gorush_proto protoreflect.FileDescriptor + +var file_gorush_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x67, 0x6f, 0x72, 0x75, 0x73, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xa3, 0x02, 0x0a, 0x05, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x75, 0x62, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x20, 0x0a, 0x0b, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6c, + 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, + 0x63, 0x41, 0x72, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x4c, 0x6f, + 0x63, 0x41, 0x72, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x69, 0x74, + 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x22, 0xb3, 0x04, 0x0a, 0x13, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x62, 0x61, 0x64, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x61, + 0x64, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, + 0x22, 0x0a, 0x05, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52, 0x05, 0x61, 0x6c, + 0x65, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x41, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x49, + 0x44, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x49, + 0x44, 0x12, 0x26, 0x0a, 0x0e, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, + 0x02, 0x49, 0x44, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0x20, 0x0a, + 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, + 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x01, 0x22, + 0x45, 0x0a, 0x11, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x3a, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, + 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, + 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x32, 0x48, 0x0a, 0x06, + 0x47, 0x6f, 0x72, 0x75, 0x73, 0x68, 0x12, 0x3e, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x12, 0x3e, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_gorush_proto_rawDescOnce sync.Once + file_gorush_proto_rawDescData = file_gorush_proto_rawDesc +) + +func file_gorush_proto_rawDescGZIP() []byte { + file_gorush_proto_rawDescOnce.Do(func() { + file_gorush_proto_rawDescData = protoimpl.X.CompressGZIP(file_gorush_proto_rawDescData) + }) + return file_gorush_proto_rawDescData +} + +var ( + file_gorush_proto_enumTypes = make([]protoimpl.EnumInfo, 2) + file_gorush_proto_msgTypes = make([]protoimpl.MessageInfo, 5) + file_gorush_proto_goTypes = []interface{}{ + (NotificationRequest_Priority)(0), // 0: proto.NotificationRequest.Priority + (HealthCheckResponse_ServingStatus)(0), // 1: proto.HealthCheckResponse.ServingStatus + (*Alert)(nil), // 2: proto.Alert + (*NotificationRequest)(nil), // 3: proto.NotificationRequest + (*NotificationReply)(nil), // 4: proto.NotificationReply + (*HealthCheckRequest)(nil), // 5: proto.HealthCheckRequest + (*HealthCheckResponse)(nil), // 6: proto.HealthCheckResponse + (*structpb.Struct)(nil), // 7: google.protobuf.Struct + } +) + +var file_gorush_proto_depIdxs = []int32{ + 2, // 0: proto.NotificationRequest.alert:type_name -> proto.Alert + 7, // 1: proto.NotificationRequest.data:type_name -> google.protobuf.Struct + 0, // 2: proto.NotificationRequest.priority:type_name -> proto.NotificationRequest.Priority + 1, // 3: proto.HealthCheckResponse.status:type_name -> proto.HealthCheckResponse.ServingStatus + 3, // 4: proto.Gorush.Send:input_type -> proto.NotificationRequest + 5, // 5: proto.Health.Check:input_type -> proto.HealthCheckRequest + 4, // 6: proto.Gorush.Send:output_type -> proto.NotificationReply + 6, // 7: proto.Health.Check:output_type -> proto.HealthCheckResponse + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_gorush_proto_init() } +func file_gorush_proto_init() { + if File_gorush_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_gorush_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Alert); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gorush_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NotificationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gorush_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NotificationReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gorush_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gorush_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_gorush_proto_rawDesc, + NumEnums: 2, + NumMessages: 5, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_gorush_proto_goTypes, + DependencyIndexes: file_gorush_proto_depIdxs, + EnumInfos: file_gorush_proto_enumTypes, + MessageInfos: file_gorush_proto_msgTypes, + }.Build() + File_gorush_proto = out.File + file_gorush_proto_rawDesc = nil + file_gorush_proto_goTypes = nil + file_gorush_proto_depIdxs = nil +} diff --git a/push/gorush-with-mipush/src/rpc/proto/gorush.proto b/push/gorush-with-mipush/src/rpc/proto/gorush.proto new file mode 100644 index 0000000..b868155 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/proto/gorush.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; +import "google/protobuf/struct.proto"; + +package proto; + +// option go_package = "./;proto"; + +message Alert { + string title = 1; + string body = 2; + string subtitle = 3; + string action = 4; + string actionLocKey = 5; + string launchImage = 6; + string locKey = 7; + string titleLocKey = 8; + repeated string locArgs = 9; + repeated string titleLocArgs = 10; +} + +message NotificationRequest { + repeated string tokens = 1; + int32 platform = 2; + string message = 3; + string title = 4; + string topic = 5; + string key = 6; + int32 badge = 7; + string category = 8; + Alert alert = 9; + string sound = 10; + bool contentAvailable = 11; + string threadID = 12; + bool mutableContent = 13; + google.protobuf.Struct data = 14; + string image = 15; + enum Priority { + NORMAL = 0; + HIGH = 1; + } + Priority priority = 16; + string ID = 17; +} + +message NotificationReply { + bool success = 1; + int32 counts = 2; +} + +service Gorush { + rpc Send (NotificationRequest) returns (NotificationReply) {} +} + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/push/gorush-with-mipush/src/rpc/proto/gorush_grpc.pb.go b/push/gorush-with-mipush/src/rpc/proto/gorush_grpc.pb.go new file mode 100644 index 0000000..767f798 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/proto/gorush_grpc.pb.go @@ -0,0 +1,182 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package proto + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GorushClient is the client API for Gorush service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GorushClient interface { + Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) +} + +type gorushClient struct { + cc grpc.ClientConnInterface +} + +func NewGorushClient(cc grpc.ClientConnInterface) GorushClient { + return &gorushClient{cc} +} + +func (c *gorushClient) Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) { + out := new(NotificationReply) + err := c.cc.Invoke(ctx, "/proto.Gorush/Send", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GorushServer is the server API for Gorush service. +// All implementations should embed UnimplementedGorushServer +// for forward compatibility +type GorushServer interface { + Send(context.Context, *NotificationRequest) (*NotificationReply, error) +} + +// UnimplementedGorushServer should be embedded to have forward compatible implementations. +type UnimplementedGorushServer struct{} + +func (UnimplementedGorushServer) Send(context.Context, *NotificationRequest) (*NotificationReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") +} + +// UnsafeGorushServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GorushServer will +// result in compilation errors. +type UnsafeGorushServer interface { + mustEmbedUnimplementedGorushServer() +} + +func RegisterGorushServer(s grpc.ServiceRegistrar, srv GorushServer) { + s.RegisterService(&Gorush_ServiceDesc, srv) +} + +func _Gorush_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NotificationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GorushServer).Send(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Gorush/Send", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GorushServer).Send(ctx, req.(*NotificationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Gorush_ServiceDesc is the grpc.ServiceDesc for Gorush service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Gorush_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Gorush", + HandlerType: (*GorushServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Send", + Handler: _Gorush_Send_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gorush.proto", +} + +// HealthClient is the client API for Health service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HealthClient interface { + Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) +} + +type healthClient struct { + cc grpc.ClientConnInterface +} + +func NewHealthClient(cc grpc.ClientConnInterface) HealthClient { + return &healthClient{cc} +} + +func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + out := new(HealthCheckResponse) + err := c.cc.Invoke(ctx, "/proto.Health/Check", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HealthServer is the server API for Health service. +// All implementations should embed UnimplementedHealthServer +// for forward compatibility +type HealthServer interface { + Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} + +// UnimplementedHealthServer should be embedded to have forward compatible implementations. +type UnimplementedHealthServer struct{} + +func (UnimplementedHealthServer) Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") +} + +// UnsafeHealthServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HealthServer will +// result in compilation errors. +type UnsafeHealthServer interface { + mustEmbedUnimplementedHealthServer() +} + +func RegisterHealthServer(s grpc.ServiceRegistrar, srv HealthServer) { + s.RegisterService(&Health_ServiceDesc, srv) +} + +func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HealthServer).Check(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Health/Check", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Health_ServiceDesc is the grpc.ServiceDesc for Health service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Health_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Health", + HandlerType: (*HealthServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Check", + Handler: _Health_Check_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gorush.proto", +} diff --git a/push/gorush-with-mipush/src/rpc/server.go b/push/gorush-with-mipush/src/rpc/server.go new file mode 100644 index 0000000..421ebab --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/server.go @@ -0,0 +1,148 @@ +package rpc + +import ( + "context" + "net" + "strings" + "sync" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/rpc/proto" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" +) + +// Server is used to implement gorush grpc server. +type Server struct { + cfg *config.ConfYaml + mu sync.Mutex + // statusMap stores the serving status of the services this Server monitors. + statusMap map[string]proto.HealthCheckResponse_ServingStatus +} + +// NewServer returns a new Server. +func NewServer(cfg *config.ConfYaml) *Server { + return &Server{ + cfg: cfg, + statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus), + } +} + +// Check implements `service Health`. +func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if in.Service == "" { + // check the server overall health status. + return &proto.HealthCheckResponse{ + Status: proto.HealthCheckResponse_SERVING, + }, nil + } + if status, ok := s.statusMap[in.Service]; ok { + return &proto.HealthCheckResponse{ + Status: status, + }, nil + } + return nil, status.Error(codes.NotFound, "unknown service") +} + +// Send implements helloworld.GreeterServer +func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) { + badge := int(in.Badge) + notification := notify.PushNotification{ + ID: in.ID, + Platform: int(in.Platform), + Tokens: in.Tokens, + Message: in.Message, + Title: in.Title, + Topic: in.Topic, + APIKey: in.Key, + Category: in.Category, + Sound: in.Sound, + ContentAvailable: in.ContentAvailable, + ThreadID: in.ThreadID, + MutableContent: in.MutableContent, + Image: in.Image, + Priority: strings.ToLower(in.GetPriority().String()), + } + + if badge > 0 { + notification.Badge = &badge + } + + if in.Topic != "" && in.Platform == core.PlatFormAndroid { + notification.To = in.Topic + } + + if in.Alert != nil { + notification.Alert = notify.Alert{ + Title: in.Alert.Title, + Body: in.Alert.Body, + Subtitle: in.Alert.Subtitle, + Action: in.Alert.Action, + ActionLocKey: in.Alert.Action, + LaunchImage: in.Alert.LaunchImage, + LocArgs: in.Alert.LocArgs, + LocKey: in.Alert.LocKey, + TitleLocArgs: in.Alert.TitleLocArgs, + TitleLocKey: in.Alert.TitleLocKey, + } + } + + if in.Data != nil { + notification.Data = map[string]interface{}{} + for k, v := range in.Data.Fields { + notification.Data[k] = v + } + } + + go func() { + _, err := notify.SendNotification(¬ification, s.cfg) + if err != nil { + logx.LogError.Error(err) + } + }() + + return &proto.NotificationReply{ + Success: true, + Counts: int32(len(notification.Tokens)), + }, nil +} + +// RunGRPCServer run gorush grpc server +func RunGRPCServer(ctx context.Context, cfg *config.ConfYaml) error { + if !cfg.GRPC.Enabled { + logx.LogAccess.Info("gRPC server is disabled.") + return nil + } + + s := grpc.NewServer() + rpcSrv := NewServer(cfg) + proto.RegisterGorushServer(s, rpcSrv) + proto.RegisterHealthServer(s, rpcSrv) + + // Register reflection service on gRPC server. + reflection.Register(s) + + lis, err := net.Listen("tcp", ":"+cfg.GRPC.Port) + if err != nil { + logx.LogError.Fatalln(err) + return err + } + logx.LogAccess.Info("gRPC server is running on " + cfg.GRPC.Port + " port.") + go func() { + <-ctx.Done() + s.GracefulStop() // graceful shutdown + logx.LogAccess.Info("shutdown the gRPC server") + }() + if err = s.Serve(lis); err != nil { + logx.LogError.Fatalln(err) + } + return err +} diff --git a/push/gorush-with-mipush/src/rpc/server_test.go b/push/gorush-with-mipush/src/rpc/server_test.go new file mode 100644 index 0000000..5d72061 --- /dev/null +++ b/push/gorush-with-mipush/src/rpc/server_test.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "context" + "testing" + + "github.com/appleboy/gorush/config" + + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" +) + +const gRPCAddr = "localhost:9000" + +func initTest() *config.ConfYaml { + cfg, _ := config.LoadConf() + cfg.Core.Mode = "test" + return cfg +} + +func TestGracefulShutDownGRPCServer(t *testing.T) { + cfg := initTest() + cfg.GRPC.Enabled = true + cfg.GRPC.Port = "9000" + cfg.Log.Format = "json" + + // Run gRPC server + ctx, gRPCContextCancel := context.WithCancel(context.Background()) + go func() { + if err := RunGRPCServer(ctx, cfg); err != nil { + panic(err) + } + }() + + // gRPC client conn + conn, err := grpc.Dial( + gRPCAddr, + grpc.WithInsecure(), + grpc.WithDefaultCallOptions(grpc.WaitForReady(true)), + ) // wait for server ready + if err != nil { + t.Error(err) + } + + // Stop gRPC server + go gRPCContextCancel() + + // wait for client connection would be closed + for conn.GetState() != connectivity.TransientFailure { + } + conn.Close() +} diff --git a/push/gorush-with-mipush/src/screenshot/lambda.png b/push/gorush-with-mipush/src/screenshot/lambda.png new file mode 100644 index 0000000..5411fa5 Binary files /dev/null and b/push/gorush-with-mipush/src/screenshot/lambda.png differ diff --git a/push/gorush-with-mipush/src/screenshot/memory.png b/push/gorush-with-mipush/src/screenshot/memory.png new file mode 100644 index 0000000..d706208 Binary files /dev/null and b/push/gorush-with-mipush/src/screenshot/memory.png differ diff --git a/push/gorush-with-mipush/src/screenshot/metrics.png b/push/gorush-with-mipush/src/screenshot/metrics.png new file mode 100644 index 0000000..2391dbc Binary files /dev/null and b/push/gorush-with-mipush/src/screenshot/metrics.png differ diff --git a/push/gorush-with-mipush/src/screenshot/status.png b/push/gorush-with-mipush/src/screenshot/status.png new file mode 100644 index 0000000..afccb17 Binary files /dev/null and b/push/gorush-with-mipush/src/screenshot/status.png differ diff --git a/push/gorush-with-mipush/src/snapcraft.yaml b/push/gorush-with-mipush/src/snapcraft.yaml new file mode 100644 index 0000000..3720fab --- /dev/null +++ b/push/gorush-with-mipush/src/snapcraft.yaml @@ -0,0 +1,24 @@ +name: gorush +version: '1.0' +summary: A push notification server written in Go +description: | + A push notification server written in Go. +grade: stable +confinement: strict +base: core18 +parts: + gorush: + plugin: go + source: https://github.com/appleboy/gorush.git + go-importpath: github.com/appleboy/gorush + build-packages: + - build-essential +apps: + gorush: + command: bin/gorush + daemon: simple + restart-condition: on-abnormal + plugs: + - home + - network + - network-bind diff --git a/push/gorush-with-mipush/src/status/status.go b/push/gorush-with-mipush/src/status/status.go new file mode 100644 index 0000000..68b423d --- /dev/null +++ b/push/gorush-with-mipush/src/status/status.go @@ -0,0 +1,84 @@ +package status + +import ( + "errors" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/storage" + "github.com/appleboy/gorush/storage/badger" + "github.com/appleboy/gorush/storage/boltdb" + "github.com/appleboy/gorush/storage/buntdb" + "github.com/appleboy/gorush/storage/leveldb" + "github.com/appleboy/gorush/storage/memory" + "github.com/appleboy/gorush/storage/redis" + + "github.com/thoas/stats" +) + +// Stats provide response time, status code count, etc. +var Stats *stats.Stats + +// StatStorage implements the storage interface +var StatStorage storage.Storage + +// App is status structure +type App struct { + Version string `json:"version"` + QueueMax int `json:"queue_max"` + QueueUsage int `json:"queue_usage"` + TotalCount int64 `json:"total_count"` + Ios IosStatus `json:"ios"` + Android AndroidStatus `json:"android"` + Huawei HuaweiStatus `json:"huawei"` +} + +// AndroidStatus is android structure +type AndroidStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// IosStatus is iOS structure +type IosStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// HuaweiStatus is huawei structure +type HuaweiStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// InitAppStatus for initialize app status +func InitAppStatus(conf *config.ConfYaml) error { + logx.LogAccess.Info("Init App Status Engine as ", conf.Stat.Engine) + switch conf.Stat.Engine { + case "memory": + StatStorage = memory.New() + case "redis": + StatStorage = redis.New(conf) + case "boltdb": + StatStorage = boltdb.New(conf) + case "buntdb": + StatStorage = buntdb.New(conf) + case "leveldb": + StatStorage = leveldb.New(conf) + case "badger": + StatStorage = badger.New(conf) + default: + logx.LogError.Error("storage error: can't find storage driver") + return errors.New("can't find storage driver") + } + + if err := StatStorage.Init(); err != nil { + logx.LogError.Error("storage error: " + err.Error()) + + return err + } + + Stats = stats.New() + + return nil +} diff --git a/push/gorush-with-mipush/src/status/status_test.go b/push/gorush-with-mipush/src/status/status_test.go new file mode 100644 index 0000000..b057f43 --- /dev/null +++ b/push/gorush-with-mipush/src/status/status_test.go @@ -0,0 +1,231 @@ +package status + +import ( + "os" + "testing" + "time" + + "github.com/appleboy/gorush/config" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestStorageDriverExist(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "Test" + err := InitAppStatus(cfg) + assert.Error(t, err) +} + +func TestStatForMemoryEngine(t *testing.T) { + // wait android push notification response. + time.Sleep(5 * time.Second) + + var val int64 + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "memory" + err := InitAppStatus(cfg) + assert.Nil(t, err) + + StatStorage.AddTotalCount(100) + StatStorage.AddIosSuccess(200) + StatStorage.AddIosError(300) + StatStorage.AddAndroidSuccess(400) + StatStorage.AddAndroidError(500) + + val = StatStorage.GetTotalCount() + assert.Equal(t, int64(100), val) + val = StatStorage.GetIosSuccess() + assert.Equal(t, int64(200), val) + val = StatStorage.GetIosError() + assert.Equal(t, int64(300), val) + val = StatStorage.GetAndroidSuccess() + assert.Equal(t, int64(400), val) + val = StatStorage.GetAndroidError() + assert.Equal(t, int64(500), val) +} + +func TestRedisServerSuccess(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6379" + + err := InitAppStatus(cfg) + + assert.NoError(t, err) +} + +func TestRedisServerError(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6370" + + err := InitAppStatus(cfg) + + assert.Error(t, err) +} + +func TestStatForRedisEngine(t *testing.T) { + var val int64 + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6379" + err := InitAppStatus(cfg) + assert.Nil(t, err) + + assert.Nil(t, StatStorage.Init()) + StatStorage.Reset() + + StatStorage.AddTotalCount(100) + StatStorage.AddIosSuccess(200) + StatStorage.AddIosError(300) + StatStorage.AddAndroidSuccess(400) + StatStorage.AddAndroidError(500) + + val = StatStorage.GetTotalCount() + assert.Equal(t, int64(100), val) + val = StatStorage.GetIosSuccess() + assert.Equal(t, int64(200), val) + val = StatStorage.GetIosError() + assert.Equal(t, int64(300), val) + val = StatStorage.GetAndroidSuccess() + assert.Equal(t, int64(400), val) + val = StatStorage.GetAndroidError() + assert.Equal(t, int64(500), val) +} + +func TestDefaultEngine(t *testing.T) { + var val int64 + // defaul engine as memory + cfg, _ := config.LoadConf() + err := InitAppStatus(cfg) + assert.Nil(t, err) + + StatStorage.Reset() + + StatStorage.AddTotalCount(100) + StatStorage.AddIosSuccess(200) + StatStorage.AddIosError(300) + StatStorage.AddAndroidSuccess(400) + StatStorage.AddAndroidError(500) + + val = StatStorage.GetTotalCount() + assert.Equal(t, int64(100), val) + val = StatStorage.GetIosSuccess() + assert.Equal(t, int64(200), val) + val = StatStorage.GetIosError() + assert.Equal(t, int64(300), val) + val = StatStorage.GetAndroidSuccess() + assert.Equal(t, int64(400), val) + val = StatStorage.GetAndroidError() + assert.Equal(t, int64(500), val) +} + +func TestStatForBoltDBEngine(t *testing.T) { + var val int64 + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "boltdb" + err := InitAppStatus(cfg) + assert.Nil(t, err) + + StatStorage.Reset() + + StatStorage.AddTotalCount(100) + StatStorage.AddIosSuccess(200) + StatStorage.AddIosError(300) + StatStorage.AddAndroidSuccess(400) + StatStorage.AddAndroidError(500) + + val = StatStorage.GetTotalCount() + assert.Equal(t, int64(100), val) + val = StatStorage.GetIosSuccess() + assert.Equal(t, int64(200), val) + val = StatStorage.GetIosError() + assert.Equal(t, int64(300), val) + val = StatStorage.GetAndroidSuccess() + assert.Equal(t, int64(400), val) + val = StatStorage.GetAndroidError() + assert.Equal(t, int64(500), val) +} + +// func TestStatForBuntDBEngine(t *testing.T) { +// var val int64 +// cfg.Stat.Engine = "buntdb" +// err := InitAppStatus() +// assert.Nil(t, err) + +// StatStorage.Reset() + +// StatStorage.AddTotalCount(100) +// StatStorage.AddIosSuccess(200) +// StatStorage.AddIosError(300) +// StatStorage.AddAndroidSuccess(400) +// StatStorage.AddAndroidError(500) + +// val = StatStorage.GetTotalCount() +// assert.Equal(t, int64(100), val) +// val = StatStorage.GetIosSuccess() +// assert.Equal(t, int64(200), val) +// val = StatStorage.GetIosError() +// assert.Equal(t, int64(300), val) +// val = StatStorage.GetAndroidSuccess() +// assert.Equal(t, int64(400), val) +// val = StatStorage.GetAndroidError() +// assert.Equal(t, int64(500), val) +// } + +// func TestStatForLevelDBEngine(t *testing.T) { +// var val int64 +// cfg.Stat.Engine = "leveldb" +// err := InitAppStatus() +// assert.Nil(t, err) + +// StatStorage.Reset() + +// StatStorage.AddTotalCount(100) +// StatStorage.AddIosSuccess(200) +// StatStorage.AddIosError(300) +// StatStorage.AddAndroidSuccess(400) +// StatStorage.AddAndroidError(500) + +// val = StatStorage.GetTotalCount() +// assert.Equal(t, int64(100), val) +// val = StatStorage.GetIosSuccess() +// assert.Equal(t, int64(200), val) +// val = StatStorage.GetIosError() +// assert.Equal(t, int64(300), val) +// val = StatStorage.GetAndroidSuccess() +// assert.Equal(t, int64(400), val) +// val = StatStorage.GetAndroidError() +// assert.Equal(t, int64(500), val) +// } + +// func TestStatForBadgerEngine(t *testing.T) { +// var val int64 +// cfg.Stat.Engine = "badger" +// err := InitAppStatus() +// assert.Nil(t, err) + +// StatStorage.Reset() + +// StatStorage.AddTotalCount(100) +// StatStorage.AddIosSuccess(200) +// StatStorage.AddIosError(300) +// StatStorage.AddAndroidSuccess(400) +// StatStorage.AddAndroidError(500) + +// val = StatStorage.GetTotalCount() +// assert.Equal(t, int64(100), val) +// val = StatStorage.GetIosSuccess() +// assert.Equal(t, int64(200), val) +// val = StatStorage.GetIosError() +// assert.Equal(t, int64(300), val) +// val = StatStorage.GetAndroidSuccess() +// assert.Equal(t, int64(400), val) +// val = StatStorage.GetAndroidError() +// assert.Equal(t, int64(500), val) +// } diff --git a/push/gorush-with-mipush/src/storage/badger/badger.go b/push/gorush-with-mipush/src/storage/badger/badger.go new file mode 100644 index 0000000..2c6fec3 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/badger/badger.go @@ -0,0 +1,224 @@ +package badger + +import ( + "log" + "os" + "strconv" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/storage" + + "github.com/dgraph-io/badger/v3" +) + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New(config *config.ConfYaml) *Storage { + return &Storage{ + config: config, + } +} + +// Storage is interface structure +type Storage struct { + config *config.ConfYaml + opts badger.Options + name string + db *badger.DB +} + +// Init client storage. +func (s *Storage) Init() error { + var err error + s.name = "badger" + dbPath := s.config.Stat.BadgerDB.Path + if dbPath == "" { + dbPath = os.TempDir() + "badger" + } + s.opts = badger.DefaultOptions(dbPath) + + s.db, err = badger.Open(s.opts) + + return err +} + +// Close the storage connection +func (s *Storage) Close() error { + if s.db == nil { + return nil + } + + return s.db.Close() +} + +// Reset Client storage. +func (s *Storage) Reset() { + s.setBadger(storage.TotalCountKey, 0) + s.setBadger(storage.IosSuccessKey, 0) + s.setBadger(storage.IosErrorKey, 0) + s.setBadger(storage.AndroidSuccessKey, 0) + s.setBadger(storage.AndroidErrorKey, 0) + s.setBadger(storage.HuaweiSuccessKey, 0) + s.setBadger(storage.HuaweiErrorKey, 0) +} + +func (s *Storage) setBadger(key string, count int64) { + err := s.db.Update(func(txn *badger.Txn) error { + value := strconv.FormatInt(count, 10) + return txn.Set([]byte(key), []byte(value)) + }) + if err != nil { + log.Println(s.name, "update error:", err.Error()) + } +} + +func (s *Storage) getBadger(key string, count *int64) { + err := s.db.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(key)) + if err != nil { + return err + } + dst := []byte{} + val, err := item.ValueCopy(dst) + if err != nil { + return err + } + + i, err := strconv.ParseInt(string(val), 10, 64) + if err != nil { + return err + } + + *count = i + + return nil + }) + if err != nil { + log.Println(s.name, "get error:", err.Error()) + } +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + total := s.GetTotalCount() + count + s.setBadger(storage.TotalCountKey, total) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + total := s.GetIosSuccess() + count + s.setBadger(storage.IosSuccessKey, total) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + total := s.GetIosError() + count + s.setBadger(storage.IosErrorKey, total) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + total := s.GetAndroidSuccess() + count + s.setBadger(storage.AndroidSuccessKey, total) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + total := s.GetAndroidError() + count + s.setBadger(storage.AndroidErrorKey, total) +} + +// AddHuaweiSuccess record counts of success Huawei push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + total := s.GetHuaweiSuccess() + count + s.setBadger(storage.HuaweiSuccessKey, total) +} + +// AddHuaweiError record counts of error Huawei push notification. +func (s *Storage) AddHuaweiError(count int64) { + total := s.GetHuaweiError() + count + s.setBadger(storage.HuaweiErrorKey, total) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + total := s.GetMISuccess() + count + s.setBadger(storage.MISuccessKey, total) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + total := s.GetMIError() + count + s.setBadger(storage.MIErrorKey, total) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + var count int64 + s.getBadger(storage.TotalCountKey, &count) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + var count int64 + s.getBadger(storage.IosSuccessKey, &count) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + var count int64 + s.getBadger(storage.IosErrorKey, &count) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + var count int64 + s.getBadger(storage.AndroidSuccessKey, &count) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + var count int64 + s.getBadger(storage.AndroidErrorKey, &count) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + var count int64 + s.getBadger(storage.HuaweiSuccessKey, &count) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + var count int64 + s.getBadger(storage.HuaweiErrorKey, &count) + + return count +} + +// GetMISuccess show success counts of MI notification. +func (s *Storage) GetMISuccess() int64 { + var count int64 + s.getBadger(storage.MISuccessKey, &count) + + return count +} + +// GetMIError show error counts of MI notification. +func (s *Storage) GetMIError() int64 { + var count int64 + s.getBadger(storage.MIErrorKey, &count) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/badger/badger_test.go b/push/gorush-with-mipush/src/storage/badger/badger_test.go new file mode 100644 index 0000000..3c26b27 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/badger/badger_test.go @@ -0,0 +1,49 @@ +package badger + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestBadgerEngine(t *testing.T) { + var val int64 + + cfg, _ := config.LoadConf() + + badger := New(cfg) + err := badger.Init() + assert.Nil(t, err) + badger.Reset() + + badger.AddTotalCount(10) + val = badger.GetTotalCount() + assert.Equal(t, int64(10), val) + badger.AddTotalCount(10) + val = badger.GetTotalCount() + assert.Equal(t, int64(20), val) + + badger.AddIosSuccess(20) + val = badger.GetIosSuccess() + assert.Equal(t, int64(20), val) + + badger.AddIosError(30) + val = badger.GetIosError() + assert.Equal(t, int64(30), val) + + badger.AddAndroidSuccess(40) + val = badger.GetAndroidSuccess() + assert.Equal(t, int64(40), val) + + badger.AddAndroidError(50) + val = badger.GetAndroidError() + assert.Equal(t, int64(50), val) + + // test reset db + badger.Reset() + val = badger.GetAndroidError() + assert.Equal(t, int64(0), val) + + assert.NoError(t, badger.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/boltdb/boltdb.go b/push/gorush-with-mipush/src/storage/boltdb/boltdb.go new file mode 100644 index 0000000..f7377de --- /dev/null +++ b/push/gorush-with-mipush/src/storage/boltdb/boltdb.go @@ -0,0 +1,174 @@ +package boltdb + +import ( + "log" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/storage" + + "github.com/asdine/storm/v3" +) + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New(config *config.ConfYaml) *Storage { + return &Storage{ + config: config, + } +} + +// Storage is interface structure +type Storage struct { + config *config.ConfYaml + db *storm.DB +} + +// Init client storage. +func (s *Storage) Init() error { + var err error + s.db, err = storm.Open(s.config.Stat.BoltDB.Path) + return err +} + +// Close the storage connection +func (s *Storage) Close() error { + if s.db == nil { + return nil + } + + return s.db.Close() +} + +// Reset Client storage. +func (s *Storage) Reset() { + s.setBoltDB(storage.TotalCountKey, 0) + s.setBoltDB(storage.IosSuccessKey, 0) + s.setBoltDB(storage.IosErrorKey, 0) + s.setBoltDB(storage.AndroidSuccessKey, 0) + s.setBoltDB(storage.AndroidErrorKey, 0) + s.setBoltDB(storage.HuaweiSuccessKey, 0) + s.setBoltDB(storage.HuaweiErrorKey, 0) +} + +func (s *Storage) setBoltDB(key string, count int64) { + err := s.db.Set(s.config.Stat.BoltDB.Bucket, key, count) + if err != nil { + log.Println("BoltDB set error:", err.Error()) + } +} + +func (s *Storage) getBoltDB(key string, count *int64) { + err := s.db.Get(s.config.Stat.BoltDB.Bucket, key, count) + if err != nil { + log.Println("BoltDB get error:", err.Error()) + } +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + total := s.GetTotalCount() + count + s.setBoltDB(storage.TotalCountKey, total) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + total := s.GetIosSuccess() + count + s.setBoltDB(storage.IosSuccessKey, total) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + total := s.GetIosError() + count + s.setBoltDB(storage.IosErrorKey, total) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + total := s.GetAndroidSuccess() + count + s.setBoltDB(storage.AndroidSuccessKey, total) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + total := s.GetAndroidError() + count + s.setBoltDB(storage.AndroidErrorKey, total) +} + +// AddHuaweiSuccess record counts of success Huawei push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + total := s.GetHuaweiSuccess() + count + s.setBoltDB(storage.HuaweiSuccessKey, total) +} + +// AddHuaweiError record counts of error Huawei push notification. +func (s *Storage) AddHuaweiError(count int64) { + total := s.GetHuaweiError() + count + s.setBoltDB(storage.HuaweiErrorKey, total) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + total := s.GetHuaweiSuccess() + count + s.setBoltDB(storage.MISuccessKey, total) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + total := s.GetHuaweiError() + count + s.setBoltDB(storage.MIErrorKey, total) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + var count int64 + s.getBoltDB(storage.TotalCountKey, &count) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + var count int64 + s.getBoltDB(storage.IosSuccessKey, &count) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + var count int64 + s.getBoltDB(storage.IosErrorKey, &count) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + var count int64 + s.getBoltDB(storage.AndroidSuccessKey, &count) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + var count int64 + s.getBoltDB(storage.AndroidErrorKey, &count) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + var count int64 + s.getBoltDB(storage.HuaweiSuccessKey, &count) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + var count int64 + s.getBoltDB(storage.HuaweiErrorKey, &count) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/boltdb/boltdb_test.go b/push/gorush-with-mipush/src/storage/boltdb/boltdb_test.go new file mode 100644 index 0000000..07e1687 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/boltdb/boltdb_test.go @@ -0,0 +1,49 @@ +package boltdb + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestBoltDBEngine(t *testing.T) { + var val int64 + + cfg, _ := config.LoadConf() + + boltDB := New(cfg) + err := boltDB.Init() + assert.Nil(t, err) + boltDB.Reset() + + boltDB.AddTotalCount(10) + val = boltDB.GetTotalCount() + assert.Equal(t, int64(10), val) + boltDB.AddTotalCount(10) + val = boltDB.GetTotalCount() + assert.Equal(t, int64(20), val) + + boltDB.AddIosSuccess(20) + val = boltDB.GetIosSuccess() + assert.Equal(t, int64(20), val) + + boltDB.AddIosError(30) + val = boltDB.GetIosError() + assert.Equal(t, int64(30), val) + + boltDB.AddAndroidSuccess(40) + val = boltDB.GetAndroidSuccess() + assert.Equal(t, int64(40), val) + + boltDB.AddAndroidError(50) + val = boltDB.GetAndroidError() + assert.Equal(t, int64(50), val) + + // test reset db + boltDB.Reset() + val = boltDB.GetAndroidError() + assert.Equal(t, int64(0), val) + + assert.NoError(t, boltDB.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/buntdb/buntdb.go b/push/gorush-with-mipush/src/storage/buntdb/buntdb.go new file mode 100644 index 0000000..06acdd4 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/buntdb/buntdb.go @@ -0,0 +1,201 @@ +package buntdb + +import ( + "fmt" + "log" + "strconv" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/storage" + + "github.com/tidwall/buntdb" +) + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New(config *config.ConfYaml) *Storage { + return &Storage{ + config: config, + } +} + +// Storage is interface structure +type Storage struct { + config *config.ConfYaml + db *buntdb.DB +} + +// Init client storage. +func (s *Storage) Init() error { + var err error + s.db, err = buntdb.Open(s.config.Stat.BuntDB.Path) + return err +} + +// Close the storage connection +func (s *Storage) Close() error { + if s.db == nil { + return nil + } + + return s.db.Close() +} + +// Reset Client storage. +func (s *Storage) Reset() { + s.setBuntDB(storage.TotalCountKey, 0) + s.setBuntDB(storage.IosSuccessKey, 0) + s.setBuntDB(storage.IosErrorKey, 0) + s.setBuntDB(storage.AndroidSuccessKey, 0) + s.setBuntDB(storage.AndroidErrorKey, 0) + s.setBuntDB(storage.HuaweiSuccessKey, 0) + s.setBuntDB(storage.HuaweiErrorKey, 0) +} + +func (s *Storage) setBuntDB(key string, count int64) { + err := s.db.Update(func(tx *buntdb.Tx) error { + if _, _, err := tx.Set(key, fmt.Sprintf("%d", count), nil); err != nil { + return err + } + return nil + }) + if err != nil { + log.Println("BuntDB update error:", err.Error()) + } +} + +func (s *Storage) getBuntDB(key string, count *int64) { + err := s.db.View(func(tx *buntdb.Tx) error { + val, _ := tx.Get(key) + *count, _ = strconv.ParseInt(val, 10, 64) + return nil + }) + if err != nil { + log.Println("BuntDB get error:", err.Error()) + } +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + total := s.GetTotalCount() + count + s.setBuntDB(storage.TotalCountKey, total) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + total := s.GetIosSuccess() + count + s.setBuntDB(storage.IosSuccessKey, total) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + total := s.GetIosError() + count + s.setBuntDB(storage.IosErrorKey, total) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + total := s.GetAndroidSuccess() + count + s.setBuntDB(storage.AndroidSuccessKey, total) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + total := s.GetAndroidError() + count + s.setBuntDB(storage.AndroidErrorKey, total) +} + +// AddHuaweiSuccess record counts of success Huawei push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + total := s.GetHuaweiSuccess() + count + s.setBuntDB(storage.HuaweiSuccessKey, total) +} + +// AddHuaweiError record counts of error Huawei push notification. +func (s *Storage) AddHuaweiError(count int64) { + total := s.GetHuaweiError() + count + s.setBuntDB(storage.HuaweiErrorKey, total) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + total := s.GetMISuccess() + count + s.setBuntDB(storage.MISuccessKey, total) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + total := s.GetMIError() + count + s.setBuntDB(storage.MIErrorKey, total) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + var count int64 + s.getBuntDB(storage.TotalCountKey, &count) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + var count int64 + s.getBuntDB(storage.IosSuccessKey, &count) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + var count int64 + s.getBuntDB(storage.IosErrorKey, &count) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + var count int64 + s.getBuntDB(storage.AndroidSuccessKey, &count) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + var count int64 + s.getBuntDB(storage.AndroidErrorKey, &count) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + var count int64 + s.getBuntDB(storage.HuaweiSuccessKey, &count) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + var count int64 + s.getBuntDB(storage.HuaweiErrorKey, &count) + + return count +} + +// GetMISuccess show success counts of MI notification. +func (s *Storage) GetMISuccess() int64 { + var count int64 + s.getBuntDB(storage.MISuccessKey, &count) + + return count +} + +// GetMIError show error counts of MI notification. +func (s *Storage) GetMIError() int64 { + var count int64 + s.getBuntDB(storage.MIErrorKey, &count) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/buntdb/buntdb_test.go b/push/gorush-with-mipush/src/storage/buntdb/buntdb_test.go new file mode 100644 index 0000000..022ce42 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/buntdb/buntdb_test.go @@ -0,0 +1,54 @@ +package buntdb + +import ( + "os" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestBuntDBEngine(t *testing.T) { + var val int64 + + cfg, _ := config.LoadConf() + + if _, err := os.Stat(cfg.Stat.BuntDB.Path); os.IsNotExist(err) { + err := os.RemoveAll(cfg.Stat.BuntDB.Path) + assert.Nil(t, err) + } + + buntDB := New(cfg) + err := buntDB.Init() + assert.Nil(t, err) + buntDB.Reset() + + buntDB.AddTotalCount(10) + val = buntDB.GetTotalCount() + assert.Equal(t, int64(10), val) + buntDB.AddTotalCount(10) + val = buntDB.GetTotalCount() + assert.Equal(t, int64(20), val) + + buntDB.AddIosSuccess(20) + val = buntDB.GetIosSuccess() + assert.Equal(t, int64(20), val) + + buntDB.AddIosError(30) + val = buntDB.GetIosError() + assert.Equal(t, int64(30), val) + + buntDB.AddAndroidSuccess(40) + val = buntDB.GetAndroidSuccess() + assert.Equal(t, int64(40), val) + + buntDB.AddAndroidError(50) + val = buntDB.GetAndroidError() + assert.Equal(t, int64(50), val) + + buntDB.Reset() + val = buntDB.GetAndroidError() + assert.Equal(t, int64(0), val) + + assert.NoError(t, buntDB.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/leveldb/leveldb.go b/push/gorush-with-mipush/src/storage/leveldb/leveldb.go new file mode 100644 index 0000000..a71bc46 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/leveldb/leveldb.go @@ -0,0 +1,187 @@ +package leveldb + +import ( + "fmt" + "strconv" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/storage" + + "github.com/syndtr/goleveldb/leveldb" +) + +func (s *Storage) setLevelDB(key string, count int64) { + value := fmt.Sprintf("%d", count) + _ = s.db.Put([]byte(key), []byte(value), nil) +} + +func (s *Storage) getLevelDB(key string, count *int64) { + data, _ := s.db.Get([]byte(key), nil) + *count, _ = strconv.ParseInt(string(data), 10, 64) +} + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New(config *config.ConfYaml) *Storage { + return &Storage{ + config: config, + } +} + +// Storage is interface structure +type Storage struct { + config *config.ConfYaml + db *leveldb.DB +} + +// Init client storage. +func (s *Storage) Init() error { + var err error + s.db, err = leveldb.OpenFile(s.config.Stat.LevelDB.Path, nil) + return err +} + +// Close the storage connection +func (s *Storage) Close() error { + if s.db == nil { + return nil + } + + return s.db.Close() +} + +// Reset Client storage. +func (s *Storage) Reset() { + s.setLevelDB(storage.TotalCountKey, 0) + s.setLevelDB(storage.IosSuccessKey, 0) + s.setLevelDB(storage.IosErrorKey, 0) + s.setLevelDB(storage.AndroidSuccessKey, 0) + s.setLevelDB(storage.AndroidErrorKey, 0) + s.setLevelDB(storage.HuaweiSuccessKey, 0) + s.setLevelDB(storage.HuaweiErrorKey, 0) +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + total := s.GetTotalCount() + count + s.setLevelDB(storage.TotalCountKey, total) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + total := s.GetIosSuccess() + count + s.setLevelDB(storage.IosSuccessKey, total) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + total := s.GetIosError() + count + s.setLevelDB(storage.IosErrorKey, total) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + total := s.GetAndroidSuccess() + count + s.setLevelDB(storage.AndroidSuccessKey, total) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + total := s.GetAndroidError() + count + s.setLevelDB(storage.AndroidErrorKey, total) +} + +// AddHuaweiSuccess record counts of success Huawei push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + total := s.GetHuaweiSuccess() + count + s.setLevelDB(storage.HuaweiSuccessKey, total) +} + +// AddHuaweiError record counts of error Huawei push notification. +func (s *Storage) AddHuaweiError(count int64) { + total := s.GetHuaweiError() + count + s.setLevelDB(storage.HuaweiErrorKey, total) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + total := s.GetMISuccess() + count + s.setLevelDB(storage.MISuccessKey, total) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + total := s.GetMIError() + count + s.setLevelDB(storage.MIErrorKey, total) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + var count int64 + s.getLevelDB(storage.TotalCountKey, &count) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + var count int64 + s.getLevelDB(storage.IosSuccessKey, &count) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + var count int64 + s.getLevelDB(storage.IosErrorKey, &count) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + var count int64 + s.getLevelDB(storage.AndroidSuccessKey, &count) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + var count int64 + s.getLevelDB(storage.AndroidErrorKey, &count) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + var count int64 + s.getLevelDB(storage.HuaweiSuccessKey, &count) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + var count int64 + s.getLevelDB(storage.HuaweiErrorKey, &count) + + return count +} + +// GetMISuccess show success counts of MI notification. +func (s *Storage) GetMISuccess() int64 { + var count int64 + s.getLevelDB(storage.MISuccessKey, &count) + + return count +} + +// GetMIError show error counts of MI notification. +func (s *Storage) GetMIError() int64 { + var count int64 + s.getLevelDB(storage.MIErrorKey, &count) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/leveldb/leveldb_test.go b/push/gorush-with-mipush/src/storage/leveldb/leveldb_test.go new file mode 100644 index 0000000..8268214 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/leveldb/leveldb_test.go @@ -0,0 +1,54 @@ +package leveldb + +import ( + "os" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestLevelDBEngine(t *testing.T) { + var val int64 + + cfg, _ := config.LoadConf() + + if _, err := os.Stat(cfg.Stat.LevelDB.Path); os.IsNotExist(err) { + err = os.RemoveAll(cfg.Stat.LevelDB.Path) + assert.Nil(t, err) + } + + levelDB := New(cfg) + err := levelDB.Init() + assert.Nil(t, err) + levelDB.Reset() + + levelDB.AddTotalCount(10) + val = levelDB.GetTotalCount() + assert.Equal(t, int64(10), val) + levelDB.AddTotalCount(10) + val = levelDB.GetTotalCount() + assert.Equal(t, int64(20), val) + + levelDB.AddIosSuccess(20) + val = levelDB.GetIosSuccess() + assert.Equal(t, int64(20), val) + + levelDB.AddIosError(30) + val = levelDB.GetIosError() + assert.Equal(t, int64(30), val) + + levelDB.AddAndroidSuccess(40) + val = levelDB.GetAndroidSuccess() + assert.Equal(t, int64(40), val) + + levelDB.AddAndroidError(50) + val = levelDB.GetAndroidError() + assert.Equal(t, int64(50), val) + + levelDB.Reset() + val = levelDB.GetAndroidError() + assert.Equal(t, int64(0), val) + + assert.NoError(t, levelDB.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/memory/memory.go b/push/gorush-with-mipush/src/storage/memory/memory.go new file mode 100644 index 0000000..77495ef --- /dev/null +++ b/push/gorush-with-mipush/src/storage/memory/memory.go @@ -0,0 +1,165 @@ +package memory + +import ( + "sync/atomic" +) + +// statApp is app status structure +type statApp struct { + TotalCount int64 `json:"total_count"` + Ios IosStatus `json:"ios"` + Android AndroidStatus `json:"android"` + Huawei HuaweiStatus `json:"huawei"` + MI MIStatus `json:"mi"` +} + +// AndroidStatus is android structure +type AndroidStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// IosStatus is iOS structure +type IosStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// HuaweiStatus is android structure +type HuaweiStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// MIStatus is mi structure +type MIStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New() *Storage { + return &Storage{ + stat: &statApp{}, + } +} + +// Storage is interface structure +type Storage struct { + stat *statApp +} + +// Init client storage. +func (s *Storage) Init() error { + return nil +} + +// Close the storage connection +func (s *Storage) Close() error { + return nil +} + +// Reset Client storage. +func (s *Storage) Reset() { + atomic.StoreInt64(&s.stat.TotalCount, 0) + atomic.StoreInt64(&s.stat.Ios.PushSuccess, 0) + atomic.StoreInt64(&s.stat.Ios.PushError, 0) + atomic.StoreInt64(&s.stat.Android.PushSuccess, 0) + atomic.StoreInt64(&s.stat.Android.PushError, 0) + atomic.StoreInt64(&s.stat.Huawei.PushSuccess, 0) + atomic.StoreInt64(&s.stat.Huawei.PushError, 0) +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + atomic.AddInt64(&s.stat.TotalCount, count) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + atomic.AddInt64(&s.stat.Ios.PushSuccess, count) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + atomic.AddInt64(&s.stat.Ios.PushError, count) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + atomic.AddInt64(&s.stat.Android.PushSuccess, count) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + atomic.AddInt64(&s.stat.Android.PushError, count) +} + +// AddHuaweiSuccess record counts of success Huawei push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + atomic.AddInt64(&s.stat.Huawei.PushSuccess, count) +} + +// AddHuaweiError record counts of error Huawei push notification. +func (s *Storage) AddHuaweiError(count int64) { + atomic.AddInt64(&s.stat.Huawei.PushError, count) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + atomic.AddInt64(&s.stat.MI.PushSuccess, count) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + atomic.AddInt64(&s.stat.MI.PushError, count) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + count := atomic.LoadInt64(&s.stat.TotalCount) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + count := atomic.LoadInt64(&s.stat.Ios.PushSuccess) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + count := atomic.LoadInt64(&s.stat.Ios.PushError) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + count := atomic.LoadInt64(&s.stat.Android.PushSuccess) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + count := atomic.LoadInt64(&s.stat.Android.PushError) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + count := atomic.LoadInt64(&s.stat.Huawei.PushSuccess) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + count := atomic.LoadInt64(&s.stat.Huawei.PushError) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/memory/memory_test.go b/push/gorush-with-mipush/src/storage/memory/memory_test.go new file mode 100644 index 0000000..2e088c1 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/memory/memory_test.go @@ -0,0 +1,46 @@ +package memory + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMemoryEngine(t *testing.T) { + var val int64 + + memory := New() + + assert.Nil(t, memory.Init()) + + memory.AddTotalCount(1) + val = memory.GetTotalCount() + assert.Equal(t, int64(1), val) + + memory.AddTotalCount(100) + val = memory.GetTotalCount() + assert.Equal(t, int64(101), val) + + memory.AddIosSuccess(2) + val = memory.GetIosSuccess() + assert.Equal(t, int64(2), val) + + memory.AddIosError(3) + val = memory.GetIosError() + assert.Equal(t, int64(3), val) + + memory.AddAndroidSuccess(4) + val = memory.GetAndroidSuccess() + assert.Equal(t, int64(4), val) + + memory.AddAndroidError(5) + val = memory.GetAndroidError() + assert.Equal(t, int64(5), val) + + // test reset db + memory.Reset() + val = memory.GetTotalCount() + assert.Equal(t, int64(0), val) + + assert.NoError(t, memory.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/redis/redis.go b/push/gorush-with-mipush/src/storage/redis/redis.go new file mode 100644 index 0000000..7325589 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/redis/redis.go @@ -0,0 +1,183 @@ +package redis + +import ( + "context" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/storage" + + "github.com/go-redis/redis/v8" +) + +// New func implements the storage interface for gorush (https://github.com/appleboy/gorush) +func New(config *config.ConfYaml) *Storage { + return &Storage{ + ctx: context.Background(), + config: config, + } +} + +func (s *Storage) getInt64(key string, count *int64) { + val, _ := s.client.Get(s.ctx, key).Result() + *count, _ = strconv.ParseInt(val, 10, 64) +} + +// Storage is interface structure +type Storage struct { + ctx context.Context + config *config.ConfYaml + client redis.Cmdable +} + +// Init client storage. +func (s *Storage) Init() error { + if s.config.Stat.Redis.Cluster { + s.client = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: strings.Split(s.config.Stat.Redis.Addr, ","), + Password: s.config.Stat.Redis.Password, + }) + } else { + s.client = redis.NewClient(&redis.Options{ + Addr: s.config.Stat.Redis.Addr, + Password: s.config.Stat.Redis.Password, + DB: s.config.Stat.Redis.DB, + }) + } + + if err := s.client.Ping(s.ctx).Err(); err != nil { + return err + } + + return nil +} + +// Close the storage connection +func (s *Storage) Close() error { + switch v := s.client.(type) { + case *redis.Client: + return v.Close() + case *redis.ClusterClient: + return v.Close() + case nil: + return nil + default: + // this will not happen anyway, unless we mishandle it on `Init` + panic(fmt.Sprintf("invalid redis client: %v", reflect.TypeOf(v))) + } +} + +// Reset Client storage. +func (s *Storage) Reset() { + s.client.Set(s.ctx, storage.TotalCountKey, int64(0), 0) + s.client.Set(s.ctx, storage.IosSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.IosErrorKey, int64(0), 0) + s.client.Set(s.ctx, storage.AndroidSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.AndroidErrorKey, int64(0), 0) + s.client.Set(s.ctx, storage.HuaweiSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.HuaweiErrorKey, int64(0), 0) +} + +// AddTotalCount record push notification count. +func (s *Storage) AddTotalCount(count int64) { + s.client.IncrBy(s.ctx, storage.TotalCountKey, count) +} + +// AddIosSuccess record counts of success iOS push notification. +func (s *Storage) AddIosSuccess(count int64) { + s.client.IncrBy(s.ctx, storage.IosSuccessKey, count) +} + +// AddIosError record counts of error iOS push notification. +func (s *Storage) AddIosError(count int64) { + s.client.IncrBy(s.ctx, storage.IosErrorKey, count) +} + +// AddAndroidSuccess record counts of success Android push notification. +func (s *Storage) AddAndroidSuccess(count int64) { + s.client.IncrBy(s.ctx, storage.AndroidSuccessKey, count) +} + +// AddAndroidError record counts of error Android push notification. +func (s *Storage) AddAndroidError(count int64) { + s.client.IncrBy(s.ctx, storage.AndroidErrorKey, count) +} + +// AddHuaweiSuccess record counts of success Android push notification. +func (s *Storage) AddHuaweiSuccess(count int64) { + s.client.IncrBy(s.ctx, storage.HuaweiSuccessKey, count) +} + +// AddHuaweiError record counts of error Android push notification. +func (s *Storage) AddHuaweiError(count int64) { + s.client.IncrBy(s.ctx, storage.HuaweiErrorKey, count) +} + +// AddMISuccess record counts of success MI push notification. +func (s *Storage) AddMISuccess(count int64) { + s.client.IncrBy(s.ctx, storage.MISuccessKey, count) +} + +// AddMIError record counts of error MI push notification. +func (s *Storage) AddMIError(count int64) { + s.client.IncrBy(s.ctx, storage.MIErrorKey, count) +} + +// GetTotalCount show counts of all notification. +func (s *Storage) GetTotalCount() int64 { + var count int64 + s.getInt64(storage.TotalCountKey, &count) + + return count +} + +// GetIosSuccess show success counts of iOS notification. +func (s *Storage) GetIosSuccess() int64 { + var count int64 + s.getInt64(storage.IosSuccessKey, &count) + + return count +} + +// GetIosError show error counts of iOS notification. +func (s *Storage) GetIosError() int64 { + var count int64 + s.getInt64(storage.IosErrorKey, &count) + + return count +} + +// GetAndroidSuccess show success counts of Android notification. +func (s *Storage) GetAndroidSuccess() int64 { + var count int64 + s.getInt64(storage.AndroidSuccessKey, &count) + + return count +} + +// GetAndroidError show error counts of Android notification. +func (s *Storage) GetAndroidError() int64 { + var count int64 + s.getInt64(storage.AndroidErrorKey, &count) + + return count +} + +// GetHuaweiSuccess show success counts of Huawei notification. +func (s *Storage) GetHuaweiSuccess() int64 { + var count int64 + s.getInt64(storage.HuaweiSuccessKey, &count) + + return count +} + +// GetHuaweiError show error counts of Huawei notification. +func (s *Storage) GetHuaweiError() int64 { + var count int64 + s.getInt64(storage.HuaweiErrorKey, &count) + + return count +} diff --git a/push/gorush-with-mipush/src/storage/redis/redis_test.go b/push/gorush-with-mipush/src/storage/redis/redis_test.go new file mode 100644 index 0000000..0f31072 --- /dev/null +++ b/push/gorush-with-mipush/src/storage/redis/redis_test.go @@ -0,0 +1,74 @@ +package redis + +import ( + "sync" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestRedisServerError(t *testing.T) { + cfg, _ := config.LoadConf() + cfg.Stat.Redis.Addr = "redis:6370" + + redis := New(cfg) + err := redis.Init() + + assert.Error(t, err) +} + +func TestRedisEngine(t *testing.T) { + var val int64 + + cfg, _ := config.LoadConf() + cfg.Stat.Redis.Addr = "redis:6379" + + redis := New(cfg) + err := redis.Init() + assert.Nil(t, err) + redis.Reset() + + redis.AddTotalCount(10) + val = redis.GetTotalCount() + assert.Equal(t, int64(10), val) + redis.AddTotalCount(10) + val = redis.GetTotalCount() + assert.Equal(t, int64(20), val) + + redis.AddIosSuccess(20) + val = redis.GetIosSuccess() + assert.Equal(t, int64(20), val) + + redis.AddIosError(30) + val = redis.GetIosError() + assert.Equal(t, int64(30), val) + + redis.AddAndroidSuccess(40) + val = redis.GetAndroidSuccess() + assert.Equal(t, int64(40), val) + + redis.AddAndroidError(50) + val = redis.GetAndroidError() + assert.Equal(t, int64(50), val) + + // test reset db + redis.Reset() + val = redis.GetAndroidError() + assert.Equal(t, int64(0), val) + + // test concurrency issues + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + redis.AddTotalCount(1) + wg.Done() + }() + } + wg.Wait() + val = redis.GetTotalCount() + assert.Equal(t, int64(10), val) + + assert.NoError(t, redis.Close()) +} diff --git a/push/gorush-with-mipush/src/storage/storage.go b/push/gorush-with-mipush/src/storage/storage.go new file mode 100644 index 0000000..b661a1d --- /dev/null +++ b/push/gorush-with-mipush/src/storage/storage.go @@ -0,0 +1,53 @@ +package storage + +const ( + // TotalCountKey is key name for total count of storage + TotalCountKey = "gorush-total-count" + + // IosSuccessKey is key name or ios success count of storage + IosSuccessKey = "gorush-ios-success-count" + + // IosErrorKey is key name or ios success error of storage + IosErrorKey = "gorush-ios-error-count" + + // AndroidSuccessKey is key name for android success count of storage + AndroidSuccessKey = "gorush-android-success-count" + + // AndroidErrorKey is key name for android error count of storage + AndroidErrorKey = "gorush-android-error-count" + + // HuaweiSuccessKey is key name for huawei success count of storage + HuaweiSuccessKey = "gorush-huawei-success-count" + + // HuaweiErrorKey is key name for huawei error count of storage + HuaweiErrorKey = "gorush-huawei-error-count" + + // MISuccessKey is key name for mi success count of storage + MISuccessKey = "gorush-mi-success-count" + + // MIErrorKey is key name for mi error count of storage + MIErrorKey = "gorush-mi-error-count" +) + +// Storage interface +type Storage interface { + Init() error + Reset() + AddTotalCount(int64) + AddIosSuccess(int64) + AddIosError(int64) + AddAndroidSuccess(int64) + AddAndroidError(int64) + AddHuaweiSuccess(int64) + AddHuaweiError(int64) + AddMISuccess(int64) + AddMIError(int64) + GetTotalCount() int64 + GetIosSuccess() int64 + GetIosError() int64 + GetAndroidSuccess() int64 + GetAndroidError() int64 + GetHuaweiSuccess() int64 + GetHuaweiError() int64 + Close() error +} diff --git a/push/gorush-with-mipush/src/tests/README.md b/push/gorush-with-mipush/src/tests/README.md new file mode 100644 index 0000000..c2a051d --- /dev/null +++ b/push/gorush-with-mipush/src/tests/README.md @@ -0,0 +1,34 @@ +# Testing + +How to test gorush with http request? + +## download bat tool + +Download [cURL-like tool for humans](https://github.com/astaxie/bat). + +## testing + +see the JSON format: + +```json +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!" + }, + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!" + } + ] +} +``` + +run the following command. + +```sh +bat POST localhost:8088/api/push < tests/test.json +``` diff --git a/push/gorush-with-mipush/src/tests/test.json b/push/gorush-with-mipush/src/tests/test.json new file mode 100644 index 0000000..90a28e0 --- /dev/null +++ b/push/gorush-with-mipush/src/tests/test.json @@ -0,0 +1,14 @@ +{ + "notifications": [ + { + "tokens": ["token_a", "token_b"], + "platform": 1, + "message": "Hello World iOS!" + }, + { + "tokens": ["token_a", "token_b"], + "platform": 2, + "message": "Hello World Android!" + } + ] +}