mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd8db6fd1c | |||
| a5e0f85a58 | |||
| 82607efe1c | |||
| 961dc9435f | |||
| b574ccb6ea | |||
| 2569e83e51 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,7 +7,6 @@
|
|||||||
*~
|
*~
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.vs
|
|
||||||
|
|
||||||
# Test files
|
# Test files
|
||||||
godog.test
|
godog.test
|
||||||
@ -36,8 +35,6 @@ cmd/Import-Export/deploy
|
|||||||
proton-bridge
|
proton-bridge
|
||||||
cmd/Desktop-Bridge/*.exe
|
cmd/Desktop-Bridge/*.exe
|
||||||
cmd/launcher/*.exe
|
cmd/launcher/*.exe
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
|
|
||||||
# Jetbrains (CLion, Golang) cmake build dirs
|
# Jetbrains (CLion, Golang) cmake build dirs
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
|
|||||||
@ -25,16 +25,11 @@ variables:
|
|||||||
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- apt update && apt-get -y install libsecret-1-dev
|
||||||
if [ "$CI_JOB_NAME" != "grype-scan-code-dependencies" ]; then
|
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
apt update && apt-get -y install libsecret-1-dev
|
|
||||||
git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
|
||||||
fi
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- analyse
|
|
||||||
- test
|
- test
|
||||||
- report
|
|
||||||
- build
|
- build
|
||||||
|
|
||||||
include:
|
include:
|
||||||
@ -42,9 +37,5 @@ include:
|
|||||||
- local: ci/rules.yml
|
- local: ci/rules.yml
|
||||||
- local: ci/env.yml
|
- local: ci/env.yml
|
||||||
- local: ci/test.yml
|
- local: ci/test.yml
|
||||||
- local: ci/report.yml
|
|
||||||
- local: ci/build.yml
|
- local: ci/build.yml
|
||||||
- component: gitlab.protontech.ch/proton/devops/cicd-components/kits/devsecops/go@~latest
|
|
||||||
inputs:
|
|
||||||
stage: analyse
|
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
|
- pkg/mime
|
||||||
|
- extern
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
exclude-dirs:
|
|
||||||
- pkg/mime
|
|
||||||
- extern
|
|
||||||
exclude:
|
exclude:
|
||||||
- Using the variable on range scope `tt` in function literal
|
- Using the variable on range scope `tt` in function literal
|
||||||
# For now we are missing a lot of comments.
|
# For now we are missing a lot of comments.
|
||||||
@ -88,7 +87,6 @@ linters:
|
|||||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||||
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||||
- copyloopvar # detects places where loop variables are copied.
|
|
||||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||||
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
# Check out for configuration details: https://github.com/anchore/grype?tab=readme-ov-file#configuration
|
|
||||||
fail-on-severity: "medium"
|
|
||||||
@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
* 64-bit OS:
|
* 64-bit OS:
|
||||||
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
||||||
* Go 1.21.9
|
* Go 1.21.6
|
||||||
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
||||||
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||||
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
||||||
|
|||||||
@ -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)
|
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||||
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||||
* [oauth2](https://golang.org/x/oauth2) available under [license](https://cs.opensource.google/go/x/oauth2/+/master:LICENSE)
|
|
||||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||||
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
||||||
* [api](https://google.golang.org/api) available under [license](https://pkg.go.dev/google.golang.org/api?tab=licenses)
|
|
||||||
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||||
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||||
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
|
|
||||||
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
|
|
||||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||||
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||||
@ -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)
|
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
||||||
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||||
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||||
* [groupcache](https://github.com/golang/groupcache) available under [license](https://github.com/golang/groupcache/blob/master/LICENSE)
|
|
||||||
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||||
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
||||||
* [enterprise-certificate-proxy](https://github.com/googleapis/enterprise-certificate-proxy) available under [license](https://github.com/googleapis/enterprise-certificate-proxy/blob/master/LICENSE)
|
|
||||||
* [gax-go](https://github.com/googleapis/gax-go/v2) available under [license](https://github.com/googleapis/gax-go/v2/blob/master/LICENSE)
|
|
||||||
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
||||||
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
||||||
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
||||||
@ -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)
|
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
||||||
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
||||||
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
||||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
|
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
||||||
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
|
|
||||||
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
||||||
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
||||||
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||||
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||||
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||||
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
|
|
||||||
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||||
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) 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-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||||
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
||||||
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
||||||
|
|||||||
96
Changelog.md
96
Changelog.md
@ -3,102 +3,6 @@
|
|||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
|
||||||
## Flavien Bridge 3.16.0
|
|
||||||
|
|
||||||
### Added
|
|
||||||
* BRIDGE-205: Add support for the IMAP AUTHENTICATE command.
|
|
||||||
* BRIDGE-268: Add kill switch feature flag for the IMAP AUTHENTICATE command.
|
|
||||||
* BRIDGE-261: Delete gluon data during user deletion.
|
|
||||||
* BRIDGE-246: Test: Add Settings Menu Bridge UI e2e automation tests.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* BRIDGE-107: Improved human verification UX.
|
|
||||||
* BRIDGE-281: Disable keychain test on macOS.
|
|
||||||
* BRIDGE-266: Heartbeat telemetry update.
|
|
||||||
* BRIDGE-253: Removed unused telemetry (activation and troubleshooting).
|
|
||||||
* BRIDGE-252: Restored the -h shortcut for the CLI --help switch.
|
|
||||||
* BRIDGE-264: Ignore apple notes as UserAgent.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* BRIDGE-256: Fix reversed order of headers with multiple values.
|
|
||||||
* BRIDGE-258: Fixed issue with draft updates and sending during synchronization.
|
|
||||||
|
|
||||||
|
|
||||||
## Erasmus Bridge 3.15.0
|
|
||||||
|
|
||||||
### Added
|
|
||||||
* BRIDGE-238: Added host information to sentry events; new sentry event for keychain issues.
|
|
||||||
* BRIDGE-236: Added SMTP observability metrics.
|
|
||||||
* BRIDGE-217: Added missing parameter to the CLI help command.
|
|
||||||
* BRIDGE-234: Add accessibility name in QML for UI automation.
|
|
||||||
* BRIDGE-232: Test: Add Home Menu Bridge UI e2e automation tests.
|
|
||||||
* BRIDGE-220: Test: Add Bridge E2E UI login/logout tests for Windows.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* BRIDGE-228: Removed sentry events.
|
|
||||||
* BRIDGE-218: Observability adapter; gluon observability metrics and tests.
|
|
||||||
* BRIDGE-215: Tweak wording on macOS profile install page.
|
|
||||||
* BRIDGE-131: Test: Integration tests for messages from Proton <-> Gmail.
|
|
||||||
* BRIDGE-142: Bridge icon can be removed from the menu bar on macOS.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* BRIDGE-240: Fix for running against Qt 6.8 (contribution of GitHub user Cimbali).
|
|
||||||
* BRIDGE-231: Fix reversed header order in messages.
|
|
||||||
* BRIDGE-235: Fix compilation of Bridge GUI Tester on Windows.
|
|
||||||
* BRIDGE-120: Use appropriate address key when importing / saving draft.
|
|
||||||
|
|
||||||
|
|
||||||
## Dragon Bridge 3.14.0
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* BRIDGE-207: Failure to download or verify an update now fails silently.
|
|
||||||
* BRIDGE-204: Removed redundant Sentry events.
|
|
||||||
* BRIDGE-150: Observability service modification.
|
|
||||||
* BRIDGE-210: Reduced log level of cache events so they won't be printed to stdout.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* BRIDGE-106: Fixed import of multipart-related messages.
|
|
||||||
* BRIDGE-108: Fixed GetInitials when empty username is passed.
|
|
||||||
|
|
||||||
|
|
||||||
## Colorado Bridge 3.13.0
|
|
||||||
|
|
||||||
### Added
|
|
||||||
* BRIDGE-37: added message broadcasting functionality.
|
|
||||||
* BRIDGE-122: added observability service.
|
|
||||||
* BRIDGE-119: added support for Feature Flags.
|
|
||||||
* BRIDGE-116: added command-line switches to enable/disable keychain check on macOS.
|
|
||||||
* BRIDGE-88: added context menu for quick actions on input labels: cut, copy, paste.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* BRIDGE-81: KB article suggestion updates + more weight for long keywords.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* BRIDGE-67: Added detection for username changes on macOS & automatic reconfiguration.
|
|
||||||
* BRIDGE-138: Remove deprecated doc.
|
|
||||||
|
|
||||||
|
|
||||||
## Bastei Bridge 3.12.0
|
|
||||||
|
|
||||||
### Added
|
|
||||||
* BRIDGE-75: Bridge repair button.
|
|
||||||
* BRIDGE-79: Add New Outlook for Mac KB disclaimer.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* BRIDGE-16: Bump version Go 1.21.9 Qt 6.4.3.
|
|
||||||
* BRIDGE-23: Update gluon to go 1.21.
|
|
||||||
* BRIDGE-22: Update gpa to go 1.21.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* BRIDGE-90: Disable repair button when bridge cannot connect to proton servers; bump GPA.
|
|
||||||
* BRIDGE-69: Explicitly handle semver panic for last bridge version from vault.
|
|
||||||
* BRIDGE-29: Bump gluon version.
|
|
||||||
* BRIDGE-49: Configure gitleaks baseline and grype config.
|
|
||||||
* BRIDGE-21: Missing panic handling.
|
|
||||||
* BRIDGE-17: Broken telemetry heartbeat test.
|
|
||||||
* BRIDGE-10: Bumped gluon version.
|
|
||||||
|
|
||||||
|
|
||||||
## Alcantara Bridge 3.11.1
|
## Alcantara Bridge 3.11.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
7
Makefile
7
Makefile
@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
|
|||||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=3.16.0+git
|
BRIDGE_APP_VERSION?=3.11.1+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
APP_FULL_NAME:=Proton Mail Bridge
|
APP_FULL_NAME:=Proton Mail Bridge
|
||||||
APP_VENDOR:=Proton AG
|
APP_VENDOR:=Proton AG
|
||||||
@ -189,7 +189,7 @@ ${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
|||||||
|
|
||||||
## Dev dependencies
|
## Dev dependencies
|
||||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||||
LINTVER:="v1.61.0"
|
LINTVER:="v1.55.2"
|
||||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||||
|
|
||||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||||
@ -264,8 +264,7 @@ test-integration-race: gofiles
|
|||||||
|
|
||||||
test-integration-nightly: gofiles
|
test-integration-nightly: gofiles
|
||||||
mkdir -p coverage/integration
|
mkdir -p coverage/integration
|
||||||
gotestsum \
|
go test \
|
||||||
--junitfile tests/result/feature-tests.xml -- \
|
|
||||||
-v -timeout=90m -p=1 -count=1 -tags=test_integration \
|
-v -timeout=90m -p=1 -count=1 -tags=test_integration \
|
||||||
${GOCOVERAGE} \
|
${GOCOVERAGE} \
|
||||||
github.com/ProtonMail/proton-bridge/v3/tests \
|
github.com/ProtonMail/proton-bridge/v3/tests \
|
||||||
|
|||||||
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
|
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).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||||
@ -13,7 +13,7 @@ Proton Mail Bridge for e-mail clients.
|
|||||||
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
||||||
its GUI.
|
its GUI.
|
||||||
|
|
||||||
To configure an e-mail client, 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
|
Open your e-mail client and add a new account using the settings which are
|
||||||
located in the Bridge GUI. The client will only be able to sync with
|
located in the Bridge GUI. The client will only be able to sync with
|
||||||
your Proton Mail account when the Bridge is running, thus the option
|
your Proton Mail account when the Bridge is running, thus the option
|
||||||
@ -24,10 +24,10 @@ background.
|
|||||||
|
|
||||||
More details [on the public website](https://proton.me/mail/bridge).
|
More details [on the public website](https://proton.me/mail/bridge).
|
||||||
|
|
||||||
## Launcher
|
## Launchers
|
||||||
The launcher is a binary used to run the Proton Mail Bridge.
|
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
|
both a launcher and the app itself. The launcher is installed in a protected
|
||||||
area of the system (i.e. an area accessible only with admin privileges) and is
|
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||||
used to run the app. The launcher ensures that nobody tampered with the app's
|
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||||
@ -37,7 +37,7 @@ feature enables the app to securely update itself automatically without asking
|
|||||||
the user for a password.
|
the user for a password.
|
||||||
|
|
||||||
## Keychain
|
## Keychain
|
||||||
You need to have a keychain in order to run 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
|
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||||
or
|
or
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
include:
|
|
||||||
- project: 'tpe/testmo-reporter'
|
|
||||||
ref: master
|
|
||||||
file: '/scenarios/testmo-script.yml'
|
|
||||||
|
|
||||||
testmo-upload:
|
|
||||||
stage: report
|
|
||||||
extends:
|
|
||||||
- .testmo-upload
|
|
||||||
- .rules-branch-manual-scheduled-and-test-branch-always
|
|
||||||
needs:
|
|
||||||
- test-integration-nightly
|
|
||||||
before_script: []
|
|
||||||
variables:
|
|
||||||
TESTMO_TOKEN: "$TESTMO_TOKEN"
|
|
||||||
TESTMO_URL: "https://proton.testmo.net"
|
|
||||||
PROJECT_ID: "9"
|
|
||||||
NAME: "Nightly integration tests"
|
|
||||||
MILESTONE: "Nightly integration tests"
|
|
||||||
SOURCE: "test-integration-nightly"
|
|
||||||
TAGS: "$CI_COMMIT_REF_SLUG"
|
|
||||||
RESULT_FOLDER: "tests/result/*.xml"
|
|
||||||
@ -108,7 +108,6 @@ test-integration-nightly:
|
|||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
paths:
|
paths:
|
||||||
- tests/result/feature-tests.xml
|
|
||||||
- nightly-job.log
|
- nightly-job.log
|
||||||
|
|
||||||
test-coverage:
|
test-coverage:
|
||||||
|
|||||||
135
doc/bridge.md
Normal file
135
doc/bridge.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Bridge
|
||||||
|
|
||||||
|
## Main blocks
|
||||||
|
|
||||||
|
This is basic overview of the main bridge blocks.
|
||||||
|
|
||||||
|
Note connection between IMAP/SMTP and PMAPI. IMAP and SMTP packages are in the queue to be refactored
|
||||||
|
and we would like to try to have functionality in bridge core or bridge utilities (such as messages)
|
||||||
|
than direct usage of PMAPI from IMAP or SMTP. Also database (BoltDB) should be moved to bridge core.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
S[Server]
|
||||||
|
C[Client]
|
||||||
|
U[User]
|
||||||
|
|
||||||
|
subgraph "Bridge app"
|
||||||
|
Core[Bridge core]
|
||||||
|
API[PMAPI]
|
||||||
|
Store
|
||||||
|
DB[BoltDB]
|
||||||
|
Frontend["Qt / CLI"]
|
||||||
|
IMAP
|
||||||
|
SMTP
|
||||||
|
|
||||||
|
IMAP --> Store
|
||||||
|
IMAP --> Core
|
||||||
|
SMTP --> Core
|
||||||
|
SMTP --> API
|
||||||
|
Core --> API
|
||||||
|
Core --> Store
|
||||||
|
Store --> API
|
||||||
|
Store --> DB
|
||||||
|
Frontend --> Core
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
C --> IMAP
|
||||||
|
C --> SMTP
|
||||||
|
U --> Frontend
|
||||||
|
API --> S
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code structure
|
||||||
|
|
||||||
|
More detailed graph of main types used in Bridge app and connection between them. Here is already
|
||||||
|
communication to PMAPI only from bridge core which is not true, yet. IMAP and SMTP are still calling
|
||||||
|
PMAPI directly.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
|
||||||
|
C["Client (e.g. Thunderbird)"]
|
||||||
|
PM[Proton Mail Server]
|
||||||
|
|
||||||
|
subgraph "Bridge app"
|
||||||
|
subgraph "Bridge core"
|
||||||
|
B[Bridge]
|
||||||
|
U[User]
|
||||||
|
|
||||||
|
B --> U
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Store
|
||||||
|
StoreU[Store User]
|
||||||
|
StoreA[Address]
|
||||||
|
StoreM[Mailbox]
|
||||||
|
|
||||||
|
StoreU --> StoreA
|
||||||
|
StoreA --> StoreM
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Credentials
|
||||||
|
CredStore[Store]
|
||||||
|
Creds[Credentials]
|
||||||
|
|
||||||
|
CredStore --> Creds
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Frontend
|
||||||
|
CLI
|
||||||
|
Qt
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph IMAP
|
||||||
|
IB[IMAP backend]
|
||||||
|
IA[IMAP address]
|
||||||
|
IM[IMAP mailbox]
|
||||||
|
|
||||||
|
IB --> B
|
||||||
|
IB --> IA
|
||||||
|
IA --> IM
|
||||||
|
IA --> U
|
||||||
|
IA --> StoreA
|
||||||
|
IM --> StoreM
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SMTP
|
||||||
|
SB[SMTP backend]
|
||||||
|
SS[SMTP session]
|
||||||
|
|
||||||
|
SB --> B
|
||||||
|
SB --> SS
|
||||||
|
SS --> U
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph PMAPI
|
||||||
|
AC[Client]
|
||||||
|
end
|
||||||
|
|
||||||
|
C --> IB
|
||||||
|
C --> SB
|
||||||
|
|
||||||
|
CLI --> B
|
||||||
|
Qt --> B
|
||||||
|
|
||||||
|
U --> CredStore
|
||||||
|
U --> Creds
|
||||||
|
|
||||||
|
U --> StoreU
|
||||||
|
|
||||||
|
StoreU --> AC
|
||||||
|
StoreA --> AC
|
||||||
|
StoreM --> AC
|
||||||
|
|
||||||
|
B --> AC
|
||||||
|
U --> AC
|
||||||
|
|
||||||
|
AC --> PM
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to debug
|
||||||
|
|
||||||
|
Run `make run-debug` which starts [Delve](https://github.com/go-delve/delve).
|
||||||
114
doc/communication.md
Normal file
114
doc/communication.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Communication
|
||||||
|
|
||||||
|
## First login and sync
|
||||||
|
|
||||||
|
When user logs in to the bridge for the first time, immediately starts the first sync.
|
||||||
|
First sync downloads all headers of all e-mails and creates database to have proper UIDs
|
||||||
|
and indexes for IMAP. See [database](database.md) for more information.
|
||||||
|
|
||||||
|
By default, whenever it's possible, sync downloads only all e-mails maiblox which already
|
||||||
|
have list of labels so we can construct all mailboxes (inbox, sent, trash, custom folders
|
||||||
|
and labels) without need to download each e-mail headers many times.
|
||||||
|
|
||||||
|
Note that we need to download also bodies to calculate size of the e-mail and set proper
|
||||||
|
content type (clients uses content type for guess if e-mail contains attachment)--but only
|
||||||
|
body, not attachment. Also it's downloaded only for the first time. After that we store
|
||||||
|
those information in our database so next time we only sync headers, labels and so on.
|
||||||
|
|
||||||
|
First sync takes some time. List of 150 messages takes about second and then we need to
|
||||||
|
download bodies for each message. We still need to do some optimalizations. Anyway, if
|
||||||
|
user has reasonable amount of e-mails, there is good chance user will see e-mails in the
|
||||||
|
client right after adding account.
|
||||||
|
|
||||||
|
When account is added to client, client start the sync. This sync will ask Bridge app
|
||||||
|
for all headers (done quickly) and then starts to download all bodies and attachment.
|
||||||
|
Unfortunately for some e-mail more than once if the same e-mail is in more mailboxes
|
||||||
|
(e.g. inbox and all mail)--there is no way to tell over IMAP it's the same message.
|
||||||
|
|
||||||
|
After successful login of client to IMAP, Bridge starts event loop. That periodicly ask
|
||||||
|
servers (each 30 seconds) for new updates (new message, keys, …).
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant S as Server
|
||||||
|
participant B as Bridge
|
||||||
|
participant C as Client
|
||||||
|
|
||||||
|
Note right of B: Set up PM account<br/>by user
|
||||||
|
|
||||||
|
loop First sync
|
||||||
|
B ->> S: Fetch body and attachments
|
||||||
|
Note right of B: Build local database<br/>(e-mail UIDs)
|
||||||
|
end
|
||||||
|
|
||||||
|
Note right of C: Set up IMAP/SMTP<br/>by user
|
||||||
|
|
||||||
|
C ->> B: IMAP login
|
||||||
|
B ->> S: Authenticate user
|
||||||
|
Note right of B: Create IMAP user
|
||||||
|
|
||||||
|
loop Event loop, every 30 sec
|
||||||
|
B ->> S: Fetch e-mail headers
|
||||||
|
B ->> C: Send IMAP IDLE response
|
||||||
|
end
|
||||||
|
|
||||||
|
C ->> B: IMAP LIST directories
|
||||||
|
|
||||||
|
loop Client sync
|
||||||
|
C ->> B: IMAP SELECT directory
|
||||||
|
C ->> B: IMAP SEARCH e-mails UIDs
|
||||||
|
C ->> B: IMAP FETCH of e-mail UID
|
||||||
|
B ->> S: Fetch body and attachments
|
||||||
|
Note right of B: Decrypt message<br/>and attachment
|
||||||
|
B ->> C: IMAP response
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## IMAP IDLE extension
|
||||||
|
|
||||||
|
IMAP IDLE is extension, it has to be supported by both client and server. IMAP server (in our case
|
||||||
|
the bridge) supports it so clients can use it. It works by issuing `IDLE` command by the client and
|
||||||
|
keeps the connection open. When the server has some update, server (the bridge) will respond to that
|
||||||
|
by `EXISTS` (new message), `APPEND` (imported message), `EXPUNGE` (deleted message) or `MOVE` response.
|
||||||
|
|
||||||
|
Even when there is connection with IDLE open, server can mark the client as inactive. Therefore,
|
||||||
|
it's recommended the client should reissue the connection after each 29 minutes. This is not the
|
||||||
|
real push and can fail!
|
||||||
|
|
||||||
|
Our event loop is also simple pull and it will trigger IMAP IDLE when we get some new update from
|
||||||
|
the server. Would be good to have push from the server, but we need to wait for the support on API.
|
||||||
|
|
||||||
|
RFC: https://tools.ietf.org/html/rfc2177
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant S as Server
|
||||||
|
participant B as Bridge
|
||||||
|
participant C as Client
|
||||||
|
|
||||||
|
C ->> B: IMAP IDLE
|
||||||
|
|
||||||
|
loop Every 30 seconds
|
||||||
|
S ->> B: Checking events
|
||||||
|
B ->> C: IMAP response
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending e-mails
|
||||||
|
|
||||||
|
E-mail are sent over standard SMTP protocol. Our bridge takes the message, encrypts and sent it
|
||||||
|
further to our server which will then send the message to its final destination. The important
|
||||||
|
and tricky part is encryption. See [encryption](encryption.md) or [PMEL document](https://docs.google.com/document/d/1lEBkG0DC5FOWlumInKtu4a9Cc1Eszp48ZhFy9UpPQso/edit)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant S as Server
|
||||||
|
participant B as Bridge
|
||||||
|
participant C as Client
|
||||||
|
|
||||||
|
C ->> B: SMTP send e-mail
|
||||||
|
Note right of B: Encrypt messages
|
||||||
|
B ->> S: Send encrypted e-mail
|
||||||
|
B ->> C: Respond OK
|
||||||
|
```
|
||||||
27
doc/database.md
Normal file
27
doc/database.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Database
|
||||||
|
|
||||||
|
Bridge needs to have a small database to pair our IDs with IMAP UIDs and indexes. IMAP protocol
|
||||||
|
requires every message to have an unique UID in mailbox. In this context, mailbox is not an account,
|
||||||
|
but a folder or label. This means that one message can have more UIDs, one for each mailbox (folder),
|
||||||
|
and that two messages can have the same UID, but each for different mailbox (folder).
|
||||||
|
|
||||||
|
IMAP index is just an index. Look at it like to an array: `["UID1", "UID2", "UID3"]`. We can access
|
||||||
|
message by UID or index; for example index 2 and UID `UID2`. When this message is deleted, we need
|
||||||
|
to re-index all following messages. The array will look now like `["UID1", "UID3"]` and the last
|
||||||
|
message can be accessed by index 2 or UID `UID3`.
|
||||||
|
|
||||||
|
See RFCs for more information:
|
||||||
|
|
||||||
|
* https://tools.ietf.org/html/rfc822
|
||||||
|
* https://tools.ietf.org/html/rfc3501
|
||||||
|
|
||||||
|
Our database is currently built on BBolt and have those buckets (key-value storage):
|
||||||
|
|
||||||
|
* Message metadata bucket:
|
||||||
|
|
||||||
|
* `[metadataBucket][API_ID] -> pmapi.Message{subject, from, to, size, other headers...}` (without body or attachment)
|
||||||
|
|
||||||
|
* Mapping buckets
|
||||||
|
|
||||||
|
* `[mailboxesBucket][addressID-mailboxID][api_ids][API_ID] -> UID`
|
||||||
|
* `[mailboxesBucket][addressID-mailboxID][imap_ids][UID] -> API_ID`
|
||||||
12
doc/encryption.md
Normal file
12
doc/encryption.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Encryption
|
||||||
|
|
||||||
|
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
||||||
|
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
|
||||||
|
GopenPGP library on top of OpenPGP.
|
||||||
|
|
||||||
|
## `gopenpgp.KeyRing`
|
||||||
|
|
||||||
|
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
||||||
|
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
||||||
|
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
|
||||||
|
key for encryption to have message encrypted only once with primary key.
|
||||||
9
doc/index.md
Normal file
9
doc/index.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Bridge Documentation
|
||||||
|
|
||||||
|
Documentation pages in order to read for a novice:
|
||||||
|
|
||||||
|
* [Bridge code](bridge.md)
|
||||||
|
* [Internal Bridge database](database.md)
|
||||||
|
* [Communication between Bridge, Client and Server](communication.md)
|
||||||
|
* [Encryption](encryption.md)
|
||||||
|
|
||||||
103
doc/updates.md
Normal file
103
doc/updates.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Update mechanism of Bridge
|
||||||
|
|
||||||
|
There are multiple options how to change version of application:
|
||||||
|
* Automatic in-app update
|
||||||
|
* Manual in-app update
|
||||||
|
* Manual install
|
||||||
|
|
||||||
|
In-app update ends with restarting bridge into new version. Automatic in-app
|
||||||
|
update is downloading, verifying and installing the new version immediately
|
||||||
|
without user confirmation. For manual in-app update user needs to confirm first.
|
||||||
|
Update is done from special update file published on website.
|
||||||
|
|
||||||
|
The manual installation requires user to download, verify and install manually
|
||||||
|
using installer for given OS.
|
||||||
|
|
||||||
|
The bridge is installed and executed differently for given OS:
|
||||||
|
|
||||||
|
* Windows and Linux apps are using launcher mechanism:
|
||||||
|
* There is system protected installation path which is created on first
|
||||||
|
install. It contains bridge exe and launcher exe. When users starts
|
||||||
|
bridge the launcher is executed first. It will check update path compare
|
||||||
|
version with installed one. The newer version then is then executed.
|
||||||
|
* Update mechanism means to replace files in update folder which is located
|
||||||
|
in user space.
|
||||||
|
|
||||||
|
* macOS app does not use launcher
|
||||||
|
* No launcher, only one executable
|
||||||
|
* In-App update replaces the bridge files in installation path directly
|
||||||
|
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph Frontend
|
||||||
|
U[User requests<br>version check]
|
||||||
|
ManIns((Notify user about<br>manual install<br>is needed))
|
||||||
|
R((Notify user<br>about restart))
|
||||||
|
ManUp((Notify user about<br>manual update))
|
||||||
|
NF((Notify user about<br>force update))
|
||||||
|
|
||||||
|
ManUp -->|Install| InstFront[Install]
|
||||||
|
InstFront -->|Ok| R
|
||||||
|
InstFront -->|Error| ManIns
|
||||||
|
|
||||||
|
U --> CheckFront[Check online]
|
||||||
|
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
|
||||||
|
CheckFront -->|Error| ManIns
|
||||||
|
|
||||||
|
IAFront -->|No| Latest((Notify user<br>has latest version))
|
||||||
|
IAFront -->|Yes| CanInstall{Can update?}
|
||||||
|
CanInstall -->|No| ManIns
|
||||||
|
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
|
||||||
|
NotifOrInstall -->|Manual| ManUp
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
subgraph Backend
|
||||||
|
W[Wait for next check]
|
||||||
|
|
||||||
|
W --> Check[Check online]
|
||||||
|
|
||||||
|
Check --> NV{Has new<br>version?}
|
||||||
|
Check -->|Error| W
|
||||||
|
NV -->|No new version| W
|
||||||
|
IA{Is install<br>applicable?}
|
||||||
|
NV -->|New version<br>available| IA
|
||||||
|
IA -->|Local rollout<br>not enough| W
|
||||||
|
IA -->|Yes| AU{Is automatic\nupdate enabled?}
|
||||||
|
|
||||||
|
AU -->|Yes| CanUp{Can update?}
|
||||||
|
CanUp -->|No| ManIns
|
||||||
|
|
||||||
|
CanUp -->|Yes| Ins[Install]
|
||||||
|
Ins -->|Error| ManIns
|
||||||
|
Ins -->|Ok| R
|
||||||
|
|
||||||
|
AU -->|No| ManUp
|
||||||
|
ManUp -->|Ignore| W
|
||||||
|
|
||||||
|
|
||||||
|
F[Force update]
|
||||||
|
F --> NF
|
||||||
|
end
|
||||||
|
|
||||||
|
ManIns --> Web[Open web page]
|
||||||
|
NF --> Web
|
||||||
|
ManUp --> Web
|
||||||
|
R --> Re[Restart]
|
||||||
|
NF --> Q[Quit bridge]
|
||||||
|
NotifOrInstall -->|Automatic| W
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The non-trivial is to combine the update with setting change:
|
||||||
|
* turn off/on automatic in-app updates
|
||||||
|
* change from stable to beta or back
|
||||||
|
|
||||||
|
_TODO fill flow chart details_
|
||||||
|
|
||||||
|
|
||||||
|
We are not support downgrade functionality. Only some circumstances can lead to
|
||||||
|
downgrading the app version.
|
||||||
|
|
||||||
|
_TODO fill flow chart details_
|
||||||
29
go.mod
29
go.mod
@ -2,14 +2,12 @@ module github.com/ProtonMail/proton-bridge/v3
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
toolchain go1.21.9
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.2.0
|
github.com/Masterminds/semver/v3 v3.2.0
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2
|
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240405124415-8f966ca60436
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
@ -46,19 +44,15 @@ require (
|
|||||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/oauth2 v0.7.0
|
golang.org/x/sys v0.16.0
|
||||||
golang.org/x/sys v0.19.0
|
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/api v0.114.0
|
|
||||||
google.golang.org/grpc v1.56.3
|
google.golang.org/grpc v1.56.3
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.31.0
|
||||||
howett.net/plist v1.0.0
|
howett.net/plist v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.19.1 // indirect
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||||
@ -68,7 +62,7 @@ require (
|
|||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/chzyer/test v1.0.0 // indirect
|
github.com/chzyer/test v1.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||||
@ -86,11 +80,8 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||||
@ -102,7 +93,7 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
@ -119,19 +110,17 @@ require (
|
|||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.22.0 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.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/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
||||||
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
||||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
||||||
|
|||||||
95
go.sum
95
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.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
|
||||||
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
|
||||||
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
@ -34,19 +27,21 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
|||||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2 h1:iZjKvjb6VkGb52ZaBBiXC1MGYJN4C/S97JfppdzpMHQ=
|
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd h1:AjJsf5xQGmZPg6GLn+wB+eBoGRopJlG70lQBfSyfX+M=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
|
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
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 h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47 h1:a+3dOyIxJEslN5HxyICM8flY9lnCyJupXNcv6fUaivA=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240226161523-ec58ed7ea4b9 h1:tcQpGQljNsZmfuA6L4hAzio8/AIx5OXcU2JUdyX/qxw=
|
||||||
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.20240226161523-ec58ed7ea4b9/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240405124415-8f966ca60436 h1:ej+W9+UQlb2owkT5arCegmUFkicwesMyFHgBp/wwNg8=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240405124415-8f966ca60436/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||||
@ -81,7 +76,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.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
@ -95,10 +89,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
|||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
@ -150,10 +142,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-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
@ -206,8 +194,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/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@ -216,13 +202,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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
@ -230,10 +209,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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -244,15 +219,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 h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@ -344,8 +314,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
@ -398,7 +368,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_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
@ -480,8 +449,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.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
@ -496,13 +463,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -545,7 +511,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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@ -557,14 +522,11 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
|
||||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -595,7 +557,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-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -616,8 +577,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@ -655,7 +616,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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
@ -680,14 +640,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.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
|
|
||||||
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -697,31 +653,17 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -745,7 +687,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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
|||||||
@ -75,21 +75,15 @@ const (
|
|||||||
|
|
||||||
flagLogIMAP = "log-imap"
|
flagLogIMAP = "log-imap"
|
||||||
flagLogSMTP = "log-smtp"
|
flagLogSMTP = "log-smtp"
|
||||||
|
|
||||||
flagEnableKeychainTest = "enable-keychain-test"
|
|
||||||
flagDisableKeychainTest = "disable-keychain-test"
|
|
||||||
|
|
||||||
flagSoftwareRenderer = "software-renderer"
|
|
||||||
flagSetSoftwareRenderer = "set-software-renderer"
|
|
||||||
flagSetHardwareRenderer = "set-hardware-renderer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hidden flags.
|
// Hidden flags.
|
||||||
const (
|
const (
|
||||||
flagLauncher = "launcher"
|
flagLauncher = "launcher"
|
||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
flagParentPID = "parent-pid"
|
flagParentPID = "parent-pid"
|
||||||
FlagSessionID = "session-id"
|
flagSoftwareRenderer = "software-renderer"
|
||||||
|
FlagSessionID = "session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -97,23 +91,6 @@ const (
|
|||||||
appShortName = "bridge"
|
appShortName = "bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the two flags below have been deprecated by BRIDGE-281. We however keep them so that bridge does not error if they are passed on startup.
|
|
||||||
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
|
||||||
Name: flagEnableKeychainTest,
|
|
||||||
Usage: "This flag is deprecated and does nothing",
|
|
||||||
Value: false,
|
|
||||||
DisableDefaultText: true,
|
|
||||||
Hidden: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
|
||||||
Name: flagDisableKeychainTest,
|
|
||||||
Usage: "This flag is deprecated and does nothing",
|
|
||||||
Value: false,
|
|
||||||
DisableDefaultText: true,
|
|
||||||
Hidden: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
|
||||||
@ -163,24 +140,6 @@ func New() *cli.App {
|
|||||||
Name: flagLogSMTP,
|
Name: flagLogSMTP,
|
||||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
|
||||||
Usage: "Use software rendering of the GUI for the current execution of the application",
|
|
||||||
Value: false,
|
|
||||||
DisableDefaultText: true,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagSetSoftwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
|
||||||
Usage: "Toggle software rendering of the GUI for the current and future executions of the application",
|
|
||||||
Value: false,
|
|
||||||
DisableDefaultText: true,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagSetHardwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
|
||||||
Usage: "Toggle hardware rendering of the GUI for the current and future executions of the application",
|
|
||||||
Value: false,
|
|
||||||
DisableDefaultText: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Hidden flags
|
// Hidden flags
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
@ -199,26 +158,18 @@ func New() *cli.App {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Value: -1,
|
Value: -1,
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||||
|
Usage: "GUI is using software renderer",
|
||||||
|
Hidden: true,
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: FlagSessionID,
|
Name: FlagSessionID,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// We override the default help value because we want "Show" to be capitalized
|
|
||||||
cli.HelpFlag = &cli.BoolFlag{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Show help",
|
|
||||||
DisableDefaultText: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if onMacOS() {
|
|
||||||
// The two flags below were introduced for BRIDGE-116, and are available only on macOS.
|
|
||||||
// They have been later removed fro BRIDGE-281.
|
|
||||||
app.Flags = append(app.Flags, cliFlagEnableKeychainTest, cliFlagDisableKeychainTest)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
|
|
||||||
return app
|
return app
|
||||||
@ -289,7 +240,7 @@ func run(c *cli.Context) error {
|
|||||||
// Look for available keychains
|
// Look for available keychains
|
||||||
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
||||||
// Unlock the encrypted vault.
|
// 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() {
|
if !v.Migrated() {
|
||||||
// Migrate old settings into the vault.
|
// Migrate old settings into the vault.
|
||||||
if err := migrateOldSettings(v); err != nil {
|
if err := migrateOldSettings(v); err != nil {
|
||||||
@ -575,7 +526,3 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func onMacOS() bool {
|
|
||||||
return runtime.GOOS == "darwin"
|
|
||||||
}
|
|
||||||
|
|||||||
@ -25,18 +25,17 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithVault(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")
|
logrus.Debug("Creating vault")
|
||||||
defer logrus.Debug("Vault stopped")
|
defer logrus.Debug("Vault stopped")
|
||||||
|
|
||||||
// Create the encVault.
|
// Create the encVault.
|
||||||
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, panicHandler)
|
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create vault: %w", err)
|
return fmt.Errorf("could not create vault: %w", err)
|
||||||
}
|
}
|
||||||
@ -58,7 +57,7 @@ func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keycha
|
|||||||
return fn(encVault, insecure, corrupt != nil)
|
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()
|
vaultDir, err := locations.ProvideSettingsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||||
@ -72,16 +71,6 @@ func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychai
|
|||||||
)
|
)
|
||||||
|
|
||||||
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||||
if reporter != nil {
|
|
||||||
if rerr := reporter.ReportMessageWithContext("Could not load/create vault key", map[string]any{
|
|
||||||
"keychainDefaultHelper": keychains.GetDefaultHelper(),
|
|
||||||
"keychainUsableHelpersLength": len(keychains.GetHelpers()),
|
|
||||||
"error": err.Error(),
|
|
||||||
}); rerr != nil {
|
|
||||||
logrus.WithError(err).Info("Failed to report keychain issue to Sentry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithError(err).Error("Could not load/create vault key")
|
logrus.WithError(err).Error("Could not load/create vault key")
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
||||||
|
|||||||
@ -24,10 +24,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -45,11 +41,8 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/notifications"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
@ -58,8 +51,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var usernameChangeRegex = regexp.MustCompile(`^/Users/([^/]+)/`)
|
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
||||||
vault *vault.Vault
|
vault *vault.Vault
|
||||||
@ -139,15 +130,6 @@ type Bridge struct {
|
|||||||
|
|
||||||
serverManager *imapsmtpserver.Service
|
serverManager *imapsmtpserver.Service
|
||||||
syncService *syncservice.Service
|
syncService *syncservice.Service
|
||||||
|
|
||||||
// unleashService is responsible for polling the feature flags and caching
|
|
||||||
unleashService *unleash.Service
|
|
||||||
|
|
||||||
// observabilityService is responsible for handling calls to the observability system
|
|
||||||
observabilityService *observability.Service
|
|
||||||
|
|
||||||
// notificationStore is used for notification deduplication
|
|
||||||
notificationStore *notifications.Store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var logPkg = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
|
var logPkg = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
|
||||||
@ -265,10 +247,6 @@ func newBridge(
|
|||||||
return nil, fmt.Errorf("failed to create focus service: %w", err)
|
return nil, fmt.Errorf("failed to create focus service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
|
|
||||||
|
|
||||||
observabilityService := observability.NewService(ctx, panicHandler)
|
|
||||||
|
|
||||||
bridge := &Bridge{
|
bridge := &Bridge{
|
||||||
vault: vault,
|
vault: vault,
|
||||||
|
|
||||||
@ -308,13 +286,7 @@ func newBridge(
|
|||||||
lastVersion: lastVersion,
|
lastVersion: lastVersion,
|
||||||
|
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
syncService: syncservice.NewService(panicHandler, observabilityService),
|
syncService: syncservice.NewService(reporter, panicHandler),
|
||||||
|
|
||||||
unleashService: unleashService,
|
|
||||||
|
|
||||||
observabilityService: observabilityService,
|
|
||||||
|
|
||||||
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
||||||
@ -325,12 +297,8 @@ func newBridge(
|
|||||||
reporter,
|
reporter,
|
||||||
uidValidityGenerator,
|
uidValidityGenerator,
|
||||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||||
observabilityService,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check whether username has changed and correct (macOS only)
|
|
||||||
bridge.verifyUsernameChange()
|
|
||||||
|
|
||||||
if err := bridge.serverManager.Init(context.Background(), bridge.tasks, &bridgeEventSubscription{b: bridge}); err != nil {
|
if err := bridge.serverManager.Init(context.Background(), bridge.tasks, &bridgeEventSubscription{b: bridge}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -343,10 +311,6 @@ func newBridge(
|
|||||||
|
|
||||||
bridge.syncService.Run()
|
bridge.syncService.Run()
|
||||||
|
|
||||||
bridge.unleashService.Run()
|
|
||||||
|
|
||||||
bridge.observabilityService.Run(bridge)
|
|
||||||
|
|
||||||
return bridge, nil
|
return bridge, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,9 +438,6 @@ func (bridge *Bridge) GetErrors() []error {
|
|||||||
func (bridge *Bridge) Close(ctx context.Context) {
|
func (bridge *Bridge) Close(ctx context.Context) {
|
||||||
logPkg.Info("Closing bridge")
|
logPkg.Info("Closing bridge")
|
||||||
|
|
||||||
// Stop observability service
|
|
||||||
bridge.observabilityService.Stop()
|
|
||||||
|
|
||||||
// Stop heart beat before closing users.
|
// Stop heart beat before closing users.
|
||||||
bridge.heartbeat.stop()
|
bridge.heartbeat.stop()
|
||||||
|
|
||||||
@ -500,9 +461,6 @@ func (bridge *Bridge) Close(ctx context.Context) {
|
|||||||
// Close the focus service.
|
// Close the focus service.
|
||||||
bridge.focusService.Close()
|
bridge.focusService.Close()
|
||||||
|
|
||||||
// Close the unleash service.
|
|
||||||
bridge.unleashService.Close()
|
|
||||||
|
|
||||||
// Close the watchers.
|
// Close the watchers.
|
||||||
bridge.watchersLock.Lock()
|
bridge.watchersLock.Lock()
|
||||||
defer bridge.watchersLock.Unlock()
|
defer bridge.watchersLock.Unlock()
|
||||||
@ -581,49 +539,6 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) Repair() {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
userIDs := bridge.GetUserIDs()
|
|
||||||
|
|
||||||
for _, userID := range userIDs {
|
|
||||||
logPkg.Info("Initiating repair for userID:", userID)
|
|
||||||
|
|
||||||
userInfo, err := bridge.GetUserInfo(userID)
|
|
||||||
if err != nil {
|
|
||||||
logPkg.WithError(err).Error("Failed getting user info for repair; ID:", userID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if userInfo.State != Connected {
|
|
||||||
logPkg.Info("User is not connected. Repair will be executed on following successful log in.", userID)
|
|
||||||
if err := bridge.vault.GetUser(userID, func(user *vault.User) {
|
|
||||||
if err := user.SetShouldSync(true); err != nil {
|
|
||||||
logPkg.WithError(err).Error("Failed setting vault should sync for user:", userID)
|
|
||||||
}
|
|
||||||
}); err != nil {
|
|
||||||
logPkg.WithError(err).Error("Unable to get user vault when scheduling repair:", userID)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bridgeUser, ok := bridge.users[userID]
|
|
||||||
if !ok {
|
|
||||||
logPkg.Info("UserID does not exist in bridge user map", userID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(userID string) {
|
|
||||||
defer wg.Done()
|
|
||||||
if err = bridgeUser.TriggerRepair(); err != nil {
|
|
||||||
logPkg.WithError(err).Error("Failed re-syncing IMAP for userID", userID)
|
|
||||||
}
|
|
||||||
}(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
||||||
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
|
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -636,105 +551,10 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b time.Duration) time.Duration { //nolint:predeclared
|
func min(a, b time.Duration) time.Duration {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) HasAPIConnection() bool {
|
|
||||||
return bridge.api.GetStatus() == proton.StatusUp
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyUsernameChange - works only on macOS
|
|
||||||
// it attempts to check whether a username change has taken place by comparing the gluon DB path (which is static and provided by bridge)
|
|
||||||
// to the gluon Cache path - which can be modified by the user and is stored in the vault;
|
|
||||||
// if a username discrepancy is detected, and the cache folder does not exist with the "old" username
|
|
||||||
// then we verify whether the gluon cache exists using the "new" username (provided by the DB path in this case)
|
|
||||||
// if so we modify the cache directory in the user vault.
|
|
||||||
func (bridge *Bridge) verifyUsernameChange() {
|
|
||||||
if runtime.GOOS != "darwin" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gluonDBPath, err := bridge.GetGluonDataDir()
|
|
||||||
if err != nil {
|
|
||||||
logPkg.WithError(err).Error("Failed to get gluon db path")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gluonCachePath := bridge.GetGluonCacheDir()
|
|
||||||
// If the cache folder exists even on another user account or is in `/Users/Shared` we would still be able to access it
|
|
||||||
// though it depends on the permissions; this is an edge-case.
|
|
||||||
if _, err := os.Stat(gluonCachePath); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newCacheDir := GetUpdatedCachePath(gluonDBPath, gluonCachePath)
|
|
||||||
if newCacheDir == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(newCacheDir); err == nil {
|
|
||||||
logPkg.Info("Username change detected. Trying to restore gluon cache directory")
|
|
||||||
if err = bridge.vault.SetGluonDir(newCacheDir); err != nil {
|
|
||||||
logPkg.WithError(err).Error("Failed to restore gluon cache directory")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logPkg.Info("Successfully restored gluon cache directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUpdatedCachePath(gluonDBPath, gluonCachePath string) string {
|
|
||||||
// If gluon cache is moved to an external drive; regex find will fail; as is expected
|
|
||||||
cachePathMatches := usernameChangeRegex.FindStringSubmatch(gluonCachePath)
|
|
||||||
if cachePathMatches == nil || len(cachePathMatches) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheUsername := cachePathMatches[1]
|
|
||||||
dbPathMatches := usernameChangeRegex.FindStringSubmatch(gluonDBPath)
|
|
||||||
if dbPathMatches == nil || len(dbPathMatches) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
dbUsername := dbPathMatches[1]
|
|
||||||
if cacheUsername == dbUsername {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Replace(gluonCachePath, "/Users/"+cacheUsername+"/", "/Users/"+dbUsername+"/", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) GetFeatureFlagValue(key string) bool {
|
|
||||||
return bridge.unleashService.GetFlagValue(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
|
|
||||||
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
|
|
||||||
logPkg.WithFields(logrus.Fields{
|
|
||||||
"err": err,
|
|
||||||
"sentryMessage": message,
|
|
||||||
"messageCtx": messageCtx,
|
|
||||||
}).Info("Error occurred when sending Report to Sentry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers is only used for testing purposes.
|
|
||||||
func (bridge *Bridge) GetUsers() map[string]*user.User {
|
|
||||||
return bridge.users
|
|
||||||
}
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@ func init() {
|
|||||||
|
|
||||||
func TestBridge_ConnStatus(t *testing.T) {
|
func TestBridge_ConnStatus(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Get a stream of connection status events.
|
// Get a stream of connection status events.
|
||||||
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -125,7 +125,7 @@ func TestBridge_TLSIssue(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Focus(t *testing.T) {
|
func TestBridge_Focus(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Get a stream of TLS issue events.
|
// Get a stream of TLS issue events.
|
||||||
raiseCh, done := bridge.GetEvents(events.Raise{})
|
raiseCh, done := bridge.GetEvents(events.Raise{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -156,7 +156,7 @@ func TestBridge_UserAgent(t *testing.T) {
|
|||||||
calls = append(calls, call)
|
calls = append(calls, call)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Set the platform to something other than the default.
|
// Set the platform to something other than the default.
|
||||||
bridge.SetCurrentPlatform("platform")
|
bridge.SetCurrentPlatform("platform")
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ func TestBridge_UserAgent_Persistence(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ func TestBridge_UserAgent_Persistence(t *testing.T) {
|
|||||||
require.Contains(t, b.GetCurrentUserAgent(), "MyFancyClient/0.1.2")
|
require.Contains(t, b.GetCurrentUserAgent(), "MyFancyClient/0.1.2")
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
currentUserAgent := bridge.GetCurrentUserAgent()
|
currentUserAgent := bridge.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, "MyFancyClient/0.1.2")
|
require.Contains(t, currentUserAgent, "MyFancyClient/0.1.2")
|
||||||
})
|
})
|
||||||
@ -225,7 +225,7 @@ func TestBridge_UserAgentFromUnknownClient(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ func TestBridge_UserAgentFromSMTPClient(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
@ -305,7 +305,7 @@ func TestBridge_UserAgentFromIMAPID(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
||||||
|
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
@ -365,13 +365,13 @@ func TestBridge_Cookies(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge again and check that it uses the same session ID.
|
// Start bridge again and check that it uses the same session ID.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -484,7 +484,7 @@ func TestBridge_ManualUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_ForceUpdate(t *testing.T) {
|
func TestBridge_ForceUpdate(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Get a stream of update events.
|
// Get a stream of update events.
|
||||||
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -507,7 +507,7 @@ func TestBridge_BadVaultKey(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login a user.
|
// Login a user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -515,17 +515,17 @@ func TestBridge_BadVaultKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with the correct vault key -- it should load the users correctly.
|
// Start bridge with the correct vault key -- it should load the users correctly.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -535,7 +535,7 @@ func TestBridge_MissingGluonStore(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
var gluonDir string
|
var gluonDir string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -550,7 +550,7 @@ func TestBridge_MissingGluonStore(t *testing.T) {
|
|||||||
require.NoError(t, os.RemoveAll(gluonDir))
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon store dir; there should be no error.
|
// Bridge starts but can't find the gluon store dir; there should be no error.
|
||||||
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -560,7 +560,7 @@ func TestBridge_MissingGluonDatabase(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
var gluonDir string
|
var gluonDir string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -573,7 +573,7 @@ func TestBridge_MissingGluonDatabase(t *testing.T) {
|
|||||||
require.NoError(t, os.RemoveAll(gluonDir))
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon database dir; there should be no error.
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -587,7 +587,7 @@ func TestBridge_AddressWithoutKeys(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer m.Close()
|
defer m.Close()
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Watch for sync finished event.
|
// Watch for sync finished event.
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
@ -663,7 +663,7 @@ func TestBridge_FactoryReset(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_InitGluonDirectory(t *testing.T) {
|
func TestBridge_InitGluonDirectory(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
configDir, err := b.GetGluonDataDir()
|
configDir, err := b.GetGluonDataDir()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -678,7 +678,7 @@ func TestBridge_InitGluonDirectory(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginFailed(t *testing.T) {
|
func TestBridge_LoginFailed(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{}))
|
failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -706,7 +706,7 @@ func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
newCacheDir := t.TempDir()
|
newCacheDir := t.TempDir()
|
||||||
currentCacheDir := b.GetGluonCacheDir()
|
currentCacheDir := b.GetGluonCacheDir()
|
||||||
configDir, err := b.GetGluonDataDir()
|
configDir, err := b.GetGluonDataDir()
|
||||||
@ -772,7 +772,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Log the user in with its first address.
|
// Log the user in with its first address.
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
@ -800,7 +800,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) {
|
|||||||
require.NoError(t, c.OrderAddresses(ctx, proton.OrderAddressesReq{AddressIDs: []string{aliasID, addrID}}))
|
require.NoError(t, c.OrderAddresses(ctx, proton.OrderAddressesReq{AddressIDs: []string{aliasID, addrID}}))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// We should still see 10 messages in the inbox.
|
// We should still see 10 messages in the inbox.
|
||||||
info, err := b.GetUserInfo(userID)
|
info, err := b.GetUserInfo(userID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -1077,57 +1077,3 @@ func waitForIMAPServerStopped(b *bridge.Bridge) *eventWaiter {
|
|||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridge_GetUpdatedCachePath(t *testing.T) {
|
|
||||||
type TestData struct {
|
|
||||||
gluonDBPath string
|
|
||||||
gluonCachePath string
|
|
||||||
shouldChange bool
|
|
||||||
}
|
|
||||||
|
|
||||||
dataArr := []TestData{
|
|
||||||
{
|
|
||||||
gluonDBPath: "/Users/test/",
|
|
||||||
gluonCachePath: "/Users/test/gluon",
|
|
||||||
shouldChange: false,
|
|
||||||
}, {
|
|
||||||
gluonDBPath: "/Users/test/",
|
|
||||||
gluonCachePath: "/Users/tester/gluon",
|
|
||||||
shouldChange: true,
|
|
||||||
}, {
|
|
||||||
gluonDBPath: "/Users/testing/",
|
|
||||||
gluonCachePath: "/Users/test/gluon",
|
|
||||||
shouldChange: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gluonDBPath: "/Users/testing/",
|
|
||||||
gluonCachePath: "/Users/test/gluon",
|
|
||||||
shouldChange: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gluonDBPath: "/Users/testing/",
|
|
||||||
gluonCachePath: "/Volumes/test/gluon",
|
|
||||||
shouldChange: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gluonDBPath: "/Volumes/test/",
|
|
||||||
gluonCachePath: "/Users/test/gluon",
|
|
||||||
shouldChange: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gluonDBPath: "/XXX/test/",
|
|
||||||
gluonCachePath: "/Users/test/gluon",
|
|
||||||
shouldChange: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gluonDBPath: "/XXX/test/",
|
|
||||||
gluonCachePath: "/YYY/test/gluon",
|
|
||||||
shouldChange: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, el := range dataArr {
|
|
||||||
newCachePath := bridge.GetUpdatedCachePath(el.gluonDBPath, el.gluonCachePath)
|
|
||||||
require.Equal(t, el.shouldChange, newCachePath != "" && newCachePath != el.gluonCachePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,6 +80,12 @@ func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.ReportBugSent()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
|
||||||
// if we have a token we can append more attachment to the bugReport
|
// if we have a token we can append more attachment to the bugReport
|
||||||
for i, att := range attachments {
|
for i, att := range attachments {
|
||||||
if i == 0 && report.IncludeLogs {
|
if i == 0 && report.IncludeLogs {
|
||||||
|
|||||||
@ -15,29 +15,32 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package telemetry
|
package bridge
|
||||||
|
|
||||||
type RepairData struct {
|
import (
|
||||||
MeasurementGroup string
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
Event string
|
)
|
||||||
Values map[string]string
|
|
||||||
Dimensions map[string]string
|
func (bridge *Bridge) ReportBugClicked() {
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.ReportBugClicked()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepairTriggerData() RepairData {
|
func (bridge *Bridge) AutoconfigUsed(client string) {
|
||||||
return RepairData{
|
safe.RLock(func() {
|
||||||
MeasurementGroup: "bridge.any.repair",
|
for _, user := range bridge.users {
|
||||||
Event: "repair_trigger",
|
user.AutoconfigUsed(client)
|
||||||
Values: map[string]string{},
|
}
|
||||||
Dimensions: map[string]string{},
|
}, bridge.usersLock)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepairDeferredTriggerData() RepairData {
|
func (bridge *Bridge) ExternalLinkClicked(article string) {
|
||||||
return RepairData{
|
safe.RLock(func() {
|
||||||
MeasurementGroup: "bridge.any.repair",
|
for _, user := range bridge.users {
|
||||||
Event: "repair_deferred_trigger",
|
user.ExternalLinkClicked(article)
|
||||||
Values: map[string]string{},
|
}
|
||||||
Dimensions: map[string]string{},
|
}, bridge.usersLock)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -73,15 +73,15 @@ func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager
|
|||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
if user.GetAddressMode() == vault.SplitMode {
|
if user.GetAddressMode() == vault.SplitMode {
|
||||||
splitMode = true
|
splitMode = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
h.SetUserPlan(user.GetUserPlanName())
|
|
||||||
}
|
}
|
||||||
var numberConnectedAccounts = len(bridge.users)
|
var nbAccount = len(bridge.users)
|
||||||
h.SetNumberConnectedAccounts(numberConnectedAccounts)
|
h.SetNbAccount(nbAccount)
|
||||||
h.SetSplitMode(splitMode)
|
h.SetSplitMode(splitMode)
|
||||||
|
|
||||||
// Do not try to send if there is no user yet.
|
// Do not try to send if there is no user yet.
|
||||||
if numberConnectedAccounts > 0 {
|
if nbAccount > 0 {
|
||||||
defer h.start()
|
defer h.start()
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|||||||
@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import "github.com/sirupsen/logrus"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||||
return bridge.identifier.GetUserAgent()
|
return bridge.identifier.GetUserAgent()
|
||||||
@ -32,8 +30,6 @@ func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
|||||||
func (bridge *Bridge) setUserAgent(name, version string) {
|
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||||
currentUserAgent := bridge.identifier.GetClientString()
|
currentUserAgent := bridge.identifier.GetClientString()
|
||||||
|
|
||||||
bridge.heartbeat.SetContactedByAppleNotes(name)
|
|
||||||
|
|
||||||
bridge.identifier.SetClient(name, version)
|
bridge.identifier.SetClient(name, version)
|
||||||
|
|
||||||
newUserAgent := bridge.identifier.GetClientString()
|
newUserAgent := bridge.identifier.GetClientString()
|
||||||
@ -58,7 +54,6 @@ func (b *bridgeUserAgentUpdater) HasClient() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
||||||
b.heartbeat.SetContactedByAppleNotes(name)
|
|
||||||
b.identifier.SetClient(name, version)
|
b.identifier.SetClient(name, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import (
|
|||||||
imapEvents "github.com/ProtonMail/gluon/events"
|
imapEvents "github.com/ProtonMail/gluon/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -94,10 +93,6 @@ func (b *bridgeIMAPSettings) LogServer() bool {
|
|||||||
return b.b.logIMAPServer
|
return b.b.logIMAPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridgeIMAPSettings) DisableIMAPAuthenticate() bool {
|
|
||||||
return b.b.unleashService.GetFlagValue(unleash.IMAPAuthenticateCommandDisabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bridgeIMAPSettings) Port() int {
|
func (b *bridgeIMAPSettings) Port() int {
|
||||||
return b.b.vault.GetIMAPPort()
|
return b.b.vault.GetIMAPPort()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package bridge_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
|
||||||
"github.com/ProtonMail/go-proton-api/server"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBridge_Observability(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) {
|
|
||||||
throttlePeriod := time.Millisecond * 500
|
|
||||||
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.PushObservabilityMetric(testMetric)
|
|
||||||
time.Sleep(time.Millisecond * 50) // Wait for the metric to be sent
|
|
||||||
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
time.Sleep(time.Millisecond * 5) // Minor delay between each so our tests aren't flaky
|
|
||||||
bridge.PushObservabilityMetric(testMetric)
|
|
||||||
}
|
|
||||||
// We should still have only 1 metric sent as the throttleDuration has not passed
|
|
||||||
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
|
||||||
|
|
||||||
// Wait for throttle duration to pass; we should have our remaining metrics posted
|
|
||||||
time.Sleep(throttlePeriod)
|
|
||||||
require.Equal(t, 11, len(s.GetObservabilityStatistics().Metrics))
|
|
||||||
|
|
||||||
// Wait for the throttle duration to reset; i.e. so we have enough time to send a request immediately
|
|
||||||
time.Sleep(throttlePeriod)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
time.Sleep(time.Millisecond * 5)
|
|
||||||
bridge.PushObservabilityMetric(testMetric)
|
|
||||||
}
|
|
||||||
// We should only have one additional metric sent immediately
|
|
||||||
require.Equal(t, 12, len(s.GetObservabilityStatistics().Metrics))
|
|
||||||
|
|
||||||
// Wait for the others to be sent
|
|
||||||
time.Sleep(throttlePeriod)
|
|
||||||
require.Equal(t, 21, len(s.GetObservabilityStatistics().Metrics))
|
|
||||||
|
|
||||||
// Spam the endpoint a bit
|
|
||||||
for i := 0; i < 300; i++ {
|
|
||||||
if i < 200 {
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
}
|
|
||||||
bridge.PushObservabilityMetric(testMetric)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we've sent all metrics
|
|
||||||
time.Sleep(throttlePeriod)
|
|
||||||
|
|
||||||
observabilityStats := s.GetObservabilityStatistics()
|
|
||||||
require.Equal(t, 321, len(observabilityStats.Metrics))
|
|
||||||
|
|
||||||
// Verify that each request had a throttleDuration time difference between each request
|
|
||||||
for i := 0; i < len(observabilityStats.RequestTime)-1; i++ {
|
|
||||||
tOne := observabilityStats.RequestTime[i]
|
|
||||||
tTwo := observabilityStats.RequestTime[i+1]
|
|
||||||
require.True(t, tTwo.Sub(tOne).Abs() > throttlePeriod)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBridge_Report(t *testing.T) {
|
func TestBridge_Report(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *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{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -55,6 +56,12 @@ func TestBridge_Report(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { require.NoError(t, conn.Close()) }()
|
defer func() { require.NoError(t, conn.Close()) }()
|
||||||
|
|
||||||
|
// Sending garbage to the IMAP port should cause the bridge to report it.
|
||||||
|
mocks.Reporter.EXPECT().ReportMessageWithContext(
|
||||||
|
gomock.Eq("Failed to parse IMAP command"),
|
||||||
|
gomock.Any(),
|
||||||
|
).Return(nil)
|
||||||
|
|
||||||
// Read lines from the IMAP port.
|
// Read lines from the IMAP port.
|
||||||
lineCh := liner.New(conn).Lines(func() error { return nil })
|
lineCh := liner.New(conn).Lines(func() error { return nil })
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import (
|
|||||||
|
|
||||||
func TestServerManager_ServersStartWithBridge(t *testing.T) {
|
func TestServerManager_ServersStartWithBridge(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, imapClient.Logout())
|
require.NoError(t, imapClient.Logout())
|
||||||
@ -48,7 +48,7 @@ func TestServerManager_ServersStartWithBridge(t *testing.T) {
|
|||||||
|
|
||||||
func TestServerManager_ServersKeepsRunningfterUserLogsOut(t *testing.T) {
|
func TestServerManager_ServersKeepsRunningfterUserLogsOut(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func TestServerManager_ServersDoNotStopWhenThereIsStillOneActiveUser(t *testing.
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func TestServerManager_ServersDoNotStopWhenThereIsStillOneActiveUser(t *testing.
|
|||||||
|
|
||||||
func TestServerManager_NetworkLossStopsServers(t *testing.T) {
|
func TestServerManager_NetworkLossStopsServers(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
imapWaiter := waitForIMAPServerReady(bridge)
|
||||||
defer imapWaiter.Done()
|
defer imapWaiter.Done()
|
||||||
|
|
||||||
|
|||||||
@ -318,10 +318,11 @@ func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleL
|
|||||||
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
||||||
// which we need at next startup to decrypt the vault.
|
// which we need at next startup to decrypt the vault.
|
||||||
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
||||||
|
useTelemetry := !bridge.GetTelemetryDisabled()
|
||||||
// Delete all the users.
|
// Delete all the users.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
bridge.logoutUser(ctx, user, true, true)
|
bridge.logoutUser(ctx, user, true, true, useTelemetry)
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
func TestBridge_Settings_GluonDir(t *testing.T) {
|
func TestBridge_Settings_GluonDir(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Create a user.
|
// Create a user.
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -57,7 +57,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
userID, addrID, err := s.CreateUser("imap", password)
|
userID, addrID, err := s.CreateUser("imap", password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 200)
|
createNumMessages(ctx, t, c, addrID, labelID, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Create a new location for the Gluon data.
|
// Create a new location for the Gluon data.
|
||||||
newGluonDir := t.TempDir()
|
newGluonDir := t.TempDir()
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_IMAPPort(t *testing.T) {
|
func TestBridge_Settings_IMAPPort(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
curPort := bridge.GetIMAPPort()
|
curPort := bridge.GetIMAPPort()
|
||||||
|
|
||||||
// Set the port to 1144.
|
// Set the port to 1144.
|
||||||
@ -110,7 +110,7 @@ func TestBridge_Settings_IMAPPort(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// By default, IMAP SSL is disabled.
|
// By default, IMAP SSL is disabled.
|
||||||
require.False(t, bridge.GetIMAPSSL())
|
require.False(t, bridge.GetIMAPSSL())
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_SMTPPort(t *testing.T) {
|
func TestBridge_Settings_SMTPPort(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
curPort := bridge.GetSMTPPort()
|
curPort := bridge.GetSMTPPort()
|
||||||
|
|
||||||
// Set the port to 1024.
|
// Set the port to 1024.
|
||||||
@ -142,7 +142,7 @@ func TestBridge_Settings_SMTPPort(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_SMTPSSL(t *testing.T) {
|
func TestBridge_Settings_SMTPSSL(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// By default, SMTP SSL is disabled.
|
// By default, SMTP SSL is disabled.
|
||||||
require.False(t, bridge.GetSMTPSSL())
|
require.False(t, bridge.GetSMTPSSL())
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ func TestBridge_Settings_Autostart(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_FirstStart(t *testing.T) {
|
func TestBridge_Settings_FirstStart(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// By default, first start is true.
|
// By default, first start is true.
|
||||||
require.True(t, bridge.GetFirstStart())
|
require.True(t, bridge.GetFirstStart())
|
||||||
|
|
||||||
|
|||||||
@ -232,7 +232,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
var total uint64
|
var total uint64
|
||||||
|
|
||||||
// The initial user should be fully synced.
|
// The initial user should be fully synced.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Now let's remove the user and stop the network at 2/3 of the data.
|
// Now let's remove the user and stop the network at 2/3 of the data.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -254,7 +254,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(2 * total / 3)
|
netCtl.SetReadLimit(2 * total / 3)
|
||||||
|
|
||||||
// Login the user; its sync should fail.
|
// Login the user; its sync should fail.
|
||||||
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{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -592,7 +592,7 @@ func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -625,7 +625,7 @@ func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
|||||||
require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600))
|
require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon database dir; there should be no error.
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -28,12 +28,11 @@ type Locator interface {
|
|||||||
ProvideLogsPath() (string, error)
|
ProvideLogsPath() (string, error)
|
||||||
ProvideGluonCachePath() (string, error)
|
ProvideGluonCachePath() (string, error)
|
||||||
ProvideGluonDataPath() (string, error)
|
ProvideGluonDataPath() (string, error)
|
||||||
|
ProvideStatsPath() (string, error)
|
||||||
GetLicenseFilePath() string
|
GetLicenseFilePath() string
|
||||||
GetDependencyLicensesLink() string
|
GetDependencyLicensesLink() string
|
||||||
Clear(...string) error
|
Clear(...string) error
|
||||||
ProvideIMAPSyncConfigPath() (string, error)
|
ProvideIMAPSyncConfigPath() (string, error)
|
||||||
ProvideUnleashCachePath() (string, error)
|
|
||||||
ProvideNotificationsCachePath() (string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyController interface {
|
type ProxyController interface {
|
||||||
|
|||||||
@ -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 bridge_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
|
||||||
"github.com/ProtonMail/go-proton-api/server"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_UnleashService(t *testing.T) {
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
||||||
unleash.ModifyPollPeriodAndJitter(500*time.Millisecond, 0)
|
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
|
||||||
// Initial startup assumes there is no cached feature flags.
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
|
||||||
|
|
||||||
s.PushFeatureFlag("test-1")
|
|
||||||
s.PushFeatureFlag("test-2")
|
|
||||||
|
|
||||||
// Wait for poll.
|
|
||||||
time.Sleep(time.Millisecond * 700)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
|
||||||
|
|
||||||
s.PushFeatureFlag("test-3")
|
|
||||||
time.Sleep(time.Millisecond * 700) // Wait for poll again
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for Bridge to close.
|
|
||||||
time.Sleep(time.Millisecond * 500)
|
|
||||||
|
|
||||||
// Second instance should have a feature flag cache file available. Therefore, all of the flags should evaluate to true on startup.
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
|
||||||
|
|
||||||
s.DeleteFeatureFlags()
|
|
||||||
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 700)
|
|
||||||
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
|
||||||
|
|
||||||
s.PushFeatureFlag("test-3")
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 700)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
|
||||||
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -21,7 +21,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ProtonMail/gluon/reporter"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
@ -116,17 +115,6 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
|||||||
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, updater.ErrDownloadVerify):
|
|
||||||
// BRIDGE-207: if download or verification fails, we do not want to trigger a manual update. We report in the log and to Sentry
|
|
||||||
// and we fail silently.
|
|
||||||
log.WithError(err).Error("The update could not be installed, but we will fail silently")
|
|
||||||
if reporterErr := bridge.reporter.ReportMessageWithContext(
|
|
||||||
"Cannot download or verify update",
|
|
||||||
reporter.Context{"error": err},
|
|
||||||
); reporterErr != nil {
|
|
||||||
log.WithError(reporterErr).Error("Failed to report update error")
|
|
||||||
}
|
|
||||||
|
|
||||||
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
||||||
log.Info("The update was already installed")
|
log.Info("The update was already installed")
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
@ -256,7 +255,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
|||||||
return ErrNoSuchUser
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false)
|
bridge.logoutUser(ctx, user, true, false, false)
|
||||||
|
|
||||||
bridge.publish(events.UserLoggedOut{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -281,7 +280,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user, ok := bridge.users[userID]; ok {
|
if user, ok := bridge.users[userID]; ok {
|
||||||
bridge.logoutUser(ctx, user, true, true)
|
bridge.logoutUser(ctx, user, true, true, !bridge.GetTelemetryDisabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
||||||
@ -356,10 +355,24 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if doResync {
|
if doResync {
|
||||||
|
if rerr := bridge.reporter.ReportMessageWithContext(
|
||||||
|
"Failed to handle event: feedback resync",
|
||||||
|
reporter.Context{"user_id": userID},
|
||||||
|
); rerr != nil {
|
||||||
|
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||||
|
}
|
||||||
|
|
||||||
return user.BadEventFeedbackResync(ctx)
|
return user.BadEventFeedbackResync(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false)
|
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{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -528,6 +541,11 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
vault *vault.User,
|
vault *vault.User,
|
||||||
isNew bool,
|
isNew bool,
|
||||||
) error {
|
) error {
|
||||||
|
statsPath, err := bridge.locator.ProvideStatsPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get Statistics directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
||||||
@ -542,16 +560,14 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
bridge.panicHandler,
|
bridge.panicHandler,
|
||||||
bridge.vault.GetShowAllMail(),
|
bridge.vault.GetShowAllMail(),
|
||||||
bridge.vault.GetMaxSyncMemory(),
|
bridge.vault.GetMaxSyncMemory(),
|
||||||
|
statsPath,
|
||||||
bridge,
|
bridge,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
&bridgeEventSubscription{b: bridge},
|
&bridgeEventSubscription{b: bridge},
|
||||||
bridge.syncService,
|
bridge.syncService,
|
||||||
bridge.observabilityService,
|
|
||||||
syncSettingsPath,
|
syncSettingsPath,
|
||||||
isNew,
|
isNew,
|
||||||
bridge.notificationStore,
|
|
||||||
bridge.unleashService.GetFlagValue,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create user: %w", err)
|
return fmt.Errorf("failed to create user: %w", err)
|
||||||
@ -584,17 +600,12 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
// Finally, save the user in the bridge.
|
// Finally, save the user in the bridge.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.users[apiUser.ID] = user
|
bridge.users[apiUser.ID] = user
|
||||||
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users))
|
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
// Set user plan if its of a higher rank.
|
|
||||||
bridge.heartbeat.SetUserPlan(user.GetUserPlanName())
|
|
||||||
|
|
||||||
// As we need at least one user to send heartbeat, try to send it.
|
// As we need at least one user to send heartbeat, try to send it.
|
||||||
bridge.heartbeat.start()
|
bridge.heartbeat.start()
|
||||||
|
|
||||||
user.PublishEvent(ctx, events.UserLoadedCheckResync{UserID: user.ID()})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,21 +619,26 @@ func (bridge *Bridge) newVaultUser(
|
|||||||
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logoutUser logs out the given user, optionally logging them out from the API and deleting user related gluon data.
|
// logout logs out the given user, optionally logging them out from the API too.
|
||||||
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData bool) {
|
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData, withTelemetry bool) {
|
||||||
defer delete(bridge.users, user.ID())
|
defer delete(bridge.users, user.ID())
|
||||||
|
|
||||||
|
// if this is actually a remove account
|
||||||
|
if withData && withAPI {
|
||||||
|
user.SendConfigStatusAbort(ctx, withTelemetry)
|
||||||
|
}
|
||||||
|
|
||||||
logUser.WithFields(logrus.Fields{
|
logUser.WithFields(logrus.Fields{
|
||||||
"userID": user.ID(),
|
"userID": user.ID(),
|
||||||
"withAPI": withAPI,
|
"withAPI": withAPI,
|
||||||
"withData": withData,
|
"withData": withData,
|
||||||
}).Debug("Logging out user")
|
}).Debug("Logging out user")
|
||||||
|
|
||||||
if err := user.Logout(ctx, withAPI, withData, bridge.unleashService.GetFlagValue(unleash.UserRemovalGluonDataCleanupDisabled)); err != nil {
|
if err := user.Logout(ctx, withAPI); err != nil {
|
||||||
logUser.WithError(err).Error("Failed to logout user")
|
logUser.WithError(err).Error("Failed to logout user")
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users) - 1)
|
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||||
|
|
||||||
user.Close()
|
user.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
|
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, s.RefreshUser(userID, proton.RefreshMail))
|
require.NoError(t, s.RefreshUser(userID, proton.RefreshMail))
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
|
|
||||||
require.Equal(t, userID, (<-syncCh).UserID)
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
@ -82,7 +82,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||||
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
@ -191,7 +191,7 @@ func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
|
|
||||||
var messageIDs []string
|
var messageIDs []string
|
||||||
@ -368,7 +368,7 @@ func TestBridge_User_Network_NoBadEvents(t *testing.T) {
|
|||||||
_, addrID, err := s.CreateUser("user", password)
|
_, addrID, err := s.CreateUser("user", password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
|
|
||||||
// Create 10 more messages for the user, generating events.
|
// Create 10 more messages for the user, generating events.
|
||||||
@ -454,7 +454,7 @@ func TestBridge_User_UpdateDraft(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -487,7 +487,7 @@ func TestBridge_User_UpdateDraft(t *testing.T) {
|
|||||||
require.Empty(t, draft.ReplyTos)
|
require.Empty(t, draft.ReplyTos)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -513,7 +513,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -545,7 +545,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -573,7 +573,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events.
|
// Process those events.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -581,7 +581,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID))
|
require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID))
|
||||||
|
|
||||||
// Process those events.
|
// Process those events.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -595,7 +595,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -628,7 +628,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -667,7 +667,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process those events; the draft will move to the sent folder and lose the draft flag.
|
// Process those events; the draft will move to the sent folder and lose the draft flag.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -697,7 +697,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
|
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
||||||
|
|
||||||
// Initially we should list the address.
|
// Initially we should list the address.
|
||||||
@ -711,7 +711,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Eventually we shouldn't list the address.
|
// Eventually we shouldn't list the address.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -726,7 +726,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.EnableAddress(ctx, aliasID))
|
require.NoError(t, c.EnableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Eventually we should list the address.
|
// Eventually we should list the address.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -753,7 +753,7 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
||||||
|
|
||||||
// Initially we shouldn't list the address.
|
// Initially we shouldn't list the address.
|
||||||
@ -766,7 +766,7 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_User_HandleParentLabelRename(t *testing.T) {
|
func TestBridge_User_HandleParentLabelRename(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo(username)
|
info, err := bridge.QueryUserInfo(username)
|
||||||
|
|||||||
@ -36,14 +36,15 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
|
|||||||
case events.UserBadEvent:
|
case events.UserBadEvent:
|
||||||
bridge.handleUserBadEvent(ctx, user, event)
|
bridge.handleUserBadEvent(ctx, user, event)
|
||||||
|
|
||||||
case events.UserLoadedCheckResync:
|
case events.UncategorizedEventError:
|
||||||
user.VerifyResyncAndExecute()
|
bridge.handleUncategorizedErrorEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.logoutUser(ctx, user, false, false)
|
bridge.logoutUser(ctx, user, false, false, false)
|
||||||
|
user.ReportConfigStatusFailure("User deauth.")
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,3 +64,12 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e
|
|||||||
user.OnBadEvent(ctx)
|
user.OnBadEvent(ctx)
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEventError) {
|
||||||
|
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle due to uncategorized error", reporter.Context{
|
||||||
|
"error_type": internal.ErrCauseType(event.Error),
|
||||||
|
"error": event.Error,
|
||||||
|
}); rerr != nil {
|
||||||
|
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -35,12 +35,12 @@ import (
|
|||||||
|
|
||||||
func TestBridge_WithoutUsers(t *testing.T) {
|
func TestBridge_WithoutUsers(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -49,7 +49,7 @@ func TestBridge_WithoutUsers(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Login(t *testing.T) {
|
func TestBridge_Login(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -69,7 +69,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
defer func() { _ = dropListener.Close() }()
|
defer func() { _ = dropListener.Close() }()
|
||||||
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -96,7 +96,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The user is eventually connected.
|
// The user is eventually connected.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1
|
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1
|
||||||
@ -107,7 +107,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginTwice(t *testing.T) {
|
func TestBridge_LoginTwice(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -125,7 +125,7 @@ func TestBridge_LoginTwice(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginLogoutLogin(t *testing.T) {
|
func TestBridge_LoginLogoutLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ func TestBridge_LoginLogoutLogin(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginDeleteLogin(t *testing.T) {
|
func TestBridge_LoginDeleteLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ func TestBridge_LoginDeleteLogin(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginDeauthLogin(t *testing.T) {
|
func TestBridge_LoginDeauthLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ func TestBridge_LoginDeauthRestartLogin(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ func TestBridge_LoginDeauthRestartLogin(t *testing.T) {
|
|||||||
require.IsType(t, events.UserDeauth{}, <-eventCh)
|
require.IsType(t, events.UserDeauth{}, <-eventCh)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The user should be disconnected at startup.
|
// The user should be disconnected at startup.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -257,7 +257,7 @@ func TestBridge_LoginExpireLogin(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
s.SetAuthLife(authLife)
|
s.SetAuthLife(authLife)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user. Its auth will only be valid for a short time.
|
// Login the user. Its auth will only be valid for a short time.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ func TestBridge_FailToLoad(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -283,7 +283,7 @@ func TestBridge_FailToLoad(t *testing.T) {
|
|||||||
require.NoError(t, s.RevokeUser(userID))
|
require.NoError(t, s.RevokeUser(userID))
|
||||||
|
|
||||||
// When bridge starts, the user will not be logged in.
|
// When bridge starts, the user will not be logged in.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -295,7 +295,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
|
|||||||
netCtl.Disable()
|
netCtl.Disable()
|
||||||
|
|
||||||
// Start bridge without internet.
|
// Start bridge without internet.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Initially, users are not connected.
|
// Initially, users are not connected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -325,11 +325,11 @@ func TestBridge_LoginRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -340,7 +340,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
|
|||||||
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The user is still disconnected.
|
// The user is still disconnected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -360,7 +360,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -368,7 +368,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
|
|||||||
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The user is still gone.
|
// The user is still gone.
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -384,7 +384,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
|
|
||||||
// Log the user in, wait for it to sync, then log it out.
|
// Log the user in, wait for it to sync, then log it out.
|
||||||
// (We don't want to count message sync data in the test.)
|
// (We don't want to count message sync data in the test.)
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -396,7 +396,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
var total uint64
|
var total uint64
|
||||||
|
|
||||||
// Now that the user is synced, we can measure exactly how much data is needed during login.
|
// Now that the user is synced, we can measure exactly how much data is needed during login.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
total = countBytesRead(netCtl, func() {
|
total = countBytesRead(netCtl, func() {
|
||||||
must(bridge.LoginFull(ctx, username, password, nil, nil))
|
must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
@ -405,7 +405,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Now simulate failing to login.
|
// Now simulate failing to login.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Simulate a partial read.
|
// Simulate a partial read.
|
||||||
netCtl.SetReadLimit(i * total / 10)
|
netCtl.SetReadLimit(i * total / 10)
|
||||||
|
|
||||||
@ -421,7 +421,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(0)
|
netCtl.SetReadLimit(0)
|
||||||
|
|
||||||
// We should now be able to log the user in.
|
// We should now be able to log the user in.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
|
||||||
// The user should be there, now connected.
|
// The user should be there, now connected.
|
||||||
@ -441,7 +441,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
|
|
||||||
// Log the user in and wait for it to sync.
|
// Log the user in and wait for it to sync.
|
||||||
// (We don't want to count message sync data in the test.)
|
// (We don't want to count message sync data in the test.)
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -451,7 +451,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
|
|
||||||
// See how much data it takes to load the user at startup.
|
// See how much data it takes to load the user at startup.
|
||||||
total := countBytesRead(netCtl, func() {
|
total := countBytesRead(netCtl, func() {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -460,7 +460,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(i * total / 10)
|
netCtl.SetReadLimit(i * total / 10)
|
||||||
|
|
||||||
// We should fail to load the user; it should be listed but disconnected.
|
// We should fail to load the user; it should be listed but disconnected.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -469,7 +469,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(0)
|
netCtl.SetReadLimit(0)
|
||||||
|
|
||||||
// We should now be able to load the user.
|
// We should now be able to load the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -484,7 +484,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
|
|
||||||
var pass []byte
|
var pass []byte
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -501,7 +501,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
require.Equal(t, pass, pass)
|
require.Equal(t, pass, pass)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The bridge should load the user.
|
// The bridge should load the user.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
@ -514,7 +514,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_AddressMode(t *testing.T) {
|
func TestBridge_AddressMode(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -552,7 +552,7 @@ func TestBridge_AddressMode(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginLogoutRepeated(t *testing.T) {
|
func TestBridge_LoginLogoutRepeated(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
// Log the user in.
|
// Log the user in.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
@ -568,7 +568,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -590,7 +590,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
// Go back online.
|
// Go back online.
|
||||||
netCtl.Enable()
|
netCtl.Enable()
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// The user is still disconnected.
|
// The user is still disconnected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -600,7 +600,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_DeleteDisconnected(t *testing.T) {
|
func TestBridge_DeleteDisconnected(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -628,7 +628,7 @@ func TestBridge_DeleteDisconnected(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_DeleteOffline(t *testing.T) {
|
func TestBridge_DeleteOffline(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -652,7 +652,7 @@ func TestBridge_DeleteOffline(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_UserInfo_Alias(t *testing.T) {
|
func TestBridge_UserInfo_Alias(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Create a new user.
|
// Create a new user.
|
||||||
userID, _, err := s.CreateUser("primary", []byte("password"))
|
userID, _, err := s.CreateUser("primary", []byte("password"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -675,7 +675,7 @@ func TestBridge_UserInfo_Alias(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_User_Refresh(t *testing.T) {
|
func TestBridge_User_Refresh(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Get a channel of sync started events.
|
// Get a channel of sync started events.
|
||||||
syncStartCh, done := chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
|
syncStartCh, done := chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|||||||
228
internal/configstatus/config_status.go
Normal file
228
internal/configstatus/config_status.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const version = "1.0.0"
|
||||||
|
|
||||||
|
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
|
||||||
|
status := ConfigurationStatus{
|
||||||
|
FilePath: filepath,
|
||||||
|
DataLock: safe.NewRWMutex(),
|
||||||
|
Data: &ConfigurationStatusData{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath); err == nil {
|
||||||
|
if err := status.Load(); err == nil {
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
logrus.WithError(err).Warn("Cannot load configuration status file. Reset it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
status.Data.init()
|
||||||
|
if err := status.Save(); err != nil {
|
||||||
|
return &status, err
|
||||||
|
}
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) Load() error {
|
||||||
|
bytes, err := os.ReadFile(status.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata MetadataOnly
|
||||||
|
if err := json.Unmarshal(bytes, &metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.Metadata.Version != version {
|
||||||
|
return fmt.Errorf("unsupported configstatus file version %s", metadata.Metadata.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(bytes, status.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) Save() error {
|
||||||
|
temp := status.FilePath + "_temp"
|
||||||
|
f, err := os.Create(temp) //nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(f)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
err = enc.Encode(status.Data)
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Error while closing configstatus file.")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(temp, status.FilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) IsPending() bool {
|
||||||
|
status.DataLock.RLock()
|
||||||
|
defer status.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return !status.Data.DataV1.PendingSince.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) isPendingSinceMin() int {
|
||||||
|
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) IsFromFailure() bool {
|
||||||
|
status.DataLock.RLock()
|
||||||
|
defer status.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return status.Data.DataV1.FailureDetails != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) ApplySuccess() error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
status.Data.init()
|
||||||
|
status.Data.DataV1.PendingSince = time.Time{}
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) ApplyFailure(err string) error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
status.Data.init()
|
||||||
|
status.Data.DataV1.FailureDetails = err
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) ApplyProgress() error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
status.Data.DataV1.LastProgress = time.Now()
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
if !status.Data.hasLinkClicked(link) {
|
||||||
|
status.Data.setClickedLink(link)
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) ReportClicked() error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
if !status.Data.DataV1.ReportClick {
|
||||||
|
status.Data.DataV1.ReportClick = true
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) ReportSent() error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
if !status.Data.DataV1.ReportSent {
|
||||||
|
status.Data.DataV1.ReportSent = true
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) AutoconfigUsed(client string) error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
|
if client != status.Data.DataV1.Autoconf {
|
||||||
|
status.Data.DataV1.Autoconf = client
|
||||||
|
return status.Save()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) Remove() error {
|
||||||
|
status.DataLock.Lock()
|
||||||
|
defer status.DataLock.Unlock()
|
||||||
|
return os.Remove(status.FilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *ConfigurationStatusData) init() {
|
||||||
|
data.Metadata = Metadata{
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
data.DataV1.PendingSince = time.Now()
|
||||||
|
data.DataV1.LastProgress = time.Time{}
|
||||||
|
data.DataV1.Autoconf = ""
|
||||||
|
data.DataV1.ClickedLink = 0
|
||||||
|
data.DataV1.ReportSent = false
|
||||||
|
data.DataV1.ReportClick = false
|
||||||
|
data.DataV1.FailureDetails = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
|
||||||
|
data.DataV1.ClickedLink |= 1 << pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
|
||||||
|
val := data.DataV1.ClickedLink & (1 << pos)
|
||||||
|
return val > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *ConfigurationStatusData) clickedLinkToString() string {
|
||||||
|
var str = ""
|
||||||
|
var first = true
|
||||||
|
for i := 0; i < 64; i++ {
|
||||||
|
if data.hasLinkClicked(uint64(i)) {
|
||||||
|
if !first {
|
||||||
|
str += ","
|
||||||
|
} else {
|
||||||
|
first = false
|
||||||
|
str += "["
|
||||||
|
}
|
||||||
|
str += strconv.Itoa(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if str != "" {
|
||||||
|
str += "]"
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
252
internal/configstatus/config_status_test.go
Normal file
252
internal/configstatus/config_status_test.go
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigStatus_init_virgin(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||||
|
|
||||||
|
require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
||||||
|
|
||||||
|
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_init_existing(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||||
|
require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_init_bad_version(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "2.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
||||||
|
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_IsPending(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, true, config.IsPending())
|
||||||
|
config.Data.DataV1.PendingSince = time.Time{}
|
||||||
|
require.Equal(t, false, config.IsPending())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_IsFromFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, false, config.IsFromFailure())
|
||||||
|
config.Data.DataV1.FailureDetails = "test"
|
||||||
|
require.Equal(t, true, config.IsFromFailure())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_ApplySuccess(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, true, config.IsPending())
|
||||||
|
require.NoError(t, config.ApplySuccess())
|
||||||
|
require.Equal(t, false, config.IsPending())
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_ApplyFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, config.ApplySuccess())
|
||||||
|
|
||||||
|
require.NoError(t, config.ApplyFailure("Big Failure"))
|
||||||
|
require.Equal(t, true, config.IsFromFailure())
|
||||||
|
require.Equal(t, true, config.IsPending())
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_ApplyProgress(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, true, config.IsPending())
|
||||||
|
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
||||||
|
|
||||||
|
require.NoError(t, config.ApplyProgress())
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_RecordLinkClicked(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
||||||
|
require.NoError(t, config.RecordLinkClicked(0))
|
||||||
|
require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
|
||||||
|
require.NoError(t, config.RecordLinkClicked(1))
|
||||||
|
require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_ReportClicked(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
||||||
|
require.NoError(t, config.ReportClicked())
|
||||||
|
require.Equal(t, true, config.Data.DataV1.ReportClick)
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigStatus_ReportSent(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
||||||
|
require.NoError(t, config.ReportSent())
|
||||||
|
require.Equal(t, true, config.Data.DataV1.ReportSent)
|
||||||
|
|
||||||
|
config2, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
||||||
|
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
||||||
|
require.Equal(t, true, config2.Data.DataV1.ReportSent)
|
||||||
|
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
||||||
|
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
|
||||||
|
f, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
return json.NewEncoder(f).Encode(data)
|
||||||
|
}
|
||||||
59
internal/configstatus/configuration_abort.go
Normal file
59
internal/configstatus/configuration_abort.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigAbortValues struct {
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigAbortDimensions struct {
|
||||||
|
ReportClick string `json:"report_click"`
|
||||||
|
ReportSent string `json:"report_sent"`
|
||||||
|
ClickedLink string `json:"clicked_link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigAbortData struct {
|
||||||
|
MeasurementGroup string
|
||||||
|
Event string
|
||||||
|
Values ConfigSuccessValues
|
||||||
|
Dimensions ConfigSuccessDimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigAbortBuilder struct{}
|
||||||
|
|
||||||
|
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return ConfigAbortData{
|
||||||
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
|
Event: "bridge_config_abort",
|
||||||
|
Values: ConfigSuccessValues{
|
||||||
|
Duration: config.isPendingSinceMin(),
|
||||||
|
},
|
||||||
|
Dimensions: ConfigSuccessDimensions{
|
||||||
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
75
internal/configstatus/configuration_abort_test.go
Normal file
75
internal/configstatus/configuration_abort_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigurationAbort_default(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigAbortBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_abort", req.Event)
|
||||||
|
require.Equal(t, 0, req.Values.Duration)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationAbort_fed(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||||
|
LastProgress: time.Time{},
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigAbortBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_abort", req.Event)
|
||||||
|
require.Equal(t, 10, req.Values.Duration)
|
||||||
|
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||||
|
}
|
||||||
60
internal/configstatus/configuration_progress.go
Normal file
60
internal/configstatus/configuration_progress.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ConfigProgressValues struct {
|
||||||
|
NbDay int `json:"nb_day"`
|
||||||
|
NbDaySinceLast int `json:"nb_day_since_last"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigProgressData struct {
|
||||||
|
MeasurementGroup string
|
||||||
|
Event string
|
||||||
|
Values ConfigProgressValues
|
||||||
|
Dimensions struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigProgressBuilder struct{}
|
||||||
|
|
||||||
|
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return ConfigProgressData{
|
||||||
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
|
Event: "bridge_config_progress",
|
||||||
|
Values: ConfigProgressValues{
|
||||||
|
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
|
||||||
|
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfDay(now, prev time.Time) int {
|
||||||
|
if now.IsZero() || prev.IsZero() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if now.Year() > prev.Year() {
|
||||||
|
return (365 * (now.Year() - prev.Year())) + now.YearDay() - prev.YearDay()
|
||||||
|
} else if now.YearDay() > prev.YearDay() {
|
||||||
|
return now.YearDay() - prev.YearDay()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
100
internal/configstatus/configuration_progress_test.go
Normal file
100
internal/configstatus/configuration_progress_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigurationProgress_default(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
|
require.Equal(t, 0, req.Values.NbDay)
|
||||||
|
require.Equal(t, 1, req.Values.NbDaySinceLast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationProgress_fed(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().AddDate(0, 0, -5),
|
||||||
|
LastProgress: time.Now().AddDate(0, 0, -2),
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
|
require.Equal(t, 5, req.Values.NbDay)
|
||||||
|
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationProgress_fed_year_change(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().AddDate(-1, 0, -5),
|
||||||
|
LastProgress: time.Now().AddDate(0, 0, -2),
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
|
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
|
||||||
|
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||||
|
}
|
||||||
63
internal/configstatus/configuration_recovery.go
Normal file
63
internal/configstatus/configuration_recovery.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigRecoveryValues struct {
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigRecoveryDimensions struct {
|
||||||
|
Autoconf string `json:"autoconf"`
|
||||||
|
ReportClick string `json:"report_click"`
|
||||||
|
ReportSent string `json:"report_sent"`
|
||||||
|
ClickedLink string `json:"clicked_link"`
|
||||||
|
FailureDetails string `json:"failure_details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigRecoveryData struct {
|
||||||
|
MeasurementGroup string
|
||||||
|
Event string
|
||||||
|
Values ConfigRecoveryValues
|
||||||
|
Dimensions ConfigRecoveryDimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigRecoveryBuilder struct{}
|
||||||
|
|
||||||
|
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return ConfigRecoveryData{
|
||||||
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
|
Event: "bridge_config_recovery",
|
||||||
|
Values: ConfigRecoveryValues{
|
||||||
|
Duration: config.isPendingSinceMin(),
|
||||||
|
},
|
||||||
|
Dimensions: ConfigRecoveryDimensions{
|
||||||
|
Autoconf: config.Data.DataV1.Autoconf,
|
||||||
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
|
FailureDetails: config.Data.DataV1.FailureDetails,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
79
internal/configstatus/configuration_recovery_test.go
Normal file
79
internal/configstatus/configuration_recovery_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigurationRecovery_default(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||||
|
require.Equal(t, 0, req.Values.Duration)
|
||||||
|
require.Equal(t, "", req.Dimensions.Autoconf)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||||
|
require.Equal(t, "", req.Dimensions.FailureDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationRecovery_fed(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||||
|
LastProgress: time.Time{},
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||||
|
require.Equal(t, 10, req.Values.Duration)
|
||||||
|
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
||||||
|
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||||
|
require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
|
||||||
|
}
|
||||||
61
internal/configstatus/configuration_success.go
Normal file
61
internal/configstatus/configuration_success.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigSuccessValues struct {
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigSuccessDimensions struct {
|
||||||
|
Autoconf string `json:"autoconf"`
|
||||||
|
ReportClick string `json:"report_click"`
|
||||||
|
ReportSent string `json:"report_sent"`
|
||||||
|
ClickedLink string `json:"clicked_link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigSuccessData struct {
|
||||||
|
MeasurementGroup string
|
||||||
|
Event string
|
||||||
|
Values ConfigSuccessValues
|
||||||
|
Dimensions ConfigSuccessDimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigSuccessBuilder struct{}
|
||||||
|
|
||||||
|
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
|
return ConfigSuccessData{
|
||||||
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
|
Event: "bridge_config_success",
|
||||||
|
Values: ConfigSuccessValues{
|
||||||
|
Duration: config.isPendingSinceMin(),
|
||||||
|
},
|
||||||
|
Dimensions: ConfigSuccessDimensions{
|
||||||
|
Autoconf: config.Data.DataV1.Autoconf,
|
||||||
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
77
internal/configstatus/configuration_success_test.go
Normal file
77
internal/configstatus/configuration_success_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigurationSuccess_default(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigSuccessBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_success", req.Event)
|
||||||
|
require.Equal(t, 0, req.Values.Duration)
|
||||||
|
require.Equal(t, "", req.Dimensions.Autoconf)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "", req.Dimensions.ClickedLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationSuccess_fed(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().Add(-10 * time.Minute),
|
||||||
|
LastProgress: time.Time{},
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigSuccessBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_success", req.Event)
|
||||||
|
require.Equal(t, 10, req.Values.Duration)
|
||||||
|
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
||||||
|
require.Equal(t, "true", req.Dimensions.ReportClick)
|
||||||
|
require.Equal(t, "false", req.Dimensions.ReportSent)
|
||||||
|
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
||||||
|
}
|
||||||
56
internal/configstatus/types_config_status.go
Normal file
56
internal/configstatus/types_config_status.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2024 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package configstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProgressCheckInterval = time.Hour
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetadataOnly struct {
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataV1 struct {
|
||||||
|
PendingSince time.Time `json:"pending_since"`
|
||||||
|
LastProgress time.Time `json:"last_progress"`
|
||||||
|
Autoconf string `json:"auto_conf"`
|
||||||
|
ClickedLink uint64 `json:"clicked_link"`
|
||||||
|
ReportSent bool `json:"report_sent"`
|
||||||
|
ReportClick bool `json:"report_click"`
|
||||||
|
FailureDetails string `json:"failure_details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigurationStatusData struct {
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
DataV1 DataV1 `json:"dataV1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigurationStatus struct {
|
||||||
|
FilePath string
|
||||||
|
DataLock safe.RWMutex
|
||||||
|
|
||||||
|
Data *ConfigurationStatusData
|
||||||
|
}
|
||||||
@ -151,7 +151,7 @@ func getClientWithJar(t *testing.T, persister Persister) (*http.Client, *Jar) {
|
|||||||
func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
|
func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.HandleFunc("/set", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
mux.HandleFunc("/set", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, cookie := range wantCookies {
|
for _, cookie := range wantCookies {
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: cookie.name,
|
Name: cookie.name,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import (
|
|||||||
// ShowErrorNotification shows a system notification that the app with the given appName has crashed.
|
// ShowErrorNotification shows a system notification that the app with the given appName has crashed.
|
||||||
// NOTE: Icons shouldn't be hardcoded.
|
// NOTE: Icons shouldn't be hardcoded.
|
||||||
func ShowErrorNotification(appName string) RecoveryAction {
|
func ShowErrorNotification(appName string) RecoveryAction {
|
||||||
return func(_ interface{}) error {
|
return func(r interface{}) error {
|
||||||
notify := notificator.New(notificator.Options{
|
notify := notificator.New(notificator.Options{
|
||||||
DefaultIcon: "../frontend/ui/icon/icon.png",
|
DefaultIcon: "../frontend/ui/icon/icon.png",
|
||||||
AppName: appName,
|
AppName: appName,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import (
|
|||||||
func TestTLSReporter_DoubleReport(t *testing.T) {
|
func TestTLSReporter_DoubleReport(t *testing.T) {
|
||||||
reportCounter := 0
|
reportCounter := 0
|
||||||
|
|
||||||
reportServer := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
reportServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
reportCounter++
|
reportCounter++
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ func TestProxyProvider_FindProxy(t *testing.T) {
|
|||||||
defer closeServer(proxy)
|
defer closeServer(proxy)
|
||||||
|
|
||||||
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{proxy.URL}, nil }
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy.URL}, nil }
|
||||||
|
|
||||||
url, err := p.findReachableServer()
|
url, err := p.findReachableServer()
|
||||||
r.NoError(t, err)
|
r.NoError(t, err)
|
||||||
@ -49,7 +49,7 @@ func TestProxyProvider_FindProxy_ChooseReachableProxy(t *testing.T) {
|
|||||||
closeServer(unreachableProxy)
|
closeServer(unreachableProxy)
|
||||||
|
|
||||||
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) {
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
|
||||||
return []string{reachableProxy.URL, unreachableProxy.URL}, nil
|
return []string{reachableProxy.URL, unreachableProxy.URL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ func TestProxyProvider_FindProxy_ChooseTrustedProxy(t *testing.T) {
|
|||||||
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
|
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
|
||||||
|
|
||||||
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) {
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
|
||||||
return []string{untrustedProxy.URL, trustedProxy.URL}, nil
|
return []string{untrustedProxy.URL, trustedProxy.URL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ func TestProxyProvider_FindProxy_FailIfNoneReachable(t *testing.T) {
|
|||||||
closeServer(unreachableProxy2)
|
closeServer(unreachableProxy2)
|
||||||
|
|
||||||
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) {
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
|
||||||
return []string{unreachableProxy1.URL, unreachableProxy2.URL}, nil
|
return []string{unreachableProxy1.URL, unreachableProxy2.URL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ func TestProxyProvider_FindProxy_FailIfNoneTrusted(t *testing.T) {
|
|||||||
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
|
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
|
||||||
|
|
||||||
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) {
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
|
||||||
return []string{untrustedProxy1.URL, untrustedProxy2.URL}, nil
|
return []string{untrustedProxy1.URL, untrustedProxy2.URL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ func TestProxyProvider_FindProxy_FailIfNoneTrusted(t *testing.T) {
|
|||||||
func TestProxyProvider_FindProxy_RefreshCacheTimeout(t *testing.T) {
|
func TestProxyProvider_FindProxy_RefreshCacheTimeout(t *testing.T) {
|
||||||
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.cacheRefreshTimeout = 1 * time.Second
|
p.cacheRefreshTimeout = 1 * time.Second
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { time.Sleep(2 * time.Second); return nil, nil }
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { time.Sleep(2 * time.Second); return nil, nil }
|
||||||
|
|
||||||
// We should fail to refresh the proxy cache because the doh provider
|
// We should fail to refresh the proxy cache because the doh provider
|
||||||
// takes 2 seconds to respond but we timeout after just 1 second.
|
// takes 2 seconds to respond but we timeout after just 1 second.
|
||||||
@ -135,7 +135,7 @@ func TestProxyProvider_FindProxy_CanReachTimeout(t *testing.T) {
|
|||||||
|
|
||||||
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
|
||||||
p.canReachTimeout = 1 * time.Second
|
p.canReachTimeout = 1 * time.Second
|
||||||
p.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{slowProxy.URL}, nil }
|
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{slowProxy.URL}, nil }
|
||||||
|
|
||||||
// We should fail to reach the returned proxy because it takes 2 seconds
|
// We should fail to reach the returned proxy because it takes 2 seconds
|
||||||
// to reach it and we only allow 1.
|
// to reach it and we only allow 1.
|
||||||
|
|||||||
@ -112,7 +112,7 @@ vwRMog6lPhlRhHh/FZ43Cg==
|
|||||||
|
|
||||||
// getUntrustedServer returns a server but it doesn't add its public key to the list of pinned ones.
|
// getUntrustedServer returns a server but it doesn't add its public key to the list of pinned ones.
|
||||||
func getUntrustedServer() *httptest.Server {
|
func getUntrustedServer() *httptest.Server {
|
||||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
|
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
|
||||||
cert, err := tls.X509KeyPair([]byte(servercrt), []byte(serverkey))
|
cert, err := tls.X509KeyPair([]byte(servercrt), []byte(serverkey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -145,7 +145,7 @@ func TestProxyDialer_UseProxy(t *testing.T) {
|
|||||||
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
||||||
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
||||||
d.proxyProvider = provider
|
d.proxyProvider = provider
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
||||||
|
|
||||||
err := d.switchToReachableServer()
|
err := d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -163,7 +163,7 @@ func TestProxyDialer_UseProxy_MultipleTimes(t *testing.T) {
|
|||||||
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
||||||
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
||||||
d.proxyProvider = provider
|
d.proxyProvider = provider
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{proxy1.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy1.URL}, nil }
|
||||||
|
|
||||||
err := d.switchToReachableServer()
|
err := d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -172,7 +172,7 @@ func TestProxyDialer_UseProxy_MultipleTimes(t *testing.T) {
|
|||||||
// Have to wait so as to not get rejected.
|
// Have to wait so as to not get rejected.
|
||||||
time.Sleep(proxyLookupWait)
|
time.Sleep(proxyLookupWait)
|
||||||
|
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{proxy2.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy2.URL}, nil }
|
||||||
err = d.switchToReachableServer()
|
err = d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, formatAsAddress(proxy2.URL), d.proxyAddress)
|
require.Equal(t, formatAsAddress(proxy2.URL), d.proxyAddress)
|
||||||
@ -180,7 +180,7 @@ func TestProxyDialer_UseProxy_MultipleTimes(t *testing.T) {
|
|||||||
// Have to wait so as to not get rejected.
|
// Have to wait so as to not get rejected.
|
||||||
time.Sleep(proxyLookupWait)
|
time.Sleep(proxyLookupWait)
|
||||||
|
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{proxy3.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy3.URL}, nil }
|
||||||
err = d.switchToReachableServer()
|
err = d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, formatAsAddress(proxy3.URL), d.proxyAddress)
|
require.Equal(t, formatAsAddress(proxy3.URL), d.proxyAddress)
|
||||||
@ -195,7 +195,7 @@ func TestProxyDialer_UseProxy_RevertAfterTime(t *testing.T) {
|
|||||||
d.proxyProvider = provider
|
d.proxyProvider = provider
|
||||||
d.proxyUseDuration = time.Second
|
d.proxyUseDuration = time.Second
|
||||||
|
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
||||||
err := d.switchToReachableServer()
|
err := d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ func TestProxyDialer_UseProxy_RevertIfProxyStopsWorkingAndOriginalAPIIsReachable
|
|||||||
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
||||||
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
||||||
d.proxyProvider = provider
|
d.proxyProvider = provider
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{trustedProxy.URL}, nil }
|
||||||
|
|
||||||
err := d.switchToReachableServer()
|
err := d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -246,7 +246,7 @@ func TestProxyDialer_UseProxy_FindSecondAlternativeIfFirstFailsAndAPIIsStillBloc
|
|||||||
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
|
||||||
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
|
||||||
d.proxyProvider = provider
|
d.proxyProvider = provider
|
||||||
provider.dohLookup = func(_ context.Context, _, _ string) ([]string, error) { return []string{proxy1.URL, proxy2.URL}, nil }
|
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy1.URL, proxy2.URL}, nil }
|
||||||
|
|
||||||
err := d.switchToReachableServer()
|
err := d.switchToReachableServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@ -202,26 +202,3 @@ type UncategorizedEventError struct {
|
|||||||
func (event UncategorizedEventError) String() string {
|
func (event UncategorizedEventError) String() string {
|
||||||
return fmt.Sprintf("UncategorizedEventError: UserID: %s, Source:%T, Error: %s", event.UserID, event.Error, event.Error)
|
return fmt.Sprintf("UncategorizedEventError: UserID: %s, Source:%T, Error: %s", event.UserID, event.Error, event.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserLoadedCheckResync struct {
|
|
||||||
eventBase
|
|
||||||
|
|
||||||
UserID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (event UserLoadedCheckResync) String() string {
|
|
||||||
return fmt.Sprintf("UserLoadedCheckResync: UserID: %s", event.UserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserNotification struct {
|
|
||||||
eventBase
|
|
||||||
|
|
||||||
UserID string
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (event UserNotification) String() string {
|
|
||||||
return fmt.Sprintf("UserNotification: UserID: %s, Title: %s, Subtitle: %s, Body: %s", event.UserID, event.Title, event.Subtitle, event.Body)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ using namespace bridgepp;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
|
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
|
||||||
QString const HV_ERROR_TEMPLATE = "Human verification failed. Please try again.";
|
QString const HV_ERROR_TEMPLATE = "failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: CAPTCHA validation failed (Code=12087, Status=422)";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,9 +364,9 @@ grpc::Status GRPCService::RequestKnowledgeBaseSuggestions(ServerContext*, String
|
|||||||
|
|
||||||
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
|
||||||
for (qsizetype i = 1; i <= 3; ++i) {
|
for (qsizetype i = 1; i <= 3; ++i) {
|
||||||
suggestions.push_back({
|
suggestions.push_back( {
|
||||||
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
|
||||||
.title = QString("Suggested link %1").arg(i),
|
.title = QString("Suggested link %1").arg(i),
|
||||||
|
.url = QString("https://proton.me/support/bridge#%1").arg(i),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
|
||||||
@ -846,6 +846,32 @@ Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] request The request.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
Status GRPCService::ExternalLinkClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) {
|
||||||
|
app().log().debug(QString("%1 - URL = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
//
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
Status GRPCService::ReportBugClicked(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) {
|
||||||
|
app().log().debug(__FUNCTION__);
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] request The request.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
Status GRPCService::AutoconfigClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) {
|
||||||
|
app().log().debug(QString("%1 - Client = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] request The request
|
/// \param[in] request The request
|
||||||
/// \param[in] writer The writer
|
/// \param[in] writer The writer
|
||||||
|
|||||||
@ -97,6 +97,9 @@ public: // member functions.
|
|||||||
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
|
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
|
||||||
grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override;
|
grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override;
|
||||||
grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
||||||
|
grpc::Status ReportBugClicked(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *) override;
|
||||||
|
grpc::Status AutoconfigClicked(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
||||||
|
grpc::Status ExternalLinkClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
|
||||||
grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override;
|
grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override;
|
||||||
grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
|
grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
|
||||||
bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream.
|
bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream.
|
||||||
|
|||||||
@ -57,7 +57,6 @@ UsersTab::UsersTab(QWidget *parent)
|
|||||||
connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState);
|
connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState);
|
||||||
connect(ui_.checkSync, &QCheckBox::toggled, this, &UsersTab::onCheckSyncToggled);
|
connect(ui_.checkSync, &QCheckBox::toggled, this, &UsersTab::onCheckSyncToggled);
|
||||||
connect(ui_.sliderSync, &QSlider::valueChanged, this, &UsersTab::onSliderSyncValueChanged);
|
connect(ui_.sliderSync, &QSlider::valueChanged, this, &UsersTab::onSliderSyncValueChanged);
|
||||||
connect(ui_.sendNotificationButton, &QPushButton::clicked, this, &UsersTab::onSendUserNotification);
|
|
||||||
|
|
||||||
users_.append(defaultUser());
|
users_.append(defaultUser());
|
||||||
|
|
||||||
@ -217,7 +216,6 @@ void UsersTab::updateGUIState() {
|
|||||||
ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked());
|
ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked());
|
||||||
ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0);
|
ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0);
|
||||||
ui_.groupboxSync->setEnabled(user.get());
|
ui_.groupboxSync->setEnabled(user.get());
|
||||||
ui_.groupBoxNotification->setEnabled(hasSelectedUser && (UserState::Connected == state));
|
|
||||||
|
|
||||||
if (user)
|
if (user)
|
||||||
ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername());
|
ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername());
|
||||||
@ -491,41 +489,3 @@ void UsersTab::onSliderSyncValueChanged(int value) {
|
|||||||
app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining.
|
app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining.
|
||||||
this->updateGUIState();
|
this->updateGUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \return the title for the notification.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
QString UsersTab::notificationTitle() const {
|
|
||||||
return ui_.notificationTitle->text();
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \return the subtitle for the notification.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
QString UsersTab::notificationSubtitle() const {
|
|
||||||
return ui_.notificationSubtitleText->text();
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \return the body for the notification.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
QString UsersTab::notificationBody() const {
|
|
||||||
return ui_.notticationBodyText->text();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void UsersTab::onSendUserNotification() {
|
|
||||||
SPUser const user = selectedUser();
|
|
||||||
if (!user) {
|
|
||||||
app().log().error(QString("%1 failed. Unkown user.").arg(__FUNCTION__));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GRPCService &grpc = app().grpc();
|
|
||||||
|
|
||||||
if (grpc.isStreaming()) {
|
|
||||||
QString const userID = user->id();
|
|
||||||
grpc.sendEvent(newUserNotificationEvent(userID, notificationTitle(), notificationSubtitle(), notificationBody()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
#include "Tabs/ui_UsersTab.h"
|
#include "Tabs/ui_UsersTab.h"
|
||||||
#include "UserTable.h"
|
#include "UserTable.h"
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \brief The 'Users' tab of the main window.
|
/// \brief The 'Users' tab of the main window.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -49,15 +50,12 @@ public: // member functions.
|
|||||||
bool nextUserTwoPasswordsError() const; ///< Check if next user login should trigger 2nd password error.
|
bool nextUserTwoPasswordsError() const; ///< Check if next user login should trigger 2nd password error.
|
||||||
bool nextUserTwoPasswordsAbort() const; ///< Check if next user login should trigger 2nd password abort.
|
bool nextUserTwoPasswordsAbort() const; ///< Check if next user login should trigger 2nd password abort.
|
||||||
QString usernamePasswordErrorMessage() const; ///< Return the username password error message.
|
QString usernamePasswordErrorMessage() const; ///< Return the username password error message.
|
||||||
QString notificationTitle() const; ///< Return the user notification title.
|
|
||||||
QString notificationSubtitle() const; ///< Return the user notification subtitle.
|
|
||||||
QString notificationBody() const; ///< Return the user notification body.
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode.
|
void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode.
|
||||||
void logoutUser(QString const &userID); ///< slot for the logging out of a user.
|
void logoutUser(QString const &userID); ///< slot for the logging out of a user.
|
||||||
void removeUser(QString const &userID); ///< Slot for the removal of a user.
|
void removeUser(QString const &userID); ///< Slot for the removal of a user.
|
||||||
static void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
|
static void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
|
||||||
void processBadEventUserFeedback(QString const& userID, bool doResync); ///< Slot for the reception of a bad event user feedback.
|
void processBadEventUserFeedback(QString const& userID, bool doResync); ///< Slot for the reception of a bad event user feedback.
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -71,7 +69,6 @@ private slots:
|
|||||||
void onCheckSyncToggled(bool checked); ///< Slot for the 'Synchronizing' check box.
|
void onCheckSyncToggled(bool checked); ///< Slot for the 'Synchronizing' check box.
|
||||||
void onSliderSyncValueChanged(int value); ///< Slot for the sync 'Progress' slider.
|
void onSliderSyncValueChanged(int value); ///< Slot for the sync 'Progress' slider.
|
||||||
void updateGUIState(); ///< Update the GUI state.
|
void updateGUIState(); ///< Update the GUI state.
|
||||||
void onSendUserNotification(); ///< Send a user notification event to the GUI.
|
|
||||||
|
|
||||||
private: // member functions.
|
private: // member functions.
|
||||||
qint32 selectedIndex() const; ///< Get the index of the selected row.
|
qint32 selectedIndex() const; ///< Get the index of the selected row.
|
||||||
|
|||||||
@ -7,19 +7,13 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1221</width>
|
<width>1221</width>
|
||||||
<height>408</height>
|
<height>894</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableView" name="tableUserList">
|
<widget class="QTableView" name="tableUserList">
|
||||||
<property name="selectionMode">
|
<property name="selectionMode">
|
||||||
@ -37,419 +31,332 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<property name="sizePolicy">
|
<item>
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
<widget class="QPushButton" name="buttonNewUser">
|
||||||
<horstretch>0</horstretch>
|
<property name="text">
|
||||||
<verstretch>0</verstretch>
|
<string>New User</string>
|
||||||
</sizepolicy>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<property name="widgetResizable">
|
</item>
|
||||||
<bool>true</bool>
|
<item>
|
||||||
</property>
|
<widget class="QPushButton" name="buttonEditUser">
|
||||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
<property name="text">
|
||||||
<property name="geometry">
|
<string>Edit User</string>
|
||||||
<rect>
|
</property>
|
||||||
<x>0</x>
|
</widget>
|
||||||
<y>0</y>
|
</item>
|
||||||
<width>327</width>
|
<item>
|
||||||
<height>905</height>
|
<widget class="QPushButton" name="buttonRemoveUser">
|
||||||
</rect>
|
<property name="text">
|
||||||
</property>
|
<string>Remove User</string>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="buttonNewUser">
|
<spacer name="verticalSpacer">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>New User</string>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="sizeHint" stdset="0">
|
||||||
</item>
|
<size>
|
||||||
<item>
|
<width>20</width>
|
||||||
<widget class="QPushButton" name="buttonEditUser">
|
<height>40</height>
|
||||||
<property name="text">
|
</size>
|
||||||
<string>Edit User</string>
|
</property>
|
||||||
</property>
|
</spacer>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QGroupBox" name="groupboxSync">
|
||||||
<widget class="QPushButton" name="buttonRemoveUser">
|
<property name="minimumSize">
|
||||||
<property name="text">
|
<size>
|
||||||
<string>Remove User</string>
|
<width>0</width>
|
||||||
</property>
|
<height>0</height>
|
||||||
</widget>
|
</size>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="title">
|
||||||
<spacer name="verticalSpacer">
|
<string>Sync</string>
|
||||||
<property name="orientation">
|
</property>
|
||||||
<enum>Qt::Vertical</enum>
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
</property>
|
<item>
|
||||||
<property name="sizeHint" stdset="0">
|
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
|
||||||
<size>
|
<item>
|
||||||
<width>20</width>
|
<widget class="QCheckBox" name="checkSync">
|
||||||
<height>40</height>
|
<property name="text">
|
||||||
</size>
|
<string>Synchronizing</string>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBoxNotification">
|
<widget class="QLabel" name="labelSync">
|
||||||
<property name="enabled">
|
<property name="text">
|
||||||
<bool>true</bool>
|
<string>0%</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
</widget>
|
||||||
<size>
|
</item>
|
||||||
<width>300</width>
|
</layout>
|
||||||
<height>0</height>
|
</item>
|
||||||
</size>
|
<item>
|
||||||
</property>
|
<widget class="QSlider" name="sliderSync">
|
||||||
<property name="maximumSize">
|
<property name="maximum">
|
||||||
<size>
|
<number>100</number>
|
||||||
<width>300</width>
|
</property>
|
||||||
<height>400</height>
|
<property name="orientation">
|
||||||
</size>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="title">
|
<property name="tickInterval">
|
||||||
<string>Notification</string>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_6" stretch="0,0,0,0">
|
</layout>
|
||||||
<item>
|
</widget>
|
||||||
<widget class="QLineEdit" name="notificationTitle">
|
</item>
|
||||||
<property name="placeholderText">
|
<item>
|
||||||
<string>Title</string>
|
<widget class="QGroupBox" name="groupBoxBadEvent">
|
||||||
</property>
|
<property name="minimumSize">
|
||||||
</widget>
|
<size>
|
||||||
</item>
|
<width>0</width>
|
||||||
<item>
|
<height>0</height>
|
||||||
<widget class="QLineEdit" name="notificationSubtitleText">
|
</size>
|
||||||
<property name="minimumSize">
|
</property>
|
||||||
<size>
|
<property name="title">
|
||||||
<width>0</width>
|
<string>Bad Event</string>
|
||||||
<height>0</height>
|
</property>
|
||||||
</size>
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
</property>
|
<item>
|
||||||
<property name="placeholderText">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<string>Subtitle</string>
|
<item>
|
||||||
</property>
|
<widget class="QLineEdit" name="editUserBadEvent">
|
||||||
</widget>
|
<property name="minimumSize">
|
||||||
</item>
|
<size>
|
||||||
<item>
|
<width>200</width>
|
||||||
<widget class="QLineEdit" name="notticationBodyText">
|
<height>0</height>
|
||||||
<property name="placeholderText">
|
</size>
|
||||||
<string>Body</string>
|
</property>
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string/>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="placeholderText">
|
||||||
<widget class="QPushButton" name="sendNotificationButton">
|
<string>error message</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Send</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="buttonUserBadEvent">
|
||||||
</layout>
|
<property name="text">
|
||||||
</item>
|
<string>Send</string>
|
||||||
</layout>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
</layout>
|
||||||
<widget class="QGroupBox" name="groupboxSync">
|
</item>
|
||||||
<property name="minimumSize">
|
</layout>
|
||||||
<size>
|
</widget>
|
||||||
<width>0</width>
|
</item>
|
||||||
<height>0</height>
|
<item>
|
||||||
</size>
|
<widget class="QGroupBox" name="groupBoxUsedSpace">
|
||||||
</property>
|
<property name="minimumSize">
|
||||||
<property name="title">
|
<size>
|
||||||
<string>Sync</string>
|
<width>0</width>
|
||||||
</property>
|
<height>0</height>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
</size>
|
||||||
<item>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
|
<property name="title">
|
||||||
<item>
|
<string>Used Bytes Changed</string>
|
||||||
<widget class="QCheckBox" name="checkSync">
|
</property>
|
||||||
<property name="text">
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
<string>Synchronizing</string>
|
<item>
|
||||||
</property>
|
<layout class="QHBoxLayout" name="hBoxUsedBytes" stretch="1,0">
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QDoubleSpinBox" name="spinUsedBytes">
|
||||||
<item>
|
<property name="buttonSymbols">
|
||||||
<widget class="QLabel" name="labelSync">
|
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>0%</string>
|
<property name="decimals">
|
||||||
</property>
|
<number>0</number>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="maximum">
|
||||||
</layout>
|
<double>1000000000000000.000000000000000</double>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<widget class="QSlider" name="sliderSync">
|
</item>
|
||||||
<property name="maximum">
|
<item>
|
||||||
<number>100</number>
|
<widget class="QPushButton" name="buttonUsedBytesChanged">
|
||||||
</property>
|
<property name="text">
|
||||||
<property name="orientation">
|
<string>Send</string>
|
||||||
<enum>Qt::Horizontal</enum>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<property name="tickInterval">
|
</item>
|
||||||
<number>10</number>
|
</layout>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QGroupBox" name="groupBoxIMAPLoginFailed">
|
||||||
<item>
|
<property name="minimumSize">
|
||||||
<widget class="QGroupBox" name="groupBoxBadEvent">
|
<size>
|
||||||
<property name="minimumSize">
|
<width>0</width>
|
||||||
<size>
|
<height>0</height>
|
||||||
<width>0</width>
|
</size>
|
||||||
<height>0</height>
|
</property>
|
||||||
</size>
|
<property name="title">
|
||||||
</property>
|
<string>IMAP Login Failure</string>
|
||||||
<property name="title">
|
</property>
|
||||||
<string>Bad Event</string>
|
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||||
</property>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<widget class="QLineEdit" name="editIMAPLoginFailedUsername">
|
||||||
<item>
|
<property name="minimumSize">
|
||||||
<widget class="QLineEdit" name="editUserBadEvent">
|
<size>
|
||||||
<property name="minimumSize">
|
<width>200</width>
|
||||||
<size>
|
<height>0</height>
|
||||||
<width>200</width>
|
</size>
|
||||||
<height>0</height>
|
</property>
|
||||||
</size>
|
<property name="text">
|
||||||
</property>
|
<string/>
|
||||||
<property name="text">
|
</property>
|
||||||
<string/>
|
<property name="placeholderText">
|
||||||
</property>
|
<string>username or primary email</string>
|
||||||
<property name="placeholderText">
|
</property>
|
||||||
<string>error message</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="buttonImapLoginFailed">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QPushButton" name="buttonUserBadEvent">
|
<string>Send</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Send</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QGroupBox" name="groupBoxNextLogin">
|
||||||
<item>
|
<property name="minimumSize">
|
||||||
<widget class="QGroupBox" name="groupBoxUsedSpace">
|
<size>
|
||||||
<property name="minimumSize">
|
<width>0</width>
|
||||||
<size>
|
<height>100</height>
|
||||||
<width>0</width>
|
</size>
|
||||||
<height>0</height>
|
</property>
|
||||||
</size>
|
<property name="title">
|
||||||
</property>
|
<string>Next Login Attempt</string>
|
||||||
<property name="title">
|
</property>
|
||||||
<string>Used Bytes Changed</string>
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
</property>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
<widget class="QCheckBox" name="checkUsernamePasswordError">
|
||||||
<item>
|
<property name="text">
|
||||||
<layout class="QHBoxLayout" name="hBoxUsedBytes" stretch="1,0">
|
<string>Username/password error:</string>
|
||||||
<item>
|
</property>
|
||||||
<widget class="QDoubleSpinBox" name="spinUsedBytes">
|
</widget>
|
||||||
<property name="buttonSymbols">
|
</item>
|
||||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
<item>
|
||||||
</property>
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<property name="decimals">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<item>
|
||||||
<double>1000000000000000.000000000000000</double>
|
<spacer name="horizontalSpacer">
|
||||||
</property>
|
<property name="orientation">
|
||||||
</widget>
|
<enum>Qt::Horizontal</enum>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="sizeType">
|
||||||
<widget class="QPushButton" name="buttonUsedBytesChanged">
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Send</string>
|
<property name="sizeHint" stdset="0">
|
||||||
</property>
|
<size>
|
||||||
</widget>
|
<width>20</width>
|
||||||
</item>
|
<height>10</height>
|
||||||
</layout>
|
</size>
|
||||||
</item>
|
</property>
|
||||||
</layout>
|
</spacer>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QLineEdit" name="editUsernamePasswordError">
|
||||||
<widget class="QGroupBox" name="groupBoxIMAPLoginFailed">
|
<property name="minimumSize">
|
||||||
<property name="minimumSize">
|
<size>
|
||||||
<size>
|
<width>200</width>
|
||||||
<width>0</width>
|
<height>0</height>
|
||||||
<height>0</height>
|
</size>
|
||||||
</size>
|
</property>
|
||||||
</property>
|
<property name="text">
|
||||||
<property name="title">
|
<string>Username/password error.</string>
|
||||||
<string>IMAP Login Failure</string>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
</item>
|
||||||
<item>
|
</layout>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="editIMAPLoginFailedUsername">
|
<widget class="QCheckBox" name="checkHV3Required">
|
||||||
<property name="minimumSize">
|
<property name="text">
|
||||||
<size>
|
<string>HV3 required</string>
|
||||||
<width>200</width>
|
</property>
|
||||||
<height>0</height>
|
</widget>
|
||||||
</size>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
<property name="text">
|
<widget class="QCheckBox" name="checkHV3Error">
|
||||||
<string/>
|
<property name="text">
|
||||||
</property>
|
<string>HV3 error</string>
|
||||||
<property name="placeholderText">
|
</property>
|
||||||
<string>username or primary email</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QCheckBox" name="checkFreeUserError">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QPushButton" name="buttonImapLoginFailed">
|
<string>Free user error</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Send</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QCheckBox" name="checkTFARequired">
|
||||||
</layout>
|
<property name="text">
|
||||||
</item>
|
<string>2FA required</string>
|
||||||
</layout>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBoxNextLogin">
|
<widget class="QCheckBox" name="checkTFAError">
|
||||||
<property name="minimumSize">
|
<property name="text">
|
||||||
<size>
|
<string>2FA error</string>
|
||||||
<width>0</width>
|
</property>
|
||||||
<height>250</height>
|
</widget>
|
||||||
</size>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
<property name="title">
|
<widget class="QCheckBox" name="checkTFAAbort">
|
||||||
<string>Next Login Attempt</string>
|
<property name="text">
|
||||||
</property>
|
<string>2FA abort</string>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<widget class="QCheckBox" name="checkUsernamePasswordError">
|
</item>
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Username/password error:</string>
|
<widget class="QCheckBox" name="checkTwoPasswordsRequired">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>2nd password required</string>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
</item>
|
||||||
<property name="spacing">
|
<item>
|
||||||
<number>0</number>
|
<widget class="QCheckBox" name="checkTwoPasswordsError">
|
||||||
</property>
|
<property name="text">
|
||||||
<item>
|
<string>2nd password error</string>
|
||||||
<spacer name="horizontalSpacer">
|
</property>
|
||||||
<property name="orientation">
|
</widget>
|
||||||
<enum>Qt::Horizontal</enum>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
<property name="sizeType">
|
<widget class="QCheckBox" name="checkTwoPasswordsAbort">
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<property name="text">
|
||||||
</property>
|
<string>2nd password abort</string>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
</widget>
|
||||||
<width>20</width>
|
</item>
|
||||||
<height>10</height>
|
</layout>
|
||||||
</size>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</spacer>
|
</layout>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="editUsernamePasswordError">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>200</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Username/password error.</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkHV3Required">
|
|
||||||
<property name="text">
|
|
||||||
<string>HV3 required</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkHV3Error">
|
|
||||||
<property name="text">
|
|
||||||
<string>HV3 error</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkFreeUserError">
|
|
||||||
<property name="text">
|
|
||||||
<string>Free user error</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTFARequired">
|
|
||||||
<property name="text">
|
|
||||||
<string>2FA required</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTFAError">
|
|
||||||
<property name="text">
|
|
||||||
<string>2FA error</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTFAAbort">
|
|
||||||
<property name="text">
|
|
||||||
<string>2FA abort</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTwoPasswordsRequired">
|
|
||||||
<property name="text">
|
|
||||||
<string>2nd password required</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTwoPasswordsError">
|
|
||||||
<property name="text">
|
|
||||||
<string>2nd password error</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkTwoPasswordsAbort">
|
|
||||||
<property name="text">
|
|
||||||
<string>2nd password abort</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
@ -75,7 +75,7 @@ if(NOT UNIX)
|
|||||||
set(CMAKE_INSTALL_BINDIR ".")
|
set(CMAKE_INSTALL_BINDIR ".")
|
||||||
endif(NOT UNIX)
|
endif(NOT UNIX)
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg Gui REQUIRED)
|
find_package(Qt6 COMPONENTS Core Quick Qml QuickControls2 Widgets Svg REQUIRED)
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
message(STATUS "Using Qt ${Qt6_VERSION}")
|
message(STATUS "Using Qt ${Qt6_VERSION}")
|
||||||
@ -120,7 +120,6 @@ add_executable(bridge-gui
|
|||||||
UserList.cpp UserList.h
|
UserList.cpp UserList.h
|
||||||
SentryUtils.cpp SentryUtils.h
|
SentryUtils.cpp SentryUtils.h
|
||||||
Settings.cpp Settings.h
|
Settings.cpp Settings.h
|
||||||
ClipboardProxy.cpp ClipboardProxy.h
|
|
||||||
${DOCK_ICON_SRC_FILE} MacOS/DockIcon.h
|
${DOCK_ICON_SRC_FILE} MacOS/DockIcon.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -149,7 +148,6 @@ target_link_libraries(bridge-gui
|
|||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
Qt6::QuickControls2
|
Qt6::QuickControls2
|
||||||
Qt6::Svg
|
Qt6::Svg
|
||||||
Qt6::Gui
|
|
||||||
sentry::sentry
|
sentry::sentry
|
||||||
bridgepp
|
bridgepp
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,25 +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/>.
|
|
||||||
#include "ClipboardProxy.h"
|
|
||||||
|
|
||||||
// The following definitions were taken and adapted from:
|
|
||||||
// https://stackoverflow.com/questions/40092352/passing-qclipboard-to-qml
|
|
||||||
// Author: krzaq
|
|
||||||
|
|
||||||
ClipboardProxy::ClipboardProxy(QClipboard* c) : clipboard(c) {
|
|
||||||
connect(clipboard, &QClipboard::dataChanged, this, &ClipboardProxy::textChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ClipboardProxy::text() const {
|
|
||||||
return clipboard->text();
|
|
||||||
}
|
|
||||||
@ -1,38 +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/>.
|
|
||||||
#ifndef BRIDGE_GUI_CLIPBOARDPROXY_H
|
|
||||||
#define BRIDGE_GUI_CLIPBOARDPROXY_H
|
|
||||||
|
|
||||||
// The following class declarations were taken and adapted from:
|
|
||||||
// https://stackoverflow.com/questions/40092352/passing-qclipboard-to-qml
|
|
||||||
// Author: krzaq
|
|
||||||
|
|
||||||
|
|
||||||
class ClipboardProxy : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
|
||||||
public:
|
|
||||||
explicit ClipboardProxy(QClipboard*);
|
|
||||||
|
|
||||||
QString text() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void textChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QClipboard* clipboard;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //BRIDGE_GUI_CLIPBOARDPROXY_H
|
|
||||||
@ -26,7 +26,6 @@
|
|||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include <QtQuickControls2>
|
#include <QtQuickControls2>
|
||||||
#include <QtSvg>
|
#include <QtSvg>
|
||||||
#include <QtGui>
|
|
||||||
#include <AppController.h>
|
#include <AppController.h>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@
|
|||||||
#include <bridgepp/GRPC/GRPCClient.h>
|
#include <bridgepp/GRPC/GRPCClient.h>
|
||||||
#include <bridgepp/Worker/Overseer.h>
|
#include <bridgepp/Worker/Overseer.h>
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
#define HANDLE_EXCEPTION(x) try { x } \
|
#define HANDLE_EXCEPTION(x) try { x } \
|
||||||
catch (Exception const &e) { emit fatalError(e); } \
|
catch (Exception const &e) { emit fatalError(e); } \
|
||||||
@ -60,19 +59,15 @@ QMLBackend::QMLBackend()
|
|||||||
/// \param[in] serviceConfig
|
/// \param[in] serviceConfig
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||||
Log &log = app().log();
|
|
||||||
log.info(QString("Connecting to gRPC service"));
|
|
||||||
|
|
||||||
trayIcon_.reset(new TrayIcon());
|
trayIcon_.reset(new TrayIcon());
|
||||||
connect(this, &QMLBackend::trayIconVisibleChanged, trayIcon_.get(), &TrayIcon::setVisible);
|
|
||||||
log.info(QString("Tray icon is visible: %1").arg(trayIcon_->isVisible() ? "true" : "false"));
|
|
||||||
this->setNormalTrayIcon();
|
this->setNormalTrayIcon();
|
||||||
|
|
||||||
|
|
||||||
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
||||||
|
|
||||||
users_ = new UserList(this);
|
users_ = new UserList(this);
|
||||||
|
|
||||||
|
Log &log = app().log();
|
||||||
|
log.info(QString("Connecting to gRPC service"));
|
||||||
app().grpc().setLog(&log);
|
app().grpc().setLog(&log);
|
||||||
this->connectGrpcEvents();
|
this->connectGrpcEvents();
|
||||||
|
|
||||||
@ -303,6 +298,7 @@ void QMLBackend::openExternalLink(QString const &url) {
|
|||||||
HANDLE_EXCEPTION(
|
HANDLE_EXCEPTION(
|
||||||
QString const u = url.isEmpty() ? bridgeKBUrl : url;
|
QString const u = url.isEmpty() ? bridgeKBUrl : url;
|
||||||
QDesktopServices::openUrl(u);
|
QDesktopServices::openUrl(u);
|
||||||
|
emit notifyExternalLinkClicked(u);
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,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.
|
/// \param[in] active Should we activate autostart.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -1094,6 +1064,33 @@ void QMLBackend::sendBadEventUserFeedback(QString const &userID, bool doResync)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
///
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void QMLBackend::notifyReportBugClicked() const {
|
||||||
|
HANDLE_EXCEPTION(
|
||||||
|
app().grpc().reportBugClicked();
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] client The selected Mail client for autoconfig.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void QMLBackend::notifyAutoconfigClicked(QString const &client) const {
|
||||||
|
HANDLE_EXCEPTION(
|
||||||
|
app().grpc().autoconfigClicked(client);
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] article The url of the KB article.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void QMLBackend::notifyExternalLinkClicked(QString const &article) const {
|
||||||
|
HANDLE_EXCEPTION(
|
||||||
|
app().grpc().externalLinkClicked(article);
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -1331,9 +1328,6 @@ void QMLBackend::connectGrpcEvents() {
|
|||||||
connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed);
|
connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed);
|
||||||
connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); });
|
connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); });
|
||||||
connect(client, &GRPCClient::knowledgeBasSuggestionsReceived, this, &QMLBackend::receivedKnowledgeBaseSuggestions);
|
connect(client, &GRPCClient::knowledgeBasSuggestionsReceived, this, &QMLBackend::receivedKnowledgeBaseSuggestions);
|
||||||
connect(client, &GRPCClient::repairStarted, this, &QMLBackend::repairStarted);
|
|
||||||
connect(client, &GRPCClient::allUsersLoaded, this, &QMLBackend::allUsersLoaded);
|
|
||||||
connect(client, &GRPCClient::userNotificationReceived, this, &QMLBackend::processUserNotification);
|
|
||||||
|
|
||||||
// cache events
|
// cache events
|
||||||
connect(client, &GRPCClient::cantMoveDiskCache, this, &QMLBackend::cantMoveDiskCache);
|
connect(client, &GRPCClient::cantMoveDiskCache, this, &QMLBackend::cantMoveDiskCache);
|
||||||
@ -1416,31 +1410,3 @@ void QMLBackend::displayBadEventDialog(QString const &userID) {
|
|||||||
emit showMainWindow();
|
emit showMainWindow();
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
void QMLBackend::triggerRepair() const {
|
|
||||||
HANDLE_EXCEPTION(
|
|
||||||
app().grpc().triggerRepair();
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] notification The user notification received from the event loop.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
void QMLBackend::processUserNotification(bridgepp::UserNotification const& notification) {
|
|
||||||
this->userNotificationStack_.push(notification);
|
|
||||||
trayIcon_->showUserNotification(notification.title, notification.subtitle);
|
|
||||||
emit receivedUserNotification(notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QMLBackend::userNotificationDismissed() {
|
|
||||||
if (!this->userNotificationStack_.size()) return;
|
|
||||||
|
|
||||||
// Remove the user notification from the top of the queue as it has been dismissed.
|
|
||||||
this->userNotificationStack_.pop();
|
|
||||||
if (!this->userNotificationStack_.size()) return;
|
|
||||||
|
|
||||||
// Display the user notification that is on top of the queue, if there is one.
|
|
||||||
auto notification = this->userNotificationStack_.top();
|
|
||||||
emit receivedUserNotification(notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@
|
|||||||
#include <bridgepp/GRPC/GRPCClient.h>
|
#include <bridgepp/GRPC/GRPCClient.h>
|
||||||
#include <bridgepp/GRPC/GRPCUtils.h>
|
#include <bridgepp/GRPC/GRPCUtils.h>
|
||||||
#include <bridgepp/Worker/Overseer.h>
|
#include <bridgepp/Worker/Overseer.h>
|
||||||
#include <stack>
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -102,7 +101,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(QVariantList bugQuestions READ bugQuestions NOTIFY bugQuestionsChanged)
|
||||||
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
Q_PROPERTY(UserList *users MEMBER users_ NOTIFY usersChanged)
|
||||||
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged)
|
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged)
|
||||||
Q_PROPERTY(bool trayIconVisible READ trayIconVisible WRITE setTrayIconVisible NOTIFY trayIconVisibleChanged)
|
|
||||||
|
|
||||||
// Qt Property system setters & getters.
|
// Qt Property system setters & getters.
|
||||||
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
bool showOnStartup() const; ///< Getter for the 'showOnStartup' property.
|
||||||
@ -142,9 +140,6 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
|||||||
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
QVariantList bugQuestions() const; ///< Getter for the 'bugQuestions' property.
|
||||||
void setDockIconVisible(bool visible); ///< Setter for the 'dockIconVisible' property.
|
void setDockIconVisible(bool visible); ///< Setter for the 'dockIconVisible' property.
|
||||||
bool dockIconVisible() const;; ///< Getter for the 'dockIconVisible' property.
|
bool dockIconVisible() const;; ///< Getter for the 'dockIconVisible' property.
|
||||||
void setTrayIconVisible(bool visible); ///< Setter for the 'trayIconVisible' property.
|
|
||||||
bool trayIconVisible() const; ///< Getter for the 'trayIconVisible' property.
|
|
||||||
|
|
||||||
|
|
||||||
signals: // Signal used by the Qt property system. Many of them are unused but required to avoid warning from the QML engine.
|
signals: // Signal used by the Qt property system. Many of them are unused but required to avoid warning from the QML engine.
|
||||||
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
void showSplashScreenChanged(bool value); ///<Signal for the change of the 'showSplashScreen' property.
|
||||||
@ -179,9 +174,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 isAutostartOnChanged(bool value); ///<Signal for the change of the 'isAutostartOn' property.
|
||||||
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
||||||
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
||||||
void trayIconVisibleChanged(bool value); ///< Signal for the change of the 'trayIconVisible' property.
|
|
||||||
void receivedUserNotification(bridgepp::UserNotification const& notification); ///< Signal to display the userNotification modal
|
|
||||||
|
|
||||||
|
|
||||||
public slots: // slot for signals received from QML -> To be forwarded to Bridge via RPC Client calls.
|
public slots: // slot for signals received from QML -> To be forwarded to Bridge via RPC Client calls.
|
||||||
void toggleAutostart(bool active); ///< Slot for the autostart toggle.
|
void toggleAutostart(bool active); ///< Slot for the autostart toggle.
|
||||||
@ -213,8 +205,9 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
|||||||
void onVersionChanged(); ///< Slot for the version change signal.
|
void onVersionChanged(); ///< Slot for the version change signal.
|
||||||
void setMailServerSettings(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Forwards a connection mode change request from QML to gRPC
|
void setMailServerSettings(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Forwards a connection mode change request from QML to gRPC
|
||||||
void sendBadEventUserFeedback(QString const &userID, bool doResync); ///< Slot the providing user feedback for a bad event.
|
void sendBadEventUserFeedback(QString const &userID, bool doResync); ///< Slot the providing user feedback for a bad event.
|
||||||
void triggerRepair() const; ///< Slot for the triggering of the bridge repair function i.e. 'resync'.
|
void notifyReportBugClicked() const; ///< Slot for the ReportBugClicked gRPC event.
|
||||||
void userNotificationDismissed(); ///< Slot to pop the notification from the stack and display the rest.
|
void notifyAutoconfigClicked(QString const &client) const; ///< Slot for gAutoconfigClicked gRPC event.
|
||||||
|
void notifyExternalLinkClicked(QString const &article) const; ///< Slot for KBArticleClicked gRPC event.
|
||||||
|
|
||||||
public slots: // slots for functions that need to be processed locally.
|
public slots: // slots for functions that need to be processed locally.
|
||||||
void setNormalTrayIcon(); ///< Set the tray icon to normal.
|
void setNormalTrayIcon(); ///< Set the tray icon to normal.
|
||||||
@ -230,7 +223,6 @@ public slots: // slot for signals received from gRPC that need transformation in
|
|||||||
void onLoginAlreadyLoggedIn(QString const &userID); ///< Slot for the LoginAlreadyLoggedIn gRPC event.
|
void onLoginAlreadyLoggedIn(QString const &userID); ///< Slot for the LoginAlreadyLoggedIn gRPC event.
|
||||||
void onUserBadEvent(QString const& userID, QString const& errorMessage); ///< Slot for the userBadEvent gRPC event.
|
void onUserBadEvent(QString const& userID, QString const& errorMessage); ///< Slot for the userBadEvent gRPC event.
|
||||||
void onIMAPLoginFailed(QString const& username); ///< Slot the the imapLoginFailed event.
|
void onIMAPLoginFailed(QString const& username); ///< Slot the the imapLoginFailed event.
|
||||||
void processUserNotification(bridgepp::UserNotification const& notification); ///< Slot for the userNotificationReceived gRCP event.
|
|
||||||
|
|
||||||
signals: // Signals received from the Go backend, to be forwarded to QML
|
signals: // Signals received from the Go backend, to be forwarded to QML
|
||||||
void toggleAutostartFinished(); ///< Signal for the 'toggleAutostartFinished' gRPC stream event.
|
void toggleAutostartFinished(); ///< Signal for the 'toggleAutostartFinished' gRPC stream event.
|
||||||
@ -290,9 +282,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML
|
|||||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list.
|
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list.
|
||||||
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
||||||
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
||||||
void receivedKnowledgeBaseSuggestions(QList<bridgepp::KnowledgeBaseSuggestion> const& suggestions); ///< Signal for the reception of knowledge base article suggestions.
|
void receivedKnowledgeBaseSuggestions(QList<bridgepp::KnowledgeBaseSuggestion> const& suggestions); ///< Signal for the reception of knowledgebase article suggestions.
|
||||||
void repairStarted(); ///< Signal for the 'repairStarted' gRPC stream event.
|
|
||||||
void allUsersLoaded(); ///< Signal for the 'allUsersLoaded' gRPC stream event
|
|
||||||
|
|
||||||
// This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise.
|
// This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise.
|
||||||
void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs.
|
void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs.
|
||||||
@ -317,7 +307,6 @@ private: // data members
|
|||||||
QList<QString> badEventDisplayQueue_; ///< THe queue for displaying 'bad event feedback request dialog'.
|
QList<QString> badEventDisplayQueue_; ///< THe queue for displaying 'bad event feedback request dialog'.
|
||||||
std::unique_ptr<TrayIcon> trayIcon_; ///< The tray icon for the application.
|
std::unique_ptr<TrayIcon> trayIcon_; ///< The tray icon for the application.
|
||||||
bridgepp::BugReportFlow reportFlow_; ///< The bug report flow.
|
bridgepp::BugReportFlow reportFlow_; ///< The bug report flow.
|
||||||
std::stack<bridgepp::UserNotification> userNotificationStack_; ///< The stack which holds all of the active notifications that the user needs to acknowledge.
|
|
||||||
friend class AppController;
|
friend class AppController;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,6 @@
|
|||||||
<file>qml/icons/systray-mono-update.png</file>
|
<file>qml/icons/systray-mono-update.png</file>
|
||||||
<file>qml/icons/systray-mono-warn.png</file>
|
<file>qml/icons/systray-mono-warn.png</file>
|
||||||
<file>qml/icons/systray.svg</file>
|
<file>qml/icons/systray.svg</file>
|
||||||
<file>qml/icons/ic-notification-bell.svg</file>
|
|
||||||
<file alias="bridge.svg">../../../../dist/bridge.svg</file>
|
<file alias="bridge.svg">../../../../dist/bridge.svg</file>
|
||||||
<file alias="bridgeMacOS.svg">../../../../dist/bridgeMacOS.svg</file>
|
<file alias="bridgeMacOS.svg">../../../../dist/bridgeMacOS.svg</file>
|
||||||
<file>qml/KeychainSettings.qml</file>
|
<file>qml/KeychainSettings.qml</file>
|
||||||
@ -79,7 +78,6 @@
|
|||||||
<file>qml/MainWindow.qml</file>
|
<file>qml/MainWindow.qml</file>
|
||||||
<file>qml/NoAccountView.qml</file>
|
<file>qml/NoAccountView.qml</file>
|
||||||
<file>qml/NotificationDialog.qml</file>
|
<file>qml/NotificationDialog.qml</file>
|
||||||
<file>qml/UserNotificationDialog.qml</file>
|
|
||||||
<file>qml/NotificationPopups.qml</file>
|
<file>qml/NotificationPopups.qml</file>
|
||||||
<file>qml/Notifications/Notification.qml</file>
|
<file>qml/Notifications/Notification.qml</file>
|
||||||
<file>qml/Notifications/NotificationFilter.qml</file>
|
<file>qml/Notifications/NotificationFilter.qml</file>
|
||||||
@ -134,6 +132,5 @@
|
|||||||
<file>qml/ConnectionModeSettings.qml</file>
|
<file>qml/ConnectionModeSettings.qml</file>
|
||||||
<file>qml/SplashScreen.qml</file>
|
<file>qml/SplashScreen.qml</file>
|
||||||
<file>qml/Status.qml</file>
|
<file>qml/Status.qml</file>
|
||||||
<file>qml/Proton/ContextMenu.qml</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -28,7 +28,6 @@ namespace {
|
|||||||
|
|
||||||
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
||||||
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
||||||
QString const keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing the 'Tray icon visible' setting.
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ QString const keyTrayIconVisible = "TrayIconVisible"; ///< The key for storing t
|
|||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
Settings::Settings()
|
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) {
|
void Settings::setUseSoftwareRenderer(bool value) {
|
||||||
settings_.setValue(keyUseSoftwareRenderer, value);
|
settings_.setValue(keyUseSoftwareRenderer, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \param[in] value The value for the 'Tray icon visible' setting.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
void Settings::setTrayIconVisible(bool value) {
|
|
||||||
settings_.setValue(keyTrayIconVisible, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \return The value for the 'Tray icon visible' setting.
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
bool Settings::trayIconVisible() const {
|
|
||||||
return settings_.value(keyTrayIconVisible, true).toBool();
|
|
||||||
}
|
|
||||||
|
|||||||
@ -33,8 +33,6 @@ public: // member functions.
|
|||||||
|
|
||||||
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
||||||
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
||||||
void setTrayIconVisible(bool value); ///< Get the 'Tray icon visible' setting value.
|
|
||||||
bool trayIconVisible() const; ///< Set the 'Tray icon visible' setting value.
|
|
||||||
|
|
||||||
private: // member functions.
|
private: // member functions.
|
||||||
Settings(); ///< Default constructor.
|
Settings(); ///< Default constructor.
|
||||||
|
|||||||
@ -21,7 +21,6 @@
|
|||||||
#include <bridgepp/Exception/Exception.h>
|
#include <bridgepp/Exception/Exception.h>
|
||||||
#include <bridgepp/BridgeUtils.h>
|
#include <bridgepp/BridgeUtils.h>
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
|
|
||||||
@ -196,7 +195,7 @@ TrayIcon::TrayIcon()
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->setIcon();
|
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.
|
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.
|
||||||
for (QScreen *screen: QGuiApplication::screens()) {
|
for (QScreen *screen: QGuiApplication::screens()) {
|
||||||
@ -210,6 +209,7 @@ TrayIcon::TrayIcon()
|
|||||||
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
connect(&iconRefreshTimer_, &QTimer::timeout, this, &TrayIcon::onIconRefreshTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -322,42 +322,16 @@ void TrayIcon::setState(TrayIcon::State state, QString const &stateString, QStri
|
|||||||
this->generateStatusIcon(statusIconPath, stateColor(state));
|
this->generateStatusIcon(statusIconPath, stateColor(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// \brief A helper struct to temporarily force the tray to be visible. Useful for operations that do not work when the tray is not visible,
|
|
||||||
/// such as showMessage().
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
struct ScopedTrayVisibility {
|
|
||||||
explicit ScopedTrayVisibility(TrayIcon& trayIcon) : trayIcon_(trayIcon), wasVisible_(app().settings().trayIconVisible()) {
|
|
||||||
trayIcon_.setVisible(true);
|
|
||||||
}
|
|
||||||
~ScopedTrayVisibility() { trayIcon_.setVisible(wasVisible_); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
TrayIcon &trayIcon_;
|
|
||||||
bool wasVisible_;
|
|
||||||
};
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] title The title.
|
/// \param[in] title The title.
|
||||||
/// \param[in] message The message.
|
/// \param[in] message The message.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
void TrayIcon::showErrorPopupNotification(QString const &title, QString const &message) {
|
||||||
ScopedTrayVisibility visible(*this);
|
|
||||||
this->showMessage(title, message, notificationErrorIcon_);
|
this->showMessage(title, message, notificationErrorIcon_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
|
||||||
/// Used only by user notifications received from the event loop
|
|
||||||
/// \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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] svgPath The path of the SVG file for the icon.
|
/// \param[in] svgPath The path of the SVG file for the icon.
|
||||||
/// \param[in] color The color to apply to the icon.
|
/// \param[in] color The color to apply to the icon.
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public: // data members
|
|||||||
TrayIcon& operator=(TrayIcon&&) = delete; ///< Disabled move assignment operator.
|
TrayIcon& operator=(TrayIcon&&) = delete; ///< Disabled move assignment operator.
|
||||||
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
||||||
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
||||||
void showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
|
||||||
signals:
|
signals:
|
||||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@
|
|||||||
#include <bridgepp/Log/Log.h>
|
#include <bridgepp/Log/Log.h>
|
||||||
#include <bridgepp/Log/LogUtils.h>
|
#include <bridgepp/Log/LogUtils.h>
|
||||||
#include <bridgepp/ProcessMonitor.h>
|
#include <bridgepp/ProcessMonitor.h>
|
||||||
#include <ClipboardProxy.h>
|
|
||||||
|
|
||||||
#include "bridgepp/CLI/CLIUtils.h"
|
#include "bridgepp/CLI/CLIUtils.h"
|
||||||
|
|
||||||
@ -54,7 +53,6 @@ QString const bridgeGUILock = "bridge-v3-gui.lock"; ///< The file name used for
|
|||||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
||||||
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
||||||
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
||||||
QString const orphanInstanceException = "An orphan instance of bridge is already running. Please terminate it and relaunch the application.";
|
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
@ -318,7 +316,7 @@ int main(int argc, char *argv[]) {
|
|||||||
QString bridgeExe;
|
QString bridgeExe;
|
||||||
if (!cliOptions.attach) {
|
if (!cliOptions.attach) {
|
||||||
if (isBridgeRunning()) {
|
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));
|
QString(), __FUNCTION__, tailOfLatestBridgeLog(sessionID));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,8 +347,6 @@ int main(int argc, char *argv[]) {
|
|||||||
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
// Set up clipboard
|
|
||||||
engine.rootContext()->setContextProperty("clipboard", new ClipboardProxy(QGuiApplication::clipboard()));
|
|
||||||
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
||||||
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
|
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
|
||||||
if (!rootObject) {
|
if (!rootObject) {
|
||||||
@ -414,18 +410,13 @@ int main(int argc, char *argv[]) {
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception const &e) {
|
} catch (Exception const &e) {
|
||||||
|
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
||||||
QString message = e.qwhat();
|
QString message = e.qwhat();
|
||||||
if (e.showSupportLink()) {
|
if (e.showSupportLink()) {
|
||||||
message += R"(<br/><br/>If the issue persists, please contact our <a href="https://proton.me/support/contact">customer support</a>.)";
|
message += R"(<br/><br/>If the issue persists, please contact our <a href="https://proton.me/support/contact">customer support</a>.)";
|
||||||
}
|
}
|
||||||
QMessageBox::critical(nullptr, "Error", message);
|
QMessageBox::critical(nullptr, "Error", message);
|
||||||
|
QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :" << e.detailedWhat() << "\n";
|
||||||
if (e.qwhat() != orphanInstanceException) {
|
|
||||||
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
|
||||||
QTextStream(stderr) << "reportID: " << QByteArray(uuid.bytes, 16).toHex() << " Captured exception :"
|
|
||||||
<< e.detailedWhat() << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,13 +86,11 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
id: signIn
|
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
secondary: true
|
secondary: true
|
||||||
text: qsTr("Sign in")
|
text: qsTr("Sign in")
|
||||||
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
||||||
Accessible.name: text
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -101,13 +99,11 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
id: removeAccount
|
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
icon.source: "/qml/icons/ic-trash.svg"
|
icon.source: "/qml/icons/ic-trash.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
visible: root.user ? root.user.state !== EUserState.Locked : false
|
visible: root.user ? root.user.state !== EUserState.Locked : false
|
||||||
Accessible.name: qsTr("Remove account")
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.user)
|
if (!root.user)
|
||||||
@ -122,7 +118,6 @@ Item {
|
|||||||
height: root._lineThickness
|
height: root._lineThickness
|
||||||
}
|
}
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
id: configureEmailClient
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
actionText: qsTr("Configure email client")
|
actionText: qsTr("Configure email client")
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
@ -131,7 +126,6 @@ Item {
|
|||||||
text: qsTr("Email clients")
|
text: qsTr("Email clients")
|
||||||
type: SettingsItem.PrimaryButton
|
type: SettingsItem.PrimaryButton
|
||||||
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
|
||||||
Accessible.name: actionText
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.user)
|
if (!root.user)
|
||||||
@ -149,7 +143,6 @@ Item {
|
|||||||
text: qsTr("Split addresses")
|
text: qsTr("Split addresses")
|
||||||
type: SettingsItem.Toggle
|
type: SettingsItem.Toggle
|
||||||
visible: _connected && root.user.addresses.length > 1
|
visible: _connected && root.user.addresses.length > 1
|
||||||
Accessible.name: text
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!splitMode.checked) {
|
if (!splitMode.checked) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ Popup {
|
|||||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||||
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
leftMargin: (mainWindow.width - root.implicitWidth) / 2
|
||||||
modal: false
|
modal: false
|
||||||
popupPriority: ApplicationWindow.PopupPriority.Banner
|
popupType: ApplicationWindow.PopupType.Banner
|
||||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||||
topMargin: 37
|
topMargin: 37
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.impl
|
|
||||||
import Proton
|
import Proton
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@ -114,7 +114,6 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-question-circle.svg"
|
icon.source: "/qml/icons/ic-question-circle.svg"
|
||||||
Accessible.name: qsTr("Help")
|
|
||||||
|
|
||||||
onClicked: rightContent.showHelpView()
|
onClicked: rightContent.showHelpView()
|
||||||
}
|
}
|
||||||
@ -131,7 +130,6 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
icon.source: "/qml/icons/ic-cog-wheel.svg"
|
||||||
Accessible.name: qsTr("Settings")
|
|
||||||
|
|
||||||
onClicked: rightContent.showGeneralSettings()
|
onClicked: rightContent.showGeneralSettings()
|
||||||
}
|
}
|
||||||
@ -149,7 +147,6 @@ Item {
|
|||||||
colorScheme: leftBar.colorScheme
|
colorScheme: leftBar.colorScheme
|
||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||||
Accessible.name: "..."
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dotMenu.open();
|
dotMenu.open();
|
||||||
@ -322,7 +319,6 @@ Item {
|
|||||||
horizontalPadding: 0
|
horizontalPadding: 0
|
||||||
icon.source: "/qml/icons/ic-plus.svg"
|
icon.source: "/qml/icons/ic-plus.svg"
|
||||||
width: 36
|
width: 36
|
||||||
Accessible.name: qsTr("Add account")
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.showLogin("");
|
root.showLogin("");
|
||||||
|
|||||||
@ -21,8 +21,6 @@ SettingsView {
|
|||||||
|
|
||||||
property bool _isAdvancedShown: false
|
property bool _isAdvancedShown: false
|
||||||
property var notifications
|
property var notifications
|
||||||
property var allUsersLoaded: false
|
|
||||||
property var hasInternetConnection: true
|
|
||||||
|
|
||||||
fillHeight: false
|
fillHeight: false
|
||||||
|
|
||||||
@ -147,19 +145,6 @@ SettingsView {
|
|||||||
|
|
||||||
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
|
onClicked: Backend.changeColorScheme(darkMode.checked ? "light" : "dark")
|
||||||
}
|
}
|
||||||
SettingsItem {
|
|
||||||
id: trayIconVisible
|
|
||||||
Layout.fillWidth: true
|
|
||||||
checked: Backend.trayIconVisible
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
description: qsTr("Show the Bridge icon in the menu bar. When the Bridge icon is not visible, launch the " +
|
|
||||||
"application again to display the main window.")
|
|
||||||
text: qsTr("Show the Bridge icon in the menu bar")
|
|
||||||
type: SettingsItem.Toggle
|
|
||||||
visible: (Backend.goos === "darwin") && root._isAdvancedShown
|
|
||||||
|
|
||||||
onClicked: Backend.trayIconVisible = !trayIconVisible.checked
|
|
||||||
}
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
id: allMail
|
id: allMail
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -234,37 +219,6 @@ SettingsView {
|
|||||||
Backend.exportTLSCertificates();
|
Backend.exportTLSCertificates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsItem {
|
|
||||||
id: repair
|
|
||||||
Layout.fillWidth: true
|
|
||||||
actionText: qsTr("Repair")
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
description: qsTr("Reload all accounts, cached data, and download all emails again. Email clients stay connected to Bridge.")
|
|
||||||
text: qsTr("Repair Bridge")
|
|
||||||
type: SettingsItem.Button
|
|
||||||
visible: root._isAdvancedShown
|
|
||||||
enabled: root.allUsersLoaded && Backend.users.count && root.hasInternetConnection
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
root.notifications.askRepairBridge();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onInternetOff() {
|
|
||||||
root.hasInternetConnection = false;
|
|
||||||
repair.description = qsTr("This feature requires internet access to the Proton servers.")
|
|
||||||
|
|
||||||
}
|
|
||||||
function onInternetOn() {
|
|
||||||
root.hasInternetConnection = true;
|
|
||||||
repair.description = qsTr("Reload all accounts, cached data, and download all emails again. Email clients stay connected to Bridge.")
|
|
||||||
}
|
|
||||||
function onAllUsersLoaded() {
|
|
||||||
root.allUsersLoaded = true;
|
|
||||||
}
|
|
||||||
target: Backend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
id: reset
|
id: reset
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@ -83,6 +83,7 @@ SettingsView {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Backend.updateCurrentMailClient();
|
Backend.updateCurrentMailClient();
|
||||||
|
Backend.notifyReportBugClicked();
|
||||||
root.parent.showBugReport();
|
root.parent.showBugReport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ Dialog {
|
|||||||
|
|
||||||
default property alias data: additionalChildrenContainer.children
|
default property alias data: additionalChildrenContainer.children
|
||||||
property var notification
|
property var notification
|
||||||
property bool isUserNotification: false
|
|
||||||
|
|
||||||
modal: true
|
modal: true
|
||||||
shouldShow: notification && notification.active && !notification.dismissed
|
shouldShow: notification && notification.active && !notification.dismissed
|
||||||
@ -40,13 +39,13 @@ Dialog {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
switch (root.notification.type) {
|
switch (root.notification.type) {
|
||||||
case Notification.NotificationType.Info:
|
case Notification.NotificationType.Info:
|
||||||
return "/qml/icons/ic-info.svg";
|
return "/qml/icons/ic-info.svg";
|
||||||
case Notification.NotificationType.Success:
|
case Notification.NotificationType.Success:
|
||||||
return "/qml/icons/ic-success.svg";
|
return "/qml/icons/ic-success.svg";
|
||||||
case Notification.NotificationType.Warning:
|
case Notification.NotificationType.Warning:
|
||||||
case Notification.NotificationType.Danger:
|
case Notification.NotificationType.Danger:
|
||||||
return "/qml/icons/ic-alert.svg";
|
return "/qml/icons/ic-alert.svg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceSize.height: 64
|
sourceSize.height: 64
|
||||||
|
|||||||
@ -105,12 +105,4 @@ Item {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
notification: root.notifications.genericQuestion
|
notification: root.notifications.genericQuestion
|
||||||
}
|
}
|
||||||
NotificationDialog {
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
notification: root.notifications.repairBridge
|
|
||||||
}
|
|
||||||
UserNotificationDialog {
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
notification: root.notifications.userNotification
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,7 @@ QtObject {
|
|||||||
Info,
|
Info,
|
||||||
Success,
|
Success,
|
||||||
Warning,
|
Warning,
|
||||||
Danger,
|
Danger
|
||||||
UserNotification
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property list<Action> action
|
property list<Action> action
|
||||||
@ -37,9 +36,6 @@ QtObject {
|
|||||||
readonly property var occurred: active ? new Date() : undefined
|
readonly property var occurred: active ? new Date() : undefined
|
||||||
property string title // title is used in dialogs only
|
property string title // title is used in dialogs only
|
||||||
property int type
|
property int type
|
||||||
property string subtitle
|
|
||||||
property string username
|
|
||||||
|
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
dismissed = false;
|
dismissed = false;
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
import QtQml
|
import QtQml
|
||||||
import Qt.labs.platform
|
import Qt.labs.platform
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick
|
|
||||||
import "../"
|
import "../"
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
@ -62,7 +60,7 @@ QtObject {
|
|||||||
target: Backend
|
target: Backend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent, root.repairBridge, root.userNotification]
|
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent]
|
||||||
property Notification alreadyLoggedIn: Notification {
|
property Notification alreadyLoggedIn: Notification {
|
||||||
brief: qsTr("Already signed in")
|
brief: qsTr("Already signed in")
|
||||||
description: qsTr("This account is already signed in.")
|
description: qsTr("This account is already signed in.")
|
||||||
@ -1153,81 +1151,6 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
property Notification repairBridge: Notification {
|
|
||||||
brief: title
|
|
||||||
description: qsTr("This action will reload all accounts, cached data, and re-download emails. Messages may temporarily disappear but will reappear progressively. Email clients stay connected to Bridge.")
|
|
||||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
|
||||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
|
||||||
title: qsTr("Repair Bridge?")
|
|
||||||
type: Notification.NotificationType.Danger
|
|
||||||
|
|
||||||
action: [
|
|
||||||
Action {
|
|
||||||
id: repairBridge_cancel
|
|
||||||
text: qsTr("Cancel")
|
|
||||||
onTriggered: {
|
|
||||||
root.repairBridge.active = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Action {
|
|
||||||
id: repairBridge_repair
|
|
||||||
text: qsTr("Repair")
|
|
||||||
onTriggered: {
|
|
||||||
repairBridge_repair.loading = true;
|
|
||||||
repairBridge_repair.enabled = false;
|
|
||||||
repairBridge_cancel.enabled = false;
|
|
||||||
Backend.triggerRepair();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onAskRepairBridge() {
|
|
||||||
root.repairBridge.active = true;
|
|
||||||
}
|
|
||||||
target: root
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onRepairStarted() {
|
|
||||||
root.repairBridge.active = false;
|
|
||||||
repairBridge_repair.loading = false;
|
|
||||||
repairBridge_repair.enabled = true;
|
|
||||||
repairBridge_cancel.enabled = true;
|
|
||||||
}
|
|
||||||
target: Backend
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
property Notification userNotification: Notification {
|
|
||||||
brief: title
|
|
||||||
group: Notifications.Group.Dialogs
|
|
||||||
type: Notification.NotificationType.UserNotification
|
|
||||||
icon: "./icons/ic-exclamation-circle-filled.svg" // If it's not included QML complains
|
|
||||||
|
|
||||||
action: [
|
|
||||||
Action {
|
|
||||||
text: qsTr("Okay")
|
|
||||||
onTriggered: {
|
|
||||||
root.userNotification.active = false;
|
|
||||||
Backend.userNotificationDismissed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onReceivedUserNotification(notification) {
|
|
||||||
const userPrimaryEmailOrUsername = Backend.users.primaryEmailOrUsername(notification.userID)
|
|
||||||
root.userNotification.title = notification.title
|
|
||||||
root.userNotification.subtitle = notification.subtitle
|
|
||||||
root.userNotification.description = notification.body
|
|
||||||
root.userNotification.username = userPrimaryEmailOrUsername
|
|
||||||
root.userNotification.active = true
|
|
||||||
}
|
|
||||||
target: Backend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signal askChangeAllMailVisibility(var isVisibleNow)
|
signal askChangeAllMailVisibility(var isVisibleNow)
|
||||||
signal askDeleteAccount(var user)
|
signal askDeleteAccount(var user)
|
||||||
@ -1235,5 +1158,4 @@ QtObject {
|
|||||||
signal askEnableSplitMode(var user)
|
signal askEnableSplitMode(var user)
|
||||||
signal askQuestion(var title, var description, var option1, var option2, var action1, var action2)
|
signal askQuestion(var title, var description, var option1, var option2, var action1, var action2)
|
||||||
signal askResetBridge
|
signal askResetBridge
|
||||||
signal askRepairBridge
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ T.ApplicationWindow {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
// popup priority based on types
|
// popup priority based on types
|
||||||
enum PopupPriority {
|
enum PopupType {
|
||||||
Banner,
|
Banner,
|
||||||
Dialog
|
Dialog
|
||||||
}
|
}
|
||||||
@ -73,15 +73,10 @@ T.ApplicationWindow {
|
|||||||
if (obj.shouldShow === false) {
|
if (obj.shouldShow === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// User notifications should have display priority
|
if (topmost && (topmost.popupType > obj.popupType)) {
|
||||||
if (obj.shouldShow && obj.isUserNotification) {
|
|
||||||
topmost = obj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (topmost && (topmost.popupPriority > obj.popupPriority)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (topmost && (topmost.popupPriority === obj.popupPriority) && (topmost.occurred > obj.occurred)) {
|
if (topmost && (topmost.popupType === obj.popupType) && (topmost.occurred > obj.occurred)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
topmost = obj;
|
topmost = obj;
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2024 Proton AG
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
Item {
|
|
||||||
property var parentObject: null
|
|
||||||
property var colorScheme: null
|
|
||||||
property bool readOnly: false
|
|
||||||
property bool isPassword: false
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: controlMouseArea
|
|
||||||
width: parentObject ? parentObject.width : 0
|
|
||||||
height: parentObject ? parentObject.height : 0
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onClicked: controlContextMenu.popup()
|
|
||||||
|
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: controlContextMenu
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (controlContextMenu.visible) {
|
|
||||||
const hasSelectedText = parentObject.selectedText.length > 0;
|
|
||||||
const hasClipboardText = clipboard.text.length > 0;
|
|
||||||
|
|
||||||
copyMenuItem.visible = hasSelectedText && !isPassword;
|
|
||||||
cutMenuItem.visible = hasSelectedText && !readOnly && !isPassword;
|
|
||||||
pasteMenuItem.visible = hasClipboardText && !readOnly;
|
|
||||||
controlContextMenu.visible = copyMenuItem.visible || cutMenuItem.visible || pasteMenuItem.visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
id: cutMenuItem
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
text: qsTr("Cut")
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
parentObject.cut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
id: copyMenuItem
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
text: qsTr("Copy")
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
parentObject.copy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
id: pasteMenuItem
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
|
|
||||||
text: qsTr("Paste")
|
|
||||||
onTriggered: {
|
|
||||||
parentObject.paste()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -21,7 +21,7 @@ T.Dialog {
|
|||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||||
readonly property int popupPriority: ApplicationWindow.PopupPriority.Dialog
|
readonly property int popupType: ApplicationWindow.PopupType.Dialog
|
||||||
property bool shouldShow: false
|
property bool shouldShow: false
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.impl
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
ColorImage {
|
ColorImage {
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.impl
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ T.Popup {
|
|||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
readonly property var occurred: shouldShow ? new Date() : undefined
|
readonly property var occurred: shouldShow ? new Date() : undefined
|
||||||
property int popupPriority: ApplicationWindow.PopupPriority.Banner
|
property int popupType: ApplicationWindow.PopupType.Banner
|
||||||
property bool shouldShow: false
|
property bool shouldShow: false
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
@ -362,9 +362,4 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Proton.ContextMenu {
|
|
||||||
parentObject: root
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,8 +172,6 @@ FocusScope {
|
|||||||
|
|
||||||
implicitHeight: children[0].implicitHeight
|
implicitHeight: children[0].implicitHeight
|
||||||
implicitWidth: children[0].implicitWidth
|
implicitWidth: children[0].implicitWidth
|
||||||
Accessible.role: Accessible.Grouping
|
|
||||||
Accessible.name: label.text
|
|
||||||
|
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
if (!validateOnEditingFinished) {
|
if (!validateOnEditingFinished) {
|
||||||
@ -276,7 +274,6 @@ FocusScope {
|
|||||||
selectionColor: control.palette.highlight
|
selectionColor: control.palette.highlight
|
||||||
topPadding: 8
|
topPadding: 8
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
Accessible.name: label.text + qsTr(" edit")
|
|
||||||
|
|
||||||
background: Item {
|
background: Item {
|
||||||
implicitHeight: 36
|
implicitHeight: 36
|
||||||
@ -334,15 +331,6 @@ FocusScope {
|
|||||||
x: control.leftPadding
|
x: control.leftPadding
|
||||||
y: control.topPadding
|
y: control.topPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
Proton.ContextMenu {
|
|
||||||
parentObject: control
|
|
||||||
colorScheme: root.colorScheme
|
|
||||||
isPassword: control.echoMode === TextInput.Password
|
|
||||||
readOnly: control.readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Proton.Button {
|
Proton.Button {
|
||||||
id: eyeButton
|
id: eyeButton
|
||||||
@ -352,7 +340,6 @@ FocusScope {
|
|||||||
icon.color: control.color
|
icon.color: control.color
|
||||||
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
icon.source: checked ? "../icons/ic-eye-slash.svg" : "../icons/ic-eye.svg"
|
||||||
visible: root.echoMode === TextInput.Password
|
visible: root.echoMode === TextInput.Password
|
||||||
Accessible.name: label.text + qsTr(" show check")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,4 +39,3 @@ TextArea 4.0 TextArea.qml
|
|||||||
TextField 4.0 TextField.qml
|
TextField 4.0 TextField.qml
|
||||||
Toggle 4.0 Toggle.qml
|
Toggle 4.0 Toggle.qml
|
||||||
WebFrame 4.0 WebFrame.qml
|
WebFrame 4.0 WebFrame.qml
|
||||||
ContextMenu 4.0 ContextMenu.qml
|
|
||||||
|
|||||||
@ -41,8 +41,6 @@ Item {
|
|||||||
|
|
||||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||||
Accessible.name: text
|
|
||||||
Accessible.role: Accessible.Grouping
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@ -79,8 +77,6 @@ Item {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
loading: root.loading
|
loading: root.loading
|
||||||
visible: root.type === SettingsItem.ActionType.Toggle
|
visible: root.type === SettingsItem.ActionType.Toggle
|
||||||
Accessible.role: Accessible.CheckBox
|
|
||||||
Accessible.name: root.Accessible.name + " toggle"
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.loading)
|
if (!root.loading)
|
||||||
@ -96,8 +92,6 @@ Item {
|
|||||||
secondary: root.type !== SettingsItem.PrimaryButton
|
secondary: root.type !== SettingsItem.PrimaryButton
|
||||||
text: root.actionText
|
text: root.actionText
|
||||||
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: root.Accessible.name + " button"
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.loading)
|
if (!root.loading)
|
||||||
|
|||||||
@ -80,7 +80,6 @@ Item {
|
|||||||
horizontalPadding: 8
|
horizontalPadding: 8
|
||||||
icon.source: "/qml/icons/ic-arrow-left.svg"
|
icon.source: "/qml/icons/ic-arrow-left.svg"
|
||||||
secondary: true
|
secondary: true
|
||||||
Accessible.name: qsTr("Back")
|
|
||||||
|
|
||||||
onClicked: root.back()
|
onClicked: root.back()
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ Item {
|
|||||||
color: colorScheme.text_weak
|
color: colorScheme.text_weak
|
||||||
colorScheme: wizard.colorScheme
|
colorScheme: wizard.colorScheme
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("A 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
|
type: Label.LabelType.Body
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.impl
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import QtQml
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.impl
|
|
||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user