mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43cbedafb8 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,7 +7,6 @@
|
||||
*~
|
||||
.idea
|
||||
.vscode
|
||||
.vs
|
||||
|
||||
# Test files
|
||||
godog.test
|
||||
@ -36,8 +35,6 @@ cmd/Import-Export/deploy
|
||||
proton-bridge
|
||||
cmd/Desktop-Bridge/*.exe
|
||||
cmd/launcher/*.exe
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# Jetbrains (CLion, Golang) cmake build dirs
|
||||
cmake-build-*/
|
||||
|
||||
@ -88,7 +88,6 @@ linters:
|
||||
- durationcheck # check for two durations multiplied together [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]
|
||||
- copyloopvar # detects places where loop variables are copied.
|
||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||
- 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]
|
||||
|
||||
@ -63,15 +63,11 @@ 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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
@ -99,11 +95,8 @@ 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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
@ -131,16 +124,14 @@ 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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [yaml](https://gopkg.in/yaml.v3) 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-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)
|
||||
|
||||
43
Changelog.md
43
Changelog.md
@ -3,49 +3,6 @@
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
|
||||
## Erasmus Bridge 3.15.1
|
||||
|
||||
### Changed
|
||||
* BRIDGE-281: Disable keychain test on macOS.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
4
Makefile
4
Makefile
@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
|
||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=3.15.1+git
|
||||
BRIDGE_APP_VERSION?=3.13.0+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
APP_FULL_NAME:=Proton Mail Bridge
|
||||
APP_VENDOR:=Proton AG
|
||||
@ -189,7 +189,7 @@ ${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
||||
|
||||
## Dev dependencies
|
||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||
LINTVER:="v1.61.0"
|
||||
LINTVER:="v1.59.1"
|
||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||
|
||||
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
|
||||
# Proton Mail Bridge and Import Export app
|
||||
Copyright (c) 2024 Proton AG
|
||||
|
||||
This repository holds the Proton Mail Bridge application.
|
||||
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
||||
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).
|
||||
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
|
||||
its GUI.
|
||||
|
||||
To configure an e-mail client, first log in using your Proton Mail credentials.
|
||||
To configure an e-mail client, firstly log in using your Proton Mail credentials.
|
||||
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
|
||||
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).
|
||||
|
||||
## Launcher
|
||||
The launcher is a binary used to run the Proton Mail Bridge.
|
||||
## Launchers
|
||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
||||
|
||||
The Official distribution of the Proton Mail Bridge application contains
|
||||
Official distributions of the Proton Mail Bridge and Import-Export apps contain
|
||||
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
|
||||
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.
|
||||
|
||||
## Keychain
|
||||
You need to have a keychain in order to run Proton Mail Bridge. On Mac or
|
||||
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
|
||||
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||
or
|
||||
|
||||
15
go.mod
15
go.mod
@ -7,9 +7,9 @@ toolchain go1.21.9
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||
@ -47,18 +47,14 @@ require (
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sys v0.19.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/protobuf v1.33.0
|
||||
howett.net/plist v1.0.0
|
||||
)
|
||||
|
||||
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/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
@ -86,11 +82,8 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // 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/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/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||
@ -119,19 +112,17 @@ require (
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // 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/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sync v0.3.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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423
|
||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
||||
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/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
||||
|
||||
99
go.sum
99
go.sum
@ -5,16 +5,9 @@ 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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
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/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/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/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=
|
||||
@ -34,31 +27,35 @@ 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-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602 h1:EoMjWlC32tg46L/07hWoiZfLkqJyxVMcsq4Cyn+Ofqc=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002092751-3bbeea9053af h1:iMxTQUg2cB47cXqpMev3cZmQoGBOef3cSUjBbdEl33M=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002092751-3bbeea9053af/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002111651-173859b80060 h1:dcu3tT84GjoXb++n7crv8UJeG8eRwogjTYdkoJ+MjQI=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002111651-173859b80060/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002142736-ef4153d156d8 h1:YxPHSJUA87i1hc6s1YrW89++V7HpcR7LSFQ6XM0TsAE=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241002142736-ef4153d156d8/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241008123701-ddf4a459d0b4 h1:xE+V17O9HIttMpVymNCORQILk9OKpSekrrPbX7YGnF8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241008123701-ddf4a459d0b4/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241014082854-9d93627be032 h1:5bwI+mwF26c460xlq2Dw3/cVF1cU4Xo4kTKX1/pBXko=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241014082854-9d93627be032/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e h1:+UfdKOkF9JEiH9VXWBo+/nlXNVSJcxtuf4+SJTrk9fw=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c h1:P3SvCACt13Zqdj0IRDB4bgwqI68+oMB2j0uVuPQyoTw=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c/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/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-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
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.20230526094639-b62c999c85b7/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/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
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.20240918100656-b4860af56d47/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||
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.20240612082117-0f92424eed80/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/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||
@ -93,7 +90,6 @@ 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.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
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/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
@ -110,7 +106,6 @@ 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.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@ -162,10 +157,6 @@ 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-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/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.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
@ -218,8 +209,6 @@ 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/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-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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@ -228,13 +217,6 @@ 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.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.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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
@ -242,10 +224,6 @@ 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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@ -256,15 +234,10 @@ 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/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
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/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.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/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@ -410,7 +383,6 @@ 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_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/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/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@ -492,8 +464,6 @@ 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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
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/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
@ -508,7 +478,6 @@ 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-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-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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
@ -557,7 +526,6 @@ 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-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -575,8 +543,6 @@ 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-20190226205417-e64efc72b421/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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -607,7 +573,6 @@ 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-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-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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -667,7 +632,6 @@ 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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@ -692,14 +656,10 @@ 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.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -709,27 +669,13 @@ 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-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/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/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
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.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/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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
@ -757,7 +703,6 @@ 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
|
||||
@ -75,21 +75,17 @@ const (
|
||||
|
||||
flagLogIMAP = "log-imap"
|
||||
flagLogSMTP = "log-smtp"
|
||||
|
||||
flagEnableKeychainTest = "enable-keychain-test"
|
||||
flagDisableKeychainTest = "disable-keychain-test"
|
||||
|
||||
flagSoftwareRenderer = "software-renderer"
|
||||
flagSetSoftwareRenderer = "set-software-renderer"
|
||||
flagSetHardwareRenderer = "set-hardware-renderer"
|
||||
)
|
||||
|
||||
// Hidden flags.
|
||||
const (
|
||||
flagLauncher = "launcher"
|
||||
flagNoWindow = "no-window"
|
||||
flagParentPID = "parent-pid"
|
||||
FlagSessionID = "session-id"
|
||||
flagLauncher = "launcher"
|
||||
flagNoWindow = "no-window"
|
||||
flagParentPID = "parent-pid"
|
||||
flagSoftwareRenderer = "software-renderer"
|
||||
flagEnableKeychainTest = "enable-keychain-test"
|
||||
flagDisableKeychainTest = "disable-keychain-test"
|
||||
FlagSessionID = "session-id"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -97,21 +93,18 @@ const (
|
||||
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
|
||||
Name: flagEnableKeychainTest,
|
||||
Usage: "This flag is deprecated and does nothing",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
Hidden: true,
|
||||
}
|
||||
Name: flagEnableKeychainTest,
|
||||
Usage: "Enable the keychain test",
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
} //nolint:gochecknoglobals
|
||||
|
||||
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||
Name: flagDisableKeychainTest,
|
||||
Usage: "This flag is deprecated and does nothing",
|
||||
Value: false,
|
||||
DisableDefaultText: true,
|
||||
Hidden: true,
|
||||
Name: flagDisableKeychainTest,
|
||||
Usage: "Disable the keychain test",
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
}
|
||||
|
||||
func New() *cli.App {
|
||||
@ -163,24 +156,6 @@ func New() *cli.App {
|
||||
Name: flagLogSMTP,
|
||||
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
|
||||
&cli.BoolFlag{
|
||||
@ -199,23 +174,19 @@ func New() *cli.App {
|
||||
Hidden: true,
|
||||
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{
|
||||
Name: FlagSessionID,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
// We override the default help value because we want "Show" to be capitalized
|
||||
cli.HelpFlag = &cli.BoolFlag{
|
||||
Name: "help",
|
||||
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)
|
||||
// the two flags below were introduced by BRIDGE-116
|
||||
cliFlagEnableKeychainTest,
|
||||
cliFlagDisableKeychainTest,
|
||||
}
|
||||
|
||||
app.Action = run
|
||||
@ -286,9 +257,10 @@ func run(c *cli.Context) error {
|
||||
|
||||
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||
// Look for available keychains
|
||||
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
||||
skipKeychainTest := checkSkipKeychainTest(c, settings)
|
||||
return WithKeychainList(crashHandler, skipKeychainTest, func(keychains *keychain.List) error {
|
||||
// Unlock the encrypted vault.
|
||||
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||
if !v.Migrated() {
|
||||
// Migrate old settings into the vault.
|
||||
if err := migrateOldSettings(v); err != nil {
|
||||
@ -550,11 +522,11 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
||||
}
|
||||
|
||||
// WithKeychainList init the list of usable keychains.
|
||||
func WithKeychainList(panicHandler async.PanicHandler, fn func(*keychain.List) error) error {
|
||||
func WithKeychainList(panicHandler async.PanicHandler, skipKeychainTest bool, fn func(*keychain.List) error) error {
|
||||
logrus.Debug("Creating keychain list")
|
||||
defer logrus.Debug("Keychain list stop")
|
||||
defer async.HandlePanic(panicHandler)
|
||||
return fn(keychain.NewList())
|
||||
return fn(keychain.NewList(skipKeychainTest))
|
||||
}
|
||||
|
||||
func setDeviceCookies(jar *cookies.Jar) error {
|
||||
@ -575,6 +547,34 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func onMacOS() bool {
|
||||
return runtime.GOOS == "darwin"
|
||||
func checkSkipKeychainTest(c *cli.Context, settingsDir string) bool {
|
||||
if 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
|
||||
}
|
||||
|
||||
65
internal/app/app_test.go
Normal file
65
internal/app/app_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// 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,18 +25,17 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"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/pkg/keychain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||
logrus.Debug("Creating vault")
|
||||
defer logrus.Debug("Vault stopped")
|
||||
|
||||
// Create the encVault.
|
||||
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, panicHandler)
|
||||
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create vault: %w", err)
|
||||
}
|
||||
@ -58,7 +57,7 @@ func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keycha
|
||||
return fn(encVault, insecure, corrupt != nil)
|
||||
}
|
||||
|
||||
func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||
vaultDir, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||
@ -72,16 +71,6 @@ func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychai
|
||||
)
|
||||
|
||||
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")
|
||||
insecure = true
|
||||
|
||||
|
||||
@ -267,8 +267,6 @@ func newBridge(
|
||||
|
||||
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
|
||||
|
||||
observabilityService := observability.NewService(ctx, panicHandler)
|
||||
|
||||
bridge := &Bridge{
|
||||
vault: vault,
|
||||
|
||||
@ -308,11 +306,11 @@ func newBridge(
|
||||
lastVersion: lastVersion,
|
||||
|
||||
tasks: tasks,
|
||||
syncService: syncservice.NewService(panicHandler, observabilityService),
|
||||
syncService: syncservice.NewService(reporter, panicHandler),
|
||||
|
||||
unleashService: unleashService,
|
||||
|
||||
observabilityService: observabilityService,
|
||||
observabilityService: observability.NewService(ctx, panicHandler),
|
||||
|
||||
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
||||
}
|
||||
@ -325,7 +323,6 @@ func newBridge(
|
||||
reporter,
|
||||
uidValidityGenerator,
|
||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||
observabilityService,
|
||||
)
|
||||
|
||||
// Check whether username has changed and correct (macOS only)
|
||||
@ -345,7 +342,7 @@ func newBridge(
|
||||
|
||||
bridge.unleashService.Run()
|
||||
|
||||
bridge.observabilityService.Run(bridge)
|
||||
bridge.observabilityService.Run()
|
||||
|
||||
return bridge, nil
|
||||
}
|
||||
@ -636,7 +633,7 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func min(a, b time.Duration) time.Duration { //nolint:predeclared
|
||||
func min(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
@ -713,13 +710,5 @@ func (bridge *Bridge) GetFeatureFlagValue(key string) bool {
|
||||
}
|
||||
|
||||
func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric) {
|
||||
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)
|
||||
bridge.observabilityService.AddMetric(metric)
|
||||
}
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
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,70 +95,3 @@ 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,12 +29,13 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBridge_Report(t *testing.T) {
|
||||
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, _ *bridge.Mocks) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||
defer done()
|
||||
|
||||
@ -55,6 +56,12 @@ func TestBridge_Report(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
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.
|
||||
lineCh := liner.New(conn).Lines(func() error { return nil })
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
@ -116,17 +115,6 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
||||
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
||||
|
||||
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):
|
||||
log.Info("The update was already installed")
|
||||
|
||||
|
||||
@ -355,9 +355,23 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
||||
"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{
|
||||
@ -557,6 +571,7 @@ func (bridge *Bridge) addUserWithVault(
|
||||
isNew,
|
||||
bridge.notificationStore,
|
||||
bridge.unleashService.GetFlagValue,
|
||||
bridge.observabilityService.AddMetric,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user: %w", err)
|
||||
|
||||
@ -38,6 +38,9 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
|
||||
|
||||
case events.UserLoadedCheckResync:
|
||||
user.VerifyResyncAndExecute()
|
||||
|
||||
case events.UncategorizedEventError:
|
||||
bridge.handleUncategorizedErrorEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,3 +67,12 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e
|
||||
user.OnBadEvent(ctx)
|
||||
}, 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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ func (status *ConfigurationStatus) IsPending() bool {
|
||||
}
|
||||
|
||||
func (status *ConfigurationStatus) isPendingSinceMin() int {
|
||||
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 { //nolint:predeclared
|
||||
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 {
|
||||
return min
|
||||
}
|
||||
return 0
|
||||
@ -211,7 +211,7 @@ func (data *ConfigurationStatusData) clickedLinkToString() string {
|
||||
var str = ""
|
||||
var first = true
|
||||
for i := 0; i < 64; i++ {
|
||||
if data.hasLinkClicked(uint64(i)) { //nolint:gosec // disable G115
|
||||
if data.hasLinkClicked(uint64(i)) {
|
||||
if !first {
|
||||
str += ","
|
||||
} else {
|
||||
|
||||
@ -364,9 +364,9 @@ grpc::Status GRPCService::RequestKnowledgeBaseSuggestions(ServerContext*, String
|
||||
|
||||
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
||||
for (qsizetype i = 1; i <= 3; ++i) {
|
||||
suggestions.push_back({
|
||||
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
||||
suggestions.push_back( {
|
||||
.title = QString("Suggested link %1").arg(i),
|
||||
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
||||
});
|
||||
}
|
||||
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
#define HANDLE_EXCEPTION(x) try { x } \
|
||||
catch (Exception const &e) { emit fatalError(e); } \
|
||||
@ -60,19 +59,15 @@ QMLBackend::QMLBackend()
|
||||
/// \param[in] serviceConfig
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||
Log &log = app().log();
|
||||
log.info(QString("Connecting to gRPC service"));
|
||||
|
||||
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();
|
||||
|
||||
|
||||
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
||||
|
||||
users_ = new UserList(this);
|
||||
|
||||
Log &log = app().log();
|
||||
log.info(QString("Connecting to gRPC service"));
|
||||
app().grpc().setLog(&log);
|
||||
this->connectGrpcEvents();
|
||||
|
||||
@ -736,32 +731,6 @@ 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.
|
||||
//****************************************************************************************************************************************************
|
||||
|
||||
@ -102,7 +102,6 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
||||
Q_PROPERTY(QVariantList bugQuestions READ bugQuestions NOTIFY bugQuestionsChanged)
|
||||
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
||||
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.
|
||||
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
||||
@ -142,9 +141,6 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
||||
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
||||
void setDockIconVisible(bool visible); ///< Setter 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.
|
||||
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
||||
@ -179,7 +175,6 @@ 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 usersChanged(UserList *users); ///<Signal for the change of the 'users' 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
|
||||
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@ namespace {
|
||||
|
||||
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 keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing the 'Tray icon visible' setting.
|
||||
|
||||
|
||||
}
|
||||
@ -37,7 +36,7 @@ QString const keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing t
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Settings::Settings()
|
||||
: settings_(QDir(userConfigDir()).absoluteFilePath(settingsFileName), QSettings::Format::IniFormat) {
|
||||
: settings_(QDir(userConfigDir()).absoluteFilePath("bridge-gui.ini"), QSettings::Format::IniFormat) {
|
||||
}
|
||||
|
||||
|
||||
@ -55,17 +54,3 @@ bool Settings::useSoftwareRenderer() const {
|
||||
void Settings::setUseSoftwareRenderer(bool 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,8 +33,6 @@ public: // member functions.
|
||||
|
||||
bool useSoftwareRenderer() const; ///< Get 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.
|
||||
Settings(); ///< Default constructor.
|
||||
|
||||
@ -21,7 +21,6 @@
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
@ -196,7 +195,7 @@ TrayIcon::TrayIcon()
|
||||
}
|
||||
|
||||
this->setIcon();
|
||||
this->setVisible(app().settings().trayIconVisible());
|
||||
this->show();
|
||||
|
||||
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.
|
||||
for (QScreen *screen: QGuiApplication::screens()) {
|
||||
@ -210,6 +209,7 @@ TrayIcon::TrayIcon()
|
||||
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
@ -322,38 +322,21 @@ void TrayIcon::setState(TrayIcon::State state, QString const &stateString, QStri
|
||||
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] message The message.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
||||
ScopedTrayVisibility visible(*this);
|
||||
this->showMessage(title, message, notificationErrorIcon_);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// Used only by user notifications received from the event loop
|
||||
/// \param[in] title The title.
|
||||
/// \param[in] subtitle The subtitle.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::showUserNotification(QString const &title, QString const &subtitle) {
|
||||
ScopedTrayVisibility visible(*this);
|
||||
this->showMessage(title, subtitle, QSystemTrayIcon::NoIcon);
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,8 @@ public: // data members
|
||||
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 showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
||||
|
||||
|
||||
signals:
|
||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
||||
|
||||
|
||||
@ -54,7 +54,6 @@ QString const bridgeGUILock = "bridge-v3-gui.lock"; ///< The file name used for
|
||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
||||
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 orphanInstanceException = "An orphan instance of bridge is already running. Please terminate it and relaunch the application.";
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
@ -318,7 +317,7 @@ int main(int argc, char *argv[]) {
|
||||
QString bridgeExe;
|
||||
if (!cliOptions.attach) {
|
||||
if (isBridgeRunning()) {
|
||||
throw Exception(orphanInstanceException,
|
||||
throw Exception("An orphan instance of bridge is already running. Please terminate it and relaunch the application.",
|
||||
QString(), __FUNCTION__, tailOfLatestBridgeLog(sessionID));
|
||||
}
|
||||
|
||||
@ -414,18 +413,13 @@ int main(int argc, char *argv[]) {
|
||||
lock.unlock();
|
||||
return result;
|
||||
} catch (Exception const &e) {
|
||||
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
||||
QString message = e.qwhat();
|
||||
if (e.showSupportLink()) {
|
||||
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);
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :" << e.detailedWhat() << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,13 +86,11 @@ Item {
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: signIn
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
text: qsTr("Sign in")
|
||||
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
||||
Accessible.name: text
|
||||
|
||||
onClicked: {
|
||||
if (user) {
|
||||
@ -101,13 +99,11 @@ Item {
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: removeAccount
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "/qml/icons/ic-trash.svg"
|
||||
secondary: true
|
||||
visible: root.user ? root.user.state !== EUserState.Locked : false
|
||||
Accessible.name: qsTr("Remove account")
|
||||
|
||||
onClicked: {
|
||||
if (!root.user)
|
||||
@ -122,7 +118,6 @@ Item {
|
||||
height: root._lineThickness
|
||||
}
|
||||
SettingsItem {
|
||||
id: configureEmailClient
|
||||
Layout.fillWidth: true
|
||||
actionText: qsTr("Configure email client")
|
||||
colorScheme: root.colorScheme
|
||||
@ -131,7 +126,6 @@ Item {
|
||||
text: qsTr("Email clients")
|
||||
type: SettingsItem.PrimaryButton
|
||||
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
||||
Accessible.name: actionText
|
||||
|
||||
onClicked: {
|
||||
if (!root.user)
|
||||
@ -149,7 +143,6 @@ Item {
|
||||
text: qsTr("Split addresses")
|
||||
type: SettingsItem.Toggle
|
||||
visible: _connected && root.user.addresses.length > 1
|
||||
Accessible.name: text
|
||||
|
||||
onClicked: {
|
||||
if (!splitMode.checked) {
|
||||
|
||||
@ -28,7 +28,7 @@ Popup {
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
||||
modal: false
|
||||
popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||
topMargin: 37
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import Proton
|
||||
|
||||
Item {
|
||||
|
||||
@ -114,7 +114,6 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||
Accessible.name: qsTr("Help")
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
@ -131,7 +130,6 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
||||
Accessible.name: qsTr("Settings")
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
@ -149,7 +147,6 @@ Item {
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||
Accessible.name: "..."
|
||||
|
||||
onClicked: {
|
||||
dotMenu.open();
|
||||
@ -322,7 +319,6 @@ Item {
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-plus.svg"
|
||||
width: 36
|
||||
Accessible.name: qsTr("Add account")
|
||||
|
||||
onClicked: {
|
||||
root.showLogin("");
|
||||
|
||||
@ -147,19 +147,6 @@ SettingsView {
|
||||
|
||||
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 {
|
||||
id: allMail
|
||||
Layout.fillWidth: true
|
||||
|
||||
@ -21,7 +21,7 @@ T.ApplicationWindow {
|
||||
id: root
|
||||
|
||||
// popup priority based on types
|
||||
enum PopupPriority {
|
||||
enum PopupType {
|
||||
Banner,
|
||||
Dialog
|
||||
}
|
||||
@ -78,10 +78,10 @@ T.ApplicationWindow {
|
||||
topmost = obj;
|
||||
break;
|
||||
}
|
||||
if (topmost && (topmost.popupPriority > obj.popupPriority)) {
|
||||
if (topmost && (topmost.popupType > obj.popupType)) {
|
||||
continue;
|
||||
}
|
||||
if (topmost && (topmost.popupPriority === obj.popupPriority) && (topmost.occurred > obj.occurred)) {
|
||||
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
|
||||
continue;
|
||||
}
|
||||
topmost = obj;
|
||||
|
||||
@ -21,7 +21,7 @@ T.Dialog {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
readonly property int popupPriority: ApplicationWindow.PopupPriority.Dialog
|
||||
readonly property int popupType: ApplicationWindow.PopupType.Dialog
|
||||
property bool shouldShow: false
|
||||
|
||||
function close() {
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColorImage {
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import QtQuick.Layouts
|
||||
|
||||
RowLayout {
|
||||
|
||||
@ -21,7 +21,7 @@ T.Popup {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||
property int popupPriority: ApplicationWindow.PopupPriority.Banner
|
||||
property int popupType: ApplicationWindow.PopupType.Banner
|
||||
property bool shouldShow: false
|
||||
|
||||
function close() {
|
||||
|
||||
@ -172,8 +172,6 @@ FocusScope {
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
Accessible.role: Accessible.Grouping
|
||||
Accessible.name: label.text
|
||||
|
||||
onEditingFinished: {
|
||||
if (!validateOnEditingFinished) {
|
||||
@ -276,7 +274,6 @@ FocusScope {
|
||||
selectionColor: control.palette.highlight
|
||||
topPadding: 8
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
Accessible.name: label.text + qsTr(" edit")
|
||||
|
||||
background: Item {
|
||||
implicitHeight: 36
|
||||
@ -352,7 +349,6 @@ FocusScope {
|
||||
icon.color: control.color
|
||||
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
||||
visible: root.echoMode === TextInput.Password
|
||||
Accessible.name: label.text + qsTr(" show check")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,8 +41,6 @@ Item {
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
Accessible.name: text
|
||||
Accessible.role: Accessible.Grouping
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@ -79,8 +77,6 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
loading: root.loading
|
||||
visible: root.type === SettingsItem.ActionType.Toggle
|
||||
Accessible.role: Accessible.CheckBox
|
||||
Accessible.name: root.Accessible.name + " toggle"
|
||||
|
||||
onClicked: {
|
||||
if (!root.loading)
|
||||
@ -96,8 +92,6 @@ Item {
|
||||
secondary: root.type !== SettingsItem.PrimaryButton
|
||||
text: root.actionText
|
||||
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.Accessible.name + " button"
|
||||
|
||||
onClicked: {
|
||||
if (!root.loading)
|
||||
|
||||
@ -80,7 +80,6 @@ Item {
|
||||
horizontalPadding: 8
|
||||
icon.source: "/qml/icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
Accessible.name: qsTr("Back")
|
||||
|
||||
onClicked: root.back()
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ Item {
|
||||
color: colorScheme.text_weak
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("A series of pop-ups will appear. Follow the instructions to install the profile.")
|
||||
text: qsTr("A system pop-up will appear. Double click on the entry with your email, and click ’Install’ in the dialog that appears.")
|
||||
type: Label.LabelType.Body
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@ -14,7 +14,6 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
import ".."
|
||||
|
||||
Rectangle {
|
||||
|
||||
@ -14,7 +14,6 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@ -38,8 +37,6 @@ Rectangle {
|
||||
}
|
||||
height: 68
|
||||
radius: ProtonStyle.banner_radius
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.text
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@ -33,7 +33,6 @@ Button {
|
||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||
icon.width: _iconSize
|
||||
verticalPadding: 0
|
||||
Accessible.name: qsTr("Help")
|
||||
|
||||
onClicked: {
|
||||
menu.popup(-menu.width + root.width, -menu.height);
|
||||
|
||||
@ -60,7 +60,7 @@ Item {
|
||||
}
|
||||
function showAppleMailAutoconfigProfileInstall() {
|
||||
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 Mail messages.");
|
||||
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.");
|
||||
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);
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.impl
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
@ -214,10 +214,7 @@ func NewUserBadEvent(userID string, errorMessage string) *StreamEvent {
|
||||
}
|
||||
|
||||
func NewUsedBytesChangedEvent(userID string, usedBytes uint64) *StreamEvent {
|
||||
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{
|
||||
UserID: userID,
|
||||
UsedBytes: int64(usedBytes), //nolint:gosec // disable G115
|
||||
}}})
|
||||
return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{UserID: userID, UsedBytes: int64(usedBytes)}}})
|
||||
}
|
||||
|
||||
func newIMAPLoginFailedEvent(username string) *StreamEvent {
|
||||
|
||||
@ -717,8 +717,8 @@ func (s *Service) MailServerSettings(_ context.Context, _ *emptypb.Empty) (*Imap
|
||||
state: protoimpl.MessageState{},
|
||||
sizeCache: 0,
|
||||
unknownFields: nil,
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||
}, nil
|
||||
@ -864,8 +864,8 @@ func base64Decode(in []byte) ([]byte, error) {
|
||||
|
||||
func (s *Service) getMailServerSettings() *ImapSmtpSettings {
|
||||
return &ImapSmtpSettings{
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()), //nolint:gosec // disable G115
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()), //nolint:gosec // disable G115
|
||||
ImapPort: int32(s.bridge.GetIMAPPort()),
|
||||
SmtpPort: int32(s.bridge.GetSMTPPort()),
|
||||
UseSSLForImap: s.bridge.GetIMAPSSL(),
|
||||
UseSSLForSmtp: s.bridge.GetSMTPSSL(),
|
||||
}
|
||||
|
||||
@ -34,11 +34,6 @@ var (
|
||||
// getInitials based on webapp implementation:
|
||||
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
|
||||
func getInitials(fullName string) string {
|
||||
fullName = strings.TrimSpace(fullName)
|
||||
if fullName == "" {
|
||||
return "?"
|
||||
}
|
||||
|
||||
words := strings.Split(
|
||||
reMultiSpaces.ReplaceAllString(fullName, " "),
|
||||
" ",
|
||||
@ -71,8 +66,8 @@ func grpcUserFromInfo(user bridge.UserInfo) *User {
|
||||
AvatarText: getInitials(user.Username),
|
||||
State: userStateToGrpc(user.State),
|
||||
SplitMode: user.AddressMode == vault.SplitMode,
|
||||
UsedBytes: int64(user.UsedSpace), //nolint:gosec // disable G115
|
||||
TotalBytes: int64(user.MaxSpace), //nolint:gosec // disable G115
|
||||
UsedBytes: int64(user.UsedSpace),
|
||||
TotalBytes: int64(user.MaxSpace),
|
||||
Password: user.BridgePass,
|
||||
Addresses: user.Addresses,
|
||||
}
|
||||
|
||||
@ -1,45 +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 (
|
||||
"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"))
|
||||
}
|
||||
@ -99,7 +99,7 @@ func GetArticleIndex(url string) (uint64, error) {
|
||||
if index == -1 {
|
||||
return 0, ErrArticleNotFound
|
||||
}
|
||||
return uint64(index), nil //nolint:gosec // disable G115
|
||||
return uint64(index), nil
|
||||
}
|
||||
|
||||
func simplifyUserInput(input string) string {
|
||||
|
||||
@ -31,7 +31,7 @@ type CoolDownProvider interface {
|
||||
Reset()
|
||||
}
|
||||
|
||||
func jitter(max int) time.Duration { //nolint:predeclared
|
||||
func jitter(max int) time.Duration {
|
||||
return time.Duration(rand.Intn(max)) * time.Second //nolint:gosec
|
||||
}
|
||||
|
||||
|
||||
@ -21,13 +21,18 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"github.com/elastic/go-sysinfo/types"
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const translatedProcDarwin = "sysctl.proc_translated"
|
||||
|
||||
func getHostArch(host types.Host) string {
|
||||
func getHostArch() string {
|
||||
host, err := sysinfo.Host()
|
||||
if err != nil {
|
||||
return "not-detected"
|
||||
}
|
||||
|
||||
// It is not possible to retrieve real hardware architecture once using
|
||||
// rosetta. But it is possible to detect the process translation if
|
||||
// rosetta is used.
|
||||
|
||||
@ -20,10 +20,12 @@
|
||||
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"github.com/elastic/go-sysinfo/types"
|
||||
)
|
||||
import "github.com/elastic/go-sysinfo"
|
||||
|
||||
func getHostArch(host types.Host) string {
|
||||
func getHostArch() string {
|
||||
host, err := sysinfo.Host()
|
||||
if err != nil {
|
||||
return "not-detected"
|
||||
}
|
||||
return host.Info().Architecture
|
||||
}
|
||||
|
||||
@ -30,13 +30,10 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const hostNotDetectedField = "not-detected"
|
||||
|
||||
var skippedFunctions = []string{} //nolint:gochecknoglobals
|
||||
|
||||
func init() { //nolint:gochecknoinits
|
||||
@ -73,50 +70,17 @@ 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 {
|
||||
appName string
|
||||
appVersion string
|
||||
identifier Identifier
|
||||
hostInfo hostInfoData
|
||||
hostArch string
|
||||
}
|
||||
|
||||
type Identifier interface {
|
||||
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 {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
@ -136,7 +100,7 @@ func NewReporter(appName string, identifier Identifier) *Reporter {
|
||||
appName: appName,
|
||||
appVersion: constants.Revision,
|
||||
identifier: identifier,
|
||||
hostInfo: getHostInfo(),
|
||||
hostArch: getHostArch(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,14 +152,11 @@ func (r *Reporter) scopedReport(context map[string]interface{}, doReport func())
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"OS": runtime.GOOS,
|
||||
"Client": r.appName,
|
||||
"Version": r.appVersion,
|
||||
"UserAgent": r.identifier.GetUserAgent(),
|
||||
"HostArch": r.hostInfo.hostArch,
|
||||
"HostName": r.hostInfo.hostName,
|
||||
"HostVersion": r.hostInfo.hostVersion,
|
||||
"HostBuild": r.hostInfo.hostBuild,
|
||||
"OS": runtime.GOOS,
|
||||
"Client": r.appName,
|
||||
"Version": r.appVersion,
|
||||
"UserAgent": r.identifier.GetUserAgent(),
|
||||
"HostArch": r.hostArch,
|
||||
}
|
||||
|
||||
sentry.WithScope(func(scope *sentry.Scope) {
|
||||
|
||||
@ -70,8 +70,6 @@ type Connector struct {
|
||||
syncState *SyncState
|
||||
}
|
||||
|
||||
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
||||
|
||||
func NewConnector(
|
||||
addrID string,
|
||||
apiClient APIClient,
|
||||
@ -110,7 +108,6 @@ func NewConnector(
|
||||
labels: labels,
|
||||
addressMode: addressMode,
|
||||
log: logrus.WithFields(logrus.Fields{
|
||||
"pkg": "imapservice",
|
||||
"gluon-connector": addressMode,
|
||||
"addr-id": addrID,
|
||||
"user-id": userID,
|
||||
@ -680,18 +677,22 @@ func (s *Connector) importMessage(
|
||||
) (imap.Message, []byte, error) {
|
||||
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))
|
||||
if err2 != nil {
|
||||
return imap.Message{}, nil, fmt.Errorf("failed to parse literal: %w", err2)
|
||||
}
|
||||
|
||||
isDraft := slices.Contains(labelIDs, proton.DraftsLabel)
|
||||
addr, err := s.getImportAddress(p, isDraft)
|
||||
if err != nil {
|
||||
return imap.Message{}, nil, err
|
||||
}
|
||||
|
||||
if err := s.identityState.WithAddrKR(addr.ID, func(_, addrKR *crypto.KeyRing) error {
|
||||
s.reportGODT3185(isDraft, addr.Email, p, s.addressMode == usertypes.AddressModeCombined)
|
||||
|
||||
if err := s.identityState.WithAddrKR(s.addrID, func(_, addrKR *crypto.KeyRing) error {
|
||||
primaryKey, errKey := addrKR.FirstKey()
|
||||
if errKey != nil {
|
||||
return fmt.Errorf("failed to get primary key for import: %w", errKey)
|
||||
@ -718,7 +719,7 @@ func (s *Connector) importMessage(
|
||||
}
|
||||
str, err := s.client.ImportMessages(ctx, primaryKey, 1, 1, []proton.ImportReq{{
|
||||
Metadata: proton.ImportMetadata{
|
||||
AddressID: addr.ID,
|
||||
AddressID: s.addrID,
|
||||
LabelIDs: labelIDs,
|
||||
Unread: proton.Bool(unread),
|
||||
Flags: flags,
|
||||
@ -877,74 +878,79 @@ func equalAddresses(a, b string) bool {
|
||||
return strings.EqualFold(stripPlusAlias(a), stripPlusAlias(b))
|
||||
}
|
||||
|
||||
func (s *Connector) getImportAddress(p *parser.Parser, isDraft bool) (proton.Address, error) {
|
||||
// addr is primary for combined mode or active for split mode
|
||||
address, ok := s.identityState.GetAddress(s.addrID)
|
||||
if !ok {
|
||||
return proton.Address{}, errors.New("could not find account address")
|
||||
func (s *Connector) reportGODT3185(isDraft bool, defaultAddr string, p *parser.Parser, isCombinedMode bool) {
|
||||
reportAction := "draft"
|
||||
if !isDraft {
|
||||
reportAction = "import"
|
||||
}
|
||||
|
||||
inCombinedMode := s.addressMode == usertypes.AddressModeCombined
|
||||
if !inCombinedMode {
|
||||
return address, nil
|
||||
reportMode := "combined"
|
||||
if !isCombinedMode {
|
||||
reportMode = "split"
|
||||
}
|
||||
|
||||
senderAddr, err := s.getSenderProtonAddress(p)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errNoSenderAddressMatch) {
|
||||
s.log.WithError(err).Warn("Could not get import address")
|
||||
senderAddr := ""
|
||||
if p != nil && p.Root() != nil && p.Root().Header.Len() != 0 {
|
||||
addrField := p.Root().Header.Get("From")
|
||||
if addrField == "" {
|
||||
addrField = p.Root().Header.Get("Sender")
|
||||
}
|
||||
if addrField != "" {
|
||||
sender, err := rfc5322.ParseAddressList(addrField)
|
||||
if err == nil && len(sender) > 0 {
|
||||
senderAddr = sender[0].Address
|
||||
} else {
|
||||
s.log.WithError(err).Warn("Invalid sender address in reporter")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if equalAddresses(defaultAddr, senderAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
isDisabled := false
|
||||
isUserAddress := false
|
||||
for _, a := range s.identityState.GetAddresses() {
|
||||
if !equalAddresses(a.Email, senderAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We did not find a match, so we use the default address.
|
||||
return address, nil
|
||||
isUserAddress = true
|
||||
isDisabled = !bool(a.Send) || (a.Status != proton.AddressStatusEnabled)
|
||||
break
|
||||
}
|
||||
|
||||
if senderAddr.ID == address.ID {
|
||||
return address, nil
|
||||
if !isUserAddress && senderAddr != "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
reportResult := "using sender address"
|
||||
|
||||
isSenderAddressDisabled := (!bool(senderAddr.Send)) || (senderAddr.Status != proton.AddressStatusEnabled)
|
||||
if isDraft && isSenderAddressDisabled {
|
||||
return address, nil
|
||||
if !isCombinedMode {
|
||||
reportResult = "error address not match"
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
addrField := p.Root().Header.Get("From")
|
||||
if len(addrField) == 0 {
|
||||
addrField = p.Root().Header.Get("Sender")
|
||||
}
|
||||
if len(addrField) == 0 {
|
||||
return proton.Address{}, errors.New("no sender found in message headers")
|
||||
}
|
||||
|
||||
sender, err := rfc5322.ParseAddressList(addrField)
|
||||
if (err != nil) || (len(sender) == 0) {
|
||||
return proton.Address{}, fmt.Errorf("invalid sender address in message: %w", err)
|
||||
}
|
||||
|
||||
addrStr := sender[0].Address
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return addressList[index], nil
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +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 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")
|
||||
}
|
||||
@ -1,51 +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 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,7 +30,6 @@ import (
|
||||
"github.com/ProtonMail/gluon/watcher"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"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/sendrecorder"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||
@ -97,8 +96,6 @@ type Service struct {
|
||||
syncConfigPath string
|
||||
lastHandledEventID string
|
||||
isSyncing atomic.Bool
|
||||
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@ -119,7 +116,6 @@ func NewService(
|
||||
syncConfigDir string,
|
||||
maxSyncMemory uint64,
|
||||
showAllMail bool,
|
||||
observabilitySender observability.Sender,
|
||||
) *Service {
|
||||
subscriberName := fmt.Sprintf("imap-%v", identityState.User.ID)
|
||||
|
||||
@ -164,8 +160,6 @@ func NewService(
|
||||
syncMessageBuilder: syncMessageBuilder,
|
||||
syncReporter: syncReporter,
|
||||
syncConfigPath: GetSyncConfigPath(syncConfigDir, identityState.User.ID),
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -26,11 +26,11 @@ import (
|
||||
|
||||
"github.com/ProtonMail/gluon"
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal"
|
||||
"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/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
@ -46,7 +46,7 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
||||
case proton.EventCreate:
|
||||
updates, err := onMessageCreated(logging.WithLogrusField(ctx, "action", "create message"), s, event.Message, false)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureCreateMessageMetric())
|
||||
reportError(s.reporter, s.log, "Failed to apply create message event", 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,
|
||||
)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureUpdateMetric())
|
||||
reportError(s.reporter, s.log, "Failed to apply update draft message event", 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,
|
||||
)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailureUpdateMetric())
|
||||
reportError(s.reporter, s.log, "Failed to apply update message event", err)
|
||||
return fmt.Errorf("failed to handle update message event: %w", err)
|
||||
}
|
||||
|
||||
@ -113,7 +113,6 @@ func (s *Service) HandleMessageEvents(ctx context.Context, events []proton.Messa
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -159,7 +158,8 @@ func onMessageCreated(
|
||||
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
||||
}
|
||||
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailedToBuildMessage())
|
||||
reportErrorAndMessageID(s.reporter, s.log, "Failed to build message (event create)", res.err, res.messageID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -221,7 +221,8 @@ func onMessageUpdateDraftOrSent(ctx context.Context, s *Service, event proton.Me
|
||||
s.log.WithError(err).Error("Failed to add failed message ID to vault")
|
||||
}
|
||||
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventFailedToBuildDraft())
|
||||
reportErrorAndMessageID(s.reporter, s.log, "Failed to build draft message (event update)", res.err, res.messageID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -298,6 +299,24 @@ func onMessageDeleted(ctx context.Context, s *Service, event proton.MessageEvent
|
||||
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
|
||||
// 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.
|
||||
@ -322,7 +341,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)
|
||||
s.observabilitySender.AddDistinctMetrics(observability.EventLoopError, obsMetrics.GenerateMessageEventUpdateChannelDoesNotExist())
|
||||
_ = s.reporter.ReportMessage("Message Update channel does not exist")
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -23,8 +23,6 @@ import (
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -57,7 +55,7 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
s.service.observabilitySender.AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateSyncFailureCreateMessageEventMetric())
|
||||
reportError(s.service.reporter, s.service.log, "Failed to apply create message event", err)
|
||||
return fmt.Errorf("failed to handle create message event: %w", err)
|
||||
}
|
||||
|
||||
@ -73,7 +71,6 @@ func (s syncMessageEventHandler) HandleMessageEvents(ctx context.Context, events
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
default:
|
||||
|
||||
@ -36,7 +36,6 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/files"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -78,7 +77,6 @@ func newIMAPServer(
|
||||
tasks *async.Group,
|
||||
uidValidityGenerator imap.UIDValidityGenerator,
|
||||
panicHandler async.PanicHandler,
|
||||
observabilitySender observability.Sender,
|
||||
) (*gluon.Server, error) {
|
||||
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
|
||||
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
|
||||
@ -123,7 +121,6 @@ func newIMAPServer(
|
||||
gluon.WithReporter(reporter),
|
||||
gluon.WithUIDValidityGenerator(uidValidityGenerator),
|
||||
gluon.WithPanicHandler(panicHandler),
|
||||
gluon.WithObservabilitySender(observability.NewAdapter(observabilitySender), int(observability.GluonImapError), int(observability.GluonMessageError), int(observability.GluonOtherError)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -156,9 +153,9 @@ func newIMAPServer(
|
||||
|
||||
func getGluonVersionInfo(version *semver.Version) gluon.Option {
|
||||
return gluon.WithVersionInfo(
|
||||
int(version.Major()), //nolint:gosec // disable G115
|
||||
int(version.Minor()), //nolint:gosec // disable G115
|
||||
int(version.Patch()), //nolint:gosec // disable G115
|
||||
int(version.Major()),
|
||||
int(version.Minor()),
|
||||
int(version.Patch()),
|
||||
constants.FullAppName,
|
||||
"TODO",
|
||||
"TODO",
|
||||
|
||||
@ -31,7 +31,6 @@ import (
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"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/observability"
|
||||
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/pkg/cpc"
|
||||
@ -61,8 +60,6 @@ type Service struct {
|
||||
|
||||
uidValidityGenerator imap.UIDValidityGenerator
|
||||
telemetry Telemetry
|
||||
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@ -74,7 +71,6 @@ func NewService(
|
||||
reporter reporter.Reporter,
|
||||
uidValidityGenerator imap.UIDValidityGenerator,
|
||||
telemetry Telemetry,
|
||||
observabilitySender observability.Sender,
|
||||
) *Service {
|
||||
return &Service{
|
||||
requests: cpc.NewCPC(),
|
||||
@ -89,8 +85,6 @@ func NewService(
|
||||
tasks: async.NewGroup(ctx, panicHandler),
|
||||
uidValidityGenerator: uidValidityGenerator,
|
||||
telemetry: telemetry,
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +449,6 @@ func (sm *Service) createIMAPServer(ctx context.Context) (*gluon.Server, error)
|
||||
sm.tasks,
|
||||
sm.uidValidityGenerator,
|
||||
sm.panicHandler,
|
||||
sm.observabilitySender,
|
||||
)
|
||||
if err == nil {
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerCreated{})
|
||||
|
||||
@ -44,16 +44,15 @@ type Service struct {
|
||||
|
||||
store *Store
|
||||
|
||||
getFlagValueFn unleash.GetFlagValueFn
|
||||
|
||||
observabilitySender observability.Sender
|
||||
getFlagValueFn unleash.GetFlagValueFn
|
||||
pushObservabilityMetricFn observability.PushObsMetricFn
|
||||
}
|
||||
|
||||
const bitfieldRegexPattern = `^\\\d+`
|
||||
const disableNotificationsKillSwitch = "InboxBridgeEventLoopNotificationDisabled"
|
||||
|
||||
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
|
||||
getFlagFn unleash.GetFlagValueFn, observabilitySender observability.Sender) *Service {
|
||||
getFlagFn unleash.GetFlagValueFn, pushMetricFn observability.PushObsMetricFn) *Service {
|
||||
return &Service{
|
||||
userID: userID,
|
||||
|
||||
@ -69,8 +68,8 @@ func NewService(userID string, service userevents.Subscribable, eventPublisher e
|
||||
|
||||
store: store,
|
||||
|
||||
getFlagValueFn: getFlagFn,
|
||||
observabilitySender: observabilitySender,
|
||||
getFlagValueFn: getFlagFn,
|
||||
pushObservabilityMetricFn: pushMetricFn,
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +110,7 @@ func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEven
|
||||
s.log.Debug("Handling notification events")
|
||||
|
||||
// Publish observability metrics that we've received notifications
|
||||
s.observabilitySender.AddMetrics(GenerateReceivedMetric(len(notificationEvents)))
|
||||
s.pushObservabilityMetricFn(GenerateReceivedMetric(len(notificationEvents)))
|
||||
|
||||
for _, event := range notificationEvents {
|
||||
ctx = logging.WithLogrusField(ctx, "notificationID", event.ID)
|
||||
@ -134,7 +133,7 @@ func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEven
|
||||
Subtitle: event.Payload.Subtitle, Body: event.Payload.Body})
|
||||
|
||||
// Publish observability metric that we've successfully processed notifications
|
||||
s.observabilitySender.AddMetrics(GenerateProcessedMetric(1))
|
||||
s.pushObservabilityMetricFn(GenerateProcessedMetric(1))
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@ -109,7 +109,7 @@ func (s *Store) readCache() {
|
||||
|
||||
file, err := os.Open(s.cacheFilepath)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Info("Unable to open cache file")
|
||||
s.log.WithError(err).Error("Unable to open cache file")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -1,93 +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 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...)
|
||||
}
|
||||
}
|
||||
@ -1,58 +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 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)
|
||||
}
|
||||
}
|
||||
@ -1,55 +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 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
|
||||
}
|
||||
@ -1,170 +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 observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
)
|
||||
|
||||
var updateInterval = time.Minute * 5 //nolint:gochecknoglobals
|
||||
|
||||
type observabilitySender interface {
|
||||
addMetricsIfClients(metric ...proton.ObservabilityMetric)
|
||||
}
|
||||
|
||||
// distinctionUtility - used to discern whether X number of events stem from Y number of users.
|
||||
type distinctionUtility struct {
|
||||
ctx context.Context
|
||||
|
||||
panicHandler async.PanicHandler
|
||||
|
||||
lastSentMap map[DistinctionErrorTypeEnum]time.Time // Ensures we don't step over the limit of one user update every 5 mins.
|
||||
|
||||
observabilitySender observabilitySender
|
||||
settingsGetter settingsGetter
|
||||
|
||||
userPlanUnsafe string
|
||||
userPlanLock sync.Mutex
|
||||
|
||||
heartbeatData heartbeatData
|
||||
heartbeatDataLock sync.Mutex
|
||||
heartbeatTicker *time.Ticker
|
||||
}
|
||||
|
||||
func newDistinctionUtility(ctx context.Context, panicHandler async.PanicHandler, observabilitySender observabilitySender) *distinctionUtility {
|
||||
distinctionUtility := &distinctionUtility{
|
||||
ctx: ctx,
|
||||
|
||||
panicHandler: panicHandler,
|
||||
|
||||
lastSentMap: createLastSentMap(),
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
|
||||
userPlanUnsafe: planUnknown,
|
||||
|
||||
heartbeatData: heartbeatData{},
|
||||
heartbeatTicker: time.NewTicker(updateInterval),
|
||||
}
|
||||
|
||||
return distinctionUtility
|
||||
}
|
||||
|
||||
// sendMetricsWithGuard - schedules the metrics to be sent only if there are authenticated clients.
|
||||
func (d *distinctionUtility) sendMetricsWithGuard(metrics ...proton.ObservabilityMetric) {
|
||||
if d.observabilitySender == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.observabilitySender.addMetricsIfClients(metrics...)
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) setSettingsGetter(getter settingsGetter) {
|
||||
d.settingsGetter = getter
|
||||
}
|
||||
|
||||
// checkAndUpdateLastSentMap - checks whether we have sent a relevant user update metric
|
||||
// within the last 5 minutes.
|
||||
func (d *distinctionUtility) checkAndUpdateLastSentMap(key DistinctionErrorTypeEnum) bool {
|
||||
curTime := time.Now()
|
||||
val, ok := d.lastSentMap[key]
|
||||
if !ok {
|
||||
d.lastSentMap[key] = curTime
|
||||
return true
|
||||
}
|
||||
|
||||
if val.Add(updateInterval).Before(curTime) {
|
||||
d.lastSentMap[key] = curTime
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// generateUserMetric creates the relevant user update metric based on its type
|
||||
// and the relevant settings. In the future this will need to be expanded to support multiple
|
||||
// versions of the metric if we ever decide to change them.
|
||||
func (d *distinctionUtility) generateUserMetric(
|
||||
metricType DistinctionErrorTypeEnum,
|
||||
) proton.ObservabilityMetric {
|
||||
schemaName, ok := errorSchemaMap[metricType]
|
||||
if !ok {
|
||||
return proton.ObservabilityMetric{}
|
||||
}
|
||||
|
||||
return generateUserMetric(schemaName, d.getUserPlanSafe(),
|
||||
d.getEmailClientUserAgent(),
|
||||
getEnabled(d.getProxyAllowed()),
|
||||
getEnabled(d.getBetaAccessEnabled()),
|
||||
)
|
||||
}
|
||||
|
||||
func generateUserMetric(schemaName, plan, mailClient, dohEnabled, betaAccess string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: schemaName,
|
||||
Version: 1,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"plan": plan,
|
||||
"mailClient": mailClient,
|
||||
"dohEnabled": dohEnabled,
|
||||
"betaAccessEnabled": betaAccess,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) generateDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) []proton.ObservabilityMetric {
|
||||
d.updateHeartbeatData(errType)
|
||||
|
||||
if d.checkAndUpdateLastSentMap(errType) {
|
||||
metrics = append(metrics, d.generateUserMetric(errType))
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) getEmailClientUserAgent() string {
|
||||
ua := ""
|
||||
if d.settingsGetter != nil {
|
||||
ua = d.settingsGetter.GetCurrentUserAgent()
|
||||
}
|
||||
|
||||
return matchUserAgent(ua)
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) getBetaAccessEnabled() bool {
|
||||
if d.settingsGetter == nil {
|
||||
return false
|
||||
}
|
||||
return d.settingsGetter.GetUpdateChannel() == updater.EarlyChannel
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) getProxyAllowed() bool {
|
||||
if d.settingsGetter == nil {
|
||||
return false
|
||||
}
|
||||
return d.settingsGetter.GetProxyAllowed()
|
||||
}
|
||||
@ -1,127 +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 observability
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const genericHeartbeatSchemaName = "bridge_generic_user_heartbeat_total"
|
||||
const genericHeartbeatVersion = 2
|
||||
|
||||
type heartbeatData struct {
|
||||
receivedSyncError bool
|
||||
receivedEventLoopError bool
|
||||
receivedOtherError bool
|
||||
receivedGluonError bool
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) resetHeartbeatData() {
|
||||
d.heartbeatData.receivedSyncError = false
|
||||
d.heartbeatData.receivedOtherError = false
|
||||
d.heartbeatData.receivedEventLoopError = false
|
||||
d.heartbeatData.receivedGluonError = false
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) updateHeartbeatData(errType DistinctionErrorTypeEnum) {
|
||||
d.withUpdateHeartbeatDataLock(func() {
|
||||
//nolint:exhaustive
|
||||
switch errType {
|
||||
case SyncError:
|
||||
d.heartbeatData.receivedSyncError = true
|
||||
case EventLoopError:
|
||||
d.heartbeatData.receivedEventLoopError = true
|
||||
case GluonMessageError, GluonImapError, GluonOtherError:
|
||||
d.heartbeatData.receivedGluonError = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) runHeartbeat() {
|
||||
go func() {
|
||||
defer async.HandlePanic(d.panicHandler)
|
||||
defer d.heartbeatTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
return
|
||||
case <-d.heartbeatTicker.C:
|
||||
d.sendHeartbeat()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) withUpdateHeartbeatDataLock(fn func()) {
|
||||
d.heartbeatDataLock.Lock()
|
||||
defer d.heartbeatDataLock.Unlock()
|
||||
fn()
|
||||
}
|
||||
|
||||
// sendHeartbeat - will only send a heartbeat if there is an authenticated client
|
||||
// otherwise we might end up polluting the cache and therefore our metrics.
|
||||
func (d *distinctionUtility) sendHeartbeat() {
|
||||
d.withUpdateHeartbeatDataLock(func() {
|
||||
d.sendMetricsWithGuard(d.generateHeartbeatUserMetric())
|
||||
d.resetHeartbeatData()
|
||||
})
|
||||
}
|
||||
|
||||
func formatBool(value bool) string {
|
||||
return fmt.Sprintf("%t", value)
|
||||
}
|
||||
|
||||
// generateHeartbeatUserMetric creates the heartbeat user metric and includes the relevant data.
|
||||
func (d *distinctionUtility) generateHeartbeatUserMetric() proton.ObservabilityMetric {
|
||||
return generateHeartbeatMetric(
|
||||
d.getUserPlanSafe(),
|
||||
d.getEmailClientUserAgent(),
|
||||
getEnabled(d.settingsGetter.GetProxyAllowed()),
|
||||
getEnabled(d.getBetaAccessEnabled()),
|
||||
formatBool(d.heartbeatData.receivedOtherError),
|
||||
formatBool(d.heartbeatData.receivedSyncError),
|
||||
formatBool(d.heartbeatData.receivedEventLoopError),
|
||||
formatBool(d.heartbeatData.receivedGluonError),
|
||||
)
|
||||
}
|
||||
|
||||
func generateHeartbeatMetric(plan, mailClient, dohEnabled, betaAccess, otherError, syncError, eventLoopError, gluonError string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: genericHeartbeatSchemaName,
|
||||
Version: genericHeartbeatVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"plan": plan,
|
||||
"mailClient": mailClient,
|
||||
"dohEnabled": dohEnabled,
|
||||
"betaAccessEnabled": betaAccess,
|
||||
"receivedOtherError": otherError,
|
||||
"receivedSyncError": syncError,
|
||||
"receivedEventLoopError": eventLoopError,
|
||||
"receivedGluonError": gluonError,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,121 +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 observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const (
|
||||
planUnknown = "unknown"
|
||||
planOther = "other"
|
||||
planBusiness = "business"
|
||||
planIndividual = "individual"
|
||||
planGroup = "group"
|
||||
)
|
||||
|
||||
var planHierarchy = map[string]int{ //nolint:gochecknoglobals
|
||||
planBusiness: 4,
|
||||
planGroup: 3,
|
||||
planIndividual: 2,
|
||||
planOther: 1,
|
||||
planUnknown: 0,
|
||||
}
|
||||
|
||||
type planGetter interface {
|
||||
GetOrganizationData(ctx context.Context) (proton.OrganizationResponse, error)
|
||||
}
|
||||
|
||||
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 planUnknown
|
||||
}
|
||||
switch strings.TrimSpace(strings.ToLower(planName)) {
|
||||
case "mail2022":
|
||||
return planIndividual
|
||||
case "bundle2022":
|
||||
return planIndividual
|
||||
case "family2022":
|
||||
return planGroup
|
||||
case "visionary2022":
|
||||
return planGroup
|
||||
case "mailpro2022":
|
||||
return planBusiness
|
||||
case "planbiz2024":
|
||||
return planBusiness
|
||||
case "bundlepro2022":
|
||||
return planBusiness
|
||||
case "bundlepro2024":
|
||||
return planBusiness
|
||||
case "duo2024":
|
||||
return planGroup
|
||||
|
||||
default:
|
||||
return planOther
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) setUserPlan(planName string) {
|
||||
if planName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
d.userPlanLock.Lock()
|
||||
defer d.userPlanLock.Unlock()
|
||||
|
||||
userPlanMapped := mapUserPlan(planName)
|
||||
if isHigherPriority(d.userPlanUnsafe, userPlanMapped) {
|
||||
d.userPlanUnsafe = userPlanMapped
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) registerUserPlan(ctx context.Context, getter planGetter, panicHandler async.PanicHandler) {
|
||||
go func() {
|
||||
defer async.HandlePanic(panicHandler)
|
||||
|
||||
orgRes, err := getter.GetOrganizationData(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.setUserPlan(orgRes.Organization.PlanName)
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) getUserPlanSafe() string {
|
||||
d.userPlanLock.Lock()
|
||||
defer d.userPlanLock.Unlock()
|
||||
return d.userPlanUnsafe
|
||||
}
|
||||
@ -36,18 +36,13 @@ const (
|
||||
maxBatchSize = 1000
|
||||
)
|
||||
|
||||
type PushObsMetricFn func(metric proton.ObservabilityMetric)
|
||||
|
||||
type client struct {
|
||||
isTelemetryEnabled func(context.Context) bool
|
||||
sendMetrics func(context.Context, proton.ObservabilityBatch) error
|
||||
}
|
||||
|
||||
// Sender - interface maps to the observability service methods,
|
||||
// so we can easily pass them down to relevant components.
|
||||
type Sender interface {
|
||||
AddMetrics(metrics ...proton.ObservabilityMetric)
|
||||
AddDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
@ -67,8 +62,6 @@ type Service struct {
|
||||
|
||||
userClientStore map[string]*client
|
||||
userClientStoreLock sync.Mutex
|
||||
|
||||
distinctionUtility *distinctionUtility
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, panicHandler async.PanicHandler) *Service {
|
||||
@ -92,19 +85,11 @@ func NewService(ctx context.Context, panicHandler async.PanicHandler) *Service {
|
||||
userClientStore: make(map[string]*client),
|
||||
}
|
||||
|
||||
service.distinctionUtility = newDistinctionUtility(ctx, panicHandler, service)
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
// Run starts the observability service goroutine.
|
||||
// The function also sets some utility functions to a helper struct aimed at differentiating the amount of users sending metric updates.
|
||||
func (s *Service) Run(settingsGetter settingsGetter) {
|
||||
func (s *Service) Run() {
|
||||
s.log.Info("Starting service")
|
||||
|
||||
s.distinctionUtility.setSettingsGetter(settingsGetter)
|
||||
s.distinctionUtility.runHeartbeat()
|
||||
|
||||
go func() {
|
||||
s.start()
|
||||
}()
|
||||
@ -215,7 +200,7 @@ func (s *Service) scheduleDispatch() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Service) addMetrics(metric ...proton.ObservabilityMetric) {
|
||||
func (s *Service) AddMetric(metric proton.ObservabilityMetric) {
|
||||
s.withMetricStoreLock(func() {
|
||||
metricStoreLength := len(s.metricStore)
|
||||
if metricStoreLength >= maxStorageSize {
|
||||
@ -224,32 +209,12 @@ func (s *Service) addMetrics(metric ...proton.ObservabilityMetric) {
|
||||
dropCount := metricStoreLength - maxStorageSize + 1
|
||||
s.metricStore = s.metricStore[dropCount:]
|
||||
}
|
||||
s.metricStore = append(s.metricStore, metric...)
|
||||
s.metricStore = append(s.metricStore, metric)
|
||||
})
|
||||
|
||||
// If the context has been cancelled i.e. the service has been stopped then we should be free to exit.
|
||||
if s.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.sendSignal(s.signalDataArrived)
|
||||
}
|
||||
|
||||
// addMetricsIfClients - will append a metric only if there are authenticated clients
|
||||
// via which we can reach the endpoint.
|
||||
func (s *Service) addMetricsIfClients(metric ...proton.ObservabilityMetric) {
|
||||
hasClients := false
|
||||
s.withUserClientStoreLock(func() {
|
||||
hasClients = len(s.userClientStore) > 0
|
||||
})
|
||||
|
||||
if !hasClients {
|
||||
return
|
||||
}
|
||||
|
||||
s.addMetrics(metric...)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterUserClient(userID string, protonClient *proton.Client, telemetryService *telemetry.Service) {
|
||||
s.log.Info("Registering user client, ID:", userID)
|
||||
|
||||
@ -260,8 +225,6 @@ func (s *Service) RegisterUserClient(userID string, protonClient *proton.Client,
|
||||
}
|
||||
})
|
||||
|
||||
s.distinctionUtility.registerUserPlan(s.ctx, protonClient, s.panicHandler)
|
||||
|
||||
// There may be a case where we already have metric updates stored, so try to flush;
|
||||
s.sendSignal(s.signalDataArrived)
|
||||
}
|
||||
@ -316,25 +279,3 @@ func (s *Service) sendSignal(channel chan struct{}) {
|
||||
func ModifyThrottlePeriod(duration time.Duration) {
|
||||
throttleDuration = duration
|
||||
}
|
||||
|
||||
func (s *Service) AddMetrics(metrics ...proton.ObservabilityMetric) {
|
||||
s.addMetrics(metrics...)
|
||||
}
|
||||
|
||||
// AddDistinctMetrics - sends an additional metric related to the user, so we can determine
|
||||
// what number of events come from what number of users.
|
||||
// As the binning interval is what allows us to do this we
|
||||
// should not send these if there are no logged-in users at that moment.
|
||||
func (s *Service) AddDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||
metrics = s.distinctionUtility.generateDistinctMetrics(errType, metrics...)
|
||||
s.addMetricsIfClients(metrics...)
|
||||
}
|
||||
|
||||
// ModifyHeartbeatInterval - should only be used for testing. Resets the heartbeat ticker.
|
||||
func (s *Service) ModifyHeartbeatInterval(duration time.Duration) {
|
||||
s.distinctionUtility.heartbeatTicker.Reset(duration)
|
||||
}
|
||||
|
||||
func ModifyUserMetricInterval(duration time.Duration) {
|
||||
updateInterval = duration
|
||||
}
|
||||
|
||||
@ -1,127 +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 observability
|
||||
|
||||
import (
|
||||
gluonMetrics "github.com/ProtonMail/gluon/observability/metrics"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
func GenerateAllUsedDistinctionMetricPermutations() []proton.ObservabilityMetric {
|
||||
planValues := []string{
|
||||
planUnknown,
|
||||
planOther,
|
||||
planBusiness,
|
||||
planIndividual,
|
||||
planGroup}
|
||||
mailClientValues := []string{
|
||||
emailAgentAppleMail,
|
||||
emailAgentOutlook,
|
||||
emailAgentThunderbird,
|
||||
emailAgentOther,
|
||||
emailAgentUnknown,
|
||||
}
|
||||
enabledValues := []string{
|
||||
getEnabled(true), getEnabled(false),
|
||||
}
|
||||
|
||||
var metrics []proton.ObservabilityMetric
|
||||
|
||||
for _, schemaName := range errorSchemaMap {
|
||||
for _, plan := range planValues {
|
||||
for _, mailClient := range mailClientValues {
|
||||
for _, dohEnabled := range enabledValues {
|
||||
for _, betaAccess := range enabledValues {
|
||||
metrics = append(metrics, generateUserMetric(schemaName, plan, mailClient, dohEnabled, betaAccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func GenerateAllHeartbeatMetricPermutations() []proton.ObservabilityMetric {
|
||||
planValues := []string{
|
||||
planUnknown,
|
||||
planOther,
|
||||
planBusiness,
|
||||
planIndividual,
|
||||
planGroup}
|
||||
mailClientValues := []string{
|
||||
emailAgentAppleMail,
|
||||
emailAgentOutlook,
|
||||
emailAgentThunderbird,
|
||||
emailAgentOther,
|
||||
emailAgentUnknown,
|
||||
}
|
||||
enabledValues := []string{
|
||||
getEnabled(true), getEnabled(false),
|
||||
}
|
||||
|
||||
trueFalseValues := []string{
|
||||
"true", "false",
|
||||
}
|
||||
|
||||
var metrics []proton.ObservabilityMetric
|
||||
for _, plan := range planValues {
|
||||
for _, mailClient := range mailClientValues {
|
||||
for _, dohEnabled := range enabledValues {
|
||||
for _, betaAccess := range enabledValues {
|
||||
for _, receivedOtherError := range trueFalseValues {
|
||||
for _, receivedSyncError := range trueFalseValues {
|
||||
for _, receivedEventLoopError := range trueFalseValues {
|
||||
for _, receivedGluonError := range trueFalseValues {
|
||||
metrics = append(metrics,
|
||||
generateHeartbeatMetric(plan,
|
||||
mailClient,
|
||||
dohEnabled,
|
||||
betaAccess,
|
||||
receivedOtherError,
|
||||
receivedSyncError,
|
||||
receivedEventLoopError,
|
||||
receivedGluonError,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func GenerateAllGluonMetrics() []map[string]interface{} {
|
||||
var metrics []map[string]interface{}
|
||||
metrics = append(metrics,
|
||||
gluonMetrics.GenerateFailedParseIMAPCommandMetric(),
|
||||
gluonMetrics.GenerateFailedToCreateMailbox(),
|
||||
gluonMetrics.GenerateFailedToDeleteMailboxMetric(),
|
||||
gluonMetrics.GenerateFailedToCopyMessagesMetric(),
|
||||
gluonMetrics.GenerateFailedToMoveMessagesFromMailboxMetric(),
|
||||
gluonMetrics.GenerateFailedToRemoveDeletedMessagesMetric(),
|
||||
gluonMetrics.GenerateFailedToCommitDatabaseTransactionMetric(),
|
||||
gluonMetrics.GenerateAppendToDraftsMustNotReturnExistingRemoteID(),
|
||||
gluonMetrics.GenerateDatabaseMigrationFailed(),
|
||||
gluonMetrics.GenerateFailedToStoreFlagsOnMessages(),
|
||||
)
|
||||
return metrics
|
||||
}
|
||||
@ -1,68 +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 observability
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
)
|
||||
|
||||
// settingsGetter - interface that maps to bridge object methods such that we
|
||||
// can pass the whole object instead of individual function callbacks.
|
||||
type settingsGetter interface {
|
||||
GetCurrentUserAgent() string
|
||||
GetProxyAllowed() bool
|
||||
GetUpdateChannel() updater.Channel
|
||||
}
|
||||
|
||||
// User agent mapping.
|
||||
const (
|
||||
emailAgentAppleMail = "apple_mail"
|
||||
emailAgentOutlook = "outlook"
|
||||
emailAgentThunderbird = "thunderbird"
|
||||
emailAgentOther = "other"
|
||||
emailAgentUnknown = "unknown"
|
||||
)
|
||||
|
||||
func matchUserAgent(userAgent string) string {
|
||||
if userAgent == "" {
|
||||
return emailAgentUnknown
|
||||
}
|
||||
|
||||
userAgent = strings.ToLower(userAgent)
|
||||
switch {
|
||||
case strings.Contains(userAgent, "outlook"):
|
||||
return emailAgentOutlook
|
||||
case strings.Contains(userAgent, "thunderbird"):
|
||||
return emailAgentThunderbird
|
||||
case strings.Contains(userAgent, "mac") && strings.Contains(userAgent, "mail"):
|
||||
return emailAgentAppleMail
|
||||
case strings.Contains(userAgent, "mac") && strings.Contains(userAgent, "notes"):
|
||||
return emailAgentUnknown
|
||||
default:
|
||||
return emailAgentOther
|
||||
}
|
||||
}
|
||||
|
||||
func getEnabled(value bool) string {
|
||||
if !value {
|
||||
return "disabled"
|
||||
}
|
||||
return "enabled"
|
||||
}
|
||||
@ -1,111 +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 observability
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMatchUserAgent(t *testing.T) {
|
||||
type agentParseResult struct {
|
||||
agent string
|
||||
result string
|
||||
}
|
||||
|
||||
testCases := []agentParseResult{
|
||||
{
|
||||
agent: "Microsoft Outlook/16.0.17928.20114 (Windows 11 Version 23H2)",
|
||||
result: emailAgentOutlook,
|
||||
},
|
||||
{
|
||||
agent: "Mailbird/3.0.18.0 (Windows 11 Version 23H2)",
|
||||
result: emailAgentOther,
|
||||
},
|
||||
{
|
||||
agent: "Microsoft Outlook/16.0.17830.20166 (Windows 11 Version 23H2)",
|
||||
result: emailAgentOutlook,
|
||||
},
|
||||
{
|
||||
agent: "Mac OS X Mail/16.0-3776.700.51 (macOS 14.6)",
|
||||
result: emailAgentAppleMail,
|
||||
},
|
||||
{
|
||||
agent: "/ (Windows 11 Version 23H2)",
|
||||
result: emailAgentOther,
|
||||
},
|
||||
{
|
||||
agent: "Microsoft Outlook for Mac/16.88.0-BUILDDAY (macOS 14.6)",
|
||||
result: emailAgentOutlook,
|
||||
},
|
||||
{
|
||||
agent: "/ (macOS 14.5)",
|
||||
result: emailAgentOther,
|
||||
},
|
||||
{
|
||||
agent: "/ (Freedesktop SDK 23.08 (Flatpak runtime))",
|
||||
result: emailAgentOther,
|
||||
},
|
||||
{
|
||||
agent: "Mac OS X Mail/16.0-3774.600.62 (macOS 14.5)",
|
||||
result: emailAgentAppleMail,
|
||||
},
|
||||
{
|
||||
agent: "Mac OS X Notes/4.11-2817 (macOS 14.6)",
|
||||
result: emailAgentUnknown,
|
||||
},
|
||||
{
|
||||
agent: "NoClient/0.0.1 (macOS 14.6)",
|
||||
result: emailAgentOther,
|
||||
},
|
||||
{
|
||||
agent: "Thunderbird/115.15.0 (Ubuntu 20.04.6 LTS)",
|
||||
result: emailAgentThunderbird,
|
||||
},
|
||||
{
|
||||
agent: "Thunderbird/115.14.0 (macOS 14.6)",
|
||||
result: emailAgentThunderbird,
|
||||
},
|
||||
{
|
||||
agent: "Thunderbird/115.10.2 (Windows 11 Version 23H2)",
|
||||
result: emailAgentThunderbird,
|
||||
},
|
||||
{
|
||||
agent: "Mac OS X Notes/4.9-1965 (macOS Monterey (12.0))",
|
||||
result: emailAgentUnknown,
|
||||
},
|
||||
{
|
||||
agent: " Thunderbird/115.14.0 (macOS 14.6) ",
|
||||
result: emailAgentThunderbird,
|
||||
},
|
||||
{
|
||||
agent: "",
|
||||
result: emailAgentUnknown,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
require.Equal(t, testCase.result, matchUserAgent(testCase.agent))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBool(t *testing.T) {
|
||||
require.Equal(t, "false", formatBool(false))
|
||||
require.Equal(t, "true", formatBool(true))
|
||||
}
|
||||
@ -1,90 +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 observabilitymetrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const (
|
||||
smtpErrorsSchemaName = "bridge_smtp_errors_total"
|
||||
smtpErrorsSchemaVersion = 1
|
||||
|
||||
smtpSendSuccessSchemaName = "bridge_smtp_send_success_total"
|
||||
smtpSendSuccessSchemaVersion = 1
|
||||
)
|
||||
|
||||
func generateSMTPErrorObservabilityMetric(errorType string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: smtpErrorsSchemaName,
|
||||
Version: smtpErrorsSchemaVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"errorType": errorType,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateFailedGetParentID() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedGetParentId")
|
||||
}
|
||||
|
||||
func GenerateUnsupportedMIMEType() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("unsupportedMIMEType")
|
||||
}
|
||||
|
||||
func GenerateFailedCreateDraft() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedToCreateDraft")
|
||||
}
|
||||
|
||||
func GenerateFailedCreateAttachments() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedCreateAttachments")
|
||||
}
|
||||
|
||||
func GenerateFailedToGetRecipients() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedGetRecipients")
|
||||
}
|
||||
|
||||
func GenerateFailedCreatePackages() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedCreatePackages")
|
||||
}
|
||||
|
||||
func GenerateFailedSendDraft() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedSendDraft")
|
||||
}
|
||||
|
||||
func GenerateFailedDeleteFromDrafts() proton.ObservabilityMetric {
|
||||
return generateSMTPErrorObservabilityMetric("failedDeleteFromDrafts")
|
||||
}
|
||||
|
||||
func GenerateSMTPSendSuccess() proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: smtpSendSuccessSchemaName,
|
||||
Version: smtpSendSuccessSchemaVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -29,7 +29,6 @@ import (
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
bridgelogging "github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"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/sendrecorder"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
||||
@ -64,8 +63,6 @@ type Service struct {
|
||||
|
||||
addressMode usertypes.AddressMode
|
||||
serverManager ServerManager
|
||||
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@ -81,7 +78,6 @@ func NewService(
|
||||
mode usertypes.AddressMode,
|
||||
identityState *useridentity.State,
|
||||
serverManager ServerManager,
|
||||
observabilitySender observability.Sender,
|
||||
) *Service {
|
||||
subscriberName := fmt.Sprintf("smpt-%v", userID)
|
||||
|
||||
@ -107,8 +103,6 @@ func NewService(
|
||||
|
||||
addressMode: mode,
|
||||
serverManager: serverManager,
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,14 +30,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/gluon/rfc5322"
|
||||
"github.com/ProtonMail/gluon/rfc822"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/sendrecorder"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/smtp/observabilitymetrics"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/message"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/message/parser"
|
||||
@ -168,10 +167,6 @@ func (s *Service) smtpSendMail(ctx context.Context, authID string, from string,
|
||||
|
||||
// If the message was successfully sent, we can update the message ID in the record.
|
||||
s.log.Debug("Message sent successfully, signaling recorder")
|
||||
|
||||
// Send SMTP success observability metric
|
||||
s.observabilitySender.AddMetrics(observabilitymetrics.GenerateSMTPSendSuccess())
|
||||
|
||||
s.recorder.SignalMessageSent(hash, srID, sent.ID)
|
||||
|
||||
return nil
|
||||
@ -202,7 +197,13 @@ func (s *Service) sendWithKey(
|
||||
}
|
||||
parentID, draftsToDelete, err := getParentID(ctx, s.client, authAddrID, addrMode, references)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedGetParentID())
|
||||
if err := s.reporter.ReportMessageWithContext("Failed to get parent ID", reporter.Context{
|
||||
"error": err,
|
||||
"references": message.References,
|
||||
}); err != nil {
|
||||
logrus.WithError(err).Error("Failed to report error")
|
||||
}
|
||||
|
||||
s.log.WithError(err).Warn("Failed to get parent ID")
|
||||
}
|
||||
|
||||
@ -217,7 +218,6 @@ func (s *Service) sendWithKey(
|
||||
decBody = string(message.PlainBody)
|
||||
|
||||
default:
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateUnsupportedMIMEType())
|
||||
return proton.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
||||
}
|
||||
|
||||
@ -234,38 +234,32 @@ func (s *Service) sendWithKey(
|
||||
ExternalID: message.ExternalID,
|
||||
})
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedCreateDraft())
|
||||
return proton.Message{}, fmt.Errorf("failed to create draft: %w", err)
|
||||
}
|
||||
|
||||
attKeys, err := s.createAttachments(ctx, s.client, addrKR, draft.ID, message.Attachments)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedCreateAttachments())
|
||||
return proton.Message{}, fmt.Errorf("failed to create attachments: %w", err)
|
||||
}
|
||||
|
||||
recipients, err := s.getRecipients(ctx, s.client, userKR, settings, draft)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedToGetRecipients())
|
||||
return proton.Message{}, fmt.Errorf("failed to get recipients: %w", err)
|
||||
}
|
||||
|
||||
req, err := createSendReq(addrKR, message.MIMEBody, message.RichBody, message.PlainBody, recipients, attKeys)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedCreatePackages())
|
||||
return proton.Message{}, fmt.Errorf("failed to create packages: %w", err)
|
||||
}
|
||||
|
||||
res, err := s.client.SendDraft(ctx, draft.ID, req)
|
||||
if err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedSendDraft())
|
||||
return proton.Message{}, fmt.Errorf("failed to send draft: %w", err)
|
||||
}
|
||||
|
||||
// Only delete the drafts, if any, after message was successfully sent.
|
||||
if len(draftsToDelete) != 0 {
|
||||
if err := s.client.DeleteMessage(ctx, draftsToDelete...); err != nil {
|
||||
s.observabilitySender.AddDistinctMetrics(observability.SMTPError, observabilitymetrics.GenerateFailedDeleteFromDrafts())
|
||||
s.log.WithField("ids", draftsToDelete).WithError(err).Errorf("Failed to delete requested messages from Drafts")
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,6 +160,10 @@ func (s *childJob) onError(err error) {
|
||||
s.job.onError(err)
|
||||
}
|
||||
|
||||
func (s *childJob) userID() string {
|
||||
return s.job.userID
|
||||
}
|
||||
|
||||
func (s *childJob) chunkDivide(chunks [][]proton.FullMessage) []childJob {
|
||||
numChunks := len(chunks)
|
||||
|
||||
|
||||
@ -1,67 +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 observabilitymetrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const (
|
||||
errorCaseSchemaName = "bridge_sync_message_build_errors_total"
|
||||
errorCaseSchemaVersion = 1
|
||||
successCaseSchemaName = "bridge_sync_message_build_success_total"
|
||||
successCaseSchemaVersion = 1
|
||||
)
|
||||
|
||||
func generateStageBuildFailureObservabilityMetric(errorType string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: errorCaseSchemaName,
|
||||
Version: errorCaseSchemaVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"errorType": errorType,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateNoUnlockedKeyringMetric() proton.ObservabilityMetric {
|
||||
return generateStageBuildFailureObservabilityMetric("noUnlockedKeyring")
|
||||
}
|
||||
|
||||
func GenerateFailedToBuildMetric() proton.ObservabilityMetric {
|
||||
return generateStageBuildFailureObservabilityMetric("failedToBuild")
|
||||
}
|
||||
|
||||
// GenerateMessageBuiltSuccessMetric - Maybe this is incorrect, I'm not sure how metrics with no labels
|
||||
// should be dealt with. The integration tests will tell us.
|
||||
func GenerateMessageBuiltSuccessMetric() proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: successCaseSchemaName,
|
||||
Version: successCaseSchemaVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
)
|
||||
|
||||
// Service which mediates IMAP syncing in Bridge.
|
||||
@ -36,9 +36,8 @@ type Service struct {
|
||||
group *async.Group
|
||||
}
|
||||
|
||||
func NewService(
|
||||
func NewService(reporter reporter.Reporter,
|
||||
panicHandler async.PanicHandler,
|
||||
observabilitySender observability.Sender,
|
||||
) *Service {
|
||||
limits := newSyncLimits(2 * Gigabyte)
|
||||
|
||||
@ -51,7 +50,7 @@ func NewService(
|
||||
limits: limits,
|
||||
metadataStage: NewMetadataStage(metaCh, downloadCh, limits.DownloadRequestMem, panicHandler),
|
||||
downloadStage: NewDownloadStage(downloadCh, buildCh, limits.MaxParallelDownloads, panicHandler),
|
||||
buildStage: NewBuildStage(buildCh, applyCh, limits.MessageBuildMem, panicHandler, observabilitySender),
|
||||
buildStage: NewBuildStage(buildCh, applyCh, limits.MessageBuildMem, panicHandler, reporter),
|
||||
applyStage: NewApplyStage(applyCh),
|
||||
metaCh: metaCh,
|
||||
group: async.NewGroup(context.Background(), panicHandler),
|
||||
|
||||
@ -26,10 +26,9 @@ import (
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/gluon/logging"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
obsMetrics "github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice/observabilitymetrics"
|
||||
"github.com/bradenaw/juniper/parallel"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -51,10 +50,8 @@ type BuildStage struct {
|
||||
maxBuildMem uint64
|
||||
|
||||
panicHandler async.PanicHandler
|
||||
reporter reporter.Reporter
|
||||
log *logrus.Entry
|
||||
|
||||
// Observability
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
|
||||
func NewBuildStage(
|
||||
@ -62,15 +59,15 @@ func NewBuildStage(
|
||||
output BuildStageOutput,
|
||||
maxBuildMem uint64,
|
||||
panicHandler async.PanicHandler,
|
||||
observabilitySender observability.Sender,
|
||||
reporter reporter.Reporter,
|
||||
) *BuildStage {
|
||||
return &BuildStage{
|
||||
input: input,
|
||||
output: output,
|
||||
maxBuildMem: maxBuildMem,
|
||||
log: logrus.WithField("sync-stage", "build"),
|
||||
panicHandler: panicHandler,
|
||||
observabilitySender: observabilitySender,
|
||||
input: input,
|
||||
output: output,
|
||||
maxBuildMem: maxBuildMem,
|
||||
log: logrus.WithField("sync-stage", "build"),
|
||||
panicHandler: panicHandler,
|
||||
reporter: reporter,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +111,7 @@ func (b *BuildStage) run(ctx context.Context) {
|
||||
messages: nil,
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("failed to produce output for next stage: %w", err)
|
||||
logrus.Error(err.Error())
|
||||
logrus.Errorf(err.Error())
|
||||
req.job.onError(err)
|
||||
}
|
||||
|
||||
@ -150,24 +147,35 @@ func (b *BuildStage) run(ctx context.Context) {
|
||||
req.job.log.WithError(err).Error("Failed to add failed message ID")
|
||||
}
|
||||
|
||||
b.observabilitySender.AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateNoUnlockedKeyringMetric())
|
||||
if err := b.reporter.ReportMessageWithContext("Failed to build message - no unlocked keyring (sync)", reporter.Context{
|
||||
"messageID": msg.ID,
|
||||
"userID": req.userID(),
|
||||
}); err != nil {
|
||||
req.job.log.WithError(err).Error("Failed to report message build error")
|
||||
}
|
||||
return BuildResult{}, nil
|
||||
}
|
||||
|
||||
res, err := req.job.messageBuilder.BuildMessage(req.job.labels, msg, kr, new(bytes.Buffer))
|
||||
if err != nil {
|
||||
req.job.log.WithError(err).WithField("msgID", msg.ID).Error("Failed to build message (sync)")
|
||||
req.job.log.WithError(err).WithField("msgID", msg.ID).Error("Failed to build message (syn)")
|
||||
|
||||
if err := req.job.state.AddFailedMessageID(req.getContext(), msg.ID); err != nil {
|
||||
req.job.log.WithError(err).Error("Failed to add failed message ID")
|
||||
}
|
||||
|
||||
b.observabilitySender.AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateFailedToBuildMetric())
|
||||
if err := b.reporter.ReportMessageWithContext("Failed to build message (sync)", reporter.Context{
|
||||
"messageID": msg.ID,
|
||||
"error": err,
|
||||
"userID": req.userID(),
|
||||
}); err != nil {
|
||||
req.job.log.WithError(err).Error("Failed to report message build error")
|
||||
}
|
||||
|
||||
// We could sync a placeholder message here, but for now we skip it entirely.
|
||||
return BuildResult{}, nil
|
||||
}
|
||||
|
||||
b.observabilitySender.AddMetrics(obsMetrics.GenerateMessageBuiltSuccessMetric())
|
||||
return res, nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -217,7 +225,7 @@ func chunkSyncBuilderBatch(batch []proton.FullMessage, maxMemory uint64) [][]pro
|
||||
for _, v := range batch {
|
||||
var dataSize uint64
|
||||
for _, a := range v.Attachments {
|
||||
dataSize += uint64(a.Size) //nolint:gosec // disable G115
|
||||
dataSize += uint64(a.Size)
|
||||
}
|
||||
|
||||
// 2x increase for attachment due to extra memory needed for decrypting and writing
|
||||
|
||||
@ -24,11 +24,10 @@ import (
|
||||
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge/mocks"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
obsMetrics "github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice/observabilitymetrics"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -68,6 +67,7 @@ func TestBuildStage_SuccessRemovesFailedMessage(t *testing.T) {
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
reporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
labels := getTestLabels()
|
||||
|
||||
@ -105,10 +105,7 @@ func TestBuildStage_SuccessRemovesFailedMessage(t *testing.T) {
|
||||
tj.messageBuilder.EXPECT().BuildMessage(gomock.Eq(labels), gomock.Eq(msg), gomock.Any(), gomock.Any()).Return(buildResult, nil)
|
||||
tj.state.EXPECT().RemFailedMessageID(gomock.Any(), gomock.Eq("MSG"))
|
||||
|
||||
observabilityService := mocks.NewMockObservabilitySender(mockCtrl)
|
||||
observabilityService.EXPECT().AddMetrics(obsMetrics.GenerateMessageBuiltSuccessMetric())
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, observabilityService)
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, reporter)
|
||||
|
||||
go func() {
|
||||
stage.run(ctx)
|
||||
@ -128,7 +125,7 @@ func TestBuildStage_BuildFailureIsReportedButDoesNotCancelJob(t *testing.T) {
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
mockObservabilityService := mocks.NewMockObservabilitySender(mockCtrl)
|
||||
mockReporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
labels := getTestLabels()
|
||||
|
||||
@ -159,12 +156,15 @@ func TestBuildStage_BuildFailureIsReportedButDoesNotCancelJob(t *testing.T) {
|
||||
|
||||
tj.messageBuilder.EXPECT().BuildMessage(gomock.Eq(labels), gomock.Eq(msg), gomock.Any(), gomock.Any()).Return(BuildResult{}, buildError)
|
||||
tj.state.EXPECT().AddFailedMessageID(gomock.Any(), gomock.Eq([]string{"MSG"}))
|
||||
mockReporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Eq(reporter.Context{
|
||||
"userID": "u",
|
||||
"messageID": "MSG",
|
||||
"error": buildError,
|
||||
})).Return(nil)
|
||||
|
||||
tj.syncReporter.EXPECT().OnProgress(gomock.Any(), gomock.Eq(int64(10)))
|
||||
|
||||
mockObservabilityService.EXPECT().AddDistinctMetrics(observability.SyncError, obsMetrics.GenerateNoUnlockedKeyringMetric())
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mockObservabilityService)
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mockReporter)
|
||||
|
||||
go func() {
|
||||
stage.run(ctx)
|
||||
@ -183,6 +183,7 @@ func TestBuildStage_FailedToLocateKeyRingIsReportedButDoesNotFailBuild(t *testin
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
mockReporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
labels := getTestLabels()
|
||||
|
||||
@ -208,13 +209,14 @@ func TestBuildStage_FailedToLocateKeyRingIsReportedButDoesNotFailBuild(t *testin
|
||||
tj.job.end()
|
||||
|
||||
tj.state.EXPECT().AddFailedMessageID(gomock.Any(), gomock.Eq([]string{"MSG"}))
|
||||
mockReporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Eq(reporter.Context{
|
||||
"userID": "u",
|
||||
"messageID": "MSG",
|
||||
})).Return(nil)
|
||||
|
||||
tj.syncReporter.EXPECT().OnProgress(gomock.Any(), gomock.Eq(int64(10)))
|
||||
|
||||
observabilitySender := mocks.NewMockObservabilitySender(mockCtrl)
|
||||
observabilitySender.EXPECT().AddDistinctMetrics(observability.SyncError)
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, observabilitySender)
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mockReporter)
|
||||
|
||||
go func() {
|
||||
stage.run(ctx)
|
||||
@ -233,6 +235,7 @@ func TestBuildStage_OtherErrorsFailJob(t *testing.T) {
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
mockReporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
labels := getTestLabels()
|
||||
|
||||
@ -258,7 +261,7 @@ func TestBuildStage_OtherErrorsFailJob(t *testing.T) {
|
||||
childJob := tj.job.newChildJob("f", 10)
|
||||
tj.job.end()
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mocks.NewMockObservabilitySender(mockCtrl))
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mockReporter)
|
||||
|
||||
go func() {
|
||||
stage.run(ctx)
|
||||
@ -280,6 +283,7 @@ func TestBuildStage_CancelledJobIsDiscarded(t *testing.T) {
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
mockReporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
msg := proton.FullMessage{
|
||||
Message: proton.Message{
|
||||
@ -290,7 +294,7 @@ func TestBuildStage_CancelledJobIsDiscarded(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mocks.NewMockObservabilitySender(mockCtrl))
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mockReporter)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@ -323,6 +327,7 @@ func TestTask_EmptyInputDoesNotCrash(t *testing.T) {
|
||||
|
||||
input := NewChannelConsumerProducer[BuildRequest]()
|
||||
output := NewChannelConsumerProducer[ApplyRequest]()
|
||||
reporter := mocks.NewMockReporter(mockCtrl)
|
||||
|
||||
labels := getTestLabels()
|
||||
|
||||
@ -335,7 +340,7 @@ func TestTask_EmptyInputDoesNotCrash(t *testing.T) {
|
||||
childJob := tj.job.newChildJob("f", 10)
|
||||
tj.job.end()
|
||||
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, mocks.NewMockObservabilitySender(mockCtrl))
|
||||
stage := NewBuildStage(input, output, 1024, &async.NoopPanicHandler{}, reporter)
|
||||
|
||||
go func() {
|
||||
stage.run(ctx)
|
||||
|
||||
@ -231,7 +231,7 @@ func downloadAttachment(ctx context.Context, cache *DownloadCache, client APICli
|
||||
}
|
||||
|
||||
type DownloadRateModifier interface {
|
||||
Apply(wasSuccess bool, current int, max int) int //nolint:predeclared
|
||||
Apply(wasSuccess bool, current int, max int) int
|
||||
}
|
||||
|
||||
func autoDownloadRate[T any, R any](
|
||||
@ -285,7 +285,7 @@ func autoDownloadRate[T any, R any](
|
||||
|
||||
type DefaultDownloadRateModifier struct{}
|
||||
|
||||
func (d DefaultDownloadRateModifier) Apply(wasSuccess bool, current int, max int) int { //nolint:predeclared
|
||||
func (d DefaultDownloadRateModifier) Apply(wasSuccess bool, current int, max int) int {
|
||||
if !wasSuccess {
|
||||
return 2
|
||||
}
|
||||
|
||||
@ -217,7 +217,7 @@ func (m *metadataIterator) Next(maxDownloadMem uint64, metadataPageSize int, max
|
||||
}
|
||||
|
||||
for idx, meta := range m.remaining {
|
||||
nextSize := m.expectedSize + uint64(meta.Size) //nolint:gosec // disable G115
|
||||
nextSize := m.expectedSize + uint64(meta.Size)
|
||||
if nextSize >= maxDownloadMem || len(m.downloadReqIDs) >= maxMessages {
|
||||
m.expectedSize = 0
|
||||
m.remaining = m.remaining[idx:]
|
||||
|
||||
@ -105,10 +105,10 @@ func (s *Service) CheckAuth(ctx context.Context, email string, password []byte)
|
||||
func (s *Service) HandleUsedSpaceEvent(ctx context.Context, newSpace int64) error {
|
||||
s.log.Info("Handling User Space Changed event")
|
||||
|
||||
if s.identity.OnUserSpaceChanged(uint64(newSpace)) { //nolint:gosec // disable G115
|
||||
if s.identity.OnUserSpaceChanged(uint64(newSpace)) {
|
||||
s.eventPublisher.PublishEvent(ctx, events.UsedSpaceChanged{
|
||||
UserID: s.identity.User.ID,
|
||||
UsedSpace: uint64(newSpace), //nolint:gosec // disable G115
|
||||
UsedSpace: uint64(newSpace),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +111,7 @@ func (s *Service) readCacheFile() {
|
||||
|
||||
file, err := os.Open(s.cacheFilepath)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Info("Unable to open cache file")
|
||||
s.log.WithError(err).Error("Unable to open cache file")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ func (u *Updater) InstallUpdate(ctx context.Context, downloader Downloader, upda
|
||||
update.Package+".sig",
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrDownloadVerify, err)
|
||||
return ErrDownloadVerify
|
||||
}
|
||||
|
||||
if err := u.installer.InstallUpdate(update.Version, bytes.NewReader(b)); err != nil {
|
||||
|
||||
@ -115,6 +115,7 @@ func New(
|
||||
isNew bool,
|
||||
notificationStore *notifications.Store,
|
||||
getFlagValFn unleash.GetFlagValueFn,
|
||||
pushObservabilityMetric observability.PushObsMetricFn,
|
||||
) (*User, error) {
|
||||
user, err := newImpl(
|
||||
ctx,
|
||||
@ -136,6 +137,7 @@ func New(
|
||||
isNew,
|
||||
notificationStore,
|
||||
getFlagValFn,
|
||||
pushObservabilityMetric,
|
||||
)
|
||||
if err != nil {
|
||||
// Cleanup any pending resources on error
|
||||
@ -170,6 +172,7 @@ func newImpl(
|
||||
isNew bool,
|
||||
notificationStore *notifications.Store,
|
||||
getFlagValueFn unleash.GetFlagValueFn,
|
||||
pushObservabilityMetric observability.PushObsMetricFn,
|
||||
) (*User, error) {
|
||||
logrus.WithField("userID", apiUser.ID).Info("Creating new user")
|
||||
|
||||
@ -265,7 +268,6 @@ func newImpl(
|
||||
addressMode,
|
||||
identityState.Clone(),
|
||||
smtpServerManager,
|
||||
observabilityService,
|
||||
)
|
||||
|
||||
user.imapService = imapservice.NewService(
|
||||
@ -286,10 +288,9 @@ func newImpl(
|
||||
syncConfigDir,
|
||||
user.maxSyncMemory,
|
||||
showAllMail,
|
||||
observabilityService,
|
||||
)
|
||||
|
||||
user.notificationService = notifications.NewService(user.id, user.eventService, user, notificationStore, getFlagValueFn, observabilityService)
|
||||
user.notificationService = notifications.NewService(user.id, user.eventService, user, notificationStore, getFlagValueFn, pushObservabilityMetric)
|
||||
|
||||
// Check for status_progress when triggered.
|
||||
user.goStatusProgress = user.tasks.PeriodicOrTrigger(configstatus.ProgressCheckInterval, 0, func(ctx context.Context) {
|
||||
|
||||
@ -175,6 +175,7 @@ func withUser(tb testing.TB, ctx context.Context, _ *server.Server, m *proton.Ma
|
||||
func(_ string) bool {
|
||||
return false
|
||||
},
|
||||
func(_ proton.ObservabilityMetric) {},
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
defer user.Close()
|
||||
|
||||
@ -19,7 +19,6 @@ package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"golang.org/x/exp/slices"
|
||||
@ -38,10 +37,6 @@ func (user *User) Username() string {
|
||||
return user.vault.getUser(user.userID).Username
|
||||
}
|
||||
|
||||
func (user *User) usernameUnsafe() string {
|
||||
return user.vault.getUserUnsafe(user.userID).Username
|
||||
}
|
||||
|
||||
// PrimaryEmail returns the user's primary email address.
|
||||
func (user *User) PrimaryEmail() string {
|
||||
return user.vault.getUser(user.userID).PrimaryEmail
|
||||
@ -247,15 +242,3 @@ func (user *User) SetShouldSync(shouldResync bool) error {
|
||||
func (user *User) GetShouldResync() bool {
|
||||
return user.vault.getUser(user.userID).ShouldResync
|
||||
}
|
||||
|
||||
// updateUsernameUnsafe - updates the username of the relevant user, provided that the new username is not empty
|
||||
// and differs from the previous. Writes are not performed if this case is not met.
|
||||
// Should only be called from contexts where the vault mutex is already locked.
|
||||
func (user *User) updateUsernameUnsafe(username string) error {
|
||||
if strings.TrimSpace(username) == "" || user.usernameUnsafe() == username {
|
||||
return nil
|
||||
}
|
||||
return user.vault.modUserUnsafe(user.userID, func(userData *UserData) {
|
||||
userData.Username = username
|
||||
})
|
||||
}
|
||||
|
||||
@ -240,10 +240,6 @@ func (vault *Vault) GetOrAddUser(userID, username, primaryEmail, authUID, authRe
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if err := user.updateUsernameUnsafe(username); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return user, false, nil
|
||||
}
|
||||
}
|
||||
@ -454,22 +450,6 @@ func (vault *Vault) getUser(userID string) UserData {
|
||||
return users[idx]
|
||||
}
|
||||
|
||||
// getUserUnsafe - fetches the relevant UserData.
|
||||
// Should only be called from contexts in which the vault mutex has been read locked.
|
||||
func (vault *Vault) getUserUnsafe(userID string) UserData {
|
||||
users := vault.getUnsafe().Users
|
||||
|
||||
idx := xslices.IndexFunc(users, func(user UserData) bool {
|
||||
return user.UserID == userID
|
||||
})
|
||||
|
||||
if idx < 0 {
|
||||
panic("Unknown user")
|
||||
}
|
||||
|
||||
return users[idx]
|
||||
}
|
||||
|
||||
func (vault *Vault) modUser(userID string, fn func(userData *UserData)) error {
|
||||
vault.lock.Lock()
|
||||
defer vault.lock.Unlock()
|
||||
|
||||
@ -31,12 +31,21 @@ const (
|
||||
MacOSKeychain = "macos-keychain"
|
||||
)
|
||||
|
||||
func listHelpers() (Helpers, string) {
|
||||
func listHelpers(skipKeychainTest bool) (Helpers, string) {
|
||||
helpers := make(Helpers)
|
||||
|
||||
// MacOS always provides a keychain.
|
||||
logrus.WithField("pkg", "keychain").Info("Skipping macOS keychain test")
|
||||
helpers[MacOSKeychain] = newMacOSHelper
|
||||
if skipKeychainTest {
|
||||
logrus.WithField("pkg", "keychain").Info("Skipping macOS keychain test")
|
||||
helpers[MacOSKeychain] = newMacOSHelper
|
||||
} else {
|
||||
if isUsable(newMacOSHelper("")) {
|
||||
helpers[MacOSKeychain] = newMacOSHelper
|
||||
logrus.WithField("keychain", "MacOSKeychain").Info("Keychain is usable.")
|
||||
} else {
|
||||
logrus.WithField("keychain", "MacOSKeychain").Debug("Keychain is not available.")
|
||||
}
|
||||
}
|
||||
|
||||
// Use MacOSKeychain by default.
|
||||
return helpers, MacOSKeychain
|
||||
|
||||
@ -31,7 +31,7 @@ const (
|
||||
SecretServiceDBus = "secret-service-dbus"
|
||||
)
|
||||
|
||||
func listHelpers() (Helpers, string) {
|
||||
func listHelpers(_ bool) (Helpers, string) {
|
||||
helpers := make(Helpers)
|
||||
|
||||
if isUsable(newDBusHelper("")) {
|
||||
|
||||
@ -25,7 +25,7 @@ import (
|
||||
|
||||
const WindowsCredentials = "windows-credentials"
|
||||
|
||||
func listHelpers() (Helpers, string) {
|
||||
func listHelpers(_ bool) (Helpers, string) {
|
||||
helpers := make(Helpers)
|
||||
// Windows always provides a keychain.
|
||||
if isUsable(newWinCredHelper("")) {
|
||||
|
||||
@ -62,9 +62,9 @@ type List struct {
|
||||
// NewList checks availability of every keychains detected on the User Operating System
|
||||
// This will ask the user to unlock keychain(s) to check their usability.
|
||||
// This should only be called once.
|
||||
func NewList() *List {
|
||||
func NewList(skipKeychainTest bool) *List {
|
||||
var list = List{locker: &sync.Mutex{}}
|
||||
list.helpers, list.defaultHelper = listHelpers()
|
||||
list.helpers, list.defaultHelper = listHelpers(skipKeychainTest)
|
||||
return &list
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func (kc *Keychain) secretURL(userID string) string {
|
||||
}
|
||||
|
||||
// isUsable returns whether the credentials helper is usable.
|
||||
func isUsable(helper credentials.Helper, err error) bool { //nolint:unused
|
||||
func isUsable(helper credentials.Helper, err error) bool {
|
||||
l := logrus.WithField("helper", reflect.TypeOf(helper))
|
||||
|
||||
if err != nil {
|
||||
@ -240,7 +240,7 @@ func isUsable(helper credentials.Helper, err error) bool { //nolint:unused
|
||||
return true
|
||||
}
|
||||
|
||||
func getTestCredentials() *credentials.Credentials { //nolint:unused
|
||||
func getTestCredentials() *credentials.Credentials {
|
||||
// On macOS, a handful of users experience failures of the test credentials.
|
||||
if runtime.GOOS == "darwin" {
|
||||
return &credentials.Credentials{
|
||||
@ -257,7 +257,7 @@ func getTestCredentials() *credentials.Credentials { //nolint:unused
|
||||
}
|
||||
}
|
||||
|
||||
func retry(condition func() error) error { //nolint:unused
|
||||
func retry(condition func() error) error {
|
||||
var maxRetry = 5
|
||||
for r := 0; ; r++ {
|
||||
err := condition()
|
||||
|
||||
@ -117,7 +117,7 @@ func TestInsertReadRemove(t *testing.T) {
|
||||
|
||||
func TestIsErrKeychainNoItem(t *testing.T) {
|
||||
r := require.New(t)
|
||||
helpers := NewList().GetHelpers()
|
||||
helpers := NewList(false).GetHelpers()
|
||||
|
||||
for helperName := range helpers {
|
||||
kc, err := NewKeychain(helperName, "bridge-test", helpers, helperName)
|
||||
|
||||
@ -369,30 +369,30 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header {
|
||||
|
||||
// SetText will RFC2047-encode.
|
||||
if msg.Subject != "" {
|
||||
setUTF8EncodedHeaderIfNeeded(&hdr, "Subject", msg.Subject)
|
||||
hdr.SetText("Subject", msg.Subject)
|
||||
}
|
||||
|
||||
// mail.Address.String() will RFC2047-encode if necessary.
|
||||
if !addressEmpty(msg.Sender) {
|
||||
setHeaderIfNeeded(&hdr, "From", msg.Sender.String())
|
||||
hdr.Set("From", msg.Sender.String())
|
||||
}
|
||||
|
||||
if len(msg.ReplyTos) > 0 && !msg.IsDraft() {
|
||||
if !(len(msg.ReplyTos) == 1 && addressEmpty(msg.ReplyTos[0])) {
|
||||
setHeaderIfNeeded(&hdr, "Reply-To", toAddressList(msg.ReplyTos))
|
||||
hdr.Set("Reply-To", toAddressList(msg.ReplyTos))
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.ToList) > 0 {
|
||||
setHeaderIfNeeded(&hdr, "To", toAddressList(msg.ToList))
|
||||
hdr.Set("To", toAddressList(msg.ToList))
|
||||
}
|
||||
|
||||
if len(msg.CCList) > 0 {
|
||||
setHeaderIfNeeded(&hdr, "Cc", toAddressList(msg.CCList))
|
||||
hdr.Set("Cc", toAddressList(msg.CCList))
|
||||
}
|
||||
|
||||
if len(msg.BCCList) > 0 {
|
||||
setHeaderIfNeeded(&hdr, "Bcc", toAddressList(msg.BCCList))
|
||||
hdr.Set("Bcc", toAddressList(msg.BCCList))
|
||||
}
|
||||
|
||||
setMessageIDIfNeeded(msg, &hdr)
|
||||
@ -401,7 +401,7 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header {
|
||||
if opts.SanitizeDate {
|
||||
if date, err := rfc5322.ParseDateTime(hdr.Get("Date")); err != nil || date.Before(time.Unix(0, 0)) {
|
||||
msgDate := SanitizeMessageDate(msg.Time)
|
||||
setHeaderIfNeeded(&hdr, "Date", msgDate.In(time.UTC).Format(time.RFC1123Z))
|
||||
hdr.Set("Date", msgDate.In(time.UTC).Format(time.RFC1123Z))
|
||||
// We clobbered the date so we save it under X-Original-Date only if no such value exists.
|
||||
if !hdr.Has("X-Original-Date") {
|
||||
hdr.Set("X-Original-Date", date.In(time.UTC).Format(time.RFC1123Z))
|
||||
@ -412,7 +412,7 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header {
|
||||
// Set our internal ID if requested.
|
||||
// This is important for us to detect whether APPENDed things are actually "move like outlook".
|
||||
if opts.AddInternalID {
|
||||
setHeaderIfNeeded(&hdr, "X-Pm-Internal-Id", msg.ID)
|
||||
hdr.Set("X-Pm-Internal-Id", msg.ID)
|
||||
}
|
||||
|
||||
// Set our external ID if requested.
|
||||
@ -426,7 +426,7 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header {
|
||||
// Set our server date if requested.
|
||||
// Can be useful to see how long it took for a message to arrive.
|
||||
if opts.AddMessageDate {
|
||||
setHeaderIfNeeded(&hdr, "X-Pm-Date", time.Unix(msg.Time, 0).In(time.UTC).Format(time.RFC1123Z))
|
||||
hdr.Set("X-Pm-Date", time.Unix(msg.Time, 0).In(time.UTC).Format(time.RFC1123Z))
|
||||
}
|
||||
|
||||
// Include the message ID in the references (supposedly this somehow improves outlook support...).
|
||||
@ -463,25 +463,6 @@ func setMessageIDIfNeeded(msg proton.Message, hdr *message.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// setTextHeaderIfNeeded sets a text (UTF-encoded) header entry if its does not exists or if value is changed.
|
||||
// Not systematically overwriting the value prevents it from being moved to the top (Del + Add) if not changed.
|
||||
func setUTF8EncodedHeaderIfNeeded(header *message.Header, k, v string) {
|
||||
encoded := mime.QEncoding.Encode("utf-8", v)
|
||||
if header.Has(k) && (header.Get(k) == encoded) {
|
||||
return
|
||||
}
|
||||
header.Set(k, encoded)
|
||||
}
|
||||
|
||||
// setHeaderIfNeeded sets a header entry if its does not exists or if value is changed.
|
||||
// Not systematically overwriting the value prevents it from being moved to the top (Del + Add) if not changed.
|
||||
func setHeaderIfNeeded(header *message.Header, key, value string) {
|
||||
if header.Has(key) && (header.Get(key) == value) {
|
||||
return
|
||||
}
|
||||
header.Set(key, value)
|
||||
}
|
||||
|
||||
func getTextPartHeader(hdr message.Header, body []byte, mimeType rfc822.MIMEType) message.Header {
|
||||
params := make(map[string]string)
|
||||
|
||||
@ -528,9 +509,8 @@ func getAttachmentPartHeader(att proton.Attachment) message.Header {
|
||||
|
||||
func toMessageHeader(hdr proton.Headers) message.Header {
|
||||
var res message.Header
|
||||
// go-message's message.Header are in reversed order (you should only add fields at the top, so storing in reverse order offer faster performances).
|
||||
for i := len(hdr.Order) - 1; i >= 0; i-- {
|
||||
key := hdr.Order[i]
|
||||
|
||||
for _, key := range hdr.Order {
|
||||
for _, val := range hdr.Values[key] {
|
||||
// Using AddRaw instead of Add to save key-value pair as byte buffer within Header.
|
||||
// This buffer is used latter on in message writer to construct message and avoid crash
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"net/mail"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -93,67 +92,6 @@ func newRawTestMessageWithHeaders(messageID, addressID, mimeType, body string, d
|
||||
}
|
||||
}
|
||||
|
||||
func newTestMessageFromRFC822(t *testing.T, literal []byte) proton.Message {
|
||||
// Note attachment are not supported.
|
||||
p := rfc822.Parse(literal)
|
||||
h, err := p.ParseHeader()
|
||||
require.NoError(t, err)
|
||||
var parsedHeaders proton.Headers
|
||||
parsedHeaders.Values = make(map[string][]string)
|
||||
h.Entries(func(key, val string) {
|
||||
parsedHeaders.Values[key] = []string{val}
|
||||
parsedHeaders.Order = append(parsedHeaders.Order, key)
|
||||
})
|
||||
var mailHeaders = mail.Header(parsedHeaders.Values)
|
||||
require.True(t, h.Has("Content-Type"))
|
||||
mime, _, err := rfc822.ParseMIMEType(h.Get("Content-Type"))
|
||||
require.NoError(t, err)
|
||||
date, err := mailHeaders.Date()
|
||||
require.NoError(t, err)
|
||||
sender, err := mail.ParseAddress(parsedHeaders.Values["From"][0])
|
||||
require.NoError(t, err)
|
||||
|
||||
return proton.Message{
|
||||
MessageMetadata: proton.MessageMetadata{
|
||||
ID: "messageID",
|
||||
AddressID: "addressID",
|
||||
LabelIDs: []string{},
|
||||
ExternalID: "",
|
||||
Subject: parsedHeaders.Values["Subject"][0],
|
||||
Sender: sender,
|
||||
ToList: parseAddressList(t, mailHeaders, "To"),
|
||||
CCList: parseAddressList(t, mailHeaders, "Cc"),
|
||||
BCCList: parseAddressList(t, mailHeaders, "Bcc"),
|
||||
ReplyTos: parseAddressList(t, mailHeaders, "Reply-To"),
|
||||
Flags: 0,
|
||||
Time: date.Unix(),
|
||||
Size: 0,
|
||||
Unread: false,
|
||||
IsReplied: false,
|
||||
IsRepliedAll: false,
|
||||
IsForwarded: false,
|
||||
NumAttachments: 0,
|
||||
},
|
||||
Header: string(h.Raw()),
|
||||
ParsedHeaders: parsedHeaders,
|
||||
Body: string(p.Body()),
|
||||
MIMEType: mime,
|
||||
Attachments: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func parseAddressList(t *testing.T, header mail.Header, key string) []*mail.Address {
|
||||
var result []*mail.Address
|
||||
if len(header.Get(key)) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := header.AddressList(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func addTestAttachment(
|
||||
t *testing.T,
|
||||
kr *crypto.KeyRing,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user