mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-19 08:37:06 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c9455b0d | |||
| dd2448f35a | |||
| 3f78f4d672 | |||
| 5fbe94c559 | |||
| 80d556343e | |||
| 612d1054db | |||
| acf2fc32c4 | |||
| af01c63298 | |||
| 2e98d64f94 | |||
| cdcdd45bcf | |||
| b3e2a91f56 | |||
| 7d9753e2da | |||
| f1aef383b7 | |||
| 6647231278 | |||
| 531368da86 | |||
| 0c21925939 | |||
| 19a445e73a | |||
| 96e0070ed2 | |||
| 516ff5206d | |||
| 4d2b328589 | |||
| 810be2d423 | |||
| e3d0334b6f | |||
| fb523e5573 | |||
| cb8d1a2389 | |||
| 84f0a6722a | |||
| c3a495facd | |||
| 9cdc40ca05 | |||
| 7021b1c2ea | |||
| 607d9df8a9 | |||
| 93396145dc | |||
| 7457fb06d2 | |||
| bee2642aec | |||
| b481ce2203 | |||
| 040d887aae | |||
| 3710dff0cd | |||
| f5bc6ad1f0 | |||
| e8a95e26f6 | |||
| ebe54ca92e | |||
| ff7e45f395 | |||
| 79c63f5785 | |||
| 3ca9e625f5 | |||
| 5b874657cb | |||
| bfe67f3005 | |||
| 99e6f00aaa |
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,6 +7,7 @@
|
|||||||
*~
|
*~
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.vs
|
||||||
|
|
||||||
# Test files
|
# Test files
|
||||||
godog.test
|
godog.test
|
||||||
@ -35,6 +36,8 @@ cmd/Import-Export/deploy
|
|||||||
proton-bridge
|
proton-bridge
|
||||||
cmd/Desktop-Bridge/*.exe
|
cmd/Desktop-Bridge/*.exe
|
||||||
cmd/launcher/*.exe
|
cmd/launcher/*.exe
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
# Jetbrains (CLion, Golang) cmake build dirs
|
# Jetbrains (CLion, Golang) cmake build dirs
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
|
|||||||
@ -88,6 +88,7 @@ linters:
|
|||||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||||
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||||
|
- copyloopvar # detects places where loop variables are copied.
|
||||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||||
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||||
|
|||||||
@ -63,11 +63,15 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||||
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||||
|
* [oauth2](https://golang.org/x/oauth2) available under [license](https://cs.opensource.google/go/x/oauth2/+/master:LICENSE)
|
||||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||||
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
||||||
|
* [api](https://google.golang.org/api) available under [license](https://pkg.go.dev/google.golang.org/api?tab=licenses)
|
||||||
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||||
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||||
|
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
|
||||||
|
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
|
||||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||||
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||||
@ -95,8 +99,11 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
||||||
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||||
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||||
|
* [groupcache](https://github.com/golang/groupcache) available under [license](https://github.com/golang/groupcache/blob/master/LICENSE)
|
||||||
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||||
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
||||||
|
* [enterprise-certificate-proxy](https://github.com/googleapis/enterprise-certificate-proxy) available under [license](https://github.com/googleapis/enterprise-certificate-proxy/blob/master/LICENSE)
|
||||||
|
* [gax-go](https://github.com/googleapis/gax-go/v2) available under [license](https://github.com/googleapis/gax-go/v2/blob/master/LICENSE)
|
||||||
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
||||||
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
||||||
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
||||||
@ -124,14 +131,16 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
||||||
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
||||||
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
||||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
|
||||||
|
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
|
||||||
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
||||||
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
||||||
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||||
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||||
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||||
|
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
|
||||||
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||||
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||||
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||||
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
||||||
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
||||||
|
|||||||
75
Changelog.md
75
Changelog.md
@ -3,6 +3,81 @@
|
|||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
|
||||||
|
## Flavien Bridge 3.16.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-205: Add support for the IMAP AUTHENTICATE command.
|
||||||
|
* BRIDGE-268: Add kill switch feature flag for the IMAP AUTHENTICATE command.
|
||||||
|
* BRIDGE-261: Delete gluon data during user deletion.
|
||||||
|
* BRIDGE-246: Test: Add Settings Menu Bridge UI e2e automation tests.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-107: Improved human verification UX.
|
||||||
|
* BRIDGE-281: Disable keychain test on macOS.
|
||||||
|
* BRIDGE-266: Heartbeat telemetry update.
|
||||||
|
* BRIDGE-253: Removed unused telemetry (activation and troubleshooting).
|
||||||
|
* BRIDGE-252: Restored the -h shortcut for the CLI --help switch.
|
||||||
|
* BRIDGE-264: Ignore apple notes as UserAgent.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-256: Fix reversed order of headers with multiple values.
|
||||||
|
* BRIDGE-258: Fixed issue with draft updates and sending during synchronization.
|
||||||
|
|
||||||
|
|
||||||
|
## Erasmus Bridge 3.15.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-238: Added host information to sentry events; new sentry event for keychain issues.
|
||||||
|
* BRIDGE-236: Added SMTP observability metrics.
|
||||||
|
* BRIDGE-217: Added missing parameter to the CLI help command.
|
||||||
|
* BRIDGE-234: Add accessibility name in QML for UI automation.
|
||||||
|
* BRIDGE-232: Test: Add Home Menu Bridge UI e2e automation tests.
|
||||||
|
* BRIDGE-220: Test: Add Bridge E2E UI login/logout tests for Windows.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-228: Removed sentry events.
|
||||||
|
* BRIDGE-218: Observability adapter; gluon observability metrics and tests.
|
||||||
|
* BRIDGE-215: Tweak wording on macOS profile install page.
|
||||||
|
* BRIDGE-131: Test: Integration tests for messages from Proton <-> Gmail.
|
||||||
|
* BRIDGE-142: Bridge icon can be removed from the menu bar on macOS.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-240: Fix for running against Qt 6.8 (contribution of GitHub user Cimbali).
|
||||||
|
* BRIDGE-231: Fix reversed header order in messages.
|
||||||
|
* BRIDGE-235: Fix compilation of Bridge GUI Tester on Windows.
|
||||||
|
* BRIDGE-120: Use appropriate address key when importing / saving draft.
|
||||||
|
|
||||||
|
|
||||||
|
## Dragon Bridge 3.14.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-207: Failure to download or verify an update now fails silently.
|
||||||
|
* BRIDGE-204: Removed redundant Sentry events.
|
||||||
|
* BRIDGE-150: Observability service modification.
|
||||||
|
* BRIDGE-210: Reduced log level of cache events so they won't be printed to stdout.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-106: Fixed import of multipart-related messages.
|
||||||
|
* BRIDGE-108: Fixed GetInitials when empty username is passed.
|
||||||
|
|
||||||
|
|
||||||
|
## Colorado Bridge 3.13.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-37: added message broadcasting functionality.
|
||||||
|
* BRIDGE-122: added observability service.
|
||||||
|
* BRIDGE-119: added support for Feature Flags.
|
||||||
|
* BRIDGE-116: added command-line switches to enable/disable keychain check on macOS.
|
||||||
|
* BRIDGE-88: added context menu for quick actions on input labels: cut, copy, paste.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-81: KB article suggestion updates + more weight for long keywords.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-67: Added detection for username changes on macOS & automatic reconfiguration.
|
||||||
|
* BRIDGE-138: Remove deprecated doc.
|
||||||
|
|
||||||
|
|
||||||
## Bastei Bridge 3.12.0
|
## Bastei Bridge 3.12.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
|
|||||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=3.12.0+git
|
BRIDGE_APP_VERSION?=3.16.0+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
APP_FULL_NAME:=Proton Mail Bridge
|
APP_FULL_NAME:=Proton Mail Bridge
|
||||||
APP_VENDOR:=Proton AG
|
APP_VENDOR:=Proton AG
|
||||||
@ -189,7 +189,7 @@ ${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
|||||||
|
|
||||||
## Dev dependencies
|
## Dev dependencies
|
||||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||||
LINTVER:="v1.59.1"
|
LINTVER:="v1.61.0"
|
||||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||||
|
|
||||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||||
|
|||||||
14
README.md
14
README.md
@ -1,7 +1,7 @@
|
|||||||
# Proton Mail Bridge and Import Export app
|
# Proton Mail Bridge
|
||||||
Copyright (c) 2024 Proton AG
|
Copyright (c) 2024 Proton AG
|
||||||
|
|
||||||
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
This repository holds the Proton Mail Bridge application.
|
||||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||||
@ -13,7 +13,7 @@ Proton Mail Bridge for e-mail clients.
|
|||||||
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
||||||
its GUI.
|
its GUI.
|
||||||
|
|
||||||
To configure an e-mail client, firstly log in using your Proton Mail credentials.
|
To configure an e-mail client, first log in using your Proton Mail credentials.
|
||||||
Open your e-mail client and add a new account using the settings which are
|
Open your e-mail client and add a new account using the settings which are
|
||||||
located in the Bridge GUI. The client will only be able to sync with
|
located in the Bridge GUI. The client will only be able to sync with
|
||||||
your Proton Mail account when the Bridge is running, thus the option
|
your Proton Mail account when the Bridge is running, thus the option
|
||||||
@ -24,10 +24,10 @@ background.
|
|||||||
|
|
||||||
More details [on the public website](https://proton.me/mail/bridge).
|
More details [on the public website](https://proton.me/mail/bridge).
|
||||||
|
|
||||||
## Launchers
|
## Launcher
|
||||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
The launcher is a binary used to run the Proton Mail Bridge.
|
||||||
|
|
||||||
Official distributions of the Proton Mail Bridge and Import-Export apps contain
|
The Official distribution of the Proton Mail Bridge application contains
|
||||||
both a launcher and the app itself. The launcher is installed in a protected
|
both a launcher and the app itself. The launcher is installed in a protected
|
||||||
area of the system (i.e. an area accessible only with admin privileges) and is
|
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||||
used to run the app. The launcher ensures that nobody tampered with the app's
|
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||||
@ -37,7 +37,7 @@ feature enables the app to securely update itself automatically without asking
|
|||||||
the user for a password.
|
the user for a password.
|
||||||
|
|
||||||
## Keychain
|
## Keychain
|
||||||
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
|
You need to have a keychain in order to run Proton Mail Bridge. On Mac or
|
||||||
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||||
or
|
or
|
||||||
|
|||||||
15
go.mod
15
go.mod
@ -7,9 +7,9 @@ toolchain go1.21.9
|
|||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.2.0
|
github.com/Masterminds/semver/v3 v3.2.0
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c
|
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
@ -47,14 +47,18 @@ require (
|
|||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/net v0.24.0
|
||||||
|
golang.org/x/oauth2 v0.7.0
|
||||||
golang.org/x/sys v0.19.0
|
golang.org/x/sys v0.19.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
|
google.golang.org/api v0.114.0
|
||||||
google.golang.org/grpc v1.56.3
|
google.golang.org/grpc v1.56.3
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.33.0
|
||||||
howett.net/plist v1.0.0
|
howett.net/plist v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/compute v1.19.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||||
@ -82,8 +86,11 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||||
@ -112,17 +119,19 @@ require (
|
|||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.22.0 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423
|
||||||
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
||||||
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
||||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
||||||
|
|||||||
87
go.sum
87
go.sum
@ -5,9 +5,16 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
|||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
||||||
|
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
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=
|
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=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
@ -27,35 +34,19 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
|||||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c h1:P3SvCACt13Zqdj0IRDB4bgwqI68+oMB2j0uVuPQyoTw=
|
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2 h1:iZjKvjb6VkGb52ZaBBiXC1MGYJN4C/S97JfppdzpMHQ=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80 h1:cP4+6RFn9vVgYnoDwxBU4EtIAZA+eM4rzOaSZNqZ1xg=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47 h1:a+3dOyIxJEslN5HxyICM8flY9lnCyJupXNcv6fUaivA=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6 h1:nERxOYS4ndSgWEr834YYkb1j0bZK/dJAmhoyYB1MtNY=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b h1:zifGh4LS5HwQIaVCccSe5/oJGTOjFeVObMRl3QJoJ3k=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917 h1:Ma6PfXFDuw7rYYq28FXNW6ubhYquRUmBuLyZrjJWHUE=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179 h1:6Xo0iRYa4GBgZ2HA+IR3KdqiML8Z10h2F9TYe+9n1+M=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391 h1:PW6bE+mhsfAx4+wDCCNjhFrCNiiuMjY6j7RwqRUdPKI=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba h1:QtDxgIbgPqRQg7VT+nIUJlaOyNFAoGyg59oW3Hji/0A=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1 h1:gATlMoj4raG32WyGGh8SpipoQeR2AlU7g+8NAMicTcw=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2 h1:yx0iejqB5c21HIN5jn9IsbyzUns0dPUUaGfyUHF3TmQ=
|
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||||
@ -90,6 +81,7 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
|||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
@ -106,6 +98,7 @@ github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtM
|
|||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
@ -157,6 +150,10 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwo
|
|||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
@ -209,6 +206,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
|||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@ -217,6 +216,13 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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.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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
@ -224,6 +230,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
|||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -234,10 +244,15 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
|
|||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
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/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@ -383,6 +398,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
@ -464,6 +480,8 @@ gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREv
|
|||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
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.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.uber.org/atomic v1.4.0/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/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
@ -478,6 +496,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
@ -526,6 +545,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20200226121028-0de0cce0169b/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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@ -543,6 +563,8 @@ golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
|||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||||
|
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -573,6 +595,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -632,6 +655,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/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-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-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-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-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-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
@ -656,10 +680,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
|||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
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.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
|
||||||
|
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine 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.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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/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-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-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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -669,13 +697,27 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/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-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-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
|
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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
@ -703,6 +745,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-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-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-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=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
|||||||
@ -75,6 +75,13 @@ const (
|
|||||||
|
|
||||||
flagLogIMAP = "log-imap"
|
flagLogIMAP = "log-imap"
|
||||||
flagLogSMTP = "log-smtp"
|
flagLogSMTP = "log-smtp"
|
||||||
|
|
||||||
|
flagEnableKeychainTest = "enable-keychain-test"
|
||||||
|
flagDisableKeychainTest = "disable-keychain-test"
|
||||||
|
|
||||||
|
flagSoftwareRenderer = "software-renderer"
|
||||||
|
flagSetSoftwareRenderer = "set-software-renderer"
|
||||||
|
flagSetHardwareRenderer = "set-hardware-renderer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hidden flags.
|
// Hidden flags.
|
||||||
@ -82,9 +89,6 @@ const (
|
|||||||
flagLauncher = "launcher"
|
flagLauncher = "launcher"
|
||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
flagParentPID = "parent-pid"
|
flagParentPID = "parent-pid"
|
||||||
flagSoftwareRenderer = "software-renderer"
|
|
||||||
flagEnableKeychainTest = "enable-keychain-test"
|
|
||||||
flagDisableKeychainTest = "disable-keychain-test"
|
|
||||||
FlagSessionID = "session-id"
|
FlagSessionID = "session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,18 +97,21 @@ const (
|
|||||||
appShortName = "bridge"
|
appShortName = "bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// the two flags below have been deprecated by BRIDGE-281. We however keep them so that bridge does not error if they are passed on startup.
|
||||||
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
Name: flagEnableKeychainTest,
|
Name: flagEnableKeychainTest,
|
||||||
Usage: "Enable the keychain test",
|
Usage: "This flag is deprecated and does nothing",
|
||||||
Hidden: true,
|
|
||||||
Value: false,
|
Value: false,
|
||||||
} //nolint:gochecknoglobals
|
DisableDefaultText: true,
|
||||||
|
Hidden: true,
|
||||||
|
}
|
||||||
|
|
||||||
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
Name: flagDisableKeychainTest,
|
Name: flagDisableKeychainTest,
|
||||||
Usage: "Disable the keychain test",
|
Usage: "This flag is deprecated and does nothing",
|
||||||
Hidden: true,
|
|
||||||
Value: false,
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
Hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App {
|
||||||
@ -156,6 +163,24 @@ func New() *cli.App {
|
|||||||
Name: flagLogSMTP,
|
Name: flagLogSMTP,
|
||||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||||
|
Usage: "Use software rendering of the GUI for the current execution of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSetSoftwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||||
|
Usage: "Toggle software rendering of the GUI for the current and future executions of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSetHardwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||||
|
Usage: "Toggle hardware rendering of the GUI for the current and future executions of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Hidden flags
|
// Hidden flags
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
@ -174,19 +199,24 @@ func New() *cli.App {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Value: -1,
|
Value: -1,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
|
||||||
Usage: "GUI is using software renderer",
|
|
||||||
Hidden: true,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: FlagSessionID,
|
Name: FlagSessionID,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
// the two flags below were introduced by BRIDGE-116
|
}
|
||||||
cliFlagEnableKeychainTest,
|
|
||||||
cliFlagDisableKeychainTest,
|
// We override the default help value because we want "Show" to be capitalized
|
||||||
|
cli.HelpFlag = &cli.BoolFlag{
|
||||||
|
Name: "help",
|
||||||
|
Aliases: []string{"h"},
|
||||||
|
Usage: "Show help",
|
||||||
|
DisableDefaultText: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if onMacOS() {
|
||||||
|
// The two flags below were introduced for BRIDGE-116, and are available only on macOS.
|
||||||
|
// They have been later removed fro BRIDGE-281.
|
||||||
|
app.Flags = append(app.Flags, cliFlagEnableKeychainTest, cliFlagDisableKeychainTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
@ -257,10 +287,9 @@ func run(c *cli.Context) error {
|
|||||||
|
|
||||||
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||||
// Look for available keychains
|
// Look for available keychains
|
||||||
skipKeychainTest := checkSkipKeychainTest(c, settings)
|
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
||||||
return WithKeychainList(crashHandler, skipKeychainTest, func(keychains *keychain.List) error {
|
|
||||||
// Unlock the encrypted vault.
|
// Unlock the encrypted vault.
|
||||||
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||||
if !v.Migrated() {
|
if !v.Migrated() {
|
||||||
// Migrate old settings into the vault.
|
// Migrate old settings into the vault.
|
||||||
if err := migrateOldSettings(v); err != nil {
|
if err := migrateOldSettings(v); err != nil {
|
||||||
@ -522,11 +551,11 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithKeychainList init the list of usable keychains.
|
// WithKeychainList init the list of usable keychains.
|
||||||
func WithKeychainList(panicHandler async.PanicHandler, skipKeychainTest bool, fn func(*keychain.List) error) error {
|
func WithKeychainList(panicHandler async.PanicHandler, fn func(*keychain.List) error) error {
|
||||||
logrus.Debug("Creating keychain list")
|
logrus.Debug("Creating keychain list")
|
||||||
defer logrus.Debug("Keychain list stop")
|
defer logrus.Debug("Keychain list stop")
|
||||||
defer async.HandlePanic(panicHandler)
|
defer async.HandlePanic(panicHandler)
|
||||||
return fn(keychain.NewList(skipKeychainTest))
|
return fn(keychain.NewList())
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDeviceCookies(jar *cookies.Jar) error {
|
func setDeviceCookies(jar *cookies.Jar) error {
|
||||||
@ -547,34 +576,6 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSkipKeychainTest(c *cli.Context, settingsDir string) bool {
|
func onMacOS() bool {
|
||||||
if runtime.GOOS != "darwin" {
|
return runtime.GOOS == "darwin"
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
enable := c.Bool(flagEnableKeychainTest)
|
|
||||||
disable := c.Bool(flagDisableKeychainTest)
|
|
||||||
|
|
||||||
skip, err := vault.GetShouldSkipKeychainTest(settingsDir)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("Could not load keychain settings.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enable) && (!disable) {
|
|
||||||
return skip
|
|
||||||
}
|
|
||||||
|
|
||||||
// if both switches are passed, 'enable' has priority
|
|
||||||
if disable {
|
|
||||||
skip = true
|
|
||||||
}
|
|
||||||
if enable {
|
|
||||||
skip = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := vault.SetShouldSkipKeychainTest(settingsDir, skip); err != nil {
|
|
||||||
logrus.WithError(err).Error("Could not save keychain settings.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return skip
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckSkipKeychainTest(t *testing.T) {
|
|
||||||
var expectedResult bool
|
|
||||||
dir := t.TempDir()
|
|
||||||
app := cli.App{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cliFlagEnableKeychainTest,
|
|
||||||
cliFlagDisableKeychainTest,
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
require.Equal(t, expectedResult, checkSkipKeychainTest(c, dir))
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
noArgs := []string{"appName"}
|
|
||||||
enableArgs := []string{"appName", "-" + flagEnableKeychainTest}
|
|
||||||
disableArgs := []string{"appName", "-" + flagDisableKeychainTest}
|
|
||||||
bothArgs := []string{"appName", "-" + flagDisableKeychainTest, "-" + flagEnableKeychainTest}
|
|
||||||
|
|
||||||
const trueOnlyOnMac = runtime.GOOS == "darwin"
|
|
||||||
|
|
||||||
expectedResult = false
|
|
||||||
require.NoError(t, app.Run(noArgs))
|
|
||||||
|
|
||||||
expectedResult = trueOnlyOnMac
|
|
||||||
require.NoError(t, app.Run(disableArgs))
|
|
||||||
require.NoError(t, app.Run(noArgs))
|
|
||||||
|
|
||||||
expectedResult = false
|
|
||||||
require.NoError(t, app.Run(enableArgs))
|
|
||||||
require.NoError(t, app.Run(noArgs))
|
|
||||||
|
|
||||||
expectedResult = trueOnlyOnMac
|
|
||||||
require.NoError(t, app.Run(disableArgs))
|
|
||||||
|
|
||||||
expectedResult = false
|
|
||||||
require.NoError(t, app.Run(bothArgs))
|
|
||||||
}
|
|
||||||
@ -25,17 +25,18 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||||
logrus.Debug("Creating vault")
|
logrus.Debug("Creating vault")
|
||||||
defer logrus.Debug("Vault stopped")
|
defer logrus.Debug("Vault stopped")
|
||||||
|
|
||||||
// Create the encVault.
|
// Create the encVault.
|
||||||
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, panicHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create vault: %w", err)
|
return fmt.Errorf("could not create vault: %w", err)
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ func WithVault(locations *locations.Locations, keychains *keychain.List, panicHa
|
|||||||
return fn(encVault, insecure, corrupt != nil)
|
return fn(encVault, insecure, corrupt != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||||
vaultDir, err := locations.ProvideSettingsPath()
|
vaultDir, err := locations.ProvideSettingsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||||
@ -71,6 +72,16 @@ func newVault(locations *locations.Locations, keychains *keychain.List, panicHan
|
|||||||
)
|
)
|
||||||
|
|
||||||
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||||
|
if reporter != nil {
|
||||||
|
if rerr := reporter.ReportMessageWithContext("Could not load/create vault key", map[string]any{
|
||||||
|
"keychainDefaultHelper": keychains.GetDefaultHelper(),
|
||||||
|
"keychainUsableHelpersLength": len(keychains.GetHelpers()),
|
||||||
|
"error": err.Error(),
|
||||||
|
}); rerr != nil {
|
||||||
|
logrus.WithError(err).Info("Failed to report keychain issue to Sentry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logrus.WithError(err).Error("Could not load/create vault key")
|
logrus.WithError(err).Error("Could not load/create vault key")
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
||||||
|
|||||||
@ -267,6 +267,8 @@ func newBridge(
|
|||||||
|
|
||||||
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
|
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
|
||||||
|
|
||||||
|
observabilityService := observability.NewService(ctx, panicHandler)
|
||||||
|
|
||||||
bridge := &Bridge{
|
bridge := &Bridge{
|
||||||
vault: vault,
|
vault: vault,
|
||||||
|
|
||||||
@ -306,11 +308,11 @@ func newBridge(
|
|||||||
lastVersion: lastVersion,
|
lastVersion: lastVersion,
|
||||||
|
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
syncService: syncservice.NewService(reporter, panicHandler),
|
syncService: syncservice.NewService(panicHandler, observabilityService),
|
||||||
|
|
||||||
unleashService: unleashService,
|
unleashService: unleashService,
|
||||||
|
|
||||||
observabilityService: observability.NewService(ctx, panicHandler),
|
observabilityService: observabilityService,
|
||||||
|
|
||||||
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
||||||
}
|
}
|
||||||
@ -323,6 +325,7 @@ func newBridge(
|
|||||||
reporter,
|
reporter,
|
||||||
uidValidityGenerator,
|
uidValidityGenerator,
|
||||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||||
|
observabilityService,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check whether username has changed and correct (macOS only)
|
// Check whether username has changed and correct (macOS only)
|
||||||
@ -342,7 +345,7 @@ func newBridge(
|
|||||||
|
|
||||||
bridge.unleashService.Run()
|
bridge.unleashService.Run()
|
||||||
|
|
||||||
bridge.observabilityService.Run()
|
bridge.observabilityService.Run(bridge)
|
||||||
|
|
||||||
return bridge, nil
|
return bridge, nil
|
||||||
}
|
}
|
||||||
@ -633,7 +636,7 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b time.Duration) time.Duration {
|
func min(a, b time.Duration) time.Duration { //nolint:predeclared
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@ -710,5 +713,28 @@ func (bridge *Bridge) GetFeatureFlagValue(key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric) {
|
func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric) {
|
||||||
bridge.observabilityService.AddMetric(metric)
|
bridge.observabilityService.AddMetrics(metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||||
|
bridge.observabilityService.AddDistinctMetrics(errType, metrics...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) {
|
||||||
|
bridge.observabilityService.ModifyHeartbeatInterval(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
|
||||||
|
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
|
||||||
|
logPkg.WithFields(logrus.Fields{
|
||||||
|
"err": err,
|
||||||
|
"sentryMessage": message,
|
||||||
|
"messageCtx": messageCtx,
|
||||||
|
}).Info("Error occurred when sending Report to Sentry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsers is only used for testing purposes.
|
||||||
|
func (bridge *Bridge) GetUsers() map[string]*user.User {
|
||||||
|
return bridge.users
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,12 +79,6 @@ func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.ReportBugSent()
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
|
|
||||||
// if we have a token we can append more attachment to the bugReport
|
// if we have a token we can append more attachment to the bugReport
|
||||||
for i, att := range attachments {
|
for i, att := range attachments {
|
||||||
if i == 0 && report.IncludeLogs {
|
if i == 0 && report.IncludeLogs {
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package bridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bridge *Bridge) ReportBugClicked() {
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.ReportBugClicked()
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) AutoconfigUsed(client string) {
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.AutoconfigUsed(client)
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) ExternalLinkClicked(article string) {
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.ExternalLinkClicked(article)
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
}
|
|
||||||
@ -73,15 +73,15 @@ func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager
|
|||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
if user.GetAddressMode() == vault.SplitMode {
|
if user.GetAddressMode() == vault.SplitMode {
|
||||||
splitMode = true
|
splitMode = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
h.SetUserPlan(user.GetUserPlanName())
|
||||||
}
|
}
|
||||||
var nbAccount = len(bridge.users)
|
var numberConnectedAccounts = len(bridge.users)
|
||||||
h.SetNbAccount(nbAccount)
|
h.SetNumberConnectedAccounts(numberConnectedAccounts)
|
||||||
h.SetSplitMode(splitMode)
|
h.SetSplitMode(splitMode)
|
||||||
|
|
||||||
// Do not try to send if there is no user yet.
|
// Do not try to send if there is no user yet.
|
||||||
if nbAccount > 0 {
|
if numberConnectedAccounts > 0 {
|
||||||
defer h.start()
|
defer h.start()
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|||||||
@ -17,7 +17,9 @@
|
|||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import "github.com/sirupsen/logrus"
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||||
return bridge.identifier.GetUserAgent()
|
return bridge.identifier.GetUserAgent()
|
||||||
@ -30,6 +32,8 @@ func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
|||||||
func (bridge *Bridge) setUserAgent(name, version string) {
|
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||||
currentUserAgent := bridge.identifier.GetClientString()
|
currentUserAgent := bridge.identifier.GetClientString()
|
||||||
|
|
||||||
|
bridge.heartbeat.SetContactedByAppleNotes(name)
|
||||||
|
|
||||||
bridge.identifier.SetClient(name, version)
|
bridge.identifier.SetClient(name, version)
|
||||||
|
|
||||||
newUserAgent := bridge.identifier.GetClientString()
|
newUserAgent := bridge.identifier.GetClientString()
|
||||||
@ -54,6 +58,7 @@ func (b *bridgeUserAgentUpdater) HasClient() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
||||||
|
b.heartbeat.SetContactedByAppleNotes(name)
|
||||||
b.identifier.SetClient(name, version)
|
b.identifier.SetClient(name, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import (
|
|||||||
imapEvents "github.com/ProtonMail/gluon/events"
|
imapEvents "github.com/ProtonMail/gluon/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -93,6 +94,10 @@ func (b *bridgeIMAPSettings) LogServer() bool {
|
|||||||
return b.b.logIMAPServer
|
return b.b.logIMAPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) DisableIMAPAuthenticate() bool {
|
||||||
|
return b.b.unleashService.GetFlagValue(unleash.IMAPAuthenticateCommandDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *bridgeIMAPSettings) Port() int {
|
func (b *bridgeIMAPSettings) Port() int {
|
||||||
return b.b.vault.GetIMAPPort()
|
return b.b.vault.GetIMAPPort()
|
||||||
}
|
}
|
||||||
|
|||||||
49
internal/bridge/mocks/observability_mocks.go
Normal file
49
internal/bridge/mocks/observability_mocks.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockObservabilitySender struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockObservabilitySenderRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockObservabilitySenderRecorder struct {
|
||||||
|
mock *MockObservabilitySender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockObservabilitySender(ctrl *gomock.Controller) *MockObservabilitySender {
|
||||||
|
mock := &MockObservabilitySender{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockObservabilitySenderRecorder{mock: mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) EXPECT() *MockObservabilitySenderRecorder { return m.recorder }
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddDistinctMetrics", errType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) AddMetrics(metrics ...proton.ObservabilityMetric) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddMetrics", metrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock,
|
||||||
|
"AddDistinctMetrics",
|
||||||
|
reflect.TypeOf((*MockObservabilitySender)(nil).AddDistinctMetrics),
|
||||||
|
errType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MockObservabilitySenderRecorder) AddMetrics(metrics ...proton.ObservabilityMetric) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetrics", reflect.TypeOf((*MockObservabilitySender)(nil).AddMetrics), metrics)
|
||||||
|
}
|
||||||
@ -95,3 +95,70 @@ func TestBridge_Observability(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBridge_Observability_Heartbeat(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
throttlePeriod := time.Millisecond * 300
|
||||||
|
observability.ModifyThrottlePeriod(throttlePeriod)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
bridge.ModifyObservabilityHeartbeatInterval(throttlePeriod)
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 350)
|
||||||
|
require.Equal(t, 2, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 350)
|
||||||
|
require.Equal(t, 3, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_Observability_UserMetric(t *testing.T) {
|
||||||
|
testMetric := proton.ObservabilityMetric{
|
||||||
|
Name: "test1",
|
||||||
|
Version: 1,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
userMetricPeriod := time.Millisecond * 200
|
||||||
|
heartbeatPeriod := time.Second * 10
|
||||||
|
throttlePeriod := time.Millisecond * 100
|
||||||
|
observability.ModifyUserMetricInterval(userMetricPeriod)
|
||||||
|
observability.ModifyThrottlePeriod(throttlePeriod)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
bridge.ModifyObservabilityHeartbeatInterval(heartbeatPeriod)
|
||||||
|
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// We're expecting two observability metrics to be sent, the actual metric + the user metric.
|
||||||
|
require.Equal(t, 2, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// We're expecting only a single metric to be sent, since the user metric update has been sent already within the predefined period.
|
||||||
|
require.Equal(t, 3, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// Two metric updates should be sent again.
|
||||||
|
require.Equal(t, 5, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// Only a single one should be sent.
|
||||||
|
require.Equal(t, 6, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -29,13 +29,12 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBridge_Report(t *testing.T) {
|
func TestBridge_Report(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -56,12 +55,6 @@ func TestBridge_Report(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { require.NoError(t, conn.Close()) }()
|
defer func() { require.NoError(t, conn.Close()) }()
|
||||||
|
|
||||||
// Sending garbage to the IMAP port should cause the bridge to report it.
|
|
||||||
mocks.Reporter.EXPECT().ReportMessageWithContext(
|
|
||||||
gomock.Eq("Failed to parse IMAP command"),
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
// Read lines from the IMAP port.
|
// Read lines from the IMAP port.
|
||||||
lineCh := liner.New(conn).Lines(func() error { return nil })
|
lineCh := liner.New(conn).Lines(func() error { return nil })
|
||||||
|
|
||||||
|
|||||||
@ -318,11 +318,10 @@ func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleL
|
|||||||
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
||||||
// which we need at next startup to decrypt the vault.
|
// which we need at next startup to decrypt the vault.
|
||||||
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
||||||
useTelemetry := !bridge.GetTelemetryDisabled()
|
|
||||||
// Delete all the users.
|
// Delete all the users.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
bridge.logoutUser(ctx, user, true, true, useTelemetry)
|
bridge.logoutUser(ctx, user, true, true)
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@ type Locator interface {
|
|||||||
ProvideLogsPath() (string, error)
|
ProvideLogsPath() (string, error)
|
||||||
ProvideGluonCachePath() (string, error)
|
ProvideGluonCachePath() (string, error)
|
||||||
ProvideGluonDataPath() (string, error)
|
ProvideGluonDataPath() (string, error)
|
||||||
ProvideStatsPath() (string, error)
|
|
||||||
GetLicenseFilePath() string
|
GetLicenseFilePath() string
|
||||||
GetDependencyLicensesLink() string
|
GetDependencyLicensesLink() string
|
||||||
Clear(...string) error
|
Clear(...string) error
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
@ -115,6 +116,17 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
|||||||
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, updater.ErrDownloadVerify):
|
||||||
|
// BRIDGE-207: if download or verification fails, we do not want to trigger a manual update. We report in the log and to Sentry
|
||||||
|
// and we fail silently.
|
||||||
|
log.WithError(err).Error("The update could not be installed, but we will fail silently")
|
||||||
|
if reporterErr := bridge.reporter.ReportMessageWithContext(
|
||||||
|
"Cannot download or verify update",
|
||||||
|
reporter.Context{"error": err},
|
||||||
|
); reporterErr != nil {
|
||||||
|
log.WithError(reporterErr).Error("Failed to report update error")
|
||||||
|
}
|
||||||
|
|
||||||
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
||||||
log.Info("The update was already installed")
|
log.Info("The update was already installed")
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
@ -255,7 +256,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
|||||||
return ErrNoSuchUser
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false, false)
|
bridge.logoutUser(ctx, user, true, false)
|
||||||
|
|
||||||
bridge.publish(events.UserLoggedOut{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -280,7 +281,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user, ok := bridge.users[userID]; ok {
|
if user, ok := bridge.users[userID]; ok {
|
||||||
bridge.logoutUser(ctx, user, true, true, !bridge.GetTelemetryDisabled())
|
bridge.logoutUser(ctx, user, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
||||||
@ -355,24 +356,10 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if doResync {
|
if doResync {
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
|
||||||
"Failed to handle event: feedback resync",
|
|
||||||
reporter.Context{"user_id": userID},
|
|
||||||
); rerr != nil {
|
|
||||||
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.BadEventFeedbackResync(ctx)
|
return user.BadEventFeedbackResync(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
bridge.logoutUser(ctx, user, true, false)
|
||||||
"Failed to handle event: feedback logout",
|
|
||||||
reporter.Context{"user_id": userID},
|
|
||||||
); rerr != nil {
|
|
||||||
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false, false)
|
|
||||||
|
|
||||||
bridge.publish(events.UserLoggedOut{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -541,11 +528,6 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
vault *vault.User,
|
vault *vault.User,
|
||||||
isNew bool,
|
isNew bool,
|
||||||
) error {
|
) error {
|
||||||
statsPath, err := bridge.locator.ProvideStatsPath()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get Statistics directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
||||||
@ -560,7 +542,6 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
bridge.panicHandler,
|
bridge.panicHandler,
|
||||||
bridge.vault.GetShowAllMail(),
|
bridge.vault.GetShowAllMail(),
|
||||||
bridge.vault.GetMaxSyncMemory(),
|
bridge.vault.GetMaxSyncMemory(),
|
||||||
statsPath,
|
|
||||||
bridge,
|
bridge,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
@ -571,7 +552,6 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
isNew,
|
isNew,
|
||||||
bridge.notificationStore,
|
bridge.notificationStore,
|
||||||
bridge.unleashService.GetFlagValue,
|
bridge.unleashService.GetFlagValue,
|
||||||
bridge.observabilityService.AddMetric,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create user: %w", err)
|
return fmt.Errorf("failed to create user: %w", err)
|
||||||
@ -604,9 +584,12 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
// Finally, save the user in the bridge.
|
// Finally, save the user in the bridge.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.users[apiUser.ID] = user
|
bridge.users[apiUser.ID] = user
|
||||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users))
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
// Set user plan if its of a higher rank.
|
||||||
|
bridge.heartbeat.SetUserPlan(user.GetUserPlanName())
|
||||||
|
|
||||||
// As we need at least one user to send heartbeat, try to send it.
|
// As we need at least one user to send heartbeat, try to send it.
|
||||||
bridge.heartbeat.start()
|
bridge.heartbeat.start()
|
||||||
|
|
||||||
@ -625,26 +608,21 @@ func (bridge *Bridge) newVaultUser(
|
|||||||
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout logs out the given user, optionally logging them out from the API too.
|
// logoutUser logs out the given user, optionally logging them out from the API and deleting user related gluon data.
|
||||||
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData, withTelemetry bool) {
|
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData bool) {
|
||||||
defer delete(bridge.users, user.ID())
|
defer delete(bridge.users, user.ID())
|
||||||
|
|
||||||
// if this is actually a remove account
|
|
||||||
if withData && withAPI {
|
|
||||||
user.SendConfigStatusAbort(ctx, withTelemetry)
|
|
||||||
}
|
|
||||||
|
|
||||||
logUser.WithFields(logrus.Fields{
|
logUser.WithFields(logrus.Fields{
|
||||||
"userID": user.ID(),
|
"userID": user.ID(),
|
||||||
"withAPI": withAPI,
|
"withAPI": withAPI,
|
||||||
"withData": withData,
|
"withData": withData,
|
||||||
}).Debug("Logging out user")
|
}).Debug("Logging out user")
|
||||||
|
|
||||||
if err := user.Logout(ctx, withAPI); err != nil {
|
if err := user.Logout(ctx, withAPI, withData, bridge.unleashService.GetFlagValue(unleash.UserRemovalGluonDataCleanupDisabled)); err != nil {
|
||||||
logUser.WithError(err).Error("Failed to logout user")
|
logUser.WithError(err).Error("Failed to logout user")
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users) - 1)
|
||||||
|
|
||||||
user.Close()
|
user.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,16 +38,12 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
|
|||||||
|
|
||||||
case events.UserLoadedCheckResync:
|
case events.UserLoadedCheckResync:
|
||||||
user.VerifyResyncAndExecute()
|
user.VerifyResyncAndExecute()
|
||||||
|
|
||||||
case events.UncategorizedEventError:
|
|
||||||
bridge.handleUncategorizedErrorEvent(event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.logoutUser(ctx, user, false, false, false)
|
bridge.logoutUser(ctx, user, false, false)
|
||||||
user.ReportConfigStatusFailure("User deauth.")
|
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,12 +63,3 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e
|
|||||||
user.OnBadEvent(ctx)
|
user.OnBadEvent(ctx)
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEventError) {
|
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle due to uncategorized error", reporter.Context{
|
|
||||||
"error_type": internal.ErrCauseType(event.Error),
|
|
||||||
"error": event.Error,
|
|
||||||
}); rerr != nil {
|
|
||||||
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,228 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const version = "1.0.0"
|
|
||||||
|
|
||||||
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
|
|
||||||
status := ConfigurationStatus{
|
|
||||||
FilePath: filepath,
|
|
||||||
DataLock: safe.NewRWMutex(),
|
|
||||||
Data: &ConfigurationStatusData{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(filepath); err == nil {
|
|
||||||
if err := status.Load(); err == nil {
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
logrus.WithError(err).Warn("Cannot load configuration status file. Reset it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
if err := status.Save(); err != nil {
|
|
||||||
return &status, err
|
|
||||||
}
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Load() error {
|
|
||||||
bytes, err := os.ReadFile(status.FilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata MetadataOnly
|
|
||||||
if err := json.Unmarshal(bytes, &metadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.Metadata.Version != version {
|
|
||||||
return fmt.Errorf("unsupported configstatus file version %s", metadata.Metadata.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(bytes, status.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Save() error {
|
|
||||||
temp := status.FilePath + "_temp"
|
|
||||||
f, err := os.Create(temp) //nolint:gosec
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
enc := json.NewEncoder(f)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
err = enc.Encode(status.Data)
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
logrus.WithError(err).Error("Error while closing configstatus file.")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Rename(temp, status.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) IsPending() bool {
|
|
||||||
status.DataLock.RLock()
|
|
||||||
defer status.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return !status.Data.DataV1.PendingSince.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) isPendingSinceMin() int {
|
|
||||||
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 {
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) IsFromFailure() bool {
|
|
||||||
status.DataLock.RLock()
|
|
||||||
defer status.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return status.Data.DataV1.FailureDetails != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplySuccess() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
status.Data.DataV1.PendingSince = time.Time{}
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplyFailure(err string) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
status.Data.DataV1.FailureDetails = err
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplyProgress() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.DataV1.LastProgress = time.Now()
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.hasLinkClicked(link) {
|
|
||||||
status.Data.setClickedLink(link)
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ReportClicked() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.DataV1.ReportClick {
|
|
||||||
status.Data.DataV1.ReportClick = true
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ReportSent() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.DataV1.ReportSent {
|
|
||||||
status.Data.DataV1.ReportSent = true
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) AutoconfigUsed(client string) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if client != status.Data.DataV1.Autoconf {
|
|
||||||
status.Data.DataV1.Autoconf = client
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Remove() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
return os.Remove(status.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) init() {
|
|
||||||
data.Metadata = Metadata{
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
data.DataV1.PendingSince = time.Now()
|
|
||||||
data.DataV1.LastProgress = time.Time{}
|
|
||||||
data.DataV1.Autoconf = ""
|
|
||||||
data.DataV1.ClickedLink = 0
|
|
||||||
data.DataV1.ReportSent = false
|
|
||||||
data.DataV1.ReportClick = false
|
|
||||||
data.DataV1.FailureDetails = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
|
|
||||||
data.DataV1.ClickedLink |= 1 << pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
|
|
||||||
val := data.DataV1.ClickedLink & (1 << pos)
|
|
||||||
return val > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) clickedLinkToString() string {
|
|
||||||
var str = ""
|
|
||||||
var first = true
|
|
||||||
for i := 0; i < 64; i++ {
|
|
||||||
if data.hasLinkClicked(uint64(i)) {
|
|
||||||
if !first {
|
|
||||||
str += ","
|
|
||||||
} else {
|
|
||||||
first = false
|
|
||||||
str += "["
|
|
||||||
}
|
|
||||||
str += strconv.Itoa(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if str != "" {
|
|
||||||
str += "]"
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
@ -1,252 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigStatus_init_virgin(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
|
||||||
|
|
||||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_init_existing(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_init_bad_version(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "2.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_IsPending(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
config.Data.DataV1.PendingSince = time.Time{}
|
|
||||||
require.Equal(t, false, config.IsPending())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_IsFromFailure(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.IsFromFailure())
|
|
||||||
config.Data.DataV1.FailureDetails = "test"
|
|
||||||
require.Equal(t, true, config.IsFromFailure())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplySuccess(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
require.NoError(t, config.ApplySuccess())
|
|
||||||
require.Equal(t, false, config.IsPending())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplyFailure(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, config.ApplySuccess())
|
|
||||||
|
|
||||||
require.NoError(t, config.ApplyFailure("Big Failure"))
|
|
||||||
require.Equal(t, true, config.IsFromFailure())
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplyProgress(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
|
||||||
|
|
||||||
require.NoError(t, config.ApplyProgress())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_RecordLinkClicked(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
|
||||||
require.NoError(t, config.RecordLinkClicked(0))
|
|
||||||
require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
|
|
||||||
require.NoError(t, config.RecordLinkClicked(1))
|
|
||||||
require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ReportClicked(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
|
||||||
require.NoError(t, config.ReportClicked())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.ReportClick)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ReportSent(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
|
||||||
require.NoError(t, config.ReportSent())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.ReportSent)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
|
|
||||||
f, err := os.Create(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { _ = f.Close() }()
|
|
||||||
|
|
||||||
return json.NewEncoder(f).Encode(data)
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigAbortValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortDimensions struct {
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigSuccessValues
|
|
||||||
Dimensions ConfigSuccessDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigAbortData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_abort",
|
|
||||||
Values: ConfigSuccessValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigSuccessDimensions{
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationAbort_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigAbortBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationAbort_fed(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{
|
|
||||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
Autoconf: "Mr TBird",
|
|
||||||
ClickedLink: 42,
|
|
||||||
ReportSent: false,
|
|
||||||
ReportClick: true,
|
|
||||||
FailureDetails: "Not an error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigAbortBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ConfigProgressValues struct {
|
|
||||||
NbDay int `json:"nb_day"`
|
|
||||||
NbDaySinceLast int `json:"nb_day_since_last"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigProgressData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigProgressValues
|
|
||||||
Dimensions struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigProgressBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigProgressData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_progress",
|
|
||||||
Values: ConfigProgressValues{
|
|
||||||
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
|
|
||||||
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberOfDay(now, prev time.Time) int {
|
|
||||||
if now.IsZero() || prev.IsZero() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if now.Year() > prev.Year() {
|
|
||||||
return (365 * (now.Year() - prev.Year())) + now.YearDay() - prev.YearDay()
|
|
||||||
} else if now.YearDay() > prev.YearDay() {
|
|
||||||
return now.YearDay() - prev.YearDay()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationProgress_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigProgressBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_progress", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.NbDay)
|
|
||||||
require.Equal(t, 1, req.Values.NbDaySinceLast)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationProgress_fed(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{
|
|
||||||
PendingSince: time.Now().AddDate(0, 0, -5),
|
|
||||||
LastProgress: time.Now().AddDate(0, 0, -2),
|
|
||||||
Autoconf: "Mr TBird",
|
|
||||||
ClickedLink: 42,
|
|
||||||
ReportSent: false,
|
|
||||||
ReportClick: true,
|
|
||||||
FailureDetails: "Not an error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigProgressBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_progress", req.Event)
|
|
||||||
require.Equal(t, 5, req.Values.NbDay)
|
|
||||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationProgress_fed_year_change(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{
|
|
||||||
PendingSince: time.Now().AddDate(-1, 0, -5),
|
|
||||||
LastProgress: time.Now().AddDate(0, 0, -2),
|
|
||||||
Autoconf: "Mr TBird",
|
|
||||||
ClickedLink: 42,
|
|
||||||
ReportSent: false,
|
|
||||||
ReportClick: true,
|
|
||||||
FailureDetails: "Not an error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigProgressBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_progress", req.Event)
|
|
||||||
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
|
|
||||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigRecoveryValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryDimensions struct {
|
|
||||||
Autoconf string `json:"autoconf"`
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
FailureDetails string `json:"failure_details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigRecoveryValues
|
|
||||||
Dimensions ConfigRecoveryDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigRecoveryData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_recovery",
|
|
||||||
Values: ConfigRecoveryValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigRecoveryDimensions{
|
|
||||||
Autoconf: config.Data.DataV1.Autoconf,
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
FailureDetails: config.Data.DataV1.FailureDetails,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationRecovery_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
require.Equal(t, "", req.Dimensions.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationRecovery_fed(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{
|
|
||||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
Autoconf: "Mr TBird",
|
|
||||||
ClickedLink: 42,
|
|
||||||
ReportSent: false,
|
|
||||||
ReportClick: true,
|
|
||||||
FailureDetails: "Not an error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigSuccessValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessDimensions struct {
|
|
||||||
Autoconf string `json:"autoconf"`
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigSuccessValues
|
|
||||||
Dimensions ConfigSuccessDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigSuccessData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_success",
|
|
||||||
Values: ConfigSuccessValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigSuccessDimensions{
|
|
||||||
Autoconf: config.Data.DataV1.Autoconf,
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationSuccess_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigSuccessBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationSuccess_fed(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{
|
|
||||||
PendingSince: time.Now().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
Autoconf: "Mr TBird",
|
|
||||||
ClickedLink: 42,
|
|
||||||
ReportSent: false,
|
|
||||||
ReportClick: true,
|
|
||||||
FailureDetails: "Not an error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigSuccessBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ProgressCheckInterval = time.Hour
|
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetadataOnly struct {
|
|
||||||
Metadata Metadata `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataV1 struct {
|
|
||||||
PendingSince time.Time `json:"pending_since"`
|
|
||||||
LastProgress time.Time `json:"last_progress"`
|
|
||||||
Autoconf string `json:"auto_conf"`
|
|
||||||
ClickedLink uint64 `json:"clicked_link"`
|
|
||||||
ReportSent bool `json:"report_sent"`
|
|
||||||
ReportClick bool `json:"report_click"`
|
|
||||||
FailureDetails string `json:"failure_details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigurationStatusData struct {
|
|
||||||
Metadata Metadata `json:"metadata"`
|
|
||||||
DataV1 DataV1 `json:"dataV1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigurationStatus struct {
|
|
||||||
FilePath string
|
|
||||||
DataLock safe.RWMutex
|
|
||||||
|
|
||||||
Data *ConfigurationStatusData
|
|
||||||
}
|
|
||||||
@ -30,7 +30,7 @@ using namespace bridgepp;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
|
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
|
||||||
QString const HV_ERROR_TEMPLATE = "failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: CAPTCHA validation failed (Code=12087, Status=422)";
|
QString const HV_ERROR_TEMPLATE = "Human verification failed. Please try again.";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,9 +364,9 @@ grpc::Status GRPCService::RequestKnowledgeBaseSuggestions(ServerContext*, String
|
|||||||
|
|
||||||
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
||||||
for (qsizetype i = 1; i <= 3; ++i) {
|
for (qsizetype i = 1; i <= 3; ++i) {
|
||||||
suggestions.push_back( {
|
suggestions.push_back({
|
||||||
.title = QString("Suggested link %1").arg(i),
|
|
||||||
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
||||||
|
.title = QString("Suggested link %1").arg(i),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
||||||
@ -846,32 +846,6 @@ Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] request The request.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
Status GRPCService::ExternalLinkClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) {
|
|
||||||
app().log().debug(QString("%1 - URL = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
//
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
Status GRPCService::ReportBugClicked(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) {
|
|
||||||
app().log().debug(__FUNCTION__);
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] request The request.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
Status GRPCService::AutoconfigClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) {
|
|
||||||
app().log().debug(QString("%1 - Client = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] request The request
|
/// \param[in] request The request
|
||||||
/// \param[in] writer The writer
|
/// \param[in] writer The writer
|
||||||
|
|||||||
@ -97,9 +97,6 @@ public: // member functions.
|
|||||||
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
|
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
|
||||||
grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override;
|
grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override;
|
||||||
grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
||||||
grpc::Status ReportBugClicked(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *) override;
|
|
||||||
grpc::Status AutoconfigClicked(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
|
||||||
grpc::Status ExternalLinkClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
|
||||||
grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override;
|
grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override;
|
||||||
grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
|
grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
|
||||||
bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream.
|
bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream.
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
#include <bridgepp/GRPC/GRPCClient.h>
|
#include <bridgepp/GRPC/GRPCClient.h>
|
||||||
#include <bridgepp/Worker/Overseer.h>
|
#include <bridgepp/Worker/Overseer.h>
|
||||||
|
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
#define HANDLE_EXCEPTION(x) try { x } \
|
#define HANDLE_EXCEPTION(x) try { x } \
|
||||||
catch (Exception const &e) { emit fatalError(e); } \
|
catch (Exception const &e) { emit fatalError(e); } \
|
||||||
@ -59,15 +60,19 @@ QMLBackend::QMLBackend()
|
|||||||
/// \param[in] serviceConfig
|
/// \param[in] serviceConfig
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||||
|
Log &log = app().log();
|
||||||
|
log.info(QString("Connecting to gRPC service"));
|
||||||
|
|
||||||
trayIcon_.reset(new TrayIcon());
|
trayIcon_.reset(new TrayIcon());
|
||||||
|
connect(this, &QMLBackend::trayIconVisibleChanged, trayIcon_.get(), &TrayIcon::setVisible);
|
||||||
|
log.info(QString("Tray icon is visible: %1").arg(trayIcon_->isVisible() ? "true" : "false"));
|
||||||
this->setNormalTrayIcon();
|
this->setNormalTrayIcon();
|
||||||
|
|
||||||
|
|
||||||
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
||||||
|
|
||||||
users_ = new UserList(this);
|
users_ = new UserList(this);
|
||||||
|
|
||||||
Log &log = app().log();
|
|
||||||
log.info(QString("Connecting to gRPC service"));
|
|
||||||
app().grpc().setLog(&log);
|
app().grpc().setLog(&log);
|
||||||
this->connectGrpcEvents();
|
this->connectGrpcEvents();
|
||||||
|
|
||||||
@ -298,7 +303,6 @@ void QMLBackend::openExternalLink(QString const &url) {
|
|||||||
HANDLE_EXCEPTION(
|
HANDLE_EXCEPTION(
|
||||||
QString const u = url.isEmpty() ? bridgeKBUrl : url;
|
QString const u = url.isEmpty() ? bridgeKBUrl : url;
|
||||||
QDesktopServices::openUrl(u);
|
QDesktopServices::openUrl(u);
|
||||||
emit notifyExternalLinkClicked(u);
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -731,6 +735,32 @@ void QMLBackend::setDockIconVisible(bool visible) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] visible Should the tray icon be visible.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void QMLBackend::setTrayIconVisible(bool visible) {
|
||||||
|
HANDLE_EXCEPTION(
|
||||||
|
AppController& app = ::app();
|
||||||
|
if (visible == app.settings().trayIconVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.settings().setTrayIconVisible(visible);
|
||||||
|
emit trayIconVisibleChanged(visible);
|
||||||
|
app.log().info(QString("Changing tray icon visibility to %1").arg(visible ? "true" : "false"));
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
//
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
bool QMLBackend::trayIconVisible() const {
|
||||||
|
HANDLE_EXCEPTION_RETURN_BOOL(
|
||||||
|
return app().settings().trayIconVisible();
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] active Should we activate autostart.
|
/// \param[in] active Should we activate autostart.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -1064,33 +1094,6 @@ void QMLBackend::sendBadEventUserFeedback(QString const &userID, bool doResync)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
///
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
void QMLBackend::notifyReportBugClicked() const {
|
|
||||||
HANDLE_EXCEPTION(
|
|
||||||
app().grpc().reportBugClicked();
|
|
||||||
)
|
|
||||||
}
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] client The selected Mail client for autoconfig.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
void QMLBackend::notifyAutoconfigClicked(QString const &client) const {
|
|
||||||
HANDLE_EXCEPTION(
|
|
||||||
app().grpc().autoconfigClicked(client);
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] article The url of the KB article.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
void QMLBackend::notifyExternalLinkClicked(QString const &article) const {
|
|
||||||
HANDLE_EXCEPTION(
|
|
||||||
app().grpc().externalLinkClicked(article);
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
|
|||||||
@ -102,6 +102,7 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
|||||||
Q_PROPERTY(QVariantList bugQuestions READ bugQuestions NOTIFY bugQuestionsChanged)
|
Q_PROPERTY(QVariantList bugQuestions READ bugQuestions NOTIFY bugQuestionsChanged)
|
||||||
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
||||||
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged)
|
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged)
|
||||||
|
Q_PROPERTY(bool trayIconVisible READ trayIconVisible WRITE setTrayIconVisible NOTIFY trayIconVisibleChanged)
|
||||||
|
|
||||||
// Qt Property system setters & getters.
|
// Qt Property system setters & getters.
|
||||||
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
||||||
@ -141,6 +142,9 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
|||||||
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
||||||
void setDockIconVisible(bool visible); ///< Setter for the 'dockIconVisible' property.
|
void setDockIconVisible(bool visible); ///< Setter for the 'dockIconVisible' property.
|
||||||
bool dockIconVisible() const;; ///< Getter for the 'dockIconVisible' property.
|
bool dockIconVisible() const;; ///< Getter for the 'dockIconVisible' property.
|
||||||
|
void setTrayIconVisible(bool visible); ///< Setter for the 'trayIconVisible' property.
|
||||||
|
bool trayIconVisible() const; ///< Getter for the 'trayIconVisible' property.
|
||||||
|
|
||||||
|
|
||||||
signals: // Signal used by the Qt property system. Many of them are unused but required to avoid warning from the QML engine.
|
signals: // Signal used by the Qt property system. Many of them are unused but required to avoid warning from the QML engine.
|
||||||
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
||||||
@ -175,6 +179,7 @@ signals: // Signal used by the Qt property system. Many of them are unused but r
|
|||||||
void isAutostartOnChanged(bool value); ///<Signal for the change of the 'isAutostartOn' property.
|
void isAutostartOnChanged(bool value); ///<Signal for the change of the 'isAutostartOn' property.
|
||||||
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
||||||
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
||||||
|
void trayIconVisibleChanged(bool value); ///< Signal for the change of the 'trayIconVisible' property.
|
||||||
void receivedUserNotification(bridgepp::UserNotification const& notification); ///< Signal to display the userNotification modal
|
void receivedUserNotification(bridgepp::UserNotification const& notification); ///< Signal to display the userNotification modal
|
||||||
|
|
||||||
|
|
||||||
@ -208,9 +213,6 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
|||||||
void onVersionChanged(); ///< Slot for the version change signal.
|
void onVersionChanged(); ///< Slot for the version change signal.
|
||||||
void setMailServerSettings(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Forwards a connection mode change request from QML to gRPC
|
void setMailServerSettings(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Forwards a connection mode change request from QML to gRPC
|
||||||
void sendBadEventUserFeedback(QString const &userID, bool doResync); ///< Slot the providing user feedback for a bad event.
|
void sendBadEventUserFeedback(QString const &userID, bool doResync); ///< Slot the providing user feedback for a bad event.
|
||||||
void notifyReportBugClicked() const; ///< Slot for the ReportBugClicked gRPC event.
|
|
||||||
void notifyAutoconfigClicked(QString const &client) const; ///< Slot for gAutoconfigClicked gRPC event.
|
|
||||||
void notifyExternalLinkClicked(QString const &article) const; ///< Slot for KBArticleClicked gRPC event.
|
|
||||||
void triggerRepair() const; ///< Slot for the triggering of the bridge repair function i.e. 'resync'.
|
void triggerRepair() const; ///< Slot for the triggering of the bridge repair function i.e. 'resync'.
|
||||||
void userNotificationDismissed(); ///< Slot to pop the notification from the stack and display the rest.
|
void userNotificationDismissed(); ///< Slot to pop the notification from the stack and display the rest.
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ namespace {
|
|||||||
|
|
||||||
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
||||||
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
||||||
|
QString const keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing the 'Tray icon visible' setting.
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -36,7 +37,7 @@ QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for s
|
|||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
Settings::Settings()
|
Settings::Settings()
|
||||||
: settings_(QDir(userConfigDir()).absoluteFilePath("bridge-gui.ini"), QSettings::Format::IniFormat) {
|
: settings_(QDir(userConfigDir()).absoluteFilePath(settingsFileName), QSettings::Format::IniFormat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -54,3 +55,17 @@ bool Settings::useSoftwareRenderer() const {
|
|||||||
void Settings::setUseSoftwareRenderer(bool value) {
|
void Settings::setUseSoftwareRenderer(bool value) {
|
||||||
settings_.setValue(keyUseSoftwareRenderer, value);
|
settings_.setValue(keyUseSoftwareRenderer, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] value The value for the 'Tray icon visible' setting.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void Settings::setTrayIconVisible(bool value) {
|
||||||
|
settings_.setValue(keyTrayIconVisible, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \return The value for the 'Tray icon visible' setting.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
bool Settings::trayIconVisible() const {
|
||||||
|
return settings_.value(keyTrayIconVisible, true).toBool();
|
||||||
|
}
|
||||||
|
|||||||
@ -33,6 +33,8 @@ public: // member functions.
|
|||||||
|
|
||||||
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
||||||
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
||||||
|
void setTrayIconVisible(bool value); ///< Get the 'Tray icon visible' setting value.
|
||||||
|
bool trayIconVisible() const; ///< Set the 'Tray icon visible' setting value.
|
||||||
|
|
||||||
private: // member functions.
|
private: // member functions.
|
||||||
Settings(); ///< Default constructor.
|
Settings(); ///< Default constructor.
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
#include <bridgepp/Exception/Exception.h>
|
#include <bridgepp/Exception/Exception.h>
|
||||||
#include <bridgepp/BridgeUtils.h>
|
#include <bridgepp/BridgeUtils.h>
|
||||||
|
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
|
|
||||||
@ -195,7 +196,7 @@ TrayIcon::TrayIcon()
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->setIcon();
|
this->setIcon();
|
||||||
this->show();
|
this->setVisible(app().settings().trayIconVisible());
|
||||||
|
|
||||||
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.
|
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.
|
||||||
for (QScreen *screen: QGuiApplication::screens()) {
|
for (QScreen *screen: QGuiApplication::screens()) {
|
||||||
@ -209,7 +210,6 @@ TrayIcon::TrayIcon()
|
|||||||
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -322,21 +322,38 @@ void TrayIcon::setState(TrayIcon::State state, QString const &stateString, QStri
|
|||||||
this->generateStatusIcon(statusIconPath, stateColor(state));
|
this->generateStatusIcon(statusIconPath, stateColor(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \brief A helper struct to temporarily force the tray to be visible. Useful for operations that do not work when the tray is not visible,
|
||||||
|
/// such as showMessage().
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
struct ScopedTrayVisibility {
|
||||||
|
explicit ScopedTrayVisibility(TrayIcon& trayIcon) : trayIcon_(trayIcon), wasVisible_(app().settings().trayIconVisible()) {
|
||||||
|
trayIcon_.setVisible(true);
|
||||||
|
}
|
||||||
|
~ScopedTrayVisibility() { trayIcon_.setVisible(wasVisible_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TrayIcon &trayIcon_;
|
||||||
|
bool wasVisible_;
|
||||||
|
};
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] title The title.
|
/// \param[in] title The title.
|
||||||
/// \param[in] message The message.
|
/// \param[in] message The message.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
||||||
|
ScopedTrayVisibility visible(*this);
|
||||||
this->showMessage(title, message, notificationErrorIcon_);
|
this->showMessage(title, message, notificationErrorIcon_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// Used only by user notifications received from the event loop
|
/// Used only by user notifications received from the event loop
|
||||||
/// \param[in] title The title.
|
/// \param[in] title The title.
|
||||||
/// \param[in] subtitle The subtitle.
|
/// \param[in] subtitle The subtitle.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void TrayIcon::showUserNotification(QString const &title, QString const &subtitle) {
|
void TrayIcon::showUserNotification(QString const &title, QString const &subtitle) {
|
||||||
|
ScopedTrayVisibility visible(*this);
|
||||||
this->showMessage(title, subtitle, QSystemTrayIcon::NoIcon);
|
this->showMessage(title, subtitle, QSystemTrayIcon::NoIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,8 +43,6 @@ public: // data members
|
|||||||
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
||||||
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
||||||
void showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
void showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,7 @@ QString const bridgeGUILock = "bridge-v3-gui.lock"; ///< The file name used for
|
|||||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
||||||
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
||||||
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
||||||
|
QString const orphanInstanceException = "An orphan instance of bridge is already running. Please terminate it and relaunch the application.";
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
@ -317,7 +318,7 @@ int main(int argc, char *argv[]) {
|
|||||||
QString bridgeExe;
|
QString bridgeExe;
|
||||||
if (!cliOptions.attach) {
|
if (!cliOptions.attach) {
|
||||||
if (isBridgeRunning()) {
|
if (isBridgeRunning()) {
|
||||||
throw Exception("An orphan instance of bridge is already running. Please terminate it and relaunch the application.",
|
throw Exception(orphanInstanceException,
|
||||||
QString(), __FUNCTION__, tailOfLatestBridgeLog(sessionID));
|
QString(), __FUNCTION__, tailOfLatestBridgeLog(sessionID));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,13 +414,18 @@ int main(int argc, char *argv[]) {
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception const &e) {
|
} catch (Exception const &e) {
|
||||||
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
|
||||||
QString message = e.qwhat();
|
QString message = e.qwhat();
|
||||||
if (e.showSupportLink()) {
|
if (e.showSupportLink()) {
|
||||||
message += R"(<br/><br/>If the issue persists, please contact our <a href="https://proton.me/support/contact">customer support</a>.)";
|
message += R"(<br/><br/>If the issue persists, please contact our <a href="https://proton.me/support/contact">customer support</a>.)";
|
||||||
}
|
}
|
||||||
QMessageBox::critical(nullptr, "Error", message);
|
QMessageBox::critical(nullptr, "Error", message);
|
||||||
QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :" << e.detailedWhat() << "\n";
|
|
||||||
|
if (e.qwhat() != orphanInstanceException) {
|
||||||
|
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
||||||
|
QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :"
|
||||||
|
<< e.detailedWhat() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,11 +86,13 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
id: signIn
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
secondary: true
|
secondary: true
|
||||||
text: qsTr("Sign in")
|
text: qsTr("Sign in")
|
||||||
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
||||||
|
Accessible.name: text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -99,11 +101,13 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
id: removeAccount
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
icon.source: "/qml/icons/ic-trash.svg"
|
icon.source: "/qml/icons/ic-trash.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
visible: root.user ? root.user.state !== EUserState.Locked : false
|
visible: root.user ? root.user.state !== EUserState.Locked : false
|
||||||
|
Accessible.name: qsTr("Remove account")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.user)
|
if (!root.user)
|
||||||
@ -118,6 +122,7 @@ Item {
|
|||||||
height: root._lineThickness
|
height: root._lineThickness
|
||||||
}
|
}
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
|
id: configureEmailClient
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
actionText: qsTr("Configure email client")
|
actionText: qsTr("Configure email client")
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
@ -126,6 +131,7 @@ Item {
|
|||||||
text: qsTr("Email clients")
|
text: qsTr("Email clients")
|
||||||
type: SettingsItem.PrimaryButton
|
type: SettingsItem.PrimaryButton
|
||||||
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
||||||
|
Accessible.name: actionText
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.user)
|
if (!root.user)
|
||||||
@ -143,6 +149,7 @@ Item {
|
|||||||
text: qsTr("Split addresses")
|
text: qsTr("Split addresses")
|
||||||
type: SettingsItem.Toggle
|
type: SettingsItem.Toggle
|
||||||
visible: _connected && root.user.addresses.length > 1
|
visible: _connected && root.user.addresses.length > 1
|
||||||
|
Accessible.name: text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!splitMode.checked) {
|
if (!splitMode.checked) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ Popup {
|
|||||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||||
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
||||||
modal: false
|
modal: false
|
||||||
popupType: ApplicationWindow.PopupType.Banner
|
popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||||
topMargin: 37
|
topMargin: 37
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
import Proton
|
import Proton
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@ -114,6 +114,7 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||||
|
Accessible.name: qsTr("Help")
|
||||||
|
|
||||||
onClicked: rightContent.showHelpView()
|
onClicked: rightContent.showHelpView()
|
||||||
}
|
}
|
||||||
@ -130,6 +131,7 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
||||||
|
Accessible.name: qsTr("Settings")
|
||||||
|
|
||||||
onClicked: rightContent.showGeneralSettings()
|
onClicked: rightContent.showGeneralSettings()
|
||||||
}
|
}
|
||||||
@ -147,6 +149,7 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||||
|
Accessible.name: "..."
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dotMenu.open();
|
dotMenu.open();
|
||||||
@ -319,6 +322,7 @@ Item {
|
|||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-plus.svg"
|
icon.source: "/qml/icons/ic-plus.svg"
|
||||||
width: 36
|
width: 36
|
||||||
|
Accessible.name: qsTr("Add account")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.showLogin("");
|
root.showLogin("");
|
||||||
|
|||||||
@ -147,6 +147,19 @@ SettingsView {
|
|||||||
|
|
||||||
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
|
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
|
||||||
}
|
}
|
||||||
|
SettingsItem {
|
||||||
|
id: trayIconVisible
|
||||||
|
Layout.fillWidth: true
|
||||||
|
checked: Backend.trayIconVisible
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
description: qsTr("Show the Bridge icon in the menu bar. When the Bridge icon is not visible, launch the " +
|
||||||
|
"application again to display the main window.")
|
||||||
|
text: qsTr("Show the Bridge icon in the menu bar")
|
||||||
|
type: SettingsItem.Toggle
|
||||||
|
visible: (Backend.goos === "darwin") && root._isAdvancedShown
|
||||||
|
|
||||||
|
onClicked: Backend.trayIconVisible = !trayIconVisible.checked
|
||||||
|
}
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
id: allMail
|
id: allMail
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@ -83,7 +83,6 @@ SettingsView {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Backend.updateCurrentMailClient();
|
Backend.updateCurrentMailClient();
|
||||||
Backend.notifyReportBugClicked();
|
|
||||||
root.parent.showBugReport();
|
root.parent.showBugReport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ T.ApplicationWindow {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
// popup priority based on types
|
// popup priority based on types
|
||||||
enum PopupType {
|
enum PopupPriority {
|
||||||
Banner,
|
Banner,
|
||||||
Dialog
|
Dialog
|
||||||
}
|
}
|
||||||
@ -78,10 +78,10 @@ T.ApplicationWindow {
|
|||||||
topmost = obj;
|
topmost = obj;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (topmost && (topmost.popupType > obj.popupType)) {
|
if (topmost && (topmost.popupPriority > obj.popupPriority)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
|
if (topmost && (topmost.popupPriority === obj.popupPriority) && (topmost.occurred > obj.occurred)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
topmost = obj;
|
topmost = obj;
|
||||||
|
|||||||
@ -21,7 +21,7 @@ T.Dialog {
|
|||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||||
readonly property int popupType: ApplicationWindow.PopupType.Dialog
|
readonly property int popupPriority: ApplicationWindow.PopupPriority.Dialog
|
||||||
property bool shouldShow: false
|
property bool shouldShow: false
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
ColorImage {
|
ColorImage {
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ T.Popup {
|
|||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||||
property int popupType: ApplicationWindow.PopupType.Banner
|
property int popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||||
property bool shouldShow: false
|
property bool shouldShow: false
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
@ -172,6 +172,8 @@ FocusScope {
|
|||||||
|
|
||||||
implicitHeight: children[0].implicitHeight
|
implicitHeight: children[0].implicitHeight
|
||||||
implicitWidth: children[0].implicitWidth
|
implicitWidth: children[0].implicitWidth
|
||||||
|
Accessible.role: Accessible.Grouping
|
||||||
|
Accessible.name: label.text
|
||||||
|
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
if (!validateOnEditingFinished) {
|
if (!validateOnEditingFinished) {
|
||||||
@ -274,6 +276,7 @@ FocusScope {
|
|||||||
selectionColor: control.palette.highlight
|
selectionColor: control.palette.highlight
|
||||||
topPadding: 8
|
topPadding: 8
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
Accessible.name: label.text + qsTr(" edit")
|
||||||
|
|
||||||
background: Item {
|
background: Item {
|
||||||
implicitHeight: 36
|
implicitHeight: 36
|
||||||
@ -349,6 +352,7 @@ FocusScope {
|
|||||||
icon.color: control.color
|
icon.color: control.color
|
||||||
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
||||||
visible: root.echoMode === TextInput.Password
|
visible: root.echoMode === TextInput.Password
|
||||||
|
Accessible.name: label.text + qsTr(" show check")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,8 @@ Item {
|
|||||||
|
|
||||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
|
Accessible.name: text
|
||||||
|
Accessible.role: Accessible.Grouping
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@ -77,6 +79,8 @@ Item {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
loading: root.loading
|
loading: root.loading
|
||||||
visible: root.type === SettingsItem.ActionType.Toggle
|
visible: root.type === SettingsItem.ActionType.Toggle
|
||||||
|
Accessible.role: Accessible.CheckBox
|
||||||
|
Accessible.name: root.Accessible.name + " toggle"
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.loading)
|
if (!root.loading)
|
||||||
@ -92,6 +96,8 @@ Item {
|
|||||||
secondary: root.type !== SettingsItem.PrimaryButton
|
secondary: root.type !== SettingsItem.PrimaryButton
|
||||||
text: root.actionText
|
text: root.actionText
|
||||||
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: root.Accessible.name + " button"
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.loading)
|
if (!root.loading)
|
||||||
|
|||||||
@ -80,6 +80,7 @@ Item {
|
|||||||
horizontalPadding: 8
|
horizontalPadding: 8
|
||||||
icon.source: "/qml/icons/ic-arrow-left.svg"
|
icon.source: "/qml/icons/ic-arrow-left.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
|
Accessible.name: qsTr("Back")
|
||||||
|
|
||||||
onClicked: root.back()
|
onClicked: root.back()
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ Item {
|
|||||||
color: colorScheme.text_weak
|
color: colorScheme.text_weak
|
||||||
colorScheme: wizard.colorScheme
|
colorScheme: wizard.colorScheme
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("A system pop-up will appear. Double click on the entry with your email, and click ’Install’ in the dialog that appears.")
|
text: qsTr("A series of pop-ups will appear. Follow the instructions to install the profile.")
|
||||||
type: Label.LabelType.Body
|
type: Label.LabelType.Body
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@ -37,6 +38,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
height: 68
|
height: 68
|
||||||
radius: ProtonStyle.banner_radius
|
radius: ProtonStyle.banner_radius
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: root.text
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@ -33,6 +33,7 @@ Button {
|
|||||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||||
icon.width: _iconSize
|
icon.width: _iconSize
|
||||||
verticalPadding: 0
|
verticalPadding: 0
|
||||||
|
Accessible.name: qsTr("Help")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
menu.popup(-menu.width + root.width, -menu.height);
|
menu.popup(-menu.width + root.width, -menu.height);
|
||||||
|
|||||||
@ -60,7 +60,7 @@ Item {
|
|||||||
}
|
}
|
||||||
function showAppleMailAutoconfigProfileInstall() {
|
function showAppleMailAutoconfigProfileInstall() {
|
||||||
showClientConfigCommon();
|
showClientConfigCommon();
|
||||||
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails.");
|
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mail messages.");
|
||||||
linkLabel1.setCallback(function() { Backend.openExternalLink("https://proton.me/support/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true);
|
linkLabel1.setCallback(function() { Backend.openExternalLink("https://proton.me/support/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true);
|
||||||
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
|
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.impl
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: root
|
id: root
|
||||||
@ -28,6 +29,7 @@ FocusScope {
|
|||||||
property alias username: usernameTextField.text
|
property alias username: usernameTextField.text
|
||||||
property var wizard
|
property var wizard
|
||||||
property string hvLinkUrl: ""
|
property string hvLinkUrl: ""
|
||||||
|
property bool hvLinkClicked: false
|
||||||
|
|
||||||
signal loginAbort(string username, bool wasSignedOut)
|
signal loginAbort(string username, bool wasSignedOut)
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
passwordTextField.hidePassword();
|
passwordTextField.hidePassword();
|
||||||
secondPasswordTextField.hidePassword();
|
secondPasswordTextField.hidePassword();
|
||||||
|
hvLinkClicked = false;
|
||||||
}
|
}
|
||||||
function resetViaHv() {
|
function resetViaHv() {
|
||||||
usernameTextField.enabled = false;
|
usernameTextField.enabled = false;
|
||||||
@ -55,6 +58,7 @@ FocusScope {
|
|||||||
signInButton.loading = true;
|
signInButton.loading = true;
|
||||||
secondPasswordButton.loading = false;
|
secondPasswordButton.loading = false;
|
||||||
secondPasswordTextField.enabled = true;
|
secondPasswordTextField.enabled = true;
|
||||||
|
hvLinkClicked = false;
|
||||||
totpLayout.reset();
|
totpLayout.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,6 +565,7 @@ FocusScope {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Qt.openUrlExternally(hvLinkUrl);
|
Qt.openUrlExternally(hvLinkUrl);
|
||||||
|
hvLinkClicked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -573,7 +578,8 @@ FocusScope {
|
|||||||
id: hVContinueButton
|
id: hVContinueButton
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
colorScheme: wizard.colorScheme
|
colorScheme: wizard.colorScheme
|
||||||
text: qsTr("Continue")
|
text: qsTr("I’ve completed the verification")
|
||||||
|
enabled: hvLinkClicked
|
||||||
|
|
||||||
function checkAndSignInHv() {
|
function checkAndSignInHv() {
|
||||||
console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv")
|
console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv")
|
||||||
|
|||||||
@ -1572,32 +1572,6 @@ UPClientContext GRPCClient::clientContext() const {
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \return the status for the gRPC call.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
grpc::Status GRPCClient::reportBugClicked() {
|
|
||||||
return this->logGRPCCallStatus(stub_->ReportBugClicked(this->clientContext().get(), empty, &empty), __FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] client The client string.
|
|
||||||
/// \return the status for the gRPC call.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
grpc::Status GRPCClient::autoconfigClicked(QString const &client) {
|
|
||||||
StringValue s;
|
|
||||||
s.set_value(client.toStdString());
|
|
||||||
return this->logGRPCCallStatus(stub_->AutoconfigClicked(this->clientContext().get(), s, &empty), __FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] link The clicked link.
|
|
||||||
/// \return the status for the gRPC call.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
grpc::Status GRPCClient::externalLinkClicked(QString const &link) {
|
|
||||||
StringValue s;
|
|
||||||
s.set_value(link.toStdString());
|
|
||||||
return this->logGRPCCallStatus(stub_->ExternalLinkClicked(this->clientContext().get(), s, &empty), __FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
|
|||||||
@ -232,11 +232,6 @@ signals:
|
|||||||
void syncFinished(QString const &userID);
|
void syncFinished(QString const &userID);
|
||||||
void syncProgress(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs);
|
void syncProgress(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs);
|
||||||
|
|
||||||
public: // telemetry related calls
|
|
||||||
grpc::Status reportBugClicked(); ///< Performs the 'reportBugClicked' call.
|
|
||||||
grpc::Status autoconfigClicked(QString const &userID); ///< Performs the 'AutoconfigClicked' call.
|
|
||||||
grpc::Status externalLinkClicked(QString const &userID); ///< Performs the 'KBArticleClicked' call.
|
|
||||||
|
|
||||||
public: // keychain related calls
|
public: // keychain related calls
|
||||||
grpc::Status availableKeychains(QStringList &outKeychains);
|
grpc::Status availableKeychains(QStringList &outKeychains);
|
||||||
grpc::Status currentKeychain(QString &outKeychain);
|
grpc::Status currentKeychain(QString &outKeychain);
|
||||||
|
|||||||
@ -159,7 +159,10 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) {
|
|||||||
hvDetails, hvErr := hv.VerifyAndExtractHvRequest(err)
|
hvDetails, hvErr := hv.VerifyAndExtractHvRequest(err)
|
||||||
if hvErr != nil || hvDetails != nil {
|
if hvErr != nil || hvDetails != nil {
|
||||||
if hvErr != nil {
|
if hvErr != nil {
|
||||||
f.printAndLogError("Cannot login", hvErr)
|
f.printAndLogError("Cannot login:", hv.ExtractionErrorMsg)
|
||||||
|
f.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.promptHvURL(hvDetails)
|
f.promptHvURL(hvDetails)
|
||||||
|
|||||||
@ -5425,7 +5425,7 @@ var file_bridge_proto_rawDesc = []byte{
|
|||||||
0x4c, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45,
|
0x4c, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45,
|
||||||
0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45,
|
0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45,
|
||||||
0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02,
|
0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02,
|
||||||
0x32, 0x99, 0x23, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43,
|
0x32, 0xbd, 0x21, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43,
|
||||||
0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
@ -5666,51 +5666,37 @@ var file_bridge_proto_rawDesc = []byte{
|
|||||||
0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d,
|
0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d,
|
||||||
0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||||
0x74, 0x79, 0x12, 0x42, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x43,
|
0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||||
0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16,
|
|
||||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f,
|
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f,
|
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
|
|
||||||
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
|
||||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
|
||||||
0x79, 0x12, 0x4b, 0x0a, 0x13, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6e,
|
|
||||||
0x6b, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
|
|
||||||
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f,
|
|
||||||
0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
|
||||||
0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
|
||||||
0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
|
||||||
0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72,
|
|
||||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
|
||||||
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
|
||||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f,
|
|
||||||
0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
|
||||||
0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
|
|
||||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76,
|
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61,
|
||||||
0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
|
0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61,
|
0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67,
|
||||||
0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70,
|
|
||||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
|
||||||
0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x54,
|
|
||||||
0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x12, 0x16, 0x2e, 0x67,
|
|
||||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||||
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x36, 0x5a, 0x34,
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15,
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||||
0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2d, 0x62, 0x72, 0x69,
|
0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||||
0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52,
|
||||||
|
0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e,
|
||||||
|
0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||||
|
0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f,
|
||||||
|
0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
|
||||||
|
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
|
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||||
|
0x3f, 0x0a, 0x0d, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72,
|
||||||
|
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
|
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
|
0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e,
|
||||||
|
0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||||
|
0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -5938,81 +5924,75 @@ var file_bridge_proto_depIdxs = []int32{
|
|||||||
80, // 121: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue
|
80, // 121: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue
|
||||||
80, // 122: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue
|
80, // 122: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue
|
||||||
18, // 123: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest
|
18, // 123: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest
|
||||||
81, // 124: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty
|
81, // 124: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty
|
||||||
80, // 125: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue
|
81, // 125: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty
|
||||||
80, // 126: grpc.Bridge.ExternalLinkClicked:input_type -> google.protobuf.StringValue
|
80, // 126: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
||||||
81, // 127: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty
|
19, // 127: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
||||||
81, // 128: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty
|
81, // 128: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
||||||
80, // 129: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
81, // 129: grpc.Bridge.TriggerRepair:input_type -> google.protobuf.Empty
|
||||||
19, // 130: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
80, // 130: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
||||||
81, // 131: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
81, // 131: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
||||||
81, // 132: grpc.Bridge.TriggerRepair:input_type -> google.protobuf.Empty
|
8, // 132: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
||||||
80, // 133: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
81, // 133: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
||||||
81, // 134: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
81, // 134: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
||||||
8, // 135: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
82, // 135: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
||||||
81, // 136: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
81, // 136: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
||||||
81, // 137: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
82, // 137: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
||||||
82, // 138: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
81, // 138: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
||||||
81, // 139: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
82, // 139: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
||||||
82, // 140: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
81, // 140: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
||||||
81, // 141: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
82, // 141: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
||||||
82, // 142: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
81, // 142: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty
|
||||||
81, // 143: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
82, // 143: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue
|
||||||
82, // 144: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
80, // 144: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
||||||
81, // 145: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty
|
81, // 145: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
||||||
82, // 146: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue
|
80, // 146: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
||||||
80, // 147: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
80, // 147: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
||||||
81, // 148: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
80, // 148: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
||||||
80, // 149: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
80, // 149: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
||||||
80, // 150: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
80, // 150: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
||||||
80, // 151: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
80, // 151: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
||||||
80, // 152: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
81, // 152: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
||||||
80, // 153: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
80, // 153: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
||||||
80, // 154: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
80, // 154: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
||||||
81, // 155: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
81, // 155: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
||||||
80, // 156: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
81, // 156: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
||||||
80, // 157: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
81, // 157: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
||||||
81, // 158: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
81, // 158: grpc.Bridge.RequestKnowledgeBaseSuggestions:output_type -> google.protobuf.Empty
|
||||||
81, // 159: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
81, // 159: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
||||||
81, // 160: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
81, // 160: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
||||||
81, // 161: grpc.Bridge.RequestKnowledgeBaseSuggestions:output_type -> google.protobuf.Empty
|
81, // 161: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
||||||
81, // 162: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
81, // 162: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
||||||
81, // 163: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
81, // 163: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
||||||
81, // 164: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
81, // 164: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
||||||
81, // 165: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
81, // 165: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
||||||
81, // 166: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
82, // 166: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
||||||
81, // 167: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
80, // 167: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
||||||
81, // 168: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
81, // 168: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
||||||
82, // 169: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
81, // 169: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
||||||
80, // 170: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
82, // 170: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
||||||
81, // 171: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
12, // 171: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
||||||
81, // 172: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
81, // 172: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
||||||
82, // 173: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
80, // 173: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
||||||
12, // 174: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
82, // 174: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
||||||
81, // 175: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
13, // 175: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
||||||
80, // 176: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
81, // 176: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
||||||
82, // 177: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
80, // 177: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
||||||
13, // 178: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
17, // 178: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
||||||
81, // 179: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
14, // 179: grpc.Bridge.GetUser:output_type -> grpc.User
|
||||||
80, // 180: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
81, // 180: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
||||||
17, // 181: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
81, // 181: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
||||||
14, // 182: grpc.Bridge.GetUser:output_type -> grpc.User
|
81, // 182: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
||||||
81, // 183: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
81, // 183: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
||||||
81, // 184: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
81, // 184: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
||||||
81, // 185: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
82, // 185: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue
|
||||||
81, // 186: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
81, // 186: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty
|
||||||
81, // 187: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
81, // 187: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
||||||
81, // 188: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty
|
20, // 188: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
||||||
81, // 189: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty
|
81, // 189: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
||||||
81, // 190: grpc.Bridge.ExternalLinkClicked:output_type -> google.protobuf.Empty
|
81, // 190: grpc.Bridge.TriggerRepair:output_type -> google.protobuf.Empty
|
||||||
82, // 191: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue
|
130, // [130:191] is the sub-list for method output_type
|
||||||
81, // 192: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty
|
69, // [69:130] is the sub-list for method input_type
|
||||||
81, // 193: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
|
||||||
20, // 194: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
|
||||||
81, // 195: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
|
||||||
81, // 196: grpc.Bridge.TriggerRepair:output_type -> google.protobuf.Empty
|
|
||||||
133, // [133:197] is the sub-list for method output_type
|
|
||||||
69, // [69:133] is the sub-list for method input_type
|
|
||||||
69, // [69:69] is the sub-list for extension type_name
|
69, // [69:69] is the sub-list for extension type_name
|
||||||
69, // [69:69] is the sub-list for extension extendee
|
69, // [69:69] is the sub-list for extension extendee
|
||||||
0, // [0:69] is the sub-list for field type_name
|
0, // [0:69] is the sub-list for field type_name
|
||||||
|
|||||||
@ -98,11 +98,6 @@ service Bridge {
|
|||||||
rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||||
rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty);
|
rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
// Telemetry
|
|
||||||
rpc ReportBugClicked(google.protobuf.Empty) returns (google.protobuf.Empty);
|
|
||||||
rpc AutoconfigClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
|
||||||
rpc ExternalLinkClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
|
||||||
|
|
||||||
// TLS certificate related calls
|
// TLS certificate related calls
|
||||||
rpc IsTLSCertificateInstalled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
rpc IsTLSCertificateInstalled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||||
rpc InstallTLSCertificate(google.protobuf.Empty) returns (google.protobuf.Empty);
|
rpc InstallTLSCertificate(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
|||||||
@ -93,9 +93,6 @@ const (
|
|||||||
Bridge_LogoutUser_FullMethodName = "/grpc.Bridge/LogoutUser"
|
Bridge_LogoutUser_FullMethodName = "/grpc.Bridge/LogoutUser"
|
||||||
Bridge_RemoveUser_FullMethodName = "/grpc.Bridge/RemoveUser"
|
Bridge_RemoveUser_FullMethodName = "/grpc.Bridge/RemoveUser"
|
||||||
Bridge_ConfigureUserAppleMail_FullMethodName = "/grpc.Bridge/ConfigureUserAppleMail"
|
Bridge_ConfigureUserAppleMail_FullMethodName = "/grpc.Bridge/ConfigureUserAppleMail"
|
||||||
Bridge_ReportBugClicked_FullMethodName = "/grpc.Bridge/ReportBugClicked"
|
|
||||||
Bridge_AutoconfigClicked_FullMethodName = "/grpc.Bridge/AutoconfigClicked"
|
|
||||||
Bridge_ExternalLinkClicked_FullMethodName = "/grpc.Bridge/ExternalLinkClicked"
|
|
||||||
Bridge_IsTLSCertificateInstalled_FullMethodName = "/grpc.Bridge/IsTLSCertificateInstalled"
|
Bridge_IsTLSCertificateInstalled_FullMethodName = "/grpc.Bridge/IsTLSCertificateInstalled"
|
||||||
Bridge_InstallTLSCertificate_FullMethodName = "/grpc.Bridge/InstallTLSCertificate"
|
Bridge_InstallTLSCertificate_FullMethodName = "/grpc.Bridge/InstallTLSCertificate"
|
||||||
Bridge_ExportTLSCertificates_FullMethodName = "/grpc.Bridge/ExportTLSCertificates"
|
Bridge_ExportTLSCertificates_FullMethodName = "/grpc.Bridge/ExportTLSCertificates"
|
||||||
@ -170,10 +167,6 @@ type BridgeClient interface {
|
|||||||
LogoutUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
LogoutUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
RemoveUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
ConfigureUserAppleMail(ctx context.Context, in *ConfigureAppleMailRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
ConfigureUserAppleMail(ctx context.Context, in *ConfigureAppleMailRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
// Telemetry
|
|
||||||
ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
ExternalLinkClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
// TLS certificate related calls
|
// TLS certificate related calls
|
||||||
IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
|
IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
|
||||||
InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
@ -688,33 +681,6 @@ func (c *bridgeClient) ConfigureUserAppleMail(ctx context.Context, in *Configure
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bridgeClient) ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, Bridge_ReportBugClicked_FullMethodName, in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bridgeClient) AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, Bridge_AutoconfigClicked_FullMethodName, in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bridgeClient) ExternalLinkClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, Bridge_ExternalLinkClicked_FullMethodName, in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
|
func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
|
||||||
out := new(wrapperspb.BoolValue)
|
out := new(wrapperspb.BoolValue)
|
||||||
err := c.cc.Invoke(ctx, Bridge_IsTLSCertificateInstalled_FullMethodName, in, out, opts...)
|
err := c.cc.Invoke(ctx, Bridge_IsTLSCertificateInstalled_FullMethodName, in, out, opts...)
|
||||||
@ -858,10 +824,6 @@ type BridgeServer interface {
|
|||||||
LogoutUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
LogoutUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||||
RemoveUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
RemoveUser(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
||||||
ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error)
|
ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error)
|
||||||
// Telemetry
|
|
||||||
ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
|
||||||
AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
|
||||||
ExternalLinkClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
|
|
||||||
// TLS certificate related calls
|
// TLS certificate related calls
|
||||||
IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
|
IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
|
||||||
InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
@ -1043,15 +1005,6 @@ func (UnimplementedBridgeServer) RemoveUser(context.Context, *wrapperspb.StringV
|
|||||||
func (UnimplementedBridgeServer) ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
|
func (UnimplementedBridgeServer) ConfigureUserAppleMail(context.Context, *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ConfigureUserAppleMail not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ConfigureUserAppleMail not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedBridgeServer) ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ReportBugClicked not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedBridgeServer) AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AutoconfigClicked not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedBridgeServer) ExternalLinkClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ExternalLinkClicked not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented")
|
||||||
}
|
}
|
||||||
@ -2073,60 +2026,6 @@ func _Bridge_ConfigureUserAppleMail_Handler(srv interface{}, ctx context.Context
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Bridge_ReportBugClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(emptypb.Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(BridgeServer).ReportBugClicked(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Bridge_ReportBugClicked_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(BridgeServer).ReportBugClicked(ctx, req.(*emptypb.Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Bridge_AutoconfigClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(wrapperspb.StringValue)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(BridgeServer).AutoconfigClicked(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Bridge_AutoconfigClicked_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(BridgeServer).AutoconfigClicked(ctx, req.(*wrapperspb.StringValue))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Bridge_ExternalLinkClicked_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(wrapperspb.StringValue)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(BridgeServer).ExternalLinkClicked(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Bridge_ExternalLinkClicked_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(BridgeServer).ExternalLinkClicked(ctx, req.(*wrapperspb.StringValue))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Bridge_IsTLSCertificateInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Bridge_IsTLSCertificateInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(emptypb.Empty)
|
in := new(emptypb.Empty)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -2465,18 +2364,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ConfigureUserAppleMail",
|
MethodName: "ConfigureUserAppleMail",
|
||||||
Handler: _Bridge_ConfigureUserAppleMail_Handler,
|
Handler: _Bridge_ConfigureUserAppleMail_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "ReportBugClicked",
|
|
||||||
Handler: _Bridge_ReportBugClicked_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "AutoconfigClicked",
|
|
||||||
Handler: _Bridge_AutoconfigClicked_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "ExternalLinkClicked",
|
|
||||||
Handler: _Bridge_ExternalLinkClicked_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "IsTLSCertificateInstalled",
|
MethodName: "IsTLSCertificateInstalled",
|
||||||
Handler: _Bridge_IsTLSCertificateInstalled_Handler,
|
Handler: _Bridge_IsTLSCertificateInstalled_Handler,
|
||||||
|
|||||||
@ -214,7 +214,10 @@ func NewUserBadEvent(userID string, errorMessage string) *StreamEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewUsedBytesChangedEvent(userID string, usedBytes uint64) *StreamEvent {
|
func NewUsedBytesChangedEvent(userID string, usedBytes uint64) *StreamEvent {
|
||||||
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{UserID: userID, UsedBytes: int64(usedBytes)}}})
|
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{
|
||||||
|
UserID: userID,
|
||||||
|
UsedBytes: int64(usedBytes), //nolint:gosec // disable G115
|
||||||
|
}}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIMAPLoginFailedEvent(username string) *StreamEvent {
|
func newIMAPLoginFailedEvent(username string) *StreamEvent {
|
||||||
|
|||||||
@ -465,7 +465,7 @@ func (s *Service) finishLogin() {
|
|||||||
|
|
||||||
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Code == proton.HumanValidationInvalidToken {
|
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Code == proton.HumanValidationInvalidToken {
|
||||||
s.hvDetails = nil
|
s.hvDetails = nil
|
||||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error()))
|
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +643,10 @@ func (s *Service) monitorParentPID() {
|
|||||||
func (s *Service) handleHvRequest(err error) {
|
func (s *Service) handleHvRequest(err error) {
|
||||||
hvDet, hvErr := hv.VerifyAndExtractHvRequest(err)
|
hvDet, hvErr := hv.VerifyAndExtractHvRequest(err)
|
||||||
if hvErr != nil {
|
if hvErr != nil {
|
||||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hvErr.Error()))
|
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.ExtractionErrorMsg))
|
||||||
|
s.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
|
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
|
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/service"
|
"github.com/ProtonMail/proton-bridge/v3/internal/service"
|
||||||
@ -468,7 +469,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty,
|
|||||||
|
|
||||||
case proton.HumanValidationInvalidToken:
|
case proton.HumanValidationInvalidToken:
|
||||||
s.hvDetails = nil
|
s.hvDetails = nil
|
||||||
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error()))
|
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
|
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
|
||||||
@ -717,8 +718,8 @@ func (s *Service) MailServerSettings(_ context.Context, _ *emptypb.Empty) (*Imap
|
|||||||
state: protoimpl.MessageState{},
|
state: protoimpl.MessageState{},
|
||||||
sizeCache: 0,
|
sizeCache: 0,
|
||||||
unknownFields: nil,
|
unknownFields: nil,
|
||||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||||
}, nil
|
}, nil
|
||||||
@ -864,8 +865,8 @@ func base64Decode(in []byte) ([]byte, error) {
|
|||||||
|
|
||||||
func (s *Service) getMailServerSettings() *ImapSmtpSettings {
|
func (s *Service) getMailServerSettings() *ImapSmtpSettings {
|
||||||
return &ImapSmtpSettings{
|
return &ImapSmtpSettings{
|
||||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/gluon/async"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
|
||||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Service) ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
defer async.HandlePanic(s.panicHandler)
|
|
||||||
s.bridge.ReportBugClicked()
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) AutoconfigClicked(_ context.Context, client *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
|
||||||
defer async.HandlePanic(s.panicHandler)
|
|
||||||
s.bridge.AutoconfigUsed(client.Value)
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) ExternalLinkClicked(_ context.Context, article *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
|
||||||
defer async.HandlePanic(s.panicHandler)
|
|
||||||
s.bridge.ExternalLinkClicked(article.Value)
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
@ -34,6 +34,11 @@ var (
|
|||||||
// getInitials based on webapp implementation:
|
// getInitials based on webapp implementation:
|
||||||
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
|
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
|
||||||
func getInitials(fullName string) string {
|
func getInitials(fullName string) string {
|
||||||
|
fullName = strings.TrimSpace(fullName)
|
||||||
|
if fullName == "" {
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
|
||||||
words := strings.Split(
|
words := strings.Split(
|
||||||
reMultiSpaces.ReplaceAllString(fullName, " "),
|
reMultiSpaces.ReplaceAllString(fullName, " "),
|
||||||
" ",
|
" ",
|
||||||
@ -66,8 +71,8 @@ func grpcUserFromInfo(user bridge.UserInfo) *User {
|
|||||||
AvatarText: getInitials(user.Username),
|
AvatarText: getInitials(user.Username),
|
||||||
State: userStateToGrpc(user.State),
|
State: userStateToGrpc(user.State),
|
||||||
SplitMode: user.AddressMode == vault.SplitMode,
|
SplitMode: user.AddressMode == vault.SplitMode,
|
||||||
UsedBytes: int64(user.UsedSpace),
|
UsedBytes: int64(user.UsedSpace), //nolint:gosec // disable G115
|
||||||
TotalBytes: int64(user.MaxSpace),
|
TotalBytes: int64(user.MaxSpace), //nolint:gosec // disable G115
|
||||||
Password: user.BridgePass,
|
Password: user.BridgePass,
|
||||||
Addresses: user.Addresses,
|
Addresses: user.Addresses,
|
||||||
}
|
}
|
||||||
|
|||||||
45
internal/frontend/grpc/utils_test.go
Normal file
45
internal/frontend/grpc/utils_test.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_GetInitials(t *testing.T) {
|
||||||
|
require.Equal(t, "?", getInitials(""))
|
||||||
|
require.Equal(t, "T", getInitials(" test"))
|
||||||
|
require.Equal(t, "T", getInitials("test "))
|
||||||
|
require.Equal(t, "T", getInitials(" test "))
|
||||||
|
require.Equal(t, "JD", getInitials(" John Doe "))
|
||||||
|
require.Equal(t, "J", getInitials(" JohnDoe@proton.me "))
|
||||||
|
require.Equal(t, "JD", getInitials("\t\r\n John Doe \t\r\n "))
|
||||||
|
|
||||||
|
require.Equal(t, "T", getInitials("TestTestman"))
|
||||||
|
require.Equal(t, "TT", getInitials("Test Testman"))
|
||||||
|
require.Equal(t, "J", getInitials("JamesJoyce"))
|
||||||
|
require.Equal(t, "J", getInitials("JamesJoyceJeremy"))
|
||||||
|
require.Equal(t, "J", getInitials("james.joyce"))
|
||||||
|
require.Equal(t, "JJ", getInitials("James Joyce"))
|
||||||
|
require.Equal(t, "JM", getInitials("James Joyce Mahabharata"))
|
||||||
|
require.Equal(t, "JL", getInitials("James Joyce Jeremy Lin"))
|
||||||
|
require.Equal(t, "JM", getInitials("Jean Michel"))
|
||||||
|
require.Equal(t, "GC", getInitials("George Michael Carrie"))
|
||||||
|
}
|
||||||
@ -21,6 +21,11 @@ import (
|
|||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtractionErrorMsg = "Human verification requested, but an issue occurred. Please try again."
|
||||||
|
VerificationFailedErrorMsg = "Human verification failed. Please try again."
|
||||||
|
)
|
||||||
|
|
||||||
// VerifyAndExtractHvRequest expects an error request as input
|
// VerifyAndExtractHvRequest expects an error request as input
|
||||||
// determines whether the given error is a Proton human verification request; if it isn't then it returns -> nil, nil (no details, no error)
|
// determines whether the given error is a Proton human verification request; if it isn't then it returns -> nil, nil (no details, no error)
|
||||||
// if it is a HV req. then it tries to parse the json data and verify that the captcha method is included; if either fails -> nil, err
|
// if it is a HV req. then it tries to parse the json data and verify that the captcha method is included; if either fails -> nil, err
|
||||||
@ -34,7 +39,7 @@ func VerifyAndExtractHvRequest(err error) (*proton.APIHVDetails, error) {
|
|||||||
if errors.As(err, &protonErr) && protonErr.IsHVError() {
|
if errors.As(err, &protonErr) && protonErr.IsHVError() {
|
||||||
hvDetails, hvErr := protonErr.GetHVDetails()
|
hvDetails, hvErr := protonErr.GetHVDetails()
|
||||||
if hvErr != nil {
|
if hvErr != nil {
|
||||||
return nil, fmt.Errorf("received HV request, but can't decode HV details")
|
return nil, hvErr
|
||||||
}
|
}
|
||||||
return hvDetails, nil
|
return hvDetails, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,7 +99,7 @@ func GetArticleIndex(url string) (uint64, error) {
|
|||||||
if index == -1 {
|
if index == -1 {
|
||||||
return 0, ErrArticleNotFound
|
return 0, ErrArticleNotFound
|
||||||
}
|
}
|
||||||
return uint64(index), nil
|
return uint64(index), nil //nolint:gosec // disable G115
|
||||||
}
|
}
|
||||||
|
|
||||||
func simplifyUserInput(input string) string {
|
func simplifyUserInput(input string) string {
|
||||||
|
|||||||
@ -188,16 +188,6 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
|
|||||||
return l.getUpdatesPath(), nil
|
return l.getUpdatesPath(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideStatsPath returns a location for statistics files (e.g. ~/.local/share/<company>/<app>/stats).
|
|
||||||
// It creates it if it doesn't already exist.
|
|
||||||
func (l *Locations) ProvideStatsPath() (string, error) {
|
|
||||||
if err := os.MkdirAll(l.getStatsPath(), 0o700); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.getStatsPath(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Locations) ProvideIMAPSyncConfigPath() (string, error) {
|
func (l *Locations) ProvideIMAPSyncConfigPath() (string, error) {
|
||||||
if err := os.MkdirAll(l.getIMAPSyncConfigPath(), 0o700); err != nil {
|
if err := os.MkdirAll(l.getIMAPSyncConfigPath(), 0o700); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -252,10 +242,6 @@ func (l *Locations) getNotificationsCachePath() string {
|
|||||||
return filepath.Join(l.userCache, "notifications")
|
return filepath.Join(l.userCache, "notifications")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Locations) getStatsPath() string {
|
|
||||||
return filepath.Join(l.userData, "stats")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Locations) getUnleashCachePath() string { return filepath.Join(l.userCache, "unleash_cache") }
|
func (l *Locations) getUnleashCachePath() string { return filepath.Join(l.userCache, "unleash_cache") }
|
||||||
|
|
||||||
// Clear removes everything except the lock and update files.
|
// Clear removes everything except the lock and update files.
|
||||||
|
|||||||
@ -31,7 +31,7 @@ type CoolDownProvider interface {
|
|||||||
Reset()
|
Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func jitter(max int) time.Duration {
|
func jitter(max int) time.Duration { //nolint:predeclared
|
||||||
return time.Duration(rand.Intn(max)) * time.Second //nolint:gosec
|
return time.Duration(rand.Intn(max)) * time.Second //nolint:gosec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
87
internal/plan/plan.go
Normal file
87
internal/plan/plan.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package plan
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown = "unknown"
|
||||||
|
Other = "other"
|
||||||
|
Business = "business"
|
||||||
|
Individual = "individual"
|
||||||
|
Group = "group"
|
||||||
|
)
|
||||||
|
|
||||||
|
var planHierarchy = map[string]int{ //nolint:gochecknoglobals
|
||||||
|
Business: 4,
|
||||||
|
Group: 3,
|
||||||
|
Individual: 2,
|
||||||
|
Other: 1,
|
||||||
|
Unknown: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsHigherPriority(currentPlan, newPlan string) bool {
|
||||||
|
newRank, ok := planHierarchy[newPlan]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRank, ok2 := planHierarchy[currentPlan]
|
||||||
|
if !ok2 {
|
||||||
|
return true // we don't have a valid plan, might as well replace it
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRank > currentRank
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapUserPlan(planName string) string {
|
||||||
|
if planName == "" {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(strings.ToLower(planName)) {
|
||||||
|
case Individual:
|
||||||
|
return Individual
|
||||||
|
case Unknown:
|
||||||
|
return Unknown
|
||||||
|
case Business:
|
||||||
|
return Business
|
||||||
|
case Group:
|
||||||
|
return Group
|
||||||
|
case "mail2022":
|
||||||
|
return Individual
|
||||||
|
case "bundle2022":
|
||||||
|
return Individual
|
||||||
|
case "family2022":
|
||||||
|
return Group
|
||||||
|
case "visionary2022":
|
||||||
|
return Group
|
||||||
|
case "mailpro2022":
|
||||||
|
return Business
|
||||||
|
case "planbiz2024":
|
||||||
|
return Business
|
||||||
|
case "bundlepro2022":
|
||||||
|
return Business
|
||||||
|
case "bundlepro2024":
|
||||||
|
return Business
|
||||||
|
case "duo2024":
|
||||||
|
return Group
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Other
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,18 +21,13 @@
|
|||||||
package sentry
|
package sentry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/elastic/go-sysinfo"
|
"github.com/elastic/go-sysinfo/types"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const translatedProcDarwin = "sysctl.proc_translated"
|
const translatedProcDarwin = "sysctl.proc_translated"
|
||||||
|
|
||||||
func getHostArch() string {
|
func getHostArch(host types.Host) string {
|
||||||
host, err := sysinfo.Host()
|
|
||||||
if err != nil {
|
|
||||||
return "not-detected"
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is not possible to retrieve real hardware architecture once using
|
// It is not possible to retrieve real hardware architecture once using
|
||||||
// rosetta. But it is possible to detect the process translation if
|
// rosetta. But it is possible to detect the process translation if
|
||||||
// rosetta is used.
|
// rosetta is used.
|
||||||
|
|||||||
@ -20,12 +20,10 @@
|
|||||||
|
|
||||||
package sentry
|
package sentry
|
||||||
|
|
||||||
import "github.com/elastic/go-sysinfo"
|
import (
|
||||||
|
"github.com/elastic/go-sysinfo/types"
|
||||||
|
)
|
||||||
|
|
||||||
func getHostArch() string {
|
func getHostArch(host types.Host) string {
|
||||||
host, err := sysinfo.Host()
|
|
||||||
if err != nil {
|
|
||||||
return "not-detected"
|
|
||||||
}
|
|
||||||
return host.Info().Architecture
|
return host.Info().Architecture
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,10 +30,13 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||||
|
"github.com/elastic/go-sysinfo"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const hostNotDetectedField = "not-detected"
|
||||||
|
|
||||||
var skippedFunctions = []string{} //nolint:gochecknoglobals
|
var skippedFunctions = []string{} //nolint:gochecknoglobals
|
||||||
|
|
||||||
func init() { //nolint:gochecknoinits
|
func init() { //nolint:gochecknoinits
|
||||||
@ -70,17 +73,50 @@ func init() { //nolint:gochecknoinits
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type hostInfoData struct {
|
||||||
|
hostArch string
|
||||||
|
hostName string
|
||||||
|
hostVersion string
|
||||||
|
hostBuild string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostInfoData() hostInfoData {
|
||||||
|
return hostInfoData{
|
||||||
|
hostArch: hostNotDetectedField,
|
||||||
|
hostName: hostNotDetectedField,
|
||||||
|
hostVersion: hostNotDetectedField,
|
||||||
|
hostBuild: hostNotDetectedField,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Reporter struct {
|
type Reporter struct {
|
||||||
appName string
|
appName string
|
||||||
appVersion string
|
appVersion string
|
||||||
identifier Identifier
|
identifier Identifier
|
||||||
hostArch string
|
hostInfo hostInfoData
|
||||||
}
|
}
|
||||||
|
|
||||||
type Identifier interface {
|
type Identifier interface {
|
||||||
GetUserAgent() string
|
GetUserAgent() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHostInfo() hostInfoData {
|
||||||
|
data := newHostInfoData()
|
||||||
|
|
||||||
|
host, err := sysinfo.Host()
|
||||||
|
if err != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
data.hostArch = getHostArch(host)
|
||||||
|
osInfo := host.Info().OS
|
||||||
|
data.hostName = osInfo.Name
|
||||||
|
data.hostVersion = osInfo.Version
|
||||||
|
data.hostBuild = osInfo.Build
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
func GetProtectedHostname() string {
|
func GetProtectedHostname() string {
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +136,7 @@ func NewReporter(appName string, identifier Identifier) *Reporter {
|
|||||||
appName: appName,
|
appName: appName,
|
||||||
appVersion: constants.Revision,
|
appVersion: constants.Revision,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
hostArch: getHostArch(),
|
hostInfo: getHostInfo(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +192,10 @@ func (r *Reporter) scopedReport(context map[string]interface{}, doReport func())
|
|||||||
"Client": r.appName,
|
"Client": r.appName,
|
||||||
"Version": r.appVersion,
|
"Version": r.appVersion,
|
||||||
"UserAgent": r.identifier.GetUserAgent(),
|
"UserAgent": r.identifier.GetUserAgent(),
|
||||||
"HostArch": r.hostArch,
|
"HostArch": r.hostInfo.hostArch,
|
||||||
|
"HostName": r.hostInfo.hostName,
|
||||||
|
"HostVersion": r.hostInfo.hostVersion,
|
||||||
|
"HostBuild": r.hostInfo.hostBuild,
|
||||||
}
|
}
|
||||||
|
|
||||||
sentry.WithScope(func(scope *sentry.Scope) {
|
sentry.WithScope(func(scope *sentry.Scope) {
|
||||||
|
|||||||
@ -56,7 +56,6 @@ type Connector struct {
|
|||||||
|
|
||||||
identityState sharedIdentity
|
identityState sharedIdentity
|
||||||
client APIClient
|
client APIClient
|
||||||
telemetry Telemetry
|
|
||||||
reporter reporter.Reporter
|
reporter reporter.Reporter
|
||||||
panicHandler async.PanicHandler
|
panicHandler async.PanicHandler
|
||||||
sendRecorder *sendrecorder.SendRecorder
|
sendRecorder *sendrecorder.SendRecorder
|
||||||
@ -70,6 +69,8 @@ type Connector struct {
|
|||||||
syncState *SyncState
|
syncState *SyncState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
||||||
|
|
||||||
func NewConnector(
|
func NewConnector(
|
||||||
addrID string,
|
addrID string,
|
||||||
apiClient APIClient,
|
apiClient APIClient,
|
||||||
@ -78,7 +79,6 @@ func NewConnector(
|
|||||||
addressMode usertypes.AddressMode,
|
addressMode usertypes.AddressMode,
|
||||||
sendRecorder *sendrecorder.SendRecorder,
|
sendRecorder *sendrecorder.SendRecorder,
|
||||||
panicHandler async.PanicHandler,
|
panicHandler async.PanicHandler,
|
||||||
telemetry Telemetry,
|
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
showAllMail bool,
|
showAllMail bool,
|
||||||
syncState *SyncState,
|
syncState *SyncState,
|
||||||
@ -94,7 +94,6 @@ func NewConnector(
|
|||||||
attrs: defaultMailboxAttributes(),
|
attrs: defaultMailboxAttributes(),
|
||||||
|
|
||||||
client: apiClient,
|
client: apiClient,
|
||||||
telemetry: telemetry,
|
|
||||||
reporter: reporter,
|
reporter: reporter,
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
sendRecorder: sendRecorder,
|
sendRecorder: sendRecorder,
|
||||||
@ -108,6 +107,7 @@ func NewConnector(
|
|||||||
labels: labels,
|
labels: labels,
|
||||||
addressMode: addressMode,
|
addressMode: addressMode,
|
||||||
log: logrus.WithFields(logrus.Fields{
|
log: logrus.WithFields(logrus.Fields{
|
||||||
|
"pkg": "imapservice",
|
||||||
"gluon-connector": addressMode,
|
"gluon-connector": addressMode,
|
||||||
"addr-id": addrID,
|
"addr-id": addrID,
|
||||||
"user-id": userID,
|
"user-id": userID,
|
||||||
@ -166,10 +166,9 @@ func (s *Connector) Init(ctx context.Context, cache connector.IMAPState) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Connector) Authorize(ctx context.Context, username string, password []byte) bool {
|
func (s *Connector) Authorize(_ context.Context, username string, password []byte) bool {
|
||||||
addrID, err := s.identityState.CheckAuth(username, password)
|
addrID, err := s.identityState.CheckAuth(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.telemetry.ReportConfigStatusFailure("IMAP " + err.Error())
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +176,6 @@ func (s *Connector) Authorize(ctx context.Context, username string, password []b
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
s.telemetry.SendConfigStatusSuccess(ctx)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,22 +674,18 @@ func (s *Connector) importMessage(
|
|||||||
) (imap.Message, []byte, error) {
|
) (imap.Message, []byte, error) {
|
||||||
var full proton.FullMessage
|
var full proton.FullMessage
|
||||||
|
|
||||||
// addr is primary for combined mode or active for split mode
|
|
||||||
addr, ok := s.identityState.GetAddress(s.addrID)
|
|
||||||
if !ok {
|
|
||||||
return imap.Message{}, nil, fmt.Errorf("could not find address")
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err2 := parser.New(bytes.NewReader(literal))
|
p, err2 := parser.New(bytes.NewReader(literal))
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return imap.Message{}, nil, fmt.Errorf("failed to parse literal: %w", err2)
|
return imap.Message{}, nil, fmt.Errorf("failed to parse literal: %w", err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
isDraft := slices.Contains(labelIDs, proton.DraftsLabel)
|
isDraft := slices.Contains(labelIDs, proton.DraftsLabel)
|
||||||
|
addr, err := s.getImportAddress(p, isDraft)
|
||||||
|
if err != nil {
|
||||||
|
return imap.Message{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s.reportGODT3185(isDraft, addr.Email, p, s.addressMode == usertypes.AddressModeCombined)
|
if err := s.identityState.WithAddrKR(addr.ID, func(_, addrKR *crypto.KeyRing) error {
|
||||||
|
|
||||||
if err := s.identityState.WithAddrKR(s.addrID, func(_, addrKR *crypto.KeyRing) error {
|
|
||||||
primaryKey, errKey := addrKR.FirstKey()
|
primaryKey, errKey := addrKR.FirstKey()
|
||||||
if errKey != nil {
|
if errKey != nil {
|
||||||
return fmt.Errorf("failed to get primary key for import: %w", errKey)
|
return fmt.Errorf("failed to get primary key for import: %w", errKey)
|
||||||
@ -719,7 +712,7 @@ func (s *Connector) importMessage(
|
|||||||
}
|
}
|
||||||
str, err := s.client.ImportMessages(ctx, primaryKey, 1, 1, []proton.ImportReq{{
|
str, err := s.client.ImportMessages(ctx, primaryKey, 1, 1, []proton.ImportReq{{
|
||||||
Metadata: proton.ImportMetadata{
|
Metadata: proton.ImportMetadata{
|
||||||
AddressID: s.addrID,
|
AddressID: addr.ID,
|
||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
Unread: proton.Bool(unread),
|
Unread: proton.Bool(unread),
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
@ -878,79 +871,74 @@ func equalAddresses(a, b string) bool {
|
|||||||
return strings.EqualFold(stripPlusAlias(a), stripPlusAlias(b))
|
return strings.EqualFold(stripPlusAlias(a), stripPlusAlias(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Connector) reportGODT3185(isDraft bool, defaultAddr string, p *parser.Parser, isCombinedMode bool) {
|
func (s *Connector) getImportAddress(p *parser.Parser, isDraft bool) (proton.Address, error) {
|
||||||
reportAction := "draft"
|
// addr is primary for combined mode or active for split mode
|
||||||
if !isDraft {
|
address, ok := s.identityState.GetAddress(s.addrID)
|
||||||
reportAction = "import"
|
if !ok {
|
||||||
|
return proton.Address{}, errors.New("could not find account address")
|
||||||
}
|
}
|
||||||
|
|
||||||
reportMode := "combined"
|
inCombinedMode := s.addressMode == usertypes.AddressModeCombined
|
||||||
if !isCombinedMode {
|
if !inCombinedMode {
|
||||||
reportMode = "split"
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
senderAddr, err := s.getSenderProtonAddress(p)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, errNoSenderAddressMatch) {
|
||||||
|
s.log.WithError(err).Warn("Could not get import address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We did not find a match, so we use the default address.
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if senderAddr.ID == address.ID {
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GODT-3185 / BRIDGE-120 In combined mode, in certain cases we adapt the address used for encryption.
|
||||||
|
// - draft with non-default address in combined mode: using sender address
|
||||||
|
// - import with non-default address in combined mode: using sender address
|
||||||
|
// - import with non-default disabled address in combined mode: using sender address
|
||||||
|
|
||||||
|
isSenderAddressDisabled := (!bool(senderAddr.Send)) || (senderAddr.Status != proton.AddressStatusEnabled)
|
||||||
|
if isDraft && isSenderAddressDisabled {
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return senderAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Connector) getSenderProtonAddress(p *parser.Parser) (proton.Address, error) {
|
||||||
|
// Step 1: extract sender email address from message
|
||||||
|
if (p == nil) || (p.Root() == nil) || (p.Root().Header.Len() == 0) {
|
||||||
|
return proton.Address{}, errors.New("invalid message encountered while trying to extract sender address")
|
||||||
}
|
}
|
||||||
|
|
||||||
senderAddr := ""
|
|
||||||
if p != nil && p.Root() != nil && p.Root().Header.Len() != 0 {
|
|
||||||
addrField := p.Root().Header.Get("From")
|
addrField := p.Root().Header.Get("From")
|
||||||
if addrField == "" {
|
if len(addrField) == 0 {
|
||||||
addrField = p.Root().Header.Get("Sender")
|
addrField = p.Root().Header.Get("Sender")
|
||||||
}
|
}
|
||||||
if addrField != "" {
|
if len(addrField) == 0 {
|
||||||
|
return proton.Address{}, errors.New("no sender found in message headers")
|
||||||
|
}
|
||||||
|
|
||||||
sender, err := rfc5322.ParseAddressList(addrField)
|
sender, err := rfc5322.ParseAddressList(addrField)
|
||||||
if err == nil && len(sender) > 0 {
|
if (err != nil) || (len(sender) == 0) {
|
||||||
senderAddr = sender[0].Address
|
return proton.Address{}, fmt.Errorf("invalid sender address in message: %w", err)
|
||||||
} else {
|
|
||||||
s.log.WithError(err).Warn("Invalid sender address in reporter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if equalAddresses(defaultAddr, senderAddr) {
|
addrStr := sender[0].Address
|
||||||
return
|
|
||||||
|
// Step 2: match email with the user address list.
|
||||||
|
addressList := s.identityState.GetAddresses()
|
||||||
|
index := slices.IndexFunc(addressList, func(a proton.Address) bool {
|
||||||
|
return equalAddresses(a.Email, addrStr)
|
||||||
|
})
|
||||||
|
if index < 0 {
|
||||||
|
return proton.Address{}, errNoSenderAddressMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
isDisabled := false
|
return addressList[index], nil
|
||||||
isUserAddress := false
|
|
||||||
for _, a := range s.identityState.GetAddresses() {
|
|
||||||
if !equalAddresses(a.Email, senderAddr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
isUserAddress = true
|
|
||||||
isDisabled = !bool(a.Send) || (a.Status != proton.AddressStatusEnabled)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isUserAddress && senderAddr != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reportResult := "using sender address"
|
|
||||||
|
|
||||||
if !isCombinedMode {
|
|
||||||
reportResult = "error address not match"
|
|
||||||
}
|
|
||||||
|
|
||||||
reportAddress := ""
|
|
||||||
if senderAddr == "" {
|
|
||||||
reportAddress = " invalid"
|
|
||||||
reportResult = "error import/draft"
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDisabled {
|
|
||||||
reportAddress = " disabled"
|
|
||||||
if isDraft {
|
|
||||||
reportResult = "error draft"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
report := fmt.Sprintf(
|
|
||||||
"GODT-3185: %s with non-default%s address in %s mode: %s",
|
|
||||||
reportAction, reportAddress, reportMode, reportResult,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.log.Warn(report)
|
|
||||||
if s.reporter != nil {
|
|
||||||
_ = s.reporter.ReportMessage(report)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package evtloopmsgevents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
messageEventErrorCaseSchemaName = "bridge_event_loop_message_event_failures_total"
|
||||||
|
messageEventErrorCaseSchemaVersion = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateMessageEventFailureObservabilityMetric(eventType string) proton.ObservabilityMetric {
|
||||||
|
return proton.ObservabilityMetric{
|
||||||
|
Name: messageEventErrorCaseSchemaName,
|
||||||
|
Version: messageEventErrorCaseSchemaVersion,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Value": 1,
|
||||||
|
"Labels": map[string]string{
|
||||||
|
"eventType": eventType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventFailureCreateMessageMetric() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("createMessageEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventFailureDeleteMessageMetric() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("deleteMessageEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventFailureUpdateMetric() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("updateEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventFailedToBuildMessage() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("failedToBuildMessage")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventFailedToBuildDraft() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("failedToBuildDraft")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMessageEventUpdateChannelDoesNotExist() proton.ObservabilityMetric {
|
||||||
|
return generateMessageEventFailureObservabilityMetric("messageUpdateChannelDoesNotExist")
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package syncmsgevents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncEventErrorCaseSchemaName = "bridge_sync_message_event_failures_total"
|
||||||
|
syncEventErrorCaseSchemaVersion = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateSyncEventFailureObservabilityMetric(eventType string) proton.ObservabilityMetric {
|
||||||
|
return proton.ObservabilityMetric{
|
||||||
|
Name: syncEventErrorCaseSchemaName,
|
||||||
|
Version: syncEventErrorCaseSchemaVersion,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Value": 1,
|
||||||
|
"Labels": map[string]string{
|
||||||
|
"eventType": eventType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateSyncFailureCreateMessageEventMetric() proton.ObservabilityMetric {
|
||||||
|
return generateSyncEventFailureObservabilityMetric("createMessageEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateSyncFailureDeleteMessageEventMetric() proton.ObservabilityMetric {
|
||||||
|
return generateSyncEventFailureObservabilityMetric("deleteMessageEvent")
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/ProtonMail/gluon/watcher"
|
"github.com/ProtonMail/gluon/watcher"
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/sendrecorder"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/sendrecorder"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||||
@ -46,12 +47,6 @@ type EventProvider interface {
|
|||||||
RewindEventID(ctx context.Context, eventID string) error
|
RewindEventID(ctx context.Context, eventID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Telemetry interface {
|
|
||||||
useridentity.Telemetry
|
|
||||||
SendConfigStatusSuccess(ctx context.Context)
|
|
||||||
ReportConfigStatusFailure(errDetails string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type GluonIDProvider interface {
|
type GluonIDProvider interface {
|
||||||
GetGluonID(addrID string) (string, bool)
|
GetGluonID(addrID string) (string, bool)
|
||||||
GetGluonIDs() map[string]string
|
GetGluonIDs() map[string]string
|
||||||
@ -76,7 +71,6 @@ type Service struct {
|
|||||||
serverManager IMAPServerManager
|
serverManager IMAPServerManager
|
||||||
eventPublisher events.EventPublisher
|
eventPublisher events.EventPublisher
|
||||||
|
|
||||||
telemetry Telemetry
|
|
||||||
panicHandler async.PanicHandler
|
panicHandler async.PanicHandler
|
||||||
sendRecorder *sendrecorder.SendRecorder
|
sendRecorder *sendrecorder.SendRecorder
|
||||||
reporter reporter.Reporter
|
reporter reporter.Reporter
|
||||||
@ -96,6 +90,8 @@ type Service struct {
|
|||||||
syncConfigPath string
|
syncConfigPath string
|
||||||
lastHandledEventID string
|
lastHandledEventID string
|
||||||
isSyncing atomic.Bool
|
isSyncing atomic.Bool
|
||||||
|
|
||||||
|
observabilitySender observability.Sender
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
@ -109,13 +105,13 @@ func NewService(
|
|||||||
keyPassProvider useridentity.KeyPassProvider,
|
keyPassProvider useridentity.KeyPassProvider,
|
||||||
panicHandler async.PanicHandler,
|
panicHandler async.PanicHandler,
|
||||||
sendRecorder *sendrecorder.SendRecorder,
|
sendRecorder *sendrecorder.SendRecorder,
|
||||||
telemetry Telemetry,
|
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
addressMode usertypes.AddressMode,
|
addressMode usertypes.AddressMode,
|
||||||
subscription events.Subscription,
|
subscription events.Subscription,
|
||||||
syncConfigDir string,
|
syncConfigDir string,
|
||||||
maxSyncMemory uint64,
|
maxSyncMemory uint64,
|
||||||
showAllMail bool,
|
showAllMail bool,
|
||||||
|
observabilitySender observability.Sender,
|
||||||
) *Service {
|
) *Service {
|
||||||
subscriberName := fmt.Sprintf("imap-%v", identityState.User.ID)
|
subscriberName := fmt.Sprintf("imap-%v", identityState.User.ID)
|
||||||
|
|
||||||
@ -146,7 +142,6 @@ func NewService(
|
|||||||
|
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
sendRecorder: sendRecorder,
|
sendRecorder: sendRecorder,
|
||||||
telemetry: telemetry,
|
|
||||||
reporter: reporter,
|
reporter: reporter,
|
||||||
|
|
||||||
connectors: make(map[string]*Connector),
|
connectors: make(map[string]*Connector),
|
||||||
@ -160,6 +155,8 @@ func NewService(
|
|||||||
syncMessageBuilder: syncMessageBuilder,
|
syncMessageBuilder: syncMessageBuilder,
|
||||||
syncReporter: syncReporter,
|
syncReporter: syncReporter,
|
||||||
syncConfigPath: GetSyncConfigPath(syncConfigDir, identityState.User.ID),
|
syncConfigPath: GetSyncConfigPath(syncConfigDir, identityState.User.ID),
|
||||||
|
|
||||||
|
observabilitySender: observabilitySender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +233,12 @@ func (s *Service) OnLogout(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnDelete(ctx context.Context) error {
|
||||||
|
_, err := s.cpc.Send(ctx, &onDeleteReq{})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) ShowAllMail(ctx context.Context, v bool) error {
|
func (s *Service) ShowAllMail(ctx context.Context, v bool) error {
|
||||||
_, err := s.cpc.Send(ctx, &showAllMailReq{v: v})
|
_, err := s.cpc.Send(ctx, &showAllMailReq{v: v})
|
||||||
|
|
||||||
@ -365,6 +368,11 @@ func (s *Service) run(ctx context.Context) { //nolint gocyclo
|
|||||||
err := s.removeConnectorsFromServer(ctx, s.connectors, false)
|
err := s.removeConnectorsFromServer(ctx, s.connectors, false)
|
||||||
req.Reply(ctx, nil, err)
|
req.Reply(ctx, nil, err)
|
||||||
|
|
||||||
|
case *onDeleteReq:
|
||||||
|
s.log.Debug("Delete Request")
|
||||||
|
err := s.removeConnectorsFromServer(ctx, s.connectors, true)
|
||||||
|
req.Reply(ctx, nil, err)
|
||||||
|
|
||||||
case *showAllMailReq:
|
case *showAllMailReq:
|
||||||
s.log.Debug("Show all mail request")
|
s.log.Debug("Show all mail request")
|
||||||
req.Reply(ctx, nil, nil)
|
req.Reply(ctx, nil, nil)
|
||||||
@ -507,7 +515,6 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||||||
s.addressMode,
|
s.addressMode,
|
||||||
s.sendRecorder,
|
s.sendRecorder,
|
||||||
s.panicHandler,
|
s.panicHandler,
|
||||||
s.telemetry,
|
|
||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
@ -525,7 +532,6 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||||||
s.addressMode,
|
s.addressMode,
|
||||||
s.sendRecorder,
|
s.sendRecorder,
|
||||||
s.panicHandler,
|
s.panicHandler,
|
||||||
s.telemetry,
|
|
||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
@ -649,6 +655,8 @@ type onLogoutReq struct{}
|
|||||||
|
|
||||||
type showAllMailReq struct{ v bool }
|
type showAllMailReq struct{ v bool }
|
||||||
|
|
||||||
|
type onDeleteReq struct{}
|
||||||
|
|
||||||
type setAddressModeReq struct {
|
type setAddressModeReq struct {
|
||||||
mode usertypes.AddressMode
|
mode usertypes.AddressMode
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,7 +154,6 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro
|
|||||||
s.addressMode,
|
s.addressMode,
|
||||||
s.sendRecorder,
|
s.sendRecorder,
|
||||||
s.panicHandler,
|
s.panicHandler,
|
||||||
s.telemetry,
|
|
||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
|
|||||||
@ -26,11 +26,11 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/gluon"
|
"github.com/ProtonMail/gluon"
|
||||||
"github.com/ProtonMail/gluon/imap"
|
"github.com/ProtonMail/gluon/imap"
|
||||||
"github.com/ProtonMail/gluon/reporter"
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
obsMetrics "github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/observabilitymetrics/evtloopmsgevents"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
@ -46,7 +46,7 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
|||||||
case proton.EventCreate:
|
case proton.EventCreate:
|
||||||
updates, err := onMessageCreated(logging.WithLogrusField(ctx, "action", "create message"), s, event.Message, false)
|
updates, err := onMessageCreated(logging.WithLogrusField(ctx, "action", "create message"), s, event.Message, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(s.reporter, s.log, "Failed to apply create message event", err)
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureCreateMessageMetric())
|
||||||
return fmt.Errorf("failed to handle create message event: %w", err)
|
return fmt.Errorf("failed to handle create message event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
|||||||
event,
|
event,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(s.reporter, s.log, "Failed to apply update draft message event", err)
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureUpdateMetric())
|
||||||
return fmt.Errorf("failed to handle update draft event: %w", err)
|
return fmt.Errorf("failed to handle update draft event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
|||||||
event.Message,
|
event.Message,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(s.reporter, s.log, "Failed to apply update message event", err)
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureUpdateMetric())
|
||||||
return fmt.Errorf("failed to handle update message event: %w", err)
|
return fmt.Errorf("failed to handle update message event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +113,7 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
||||||
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureDeleteMessageMetric())
|
||||||
return fmt.Errorf("failed to handle delete message event in gluon: %w", err)
|
return fmt.Errorf("failed to handle delete message event in gluon: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,8 +159,7 @@ func onMessageCreated(
|
|||||||
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
||||||
}
|
}
|
||||||
|
|
||||||
reportErrorAndMessageID(s.reporter, s.log, "Failed to build message (event create)", res.err, res.messageID)
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailedToBuildMessage())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,8 +221,7 @@ func onMessageUpdateDraftOrSent(ctx context.Context, s *Service, event proton.Me
|
|||||||
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
||||||
}
|
}
|
||||||
|
|
||||||
reportErrorAndMessageID(s.reporter, s.log, "Failed to build draft message (event update)", res.err, res.messageID)
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailedToBuildDraft())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,24 +298,6 @@ func onMessageDeleted(ctx context.Context, s *Service, event proton.MessageEvent
|
|||||||
return updates
|
return updates
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportError(r reporter.Reporter, entry *logrus.Entry, title string, err error) {
|
|
||||||
reportErrorNoContextCancel(r, entry, title, err, reporter.Context{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportErrorAndMessageID(r reporter.Reporter, entry *logrus.Entry, title string, err error, messgeID string) {
|
|
||||||
reportErrorNoContextCancel(r, entry, title, err, reporter.Context{"messageID": messgeID})
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportErrorNoContextCancel(r reporter.Reporter, entry *logrus.Entry, title string, err error, reportContext reporter.Context) {
|
|
||||||
if !errors.Is(err, context.Canceled) {
|
|
||||||
reportContext["error"] = err
|
|
||||||
reportContext["error_type"] = internal.ErrCauseType(err)
|
|
||||||
if rerr := r.ReportMessageWithContext(title, reportContext); rerr != nil {
|
|
||||||
entry.WithError(err).WithField("title", title).Error("Failed to report message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// safePublishMessageUpdate handles the rare case where the address' update channel may have been deleted in the same
|
// safePublishMessageUpdate handles the rare case where the address' update channel may have been deleted in the same
|
||||||
// event. This rare case can take place if in the same event fetch request there is an update for delete address and
|
// event. This rare case can take place if in the same event fetch request there is an update for delete address and
|
||||||
// create/update message.
|
// create/update message.
|
||||||
@ -341,7 +322,7 @@ func safePublishMessageUpdate(ctx context.Context, s *Service, addressID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
logrus.Warnf("Update channel not found for address %v, it may have been already deleted", addressID)
|
logrus.Warnf("Update channel not found for address %v, it may have been already deleted", addressID)
|
||||||
_ = s.reporter.ReportMessage("Message Update channel does not exist")
|
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventUpdateChannelDoesNotExist())
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
obsMetrics "github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/observabilitymetrics/syncmsgevents"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(s.service.reporter, s.service.log, "Failed to apply create message event", err)
|
s.service.observabilitySender.AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateSyncFailureCreateMessageEventMetric())
|
||||||
return fmt.Errorf("failed to handle create message event: %w", err)
|
return fmt.Errorf("failed to handle create message event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +65,22 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case proton.EventUpdate:
|
||||||
|
if event.Message.IsDraft() || (event.Message.Flags&proton.MessageFlagSent != 0) {
|
||||||
|
updates, err := onMessageUpdateDraftOrSent(
|
||||||
|
logging.WithLogrusField(ctx, "action", "update draft or sent message (sync)"),
|
||||||
|
s.service,
|
||||||
|
event,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to handle update draft event (sync): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case proton.EventDelete:
|
case proton.EventDelete:
|
||||||
updates := onMessageDeleted(
|
updates := onMessageDeleted(
|
||||||
logging.WithLogrusField(ctx, "action", "delete message (sync)"),
|
logging.WithLogrusField(ctx, "action", "delete message (sync)"),
|
||||||
@ -71,6 +89,7 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
||||||
|
s.service.observabilitySender.AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateSyncFailureDeleteMessageEventMetric())
|
||||||
return fmt.Errorf("failed to handle delete message event in gluon: %w", err)
|
return fmt.Errorf("failed to handle delete message event in gluon: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/files"
|
"github.com/ProtonMail/proton-bridge/v3/internal/files"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ type IMAPSettingsProvider interface {
|
|||||||
Port() int
|
Port() int
|
||||||
SetPort(int) error
|
SetPort(int) error
|
||||||
UseSSL() bool
|
UseSSL() bool
|
||||||
|
DisableIMAPAuthenticate() bool
|
||||||
CacheDirectory() string
|
CacheDirectory() string
|
||||||
DataDirectory() (string, error)
|
DataDirectory() (string, error)
|
||||||
SetCacheDirectory(string) error
|
SetCacheDirectory(string) error
|
||||||
@ -73,10 +75,12 @@ func newIMAPServer(
|
|||||||
tlsConfig *tls.Config,
|
tlsConfig *tls.Config,
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
logClient, logServer bool,
|
logClient, logServer bool,
|
||||||
|
disableIMAPAuthenticate bool,
|
||||||
eventPublisher IMAPEventPublisher,
|
eventPublisher IMAPEventPublisher,
|
||||||
tasks *async.Group,
|
tasks *async.Group,
|
||||||
uidValidityGenerator imap.UIDValidityGenerator,
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
panicHandler async.PanicHandler,
|
panicHandler async.PanicHandler,
|
||||||
|
observabilitySender observability.Sender,
|
||||||
) (*gluon.Server, error) {
|
) (*gluon.Server, error) {
|
||||||
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
|
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
|
||||||
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
|
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
|
||||||
@ -111,7 +115,7 @@ func newIMAPServer(
|
|||||||
imapServerLog = io.Discard
|
imapServerLog = io.Discard
|
||||||
}
|
}
|
||||||
|
|
||||||
imapServer, err := gluon.New(
|
options := []gluon.Option{
|
||||||
gluon.WithTLS(tlsConfig),
|
gluon.WithTLS(tlsConfig),
|
||||||
gluon.WithDataDir(gluonCacheDir),
|
gluon.WithDataDir(gluonCacheDir),
|
||||||
gluon.WithDatabaseDir(gluonConfigDir),
|
gluon.WithDatabaseDir(gluonConfigDir),
|
||||||
@ -121,7 +125,14 @@ func newIMAPServer(
|
|||||||
gluon.WithReporter(reporter),
|
gluon.WithReporter(reporter),
|
||||||
gluon.WithUIDValidityGenerator(uidValidityGenerator),
|
gluon.WithUIDValidityGenerator(uidValidityGenerator),
|
||||||
gluon.WithPanicHandler(panicHandler),
|
gluon.WithPanicHandler(panicHandler),
|
||||||
)
|
gluon.WithObservabilitySender(observability.NewAdapter(observabilitySender), int(observability.GluonImapError), int(observability.GluonMessageError), int(observability.GluonOtherError)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if disableIMAPAuthenticate {
|
||||||
|
options = append(options, gluon.WithDisableIMAPAuthenticate())
|
||||||
|
}
|
||||||
|
|
||||||
|
imapServer, err := gluon.New(options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -153,9 +164,9 @@ func newIMAPServer(
|
|||||||
|
|
||||||
func getGluonVersionInfo(version *semver.Version) gluon.Option {
|
func getGluonVersionInfo(version *semver.Version) gluon.Option {
|
||||||
return gluon.WithVersionInfo(
|
return gluon.WithVersionInfo(
|
||||||
int(version.Major()),
|
int(version.Major()), //nolint:gosec // disable G115
|
||||||
int(version.Minor()),
|
int(version.Minor()), //nolint:gosec // disable G115
|
||||||
int(version.Patch()),
|
int(version.Patch()), //nolint:gosec // disable G115
|
||||||
constants.FullAppName,
|
constants.FullAppName,
|
||||||
"TODO",
|
"TODO",
|
||||||
"TODO",
|
"TODO",
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/ProtonMail/gluon/reporter"
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
bridgesmtp "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
bridgesmtp "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
|
||||||
@ -60,6 +61,8 @@ type Service struct {
|
|||||||
|
|
||||||
uidValidityGenerator imap.UIDValidityGenerator
|
uidValidityGenerator imap.UIDValidityGenerator
|
||||||
telemetry Telemetry
|
telemetry Telemetry
|
||||||
|
|
||||||
|
observabilitySender observability.Sender
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
@ -71,6 +74,7 @@ func NewService(
|
|||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
uidValidityGenerator imap.UIDValidityGenerator,
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
telemetry Telemetry,
|
telemetry Telemetry,
|
||||||
|
observabilitySender observability.Sender,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
requests: cpc.NewCPC(),
|
requests: cpc.NewCPC(),
|
||||||
@ -85,6 +89,8 @@ func NewService(
|
|||||||
tasks: async.NewGroup(ctx, panicHandler),
|
tasks: async.NewGroup(ctx, panicHandler),
|
||||||
uidValidityGenerator: uidValidityGenerator,
|
uidValidityGenerator: uidValidityGenerator,
|
||||||
telemetry: telemetry,
|
telemetry: telemetry,
|
||||||
|
|
||||||
|
observabilitySender: observabilitySender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,10 +451,12 @@ func (sm *Service) createIMAPServer(ctx context.Context) (*gluon.Server, error)
|
|||||||
sm.reporter,
|
sm.reporter,
|
||||||
sm.imapSettings.LogClient(),
|
sm.imapSettings.LogClient(),
|
||||||
sm.imapSettings.LogServer(),
|
sm.imapSettings.LogServer(),
|
||||||
|
sm.imapSettings.DisableIMAPAuthenticate(),
|
||||||
sm.imapSettings.EventPublisher(),
|
sm.imapSettings.EventPublisher(),
|
||||||
sm.tasks,
|
sm.tasks,
|
||||||
sm.uidValidityGenerator,
|
sm.uidValidityGenerator,
|
||||||
sm.panicHandler,
|
sm.panicHandler,
|
||||||
|
sm.observabilitySender,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerCreated{})
|
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerCreated{})
|
||||||
|
|||||||
@ -45,14 +45,14 @@ type Service struct {
|
|||||||
store *Store
|
store *Store
|
||||||
|
|
||||||
getFlagValueFn unleash.GetFlagValueFn
|
getFlagValueFn unleash.GetFlagValueFn
|
||||||
pushObservabilityMetricFn observability.PushObsMetricFn
|
|
||||||
|
observabilitySender observability.Sender
|
||||||
}
|
}
|
||||||
|
|
||||||
const bitfieldRegexPattern = `^\\\d+`
|
const bitfieldRegexPattern = `^\\\d+`
|
||||||
const disableNotificationsKillSwitch = "InboxBridgeEventLoopNotificationDisabled"
|
|
||||||
|
|
||||||
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
|
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
|
||||||
getFlagFn unleash.GetFlagValueFn, pushMetricFn observability.PushObsMetricFn) *Service {
|
getFlagFn unleash.GetFlagValueFn, observabilitySender observability.Sender) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
userID: userID,
|
userID: userID,
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ func NewService(userID string, service userevents.Subscribable, eventPublisher e
|
|||||||
store: store,
|
store: store,
|
||||||
|
|
||||||
getFlagValueFn: getFlagFn,
|
getFlagValueFn: getFlagFn,
|
||||||
pushObservabilityMetricFn: pushMetricFn,
|
observabilitySender: observabilitySender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ func (s *Service) run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEvents []proton.NotificationEvent) error {
|
func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEvents []proton.NotificationEvent) error {
|
||||||
if s.getFlagValueFn(disableNotificationsKillSwitch) {
|
if s.getFlagValueFn(unleash.EventLoopNotificationDisabled) {
|
||||||
s.log.Info("Received notification events. Skipping as kill switch is enabled.")
|
s.log.Info("Received notification events. Skipping as kill switch is enabled.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEven
|
|||||||
s.log.Debug("Handling notification events")
|
s.log.Debug("Handling notification events")
|
||||||
|
|
||||||
// Publish observability metrics that we've received notifications
|
// Publish observability metrics that we've received notifications
|
||||||
s.pushObservabilityMetricFn(GenerateReceivedMetric(len(notificationEvents)))
|
s.observabilitySender.AddMetrics(GenerateReceivedMetric(len(notificationEvents)))
|
||||||
|
|
||||||
for _, event := range notificationEvents {
|
for _, event := range notificationEvents {
|
||||||
ctx = logging.WithLogrusField(ctx, "notificationID", event.ID)
|
ctx = logging.WithLogrusField(ctx, "notificationID", event.ID)
|
||||||
@ -133,7 +133,7 @@ func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEven
|
|||||||
Subtitle: event.Payload.Subtitle, Body: event.Payload.Body})
|
Subtitle: event.Payload.Subtitle, Body: event.Payload.Body})
|
||||||
|
|
||||||
// Publish observability metric that we've successfully processed notifications
|
// Publish observability metric that we've successfully processed notifications
|
||||||
s.pushObservabilityMetricFn(GenerateProcessedMetric(1))
|
s.observabilitySender.AddMetrics(GenerateProcessedMetric(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -109,7 +109,7 @@ func (s *Store) readCache() {
|
|||||||
|
|
||||||
file, err := os.Open(s.cacheFilepath)
|
file, err := os.Open(s.cacheFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.WithError(err).Error("Unable to open cache file")
|
s.log.WithError(err).Info("Unable to open cache file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
internal/services/observability/adapter.go
Normal file
93
internal/services/observability/adapter.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package observability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
sender Sender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(sender Sender) *Adapter {
|
||||||
|
return &Adapter{sender: sender}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAndParseGenericMetrics parses a metric provided as an interface into a proton.ObservabilityMetric type.
|
||||||
|
// It's exported as it is also used in integration tests.
|
||||||
|
func VerifyAndParseGenericMetrics(metric map[string]interface{}) (bool, proton.ObservabilityMetric) {
|
||||||
|
name, ok := metric["Name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return false, proton.ObservabilityMetric{}
|
||||||
|
}
|
||||||
|
|
||||||
|
version, ok := metric["Version"].(int)
|
||||||
|
if !ok {
|
||||||
|
return false, proton.ObservabilityMetric{}
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp, ok := metric["Timestamp"].(int64)
|
||||||
|
if !ok {
|
||||||
|
return false, proton.ObservabilityMetric{}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, ok := metric["Data"]
|
||||||
|
if !ok {
|
||||||
|
return false, proton.ObservabilityMetric{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, proton.ObservabilityMetric{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adapter *Adapter) AddMetrics(metrics ...map[string]interface{}) {
|
||||||
|
var typedMetrics []proton.ObservabilityMetric
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if ok, m := VerifyAndParseGenericMetrics(metric); ok {
|
||||||
|
typedMetrics = append(typedMetrics, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(typedMetrics) > 0 {
|
||||||
|
adapter.sender.AddMetrics(typedMetrics...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adapter *Adapter) AddDistinctMetrics(errType interface{}, metrics ...map[string]interface{}) {
|
||||||
|
errTypeInt, ok := errType.(int)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var typedMetrics []proton.ObservabilityMetric
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if ok, m := VerifyAndParseGenericMetrics(metric); ok {
|
||||||
|
typedMetrics = append(typedMetrics, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(typedMetrics) > 0 {
|
||||||
|
adapter.sender.AddDistinctMetrics(DistinctionErrorTypeEnum(errTypeInt), typedMetrics...)
|
||||||
|
}
|
||||||
|
}
|
||||||
58
internal/services/observability/adapter_test.go
Normal file
58
internal/services/observability/adapter_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package observability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AdapterCustomMetrics(t *testing.T) {
|
||||||
|
customMetric := map[string]interface{}{
|
||||||
|
"Name": "name",
|
||||||
|
"Version": 1,
|
||||||
|
"Timestamp": time.Now().Unix(),
|
||||||
|
"Data": map[string]interface{}{
|
||||||
|
"Value": 1,
|
||||||
|
"Labels": map[string]string{
|
||||||
|
"error": "customError",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, metric := VerifyAndParseGenericMetrics(customMetric)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
require.Equal(t, metric.Name, customMetric["Name"])
|
||||||
|
require.Equal(t, metric.Timestamp, customMetric["Timestamp"])
|
||||||
|
require.Equal(t, metric.Version, customMetric["Version"])
|
||||||
|
require.Equal(t, metric.Data, customMetric["Data"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AdapterGluonMetrics(t *testing.T) {
|
||||||
|
metrics := GenerateAllGluonMetrics()
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
ok, m := VerifyAndParseGenericMetrics(metric)
|
||||||
|
fmt.Println(m)
|
||||||
|
require.True(t, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
internal/services/observability/distinction_error_types.go
Normal file
55
internal/services/observability/distinction_error_types.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package observability
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DistinctionErrorTypeEnum - maps to the specific error schema for which we
|
||||||
|
// want to send a user update.
|
||||||
|
type DistinctionErrorTypeEnum int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SyncError DistinctionErrorTypeEnum = iota
|
||||||
|
GluonImapError
|
||||||
|
GluonMessageError
|
||||||
|
GluonOtherError
|
||||||
|
SMTPError
|
||||||
|
EventLoopError // EventLoopError - should always be kept last when inserting new keys.
|
||||||
|
)
|
||||||
|
|
||||||
|
// errorSchemaMap - maps between the DistinctionErrorTypeEnum and the relevant schema name.
|
||||||
|
var errorSchemaMap = map[DistinctionErrorTypeEnum]string{ //nolint:gochecknoglobals
|
||||||
|
SyncError: "bridge_sync_errors_users_total",
|
||||||
|
EventLoopError: "bridge_event_loop_events_errors_users_total",
|
||||||
|
GluonImapError: "bridge_gluon_imap_errors_users_total",
|
||||||
|
GluonMessageError: "bridge_gluon_message_errors_users_total",
|
||||||
|
SMTPError: "bridge_smtp_errors_users_total",
|
||||||
|
GluonOtherError: "bridge_gluon_other_errors_users_total",
|
||||||
|
}
|
||||||
|
|
||||||
|
// createLastSentMap - needs to be updated whenever we make changes to the enum.
|
||||||
|
func createLastSentMap() map[DistinctionErrorTypeEnum]time.Time {
|
||||||
|
registerTime := time.Now().Add(-updateInterval)
|
||||||
|
lastSentMap := make(map[DistinctionErrorTypeEnum]time.Time)
|
||||||
|
|
||||||
|
for errType := SyncError; errType <= EventLoopError; errType++ {
|
||||||
|
lastSentMap[errType] = registerTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastSentMap
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user